fix(release): preserve 0.1.60 schema compatibility
This commit is contained in:
+7
-1
@@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.1.60-beta.2 - 2026-04-22
|
## 0.1.60 - 2026-04-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Scripts and services per worktree — define named commands in `paseo.json`, and long-running services get supervised with their own ports and nice proxy URLs like `http://web.my-app.localhost:6767`. See the [worktrees guide](https://paseo.sh/docs/worktrees).
|
- Scripts and services per worktree — define named commands in `paseo.json`, and long-running services get supervised with their own ports and nice proxy URLs like `http://web.my-app.localhost:6767`. See the [worktrees guide](https://paseo.sh/docs/worktrees).
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
- Escape reliably interrupts the active agent.
|
- Escape reliably interrupts the active agent.
|
||||||
- Checking out a pull request from a fork lands on an owner-prefixed branch so multiple forks don't collide.
|
- Checking out a pull request from a fork lands on an owner-prefixed branch so multiple forks don't collide.
|
||||||
- `paseo ls` defaults to active agents; pass `-a` to include archived.
|
- `paseo ls` defaults to active agents; pass `-a` to include archived.
|
||||||
|
- GitHub branch and PR picker loads faster — queries are deferred until the picker opens.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Composer textarea shrinks back down after sending on web.
|
- Composer textarea shrinks back down after sending on web.
|
||||||
@@ -42,6 +43,11 @@
|
|||||||
- iOS image picker no longer leaves the screen unresponsive after cancelling.
|
- iOS image picker no longer leaves the screen unresponsive after cancelling.
|
||||||
- Archiving a worktree recovers cleanly if a previous attempt was interrupted.
|
- Archiving a worktree recovers cleanly if a previous attempt was interrupted.
|
||||||
- Images in agent messages with `~`-prefixed paths load instead of spinning forever.
|
- Images in agent messages with `~`-prefixed paths load instead of spinning forever.
|
||||||
|
- Tool call blocks expand correctly on mobile while an agent is still streaming.
|
||||||
|
- Timeline no longer stutters when catch-up and projected ranges overlap.
|
||||||
|
- Codex no longer flashes idle when a replacement turn is in progress.
|
||||||
|
- Branch state recovers correctly when a rebase is in progress.
|
||||||
|
- Workspace hover card no longer clips near screen edges.
|
||||||
|
|
||||||
## 0.1.59 - 2026-04-16
|
## 0.1.59 - 2026-04-16
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,69 @@ describe("shared messages stream parsing", () => {
|
|||||||
expect(parsed.payload.entries[0]?.item.type).toBe("assistant_message");
|
expect(parsed.payload.entries[0]?.item.type).toBe("assistant_message");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("parses legacy worktree setup timeline entries without per-command log", () => {
|
||||||
|
const parsed = FetchAgentTimelineResponseMessageSchema.parse({
|
||||||
|
type: "fetch_agent_timeline_response",
|
||||||
|
payload: {
|
||||||
|
requestId: "req-legacy-setup",
|
||||||
|
agentId: "agent_legacy_setup",
|
||||||
|
agent: null,
|
||||||
|
direction: "tail",
|
||||||
|
projection: "canonical",
|
||||||
|
epoch: "epoch-setup",
|
||||||
|
reset: false,
|
||||||
|
staleCursor: false,
|
||||||
|
gap: false,
|
||||||
|
window: { minSeq: 1, maxSeq: 1, nextSeq: 2 },
|
||||||
|
startCursor: { epoch: "epoch-setup", seq: 1 },
|
||||||
|
endCursor: { epoch: "epoch-setup", seq: 1 },
|
||||||
|
hasOlder: false,
|
||||||
|
hasNewer: false,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
provider: "codex",
|
||||||
|
item: {
|
||||||
|
type: "tool_call",
|
||||||
|
callId: "setup-1",
|
||||||
|
name: "paseo_worktree_setup",
|
||||||
|
status: "completed",
|
||||||
|
detail: {
|
||||||
|
type: "worktree_setup",
|
||||||
|
worktreePath: "/repo/.paseo/worktrees/feature",
|
||||||
|
branchName: "feature",
|
||||||
|
log: "setup complete",
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
command: "npm install",
|
||||||
|
cwd: "/repo/.paseo/worktrees/feature",
|
||||||
|
status: "completed",
|
||||||
|
exitCode: 0,
|
||||||
|
durationMs: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
timestamp: "2026-04-22T00:00:00.000Z",
|
||||||
|
seqStart: 1,
|
||||||
|
seqEnd: 1,
|
||||||
|
sourceSeqRanges: [{ startSeq: 1, endSeq: 1 }],
|
||||||
|
collapsed: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = parsed.payload.entries[0]?.item;
|
||||||
|
expect(item?.type).toBe("tool_call");
|
||||||
|
if (item?.type !== "tool_call" || item.detail.type !== "worktree_setup") {
|
||||||
|
throw new Error("Expected worktree setup tool call");
|
||||||
|
}
|
||||||
|
expect(item.detail.commands[0]?.log).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
it("parses explicit shutdown and restart lifecycle request payloads as distinct message types", () => {
|
it("parses explicit shutdown and restart lifecycle request payloads as distinct message types", () => {
|
||||||
const shutdownParsed = SessionInboundMessageSchema.safeParse({
|
const shutdownParsed = SessionInboundMessageSchema.safeParse({
|
||||||
type: "shutdown_server_request",
|
type: "shutdown_server_request",
|
||||||
|
|||||||
@@ -251,7 +251,11 @@ export const AgentPermissionResponseSchema: z.ZodType<AgentPermissionResponse> =
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const AgentPermissionRequestPayloadSchema: z.ZodType<AgentPermissionRequest> = z.object({
|
export const AgentPermissionRequestPayloadSchema: z.ZodType<
|
||||||
|
AgentPermissionRequest,
|
||||||
|
z.ZodTypeDef,
|
||||||
|
unknown
|
||||||
|
> = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
provider: AgentProviderSchema,
|
provider: AgentProviderSchema,
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -286,7 +290,7 @@ const WorktreeSetupCommandSnapshotSchema = z.object({
|
|||||||
index: z.number().int().positive(),
|
index: z.number().int().positive(),
|
||||||
command: z.string(),
|
command: z.string(),
|
||||||
cwd: z.string(),
|
cwd: z.string(),
|
||||||
log: z.string(),
|
log: z.string().optional().default(""),
|
||||||
status: z.enum(["running", "completed", "failed"]),
|
status: z.enum(["running", "completed", "failed"]),
|
||||||
exitCode: z.number().nullable(),
|
exitCode: z.number().nullable(),
|
||||||
durationMs: z.number().nonnegative().optional(),
|
durationMs: z.number().nonnegative().optional(),
|
||||||
@@ -301,95 +305,96 @@ const WorktreeSetupDetailPayloadSchema = z.object({
|
|||||||
truncated: z.boolean().optional(),
|
truncated: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const ToolCallDetailPayloadSchema: z.ZodType<ToolCallDetail> = z.discriminatedUnion("type", [
|
const ToolCallDetailPayloadSchema: z.ZodType<ToolCallDetail, z.ZodTypeDef, unknown> =
|
||||||
WorktreeSetupDetailPayloadSchema,
|
z.discriminatedUnion("type", [
|
||||||
z.object({
|
WorktreeSetupDetailPayloadSchema,
|
||||||
type: z.literal("shell"),
|
z.object({
|
||||||
command: z.string(),
|
type: z.literal("shell"),
|
||||||
cwd: z.string().optional(),
|
command: z.string(),
|
||||||
output: z.string().optional(),
|
cwd: z.string().optional(),
|
||||||
exitCode: z.number().nullable().optional(),
|
output: z.string().optional(),
|
||||||
}),
|
exitCode: z.number().nullable().optional(),
|
||||||
z.object({
|
}),
|
||||||
type: z.literal("read"),
|
z.object({
|
||||||
filePath: z.string(),
|
type: z.literal("read"),
|
||||||
content: z.string().optional(),
|
filePath: z.string(),
|
||||||
offset: z.number().optional(),
|
content: z.string().optional(),
|
||||||
limit: z.number().optional(),
|
offset: z.number().optional(),
|
||||||
}),
|
limit: z.number().optional(),
|
||||||
z.object({
|
}),
|
||||||
type: z.literal("edit"),
|
z.object({
|
||||||
filePath: z.string(),
|
type: z.literal("edit"),
|
||||||
oldString: z.string().optional(),
|
filePath: z.string(),
|
||||||
newString: z.string().optional(),
|
oldString: z.string().optional(),
|
||||||
unifiedDiff: z.string().optional(),
|
newString: z.string().optional(),
|
||||||
}),
|
unifiedDiff: z.string().optional(),
|
||||||
z.object({
|
}),
|
||||||
type: z.literal("write"),
|
z.object({
|
||||||
filePath: z.string(),
|
type: z.literal("write"),
|
||||||
content: z.string().optional(),
|
filePath: z.string(),
|
||||||
}),
|
content: z.string().optional(),
|
||||||
z.object({
|
}),
|
||||||
type: z.literal("search"),
|
z.object({
|
||||||
query: z.string(),
|
type: z.literal("search"),
|
||||||
toolName: z.enum(["search", "grep", "glob", "web_search"]).optional(),
|
query: z.string(),
|
||||||
content: z.string().optional(),
|
toolName: z.enum(["search", "grep", "glob", "web_search"]).optional(),
|
||||||
filePaths: z.array(z.string()).optional(),
|
content: z.string().optional(),
|
||||||
webResults: z
|
filePaths: z.array(z.string()).optional(),
|
||||||
.array(
|
webResults: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
title: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
annotations: z.array(z.string()).optional(),
|
||||||
|
numFiles: z.number().optional(),
|
||||||
|
numMatches: z.number().optional(),
|
||||||
|
durationMs: z.number().optional(),
|
||||||
|
durationSeconds: z.number().optional(),
|
||||||
|
truncated: z.boolean().optional(),
|
||||||
|
mode: z.enum(["content", "files_with_matches", "count"]).optional(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
type: z.literal("fetch"),
|
||||||
|
url: z.string(),
|
||||||
|
prompt: z.string().optional(),
|
||||||
|
result: z.string().optional(),
|
||||||
|
code: z.number().optional(),
|
||||||
|
codeText: z.string().optional(),
|
||||||
|
bytes: z.number().optional(),
|
||||||
|
durationMs: z.number().optional(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
type: z.literal("sub_agent"),
|
||||||
|
subAgentType: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
log: z.string(),
|
||||||
|
actions: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
title: z.string(),
|
index: z.number().int().positive(),
|
||||||
url: z.string(),
|
toolName: z.string(),
|
||||||
|
summary: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
),
|
||||||
.optional(),
|
}),
|
||||||
annotations: z.array(z.string()).optional(),
|
z.object({
|
||||||
numFiles: z.number().optional(),
|
type: z.literal("plain_text"),
|
||||||
numMatches: z.number().optional(),
|
label: z.string().optional(),
|
||||||
durationMs: z.number().optional(),
|
text: z.string().optional(),
|
||||||
durationSeconds: z.number().optional(),
|
icon: z.enum(TOOL_CALL_ICON_NAMES).optional(),
|
||||||
truncated: z.boolean().optional(),
|
}),
|
||||||
mode: z.enum(["content", "files_with_matches", "count"]).optional(),
|
z.object({
|
||||||
}),
|
type: z.literal("plan"),
|
||||||
z.object({
|
text: z.string(),
|
||||||
type: z.literal("fetch"),
|
}),
|
||||||
url: z.string(),
|
z.object({
|
||||||
prompt: z.string().optional(),
|
type: z.literal("unknown"),
|
||||||
result: z.string().optional(),
|
input: UnknownValueSchema,
|
||||||
code: z.number().optional(),
|
output: UnknownValueSchema,
|
||||||
codeText: z.string().optional(),
|
}),
|
||||||
bytes: z.number().optional(),
|
]);
|
||||||
durationMs: z.number().optional(),
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
type: z.literal("sub_agent"),
|
|
||||||
subAgentType: z.string().optional(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
log: z.string(),
|
|
||||||
actions: z.array(
|
|
||||||
z.object({
|
|
||||||
index: z.number().int().positive(),
|
|
||||||
toolName: z.string(),
|
|
||||||
summary: z.string().optional(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
type: z.literal("plain_text"),
|
|
||||||
label: z.string().optional(),
|
|
||||||
text: z.string().optional(),
|
|
||||||
icon: z.enum(TOOL_CALL_ICON_NAMES).optional(),
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
type: z.literal("plan"),
|
|
||||||
text: z.string(),
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
type: z.literal("unknown"),
|
|
||||||
input: UnknownValueSchema,
|
|
||||||
output: UnknownValueSchema,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ToolCallBasePayloadSchema = z
|
const ToolCallBasePayloadSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -2060,29 +2065,34 @@ const WorkspaceGitHubRuntimePayloadSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable();
|
.nullable();
|
||||||
|
|
||||||
export const WorkspaceDescriptorPayloadSchema = z.object({
|
export const WorkspaceDescriptorPayloadSchema = z
|
||||||
id: z.string(),
|
.object({
|
||||||
projectId: z.string(),
|
id: z.string(),
|
||||||
projectDisplayName: z.string(),
|
projectId: z.string(),
|
||||||
projectRootPath: z.string(),
|
projectDisplayName: z.string(),
|
||||||
workspaceDirectory: z.string(),
|
projectRootPath: z.string(),
|
||||||
projectKind: z.enum(["git", "non_git", "directory"]),
|
workspaceDirectory: z.string().optional(),
|
||||||
// COMPAT(workspaces): keep legacy directory workspace kind parseable.
|
projectKind: z.enum(["git", "non_git", "directory"]),
|
||||||
workspaceKind: z.enum(["directory", "local_checkout", "checkout", "worktree"]),
|
// COMPAT(workspaces): keep legacy directory workspace kind parseable.
|
||||||
name: z.string(),
|
workspaceKind: z.enum(["directory", "local_checkout", "checkout", "worktree"]),
|
||||||
status: WorkspaceStateBucketSchema,
|
name: z.string(),
|
||||||
activityAt: z.string().nullable(),
|
status: WorkspaceStateBucketSchema,
|
||||||
diffStat: z
|
activityAt: z.string().nullable(),
|
||||||
.object({
|
diffStat: z
|
||||||
additions: z.number(),
|
.object({
|
||||||
deletions: z.number(),
|
additions: z.number(),
|
||||||
})
|
deletions: z.number(),
|
||||||
.nullable()
|
})
|
||||||
.optional(),
|
.nullable()
|
||||||
scripts: z.array(WorkspaceScriptPayloadSchema).default([]),
|
.optional(),
|
||||||
gitRuntime: WorkspaceGitRuntimePayloadSchema,
|
scripts: z.array(WorkspaceScriptPayloadSchema).default([]),
|
||||||
githubRuntime: WorkspaceGitHubRuntimePayloadSchema,
|
gitRuntime: WorkspaceGitRuntimePayloadSchema,
|
||||||
});
|
githubRuntime: WorkspaceGitHubRuntimePayloadSchema,
|
||||||
|
})
|
||||||
|
.transform((workspace) => ({
|
||||||
|
...workspace,
|
||||||
|
workspaceDirectory: workspace.workspaceDirectory ?? workspace.projectRootPath,
|
||||||
|
}));
|
||||||
|
|
||||||
export const AgentUpdateMessageSchema = z.object({
|
export const AgentUpdateMessageSchema = z.object({
|
||||||
type: z.literal("agent_update"),
|
type: z.literal("agent_update"),
|
||||||
|
|||||||
@@ -254,6 +254,33 @@ describe("workspace message schemas", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("parses legacy workspace descriptors without workspaceDirectory", () => {
|
||||||
|
const parsed = SessionOutboundMessageSchema.parse({
|
||||||
|
type: "workspace_update",
|
||||||
|
payload: {
|
||||||
|
kind: "upsert",
|
||||||
|
workspace: {
|
||||||
|
id: "legacy-workspace",
|
||||||
|
projectId: "legacy-project",
|
||||||
|
projectDisplayName: "repo",
|
||||||
|
projectRootPath: "/repo",
|
||||||
|
projectKind: "git",
|
||||||
|
workspaceKind: "local_checkout",
|
||||||
|
name: "repo",
|
||||||
|
status: "done",
|
||||||
|
activityAt: null,
|
||||||
|
scripts: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed.type).toBe("workspace_update");
|
||||||
|
if (parsed.type !== "workspace_update" || parsed.payload.kind !== "upsert") {
|
||||||
|
throw new Error("Expected workspace_update upsert payload");
|
||||||
|
}
|
||||||
|
expect(parsed.payload.workspace.workspaceDirectory).toBe("/repo");
|
||||||
|
});
|
||||||
|
|
||||||
test("parses legacy workspace descriptor enum values", () => {
|
test("parses legacy workspace descriptor enum values", () => {
|
||||||
const parsed = SessionOutboundMessageSchema.parse({
|
const parsed = SessionOutboundMessageSchema.parse({
|
||||||
type: "workspace_update",
|
type: "workspace_update",
|
||||||
|
|||||||
Reference in New Issue
Block a user