chore(merge): integrate main into workspace runtimes

This commit is contained in:
Mohamed Boudra
2026-08-14 13:28:32 +02:00
603 changed files with 33782 additions and 21333 deletions
@@ -1,7 +1,10 @@
import { describe, expect, test } from "vitest";
import {
getParentAgentIdFromLabels,
getOpenAgentTabLabel,
hasOpenAgentTab,
isDelegatedAgent,
isOpenAgentTabLabel,
PARENT_AGENT_ID_LABEL,
} from "./agent-labels.js";
@@ -18,4 +21,19 @@ describe("agent label policy", () => {
expect(isDelegatedAgent({ labels: { [PARENT_AGENT_ID_LABEL]: " " } })).toBe(false);
expect(isDelegatedAgent({ labels: { [PARENT_AGENT_ID_LABEL]: 42 } })).toBe(false);
});
test("treats any true client-scoped open-tab label as open", () => {
const desktopLabel = getOpenAgentTabLabel("desktop-client");
const mobileLabel = getOpenAgentTabLabel("mobile-client");
expect(hasOpenAgentTab({ [desktopLabel]: "false", [mobileLabel]: "true" })).toBe(true);
expect(hasOpenAgentTab({ [desktopLabel]: "false", [mobileLabel]: "false" })).toBe(false);
expect(hasOpenAgentTab({})).toBe(false);
});
test("recognizes only client-scoped open-tab labels", () => {
expect(isOpenAgentTabLabel(getOpenAgentTabLabel("client-a"))).toBe(true);
expect(isOpenAgentTabLabel("paseo.open-agent-tab")).toBe(false);
expect(isOpenAgentTabLabel("custom.open-agent-tab.client-a")).toBe(false);
});
});
+15
View File
@@ -1,4 +1,13 @@
export const PARENT_AGENT_ID_LABEL = "paseo.parent-agent-id";
const OPEN_AGENT_TAB_LABEL_PREFIX = "paseo.open-agent-tab.";
export function getOpenAgentTabLabel(clientId: string): string {
return `${OPEN_AGENT_TAB_LABEL_PREFIX}${clientId}`;
}
export function isOpenAgentTabLabel(label: string): boolean {
return label.startsWith(OPEN_AGENT_TAB_LABEL_PREFIX);
}
export interface AgentLabelSource {
labels?: Record<string, unknown> | null;
@@ -14,3 +23,9 @@ export function getParentAgentIdFromLabels(labels: Record<string, unknown> | nul
export function isDelegatedAgent(agent: AgentLabelSource): boolean {
return getParentAgentIdFromLabels(agent.labels) !== null;
}
export function hasOpenAgentTab(labels: Record<string, unknown> | null | undefined): boolean {
return Object.entries(labels ?? {}).some(
([label, value]) => isOpenAgentTabLabel(label) && value === "true",
);
}
+9 -1
View File
@@ -339,12 +339,20 @@ export interface CompactionTimelineItem {
preTokens?: number;
}
export interface AgentTaskItem {
text: string;
completed: boolean;
id?: string;
status?: "pending" | "in_progress" | "completed";
activeForm?: string;
}
export type AgentTimelineItem =
| { type: "user_message"; text: string; messageId?: string; clientMessageId?: string }
| { type: "assistant_message"; text: string; messageId?: string }
| { type: "reasoning"; text: string }
| ToolCallTimelineItem
| { type: "todo"; items: { text: string; completed: boolean }[] }
| { type: "todo"; items: AgentTaskItem[] }
| { type: "error"; message: string }
| CompactionTimelineItem;
@@ -1,6 +1,8 @@
import { z } from "zod";
import { ChatMessageSchema, ChatRoomDetailSchema } from "./types.js";
// COMPAT(chatRooms): retained after the v0.3.0 feature removal; remove after 2027-02-09 when mixed-version peers no longer send legacy messages.
export const ChatCreateRequestSchema = z.object({
type: z.literal("chat/create"),
requestId: z.string(),
+2
View File
@@ -1,5 +1,7 @@
import { z } from "zod";
// COMPAT(chatRooms): retained after the v0.3.0 feature removal; remove after 2027-02-09 when mixed-version peers no longer send legacy messages.
export const ChatRoomSchema = z.object({
id: z.string(),
name: z.string(),
@@ -1,6 +1,8 @@
import { z } from "zod";
import { AgentProviderSchema } from "../provider-manifest.js";
// COMPAT(agentLoops): retained after the v0.3.0 feature removal; remove after 2027-02-09 when mixed-version peers no longer send legacy messages.
export const LoopLogEntrySchema = z.object({
seq: z.number().int().positive(),
timestamp: z.string(),
@@ -0,0 +1,169 @@
import { describe, expect, test } from "vitest";
import {
CheckoutDiscardChangesRequestSchema,
CheckoutDiscardChangesResponseSchema,
FileEntryCreateRequestSchema,
FileEntryCreateResponseSchema,
FileEntryDeleteRequestSchema,
FileEntryDeleteResponseSchema,
FileEntryDuplicateRequestSchema,
FileEntryDuplicateResponseSchema,
FileEntryRenameRequestSchema,
FileEntryRenameResponseSchema,
ServerInfoStatusPayloadSchema,
} from "./messages.js";
describe("file context action messages", () => {
test("keeps context-action capabilities optional and preserves advertised support", () => {
const legacy = ServerInfoStatusPayloadSchema.parse({
status: "server_info",
serverId: "server-1",
features: {},
});
expect(legacy.features?.fsEntryOps).toBeUndefined();
expect(legacy.features?.fsEntryDuplicate).toBeUndefined();
expect(legacy.features?.checkoutDiscardChanges).toBeUndefined();
const current = ServerInfoStatusPayloadSchema.parse({
status: "server_info",
serverId: "server-1",
features: {
fsEntryOps: true,
fsEntryDuplicate: true,
checkoutDiscardChanges: true,
},
});
expect(current.features).toMatchObject({
fsEntryOps: true,
fsEntryDuplicate: true,
checkoutDiscardChanges: true,
});
});
test("rejects discard requests without a path", () => {
expect(() =>
CheckoutDiscardChangesRequestSchema.parse({
type: "checkout.discard_changes.request",
cwd: "/workspace",
paths: [],
requestId: "discard-empty",
}),
).toThrow();
});
test("round-trips file entry create requests and responses", () => {
const request = {
type: "fs.entry.create.request",
cwd: "/workspace",
parentPath: "src/components",
name: "button.tsx",
kind: "file",
requestId: "create-1",
};
expect(FileEntryCreateRequestSchema.parse(request)).toEqual(request);
const response = {
type: "fs.entry.create.response",
payload: {
cwd: "/workspace",
parentPath: "src/components",
path: "src/components/button.tsx",
success: true,
error: null,
requestId: "create-1",
},
};
expect(FileEntryCreateResponseSchema.parse(response)).toEqual(response);
});
test("round-trips file entry rename requests and responses", () => {
const request = {
type: "fs.entry.rename.request",
cwd: "/workspace",
path: "src/old.ts",
name: "new.ts",
requestId: "rename-1",
};
expect(FileEntryRenameRequestSchema.parse(request)).toEqual(request);
const response = {
type: "fs.entry.rename.response",
payload: {
cwd: "/workspace",
path: "src/old.ts",
renamedPath: "src/new.ts",
success: true,
error: null,
requestId: "rename-1",
},
};
expect(FileEntryRenameResponseSchema.parse(response)).toEqual(response);
});
test("round-trips file entry duplicate requests and responses", () => {
const request = {
type: "fs.entry.duplicate.request",
cwd: "/workspace",
path: "src/button.tsx",
requestId: "duplicate-1",
};
expect(FileEntryDuplicateRequestSchema.parse(request)).toEqual(request);
const response = {
type: "fs.entry.duplicate.response",
payload: {
cwd: "/workspace",
path: "src/button.tsx",
duplicatedPath: "src/button copy.tsx",
success: true,
error: null,
requestId: "duplicate-1",
},
};
expect(FileEntryDuplicateResponseSchema.parse(response)).toEqual(response);
});
test("round-trips file entry delete requests and responses", () => {
const request = {
type: "fs.entry.delete.request",
cwd: "/workspace",
path: "src/old.ts",
requestId: "delete-1",
};
expect(FileEntryDeleteRequestSchema.parse(request)).toEqual(request);
const response = {
type: "fs.entry.delete.response",
payload: {
cwd: "/workspace",
path: "src/old.ts",
success: false,
error: "File or folder no longer exists",
requestId: "delete-1",
},
};
expect(FileEntryDeleteResponseSchema.parse(response)).toEqual(response);
});
test("round-trips checkout discard requests and responses", () => {
const request = {
type: "checkout.discard_changes.request",
cwd: "/workspace",
workspaceId: "workspace-a",
paths: ["src", "README.md"],
requestId: "discard-1",
};
expect(CheckoutDiscardChangesRequestSchema.parse(request)).toEqual(request);
const response = {
type: "checkout.discard_changes.response",
payload: {
cwd: "/workspace",
success: false,
error: { code: "UNKNOWN", message: "discard failed" },
requestId: "discard-1",
},
};
expect(CheckoutDiscardChangesResponseSchema.parse(response)).toEqual(response);
});
});
+23
View File
@@ -5,6 +5,7 @@ import {
parseServerInfoStatusPayload,
SessionInboundMessageSchema,
SessionOutboundMessageSchema,
WorkspaceProjectDescriptorPayloadSchema,
} from "./messages.js";
function workspaceDescriptor(overrides: Record<string, unknown> = {}) {
@@ -53,6 +54,28 @@ describe("project icon message security", () => {
});
});
describe("project icon revision compatibility", () => {
const project = {
projectId: "project-1",
projectDisplayName: "Project",
projectRootPath: "/repo/project",
projectKind: "git" as const,
};
test("accepts an old project snapshot without an effective icon revision", () => {
expect(WorkspaceProjectDescriptorPayloadSchema.parse(project)).toEqual(project);
});
test("accepts an effective icon revision on a new project snapshot", () => {
expect(
WorkspaceProjectDescriptorPayloadSchema.parse({
...project,
projectIconRevision: "automatic:none:v1",
}),
).toEqual({ ...project, projectIconRevision: "automatic:none:v1" });
});
});
describe("workspace descriptor message compatibility", () => {
test("old-shaped fetch_workspaces_response without project still parses", () => {
const parsed = SessionOutboundMessageSchema.parse(
+283 -2
View File
@@ -136,6 +136,35 @@ export const TerminalProfileSchema = z
export type TerminalProfile = z.infer<typeof TerminalProfileSchema>;
/**
* A named launch bundle: a provider plus the agent-config values a client would
* otherwise set one control at a time. Field names mirror `AgentSessionConfig`
* so applying a profile is a copy rather than a translation table.
*
* There is deliberately no system prompt here. `AgentSessionConfig.systemPrompt`
* is creation-only, so a profile carrying one would apply when starting a new
* agent and silently do nothing when applied to a running one.
*/
export const AgentProfileSchema = z
.object({
id: z.string(),
name: z.string(),
/** A key into the client's icon registry, not a glyph. Unknown keys draw the default. */
icon: z.string().optional(),
/** An identity colour name shared with host badges. Unknown values draw unthemed. */
color: z.string().optional(),
provider: z.string(),
model: z.string().optional(),
modeId: z.string().optional(),
thinkingOptionId: z.string().optional(),
featureValues: z.record(z.string(), z.unknown()).optional(),
/** Free text, surfaced to orchestrating agents by the `list_profiles` MCP tool. */
notes: z.string().optional(),
})
.passthrough();
export type AgentProfile = z.infer<typeof AgentProfileSchema>;
const MutableBrowserToolsConfigSchema = z
.object({
enabled: z.boolean().default(false),
@@ -162,6 +191,7 @@ export const MutableDaemonConfigSchema = z
enableTerminalAgentHooks: z.boolean().default(false),
appendSystemPrompt: z.string().default(""),
terminalProfiles: z.array(TerminalProfileSchema).optional(),
agentProfiles: z.array(AgentProfileSchema).optional(),
})
.passthrough();
@@ -179,6 +209,7 @@ export const MutableDaemonConfigPatchSchema = z
enableTerminalAgentHooks: z.boolean().optional(),
appendSystemPrompt: z.string().optional(),
terminalProfiles: z.array(TerminalProfileSchema).optional(),
agentProfiles: z.array(AgentProfileSchema).optional(),
})
.partial()
.passthrough();
@@ -630,6 +661,9 @@ export const AgentTimelineItemPayloadSchema: z.ZodType<AgentTimelineItem, unknow
z.object({
text: z.string(),
completed: z.boolean(),
id: z.string().optional(),
status: z.enum(["pending", "in_progress", "completed"]).optional(),
activeForm: z.string().optional(),
}),
),
}),
@@ -1072,6 +1106,26 @@ export const SendAgentMessageSchema = z.object({
// Agent RPCs (requestId-correlated)
// ============================================================================
const DirectorySyncRequestSchema = z.object({
generation: z.string().optional(),
afterSeq: z.number().int().nonnegative().optional(),
});
const DirectorySyncRemovalSchema = z.object({
id: z.string(),
seq: z.number().int().positive(),
});
const DirectorySyncMetadataSchema = z.object({
generation: z.string(),
headSeq: z.number().int().nonnegative(),
mode: z.enum(["snapshot", "changes"]),
reason: z
.enum(["no_cursor", "generation_changed", "cursor_expired", "changes_too_large"])
.optional(),
removals: z.array(DirectorySyncRemovalSchema),
});
export const FetchAgentsRequestMessageSchema = z.object({
type: z.literal("fetch_agents_request"),
requestId: z.string(),
@@ -1096,6 +1150,8 @@ export const FetchAgentsRequestMessageSchema = z.object({
subscriptionId: z.string().optional(),
})
.optional(),
// COMPAT(directorySync): added in v0.3.x, remove optional after 2027-02-12.
sync: DirectorySyncRequestSchema.optional(),
});
const WorkspaceStateBucketSchema = z.enum([
@@ -1136,11 +1192,15 @@ export const FetchWorkspacesRequestMessageSchema = z.object({
subscriptionId: z.string().optional(),
})
.optional(),
// COMPAT(directorySync): added in v0.3.x, remove optional after 2027-02-12.
sync: DirectorySyncRequestSchema.optional(),
});
export const ProjectListRequestMessageSchema = z.object({
type: z.literal("project.list.request"),
requestId: z.string(),
// COMPAT(directorySync): added in v0.3.x, remove optional after 2027-02-12.
sync: DirectorySyncRequestSchema.optional(),
});
export const FetchAgentHistoryRequestMessageSchema = z.object({
@@ -1549,6 +1609,38 @@ export const SetAgentFeatureResponseMessageSchema = z.object({
payload: AgentActionResponsePayloadSchema,
});
/**
* Every agent-config value a client can change in one shot. Each field is
* optional and an omitted field is left alone; `null` on model and thinking
* clears them, matching the single-field RPCs above.
*/
export const AgentConfigApplySchema = z.object({
modelId: z.string().nullable().optional(),
modeId: z.string().optional(),
thinkingOptionId: z.string().nullable().optional(),
featureValues: z.record(z.string(), z.unknown()).optional(),
});
export type AgentConfigApply = z.infer<typeof AgentConfigApplySchema>;
/**
* Applies a whole config bundle to one agent. The four single-field RPCs above
* stay for individual control edits. One request prevents client interruption
* and other mutations from interleaving between bundle steps; provider-level
* rejection can still leave earlier steps applied.
*/
export const AgentConfigApplyRequestMessageSchema = z.object({
type: z.literal("agent.config.apply.request"),
agentId: z.string(),
config: AgentConfigApplySchema,
requestId: z.string(),
});
export const AgentConfigApplyResponseMessageSchema = z.object({
type: z.literal("agent.config.apply.response"),
payload: AgentActionResponsePayloadSchema,
});
export const AgentDetachRequestMessageSchema = z.object({
type: z.literal("agent.detach.request"),
agentId: z.string(),
@@ -1793,6 +1885,14 @@ export const CheckoutRefreshRequestSchema = z.object({
requestId: z.string(),
});
export const CheckoutDiscardChangesRequestSchema = z.object({
type: z.literal("checkout.discard_changes.request"),
cwd: z.string(),
workspaceId: OptionalWorkspaceGitIdSchema,
paths: z.array(z.string()).min(1),
requestId: z.string(),
});
export const CheckoutPrCreateRequestSchema = z.object({
type: z.literal("checkout_pr_create_request"),
cwd: WorkspaceGitCwdSchema,
@@ -2258,6 +2358,8 @@ const DiffHunkSchema = z.object({
const ParsedDiffFileSchema = z.object({
path: z.string(),
// COMPAT(diffOldPath): added in v0.3.0, remove gate after 2027-02-09.
oldPath: z.string().optional(),
isNew: z.boolean(),
isDeleted: z.boolean(),
additions: z.number(),
@@ -2348,6 +2450,37 @@ export const FileWriteRequestSchema = z.object({
workspaceId: z.string().optional(),
});
export const FileEntryCreateRequestSchema = z.object({
type: z.literal("fs.entry.create.request"),
cwd: z.string(),
parentPath: z.string(),
name: z.string(),
kind: z.enum(["file", "directory"]),
requestId: z.string(),
});
export const FileEntryRenameRequestSchema = z.object({
type: z.literal("fs.entry.rename.request"),
cwd: z.string(),
path: z.string(),
name: z.string(),
requestId: z.string(),
});
export const FileEntryDuplicateRequestSchema = z.object({
type: z.literal("fs.entry.duplicate.request"),
cwd: z.string(),
path: z.string(),
requestId: z.string(),
});
export const FileEntryDeleteRequestSchema = z.object({
type: z.literal("fs.entry.delete.request"),
cwd: z.string(),
path: z.string(),
requestId: z.string(),
});
export const ProjectIconRequestSchema = z.object({
type: z.literal("project_icon_request"),
cwd: z.string(),
@@ -2428,6 +2561,19 @@ export const RegisterPushTokenMessageSchema = z.object({
token: z.string(),
});
export const PushUnregisterRequestSchema = z.object({
type: z.literal("push.unregister.request"),
token: z.string(),
requestId: z.string(),
});
export const PushUnregisterResponseSchema = z.object({
type: z.literal("push.unregister.response"),
payload: z.object({
requestId: z.string(),
}),
});
// ============================================================================
// Terminal Messages
// ============================================================================
@@ -2708,6 +2854,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
SetAgentModelRequestMessageSchema,
SetAgentThinkingRequestMessageSchema,
SetAgentFeatureRequestMessageSchema,
AgentConfigApplyRequestMessageSchema,
AgentDetachRequestMessageSchema,
AgentRewindRequestMessageSchema,
AgentPermissionResponseMessageSchema,
@@ -2720,6 +2867,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
CheckoutPullRequestSchema,
CheckoutPushRequestSchema,
CheckoutRefreshRequestSchema,
CheckoutDiscardChangesRequestSchema,
CheckoutPrCreateRequestSchema,
CheckoutPrMergeRequestSchema,
CheckoutForgeSetAutoMergeRequestSchema,
@@ -2760,6 +2908,10 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
FileSubscribeRequestSchema,
FileUnsubscribeRequestSchema,
FileWriteRequestSchema,
FileEntryCreateRequestSchema,
FileEntryRenameRequestSchema,
FileEntryDuplicateRequestSchema,
FileEntryDeleteRequestSchema,
ProjectIconRequestSchema,
ProjectIconGetRequestSchema,
FileDownloadTokenRequestSchema,
@@ -2769,6 +2921,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
PingMessageSchema,
ListCommandsRequestSchema,
RegisterPushTokenMessageSchema,
PushUnregisterRequestSchema,
ListTerminalsRequestSchema,
SubscribeTerminalsRequestSchema,
UnsubscribeTerminalsRequestSchema,
@@ -2973,6 +3126,8 @@ export const ServerInfoStatusPayloadSchema = z
providersSnapshot: z.boolean().optional(),
// COMPAT(providersSnapshotCwd): added in v0.3.2, remove gate after 2027-02-10.
providersSnapshotCwd: z.boolean().optional(),
// COMPAT(directorySync): added in v0.3.x, remove gate after 2027-02-12.
directorySync: z.boolean().optional(),
// COMPAT(checkoutForgeSetAutoMerge): added in v0.1.106, remove old
// checkoutGithubSetAutoMerge fallback after 2026-12-28.
checkoutForgeSetAutoMerge: z.boolean().optional(),
@@ -2987,6 +3142,8 @@ export const ServerInfoStatusPayloadSchema = z
daemonStatusRpc: z.boolean().optional(),
// COMPAT(relayConfig): added in v0.2.6, remove gate after 2027-01-31.
relayConfig: z.boolean().optional(),
// COMPAT(pushTokenRevocation): added in v0.3.2, remove gate after 2027-02-10.
pushTokenRevocation: z.boolean().optional(),
// COMPAT(terminalRestoreModes): added in v0.1.81, remove gate after 2026-11-23.
"terminal-restore-modes": z.boolean().optional(),
// COMPAT(terminalInputModeReplay): added in v0.2.6, remove gate after 2027-02-02.
@@ -3067,6 +3224,19 @@ export const ServerInfoStatusPayloadSchema = z
workspaceScriptManagement: z.boolean().optional(),
// COMPAT(projectCustomIcon): added in v0.2.0, remove after 2027-01-20.
projectCustomIcon: z.boolean().optional(),
// COMPAT(fsEntryOps): added in v0.3.0, remove gate after 2027-02-08.
fsEntryOps: z.boolean().optional(),
// COMPAT(fsEntryDuplicate): added in v0.3.0, remove gate after 2027-02-09.
fsEntryDuplicate: z.boolean().optional(),
// COMPAT(checkoutDiscardChanges): added in v0.3.0, remove gate after 2027-02-08.
checkoutDiscardChanges: z.boolean().optional(),
// COMPAT(agentProfiles): added in v0.3.2, remove gate after 2027-02-11.
// An older daemon parses its persisted config strictly, so writing
// agentProfiles to one is silently dropped. The client hides the feature
// rather than letting a save appear to succeed.
agentProfiles: z.boolean().optional(),
// COMPAT(agentConfigApply): added in v0.3.2, remove gate after 2027-02-11.
agentConfigApply: z.boolean().optional(),
})
.optional(),
})
@@ -3382,6 +3552,8 @@ export const WorkspaceDescriptorPayloadSchema = z
// Old daemons omit it; absent means the client falls back to GitHub.
forge: z.string().optional(),
project: ProjectPlacementPayloadSchema.optional(),
// COMPAT(directorySync): sequence of this latest directory projection.
syncSeq: z.number().int().positive().optional(),
})
.transform((workspace) => ({
...workspace,
@@ -3395,10 +3567,14 @@ export const AgentUpdateMessageSchema = z.object({
kind: z.literal("upsert"),
agent: AgentSnapshotPayloadSchema,
project: ProjectPlacementPayloadSchema.nullable().optional(),
generation: z.string().optional(),
seq: z.number().int().positive().optional(),
}),
z.object({
kind: z.literal("remove"),
agentId: z.string(),
generation: z.string().optional(),
seq: z.number().int().positive().optional(),
}),
]),
});
@@ -3456,6 +3632,8 @@ const AgentDirectoryResponseEntrySchema = z.object({
// anyway; sending it keeps the client from re-deriving a second opinion that
// could disagree with the ranking it is explaining.
searchMatches: z.array(AgentSearchMatchSchema).optional(),
// COMPAT(directorySync): sequence of this latest directory projection.
syncSeq: z.number().int().positive().optional(),
});
const AgentDirectoryPageInfoSchema = z.object({
@@ -3471,6 +3649,7 @@ export const FetchAgentsResponseMessageSchema = z.object({
subscriptionId: z.string().nullable().optional(),
entries: z.array(AgentDirectoryResponseEntrySchema),
pageInfo: AgentDirectoryPageInfoSchema,
sync: DirectorySyncMetadataSchema.optional(),
}),
});
@@ -3508,8 +3687,14 @@ export const WorkspaceProjectDescriptorPayloadSchema = z.object({
projectCustomName: z.string().nullable().optional(),
// COMPAT(projectCustomIcon): added in v0.2.0, remove after 2027-01-20.
projectCustomIconRevision: z.string().nullable().optional(),
// Fingerprints the effective icon, including automatic discovery and the
// absence of an icon. Clients may persist icon results against this value.
// COMPAT(projectIconCache): added in v0.2.7, remove optional after 2027-02-12.
projectIconRevision: z.string().optional(),
projectRootPath: z.string(),
projectKind: z.enum(["git", "non_git", "directory"]),
// COMPAT(directorySync): sequence of this latest directory projection.
syncSeq: z.number().int().positive().optional(),
});
export const FetchWorkspacesResponseMessageSchema = z.object({
@@ -3527,6 +3712,7 @@ export const FetchWorkspacesResponseMessageSchema = z.object({
prevCursor: z.string().nullable(),
hasMore: z.boolean(),
}),
sync: DirectorySyncMetadataSchema.optional(),
}),
});
@@ -3536,6 +3722,8 @@ export const WorkspaceUpdateMessageSchema = z.object({
z.object({
kind: z.literal("upsert"),
workspace: WorkspaceDescriptorPayloadSchema,
generation: z.string().optional(),
seq: z.number().int().positive().optional(),
}),
z.object({
kind: z.literal("remove"),
@@ -3550,6 +3738,8 @@ export const WorkspaceUpdateMessageSchema = z.object({
// Project removal is represented on the existing workspace update channel
// so old clients can still parse the message and ignore the extra field.
removedProjectId: z.string().optional(),
generation: z.string().optional(),
seq: z.number().int().positive().optional(),
}),
]),
});
@@ -3557,8 +3747,18 @@ export const WorkspaceUpdateMessageSchema = z.object({
export const ProjectUpdateMessageSchema = z.object({
type: z.literal("project.update"),
payload: z.discriminatedUnion("kind", [
z.object({ kind: z.literal("upsert"), project: WorkspaceProjectDescriptorPayloadSchema }),
z.object({ kind: z.literal("remove"), projectId: z.string() }),
z.object({
kind: z.literal("upsert"),
project: WorkspaceProjectDescriptorPayloadSchema,
generation: z.string().optional(),
seq: z.number().int().positive().optional(),
}),
z.object({
kind: z.literal("remove"),
projectId: z.string(),
generation: z.string().optional(),
seq: z.number().int().positive().optional(),
}),
]),
});
@@ -3567,6 +3767,7 @@ export const ProjectListResponseMessageSchema = z.object({
payload: z.object({
requestId: z.string(),
projects: z.array(WorkspaceProjectDescriptorPayloadSchema),
sync: DirectorySyncMetadataSchema.optional(),
}),
});
@@ -4162,6 +4363,7 @@ export const ReadProjectConfigResponseMessageSchema = z.object({
ok: z.literal(true),
config: PaseoConfigRawSchema.nullable(),
revision: PaseoConfigRevisionSchema.nullable(),
hasUncommittedWorktreeSetupChanges: z.boolean().optional(),
}),
z.object({
requestId: z.string(),
@@ -4183,6 +4385,7 @@ export const WriteProjectConfigResponseMessageSchema = z.object({
ok: z.literal(true),
config: PaseoConfigRawSchema,
revision: PaseoConfigRevisionSchema,
hasUncommittedWorktreeSetupChanges: z.boolean().optional(),
}),
z.object({
requestId: z.string(),
@@ -4579,6 +4782,16 @@ export const CheckoutGithubSetAutoMergeResponseSchema = z.object({
}),
});
export const CheckoutDiscardChangesResponseSchema = z.object({
type: z.literal("checkout.discard_changes.response"),
payload: z.object({
cwd: z.string(),
success: z.boolean(),
error: CheckoutErrorSchema.nullable(),
requestId: z.string(),
}),
});
export const CheckoutCommitsListResponseSchema = z.object({
type: z.literal("checkout.commits.list.response"),
payload: z.object({
@@ -5043,6 +5256,53 @@ export const FileWriteResponseSchema = z.object({
}),
});
export const FileEntryCreateResponseSchema = z.object({
type: z.literal("fs.entry.create.response"),
payload: z.object({
cwd: z.string(),
parentPath: z.string(),
path: z.string().nullable(),
success: z.boolean(),
error: z.string().nullable(),
requestId: z.string(),
}),
});
export const FileEntryRenameResponseSchema = z.object({
type: z.literal("fs.entry.rename.response"),
payload: z.object({
cwd: z.string(),
path: z.string(),
renamedPath: z.string().nullable(),
success: z.boolean(),
error: z.string().nullable(),
requestId: z.string(),
}),
});
export const FileEntryDuplicateResponseSchema = z.object({
type: z.literal("fs.entry.duplicate.response"),
payload: z.object({
cwd: z.string(),
path: z.string(),
duplicatedPath: z.string().nullable(),
success: z.boolean(),
error: z.string().nullable(),
requestId: z.string(),
}),
});
export const FileEntryDeleteResponseSchema = z.object({
type: z.literal("fs.entry.delete.response"),
payload: z.object({
cwd: z.string(),
path: z.string(),
success: z.boolean(),
error: z.string().nullable(),
requestId: z.string(),
}),
});
export const FileUpdateSchema = z.object({
type: z.literal("fs.file.update"),
payload: z.object({
@@ -5554,6 +5814,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
DictationStreamErrorMessageSchema,
StatusMessageSchema,
PongMessageSchema,
PushUnregisterResponseSchema,
RpcErrorMessageSchema,
ArtifactMessageSchema,
AgentUpdateMessageSchema,
@@ -5612,6 +5873,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
SetAgentModelResponseMessageSchema,
SetAgentThinkingResponseMessageSchema,
SetAgentFeatureResponseMessageSchema,
AgentConfigApplyResponseMessageSchema,
AgentDetachResponseMessageSchema,
AgentRewindResponseMessageSchema,
UpdateAgentResponseMessageSchema,
@@ -5638,6 +5900,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
CheckoutPullResponseSchema,
CheckoutPushResponseSchema,
CheckoutRefreshResponseSchema,
CheckoutDiscardChangesResponseSchema,
CheckoutPrCreateResponseSchema,
CheckoutPrMergeResponseSchema,
CheckoutForgeSetAutoMergeResponseSchema,
@@ -5665,6 +5928,10 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
FileSubscribeResponseSchema,
FileUnsubscribeResponseSchema,
FileWriteResponseSchema,
FileEntryCreateResponseSchema,
FileEntryRenameResponseSchema,
FileEntryDuplicateResponseSchema,
FileEntryDeleteResponseSchema,
FileUpdateSchema,
ProjectIconResponseSchema,
ProjectIconGetResponseSchema,
@@ -5801,6 +6068,7 @@ export type SetAgentModeResponseMessage = z.infer<typeof SetAgentModeResponseMes
export type SetAgentModelResponseMessage = z.infer<typeof SetAgentModelResponseMessageSchema>;
export type SetAgentThinkingResponseMessage = z.infer<typeof SetAgentThinkingResponseMessageSchema>;
export type SetAgentFeatureResponseMessage = z.infer<typeof SetAgentFeatureResponseMessageSchema>;
export type AgentConfigApplyResponseMessage = z.infer<typeof AgentConfigApplyResponseMessageSchema>;
export type AgentDetachResponseMessage = z.infer<typeof AgentDetachResponseMessageSchema>;
export type AgentRewindResponseMessage = z.infer<typeof AgentRewindResponseMessageSchema>;
export type UpdateAgentResponseMessage = z.infer<typeof UpdateAgentResponseMessageSchema>;
@@ -5971,6 +6239,7 @@ export type SetAgentModeRequestMessage = z.infer<typeof SetAgentModeRequestMessa
export type SetAgentModelRequestMessage = z.infer<typeof SetAgentModelRequestMessageSchema>;
export type SetAgentThinkingRequestMessage = z.infer<typeof SetAgentThinkingRequestMessageSchema>;
export type SetAgentFeatureRequestMessage = z.infer<typeof SetAgentFeatureRequestMessageSchema>;
export type AgentConfigApplyRequestMessage = z.infer<typeof AgentConfigApplyRequestMessageSchema>;
export type AgentDetachRequestMessage = z.infer<typeof AgentDetachRequestMessageSchema>;
export type AgentPermissionResponseMessage = z.infer<typeof AgentPermissionResponseMessageSchema>;
export type CheckoutStatusRequest = z.infer<typeof CheckoutStatusRequestSchema>;
@@ -5992,6 +6261,8 @@ export type CheckoutPushRequest = z.infer<typeof CheckoutPushRequestSchema>;
export type CheckoutPushResponse = z.infer<typeof CheckoutPushResponseSchema>;
export type CheckoutRefreshRequest = z.infer<typeof CheckoutRefreshRequestSchema>;
export type CheckoutRefreshResponse = z.infer<typeof CheckoutRefreshResponseSchema>;
export type CheckoutDiscardChangesRequest = z.infer<typeof CheckoutDiscardChangesRequestSchema>;
export type CheckoutDiscardChangesResponse = z.infer<typeof CheckoutDiscardChangesResponseSchema>;
export type CheckoutCommitFile = z.infer<typeof CheckoutCommitFileSchema>;
export type CheckoutCommit = z.infer<typeof CheckoutCommitSchema>;
export type CheckoutCommitsListRequest = z.infer<typeof CheckoutCommitsListRequestSchema>;
@@ -6095,6 +6366,14 @@ export type FileUnsubscribeRequest = z.infer<typeof FileUnsubscribeRequestSchema
export type FileUnsubscribeResponse = z.infer<typeof FileUnsubscribeResponseSchema>;
export type FileWriteRequest = z.infer<typeof FileWriteRequestSchema>;
export type FileWriteResponse = z.infer<typeof FileWriteResponseSchema>;
export type FileEntryCreateRequest = z.infer<typeof FileEntryCreateRequestSchema>;
export type FileEntryCreateResponse = z.infer<typeof FileEntryCreateResponseSchema>;
export type FileEntryRenameRequest = z.infer<typeof FileEntryRenameRequestSchema>;
export type FileEntryRenameResponse = z.infer<typeof FileEntryRenameResponseSchema>;
export type FileEntryDuplicateRequest = z.infer<typeof FileEntryDuplicateRequestSchema>;
export type FileEntryDuplicateResponse = z.infer<typeof FileEntryDuplicateResponseSchema>;
export type FileEntryDeleteRequest = z.infer<typeof FileEntryDeleteRequestSchema>;
export type FileEntryDeleteResponse = z.infer<typeof FileEntryDeleteResponseSchema>;
export type FileWriteResult = z.infer<typeof FileWriteResultSchema>;
export type FileUpdate = z.infer<typeof FileUpdateSchema>;
export type ProjectIconRequest = z.infer<typeof ProjectIconRequestSchema>;
@@ -6116,6 +6395,8 @@ export type ClientHeartbeatMessage = z.infer<typeof ClientHeartbeatMessageSchema
export type ListCommandsRequest = z.infer<typeof ListCommandsRequestSchema>;
export type ListCommandsResponse = z.infer<typeof ListCommandsResponseSchema>;
export type RegisterPushTokenMessage = z.infer<typeof RegisterPushTokenMessageSchema>;
export type PushUnregisterRequest = z.infer<typeof PushUnregisterRequestSchema>;
export type PushUnregisterResponse = z.infer<typeof PushUnregisterResponseSchema>;
// Terminal message types
export type ListTerminalsRequest = z.infer<typeof ListTerminalsRequestSchema>;
@@ -117,6 +117,40 @@ describe("wire schema compatibility", () => {
});
});
test("task progress fields are optional on the wire", () => {
expect(
AgentTimelineItemPayloadSchema.parse({
type: "todo",
items: [{ text: "Legacy task", completed: false }],
}),
).toEqual({ type: "todo", items: [{ text: "Legacy task", completed: false }] });
expect(
AgentTimelineItemPayloadSchema.parse({
type: "todo",
items: [
{
id: "task-1",
text: "Current task",
activeForm: "Working on current task",
status: "in_progress",
completed: false,
},
],
}),
).toEqual({
type: "todo",
items: [
{
id: "task-1",
text: "Current task",
activeForm: "Working on current task",
status: "in_progress",
completed: false,
},
],
});
});
test("sub_agent tool-call payload still parses against the v0.1.65-beta.3 schema", () => {
const parsed = LegacySubAgentToolCallSchema.parse({
type: "tool_call",
@@ -97,6 +97,59 @@ describe("workspace message schemas", () => {
expect(activeScoped.scope).toBe("active");
});
test("parses optional sequenced directory requests and responses", () => {
expect(
SessionInboundMessageSchema.parse({
type: "project.list.request",
requestId: "projects-sync",
sync: { generation: "daemon-generation", afterSeq: 7 },
}),
).toMatchObject({ sync: { generation: "daemon-generation", afterSeq: 7 } });
expect(
SessionInboundMessageSchema.parse({
type: "fetch_workspaces_request",
requestId: "workspaces-sync",
sync: { generation: "daemon-generation", afterSeq: 11 },
}),
).toMatchObject({ sync: { generation: "daemon-generation", afterSeq: 11 } });
expect(
SessionInboundMessageSchema.parse({
type: "fetch_agents_request",
requestId: "agents-sync",
scope: "active",
sync: { generation: "daemon-generation", afterSeq: 13 },
}),
).toMatchObject({ sync: { generation: "daemon-generation", afterSeq: 13 } });
const response = SessionOutboundMessageSchema.parse({
type: "project.list.response",
payload: {
requestId: "projects-sync",
projects: [
{
projectId: "project-1",
projectDisplayName: "Project",
projectRootPath: "/repo",
projectKind: "git",
syncSeq: 12,
},
],
sync: {
generation: "daemon-generation",
headSeq: 12,
mode: "changes",
removals: [{ id: "project-removed", seq: 10 }],
},
},
});
expect(response).toMatchObject({
payload: {
projects: [{ projectId: "project-1", syncSeq: 12 }],
sync: { headSeq: 12, removals: [{ id: "project-removed", seq: 10 }] },
},
});
});
test("parses agent_update without project placement", () => {
const result = SessionOutboundMessageSchema.safeParse({
type: "agent_update",