fix(client): require host support for import modes

Older daemons discard unknown import fields. Reject explicit mode selection before sending the import unless the host advertises support, while preserving imports that omit a mode.
This commit is contained in:
paseo-bot[bot]
2026-10-16 21:12:12 +02:00
parent ac65d19254
commit 96dd11a053
4 changed files with 44 additions and 4 deletions
+36 -4
View File
@@ -5381,7 +5381,39 @@ test("fetches scoped recent provider sessions", async () => {
});
});
test("imports an agent by provider handle id", async () => {
test.each([undefined, false])(
"rejects explicit import modes on hosts without support (%s)",
async (supported) => {
const transport = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "clsk_import_mode_compat",
logger: createMockLogger(),
reconnect: { enabled: false },
transportFactory: () => transport.transport,
});
clients.push(client);
const connecting = client.connect();
transport.triggerOpen({ features: { importSessionMode: supported } });
await connecting;
const importing = client.importAgent({
provider: "claude",
sessionId: "native-session",
modeId: "auto",
});
void importing.catch(() => undefined);
expect(transport.sent).toHaveLength(0);
await expect(importing).rejects.toThrow(
"Update the host to select a mode when importing an agent.",
);
},
);
test.each([
["full-access", true],
[undefined, undefined],
] as const)("imports an agent by provider handle id with mode %s", async (modeId, supported) => {
const logger = createMockLogger();
const mock = createMockTransport();
@@ -5395,14 +5427,14 @@ test("imports an agent by provider handle id", async () => {
clients.push(client);
const connectPromise = client.connect();
mock.triggerOpen();
mock.triggerOpen({ features: { importSessionMode: supported } });
await connectPromise;
const promise = client.importAgent({
providerId: "custom-codex",
providerHandleId: "thread-1",
cwd: "/tmp/repo",
modeId: "full-access",
modeId,
});
expect(mock.sent).toHaveLength(1);
@@ -5423,8 +5455,8 @@ test("imports an agent by provider handle id", async () => {
providerId: "custom-codex",
providerHandleId: "thread-1",
cwd: "/tmp/repo",
modeId: "full-access",
});
expect(request.message.modeId).toBe(modeId);
expect(request.message).not.toHaveProperty("sessionId");
mock.triggerMessage(
+4
View File
@@ -3211,6 +3211,10 @@ export class DaemonClient {
}
async importAgent(input: ImportAgentInput): Promise<AgentSnapshotPayload> {
// COMPAT(importSessionMode): added after v0.11.4, remove gate after 2027-04-16.
if (input.modeId && this.lastServerInfoMessage?.features?.importSessionMode !== true) {
throw new Error("Update the host to select a mode when importing an agent.");
}
const requestId = this.createRequestId();
const message = SessionInboundMessageSchema.parse({
type: "import_agent_request",
+2
View File
@@ -3715,6 +3715,8 @@ export const ServerInfoStatusPayloadSchema = z
hopliteProvider: z.boolean().optional(),
// COMPAT(importSessionWorkspaceTarget): added in v0.1.110, remove gate after 2027-01-16.
importSessionWorkspaceTarget: z.boolean().optional(),
// COMPAT(importSessionMode): added after v0.11.4, remove gate after 2027-04-16.
importSessionMode: z.boolean().optional(),
// COMPAT(importSessionSearch): added in v0.8.0, remove gate after 2027-03-02.
importSessionSearch: z.boolean().optional(),
// COMPAT(forgeProviders): added in v0.2.0-beta.1. Drop the gate after
@@ -1918,6 +1918,8 @@ export class VoiceAssistantWebSocketServer {
hopliteProvider: this.providerSnapshotManager.supportsConfigRequiredProvider("hoplite"),
// COMPAT(importSessionWorkspaceTarget): added in v0.1.110, remove gate after 2027-01-16.
importSessionWorkspaceTarget: true,
// COMPAT(importSessionMode): added after v0.11.4, remove gate after 2027-04-16.
importSessionMode: true,
// COMPAT(importSessionSearch): added in v0.7.3, remove gate after 2027-03-02.
importSessionSearch: true,
// COMPAT(forgeProviders): added in v0.2.0-beta.1. Drop the gate after