mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-10 10:25:03 +00:00
chore(merge): integrate main into workspace runtimes
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user