fix(client): await handshake before checking import mode support

This commit is contained in:
paseo-bot[bot]
2026-10-16 21:18:25 +02:00
parent 96dd11a053
commit 3c32a43abc
2 changed files with 110 additions and 90 deletions
+105 -88
View File
@@ -5411,104 +5411,121 @@ test.each([undefined, false])(
);
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();
["full-access", true, true],
[undefined, undefined, true],
["full-access", true, false],
] as const)(
"imports an agent by provider handle id with mode %s",
async (modeId, supported, connectedBeforeImport) => {
const logger = createMockLogger();
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "clsk_unit_test",
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
});
clients.push(client);
const client = new DaemonClient({
url: "ws://test",
clientId: "clsk_unit_test",
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
});
clients.push(client);
const connectPromise = client.connect();
mock.triggerOpen({ features: { importSessionMode: supported } });
await connectPromise;
const connectPromise = client.connect();
if (connectedBeforeImport) {
mock.triggerOpen({ features: { importSessionMode: supported } });
await connectPromise;
}
const promise = client.importAgent({
providerId: "custom-codex",
providerHandleId: "thread-1",
cwd: "/tmp/repo",
modeId,
});
const promise = client.importAgent({
providerId: "custom-codex",
providerHandleId: "thread-1",
cwd: "/tmp/repo",
modeId,
});
expect(mock.sent).toHaveLength(1);
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "import_agent_request";
requestId: string;
providerId?: string;
providerHandleId?: string;
sessionId?: string;
cwd?: string;
modeId?: string;
let importError: unknown;
void promise.catch((error) => {
importError = error;
});
if (!connectedBeforeImport) {
await Promise.resolve();
expect(importError).toBeUndefined();
expect(mock.sent).toHaveLength(0);
mock.triggerOpen({ features: { importSessionMode: supported } });
await connectPromise;
}
await vi.waitFor(() => expect(mock.sent).toHaveLength(1));
const request = JSON.parse(String(mock.sent[0])) as {
type: "session";
message: {
type: "import_agent_request";
requestId: string;
providerId?: string;
providerHandleId?: string;
sessionId?: string;
cwd?: string;
modeId?: string;
};
};
};
expect(request.message).toMatchObject({
type: "import_agent_request",
providerId: "custom-codex",
providerHandleId: "thread-1",
cwd: "/tmp/repo",
});
expect(request.message.modeId).toBe(modeId);
expect(request.message).not.toHaveProperty("sessionId");
expect(request.message).toMatchObject({
type: "import_agent_request",
providerId: "custom-codex",
providerHandleId: "thread-1",
cwd: "/tmp/repo",
});
expect(request.message.modeId).toBe(modeId);
expect(request.message).not.toHaveProperty("sessionId");
mock.triggerMessage(
wrapSessionMessage({
type: "status",
payload: {
status: "agent_resumed",
requestId: request.message.requestId,
agentId: "agent-1",
timelineSize: 0,
agent: {
id: "agent-1",
provider: "custom-codex",
cwd: "/tmp/repo",
model: null,
features: [],
thinkingOptionId: null,
effectiveThinkingOptionId: null,
createdAt: "2026-04-30T00:00:00.000Z",
updatedAt: "2026-04-30T00:00:00.000Z",
lastUserMessageAt: null,
status: "idle",
capabilities: {
supportsStreaming: false,
supportsSessionPersistence: false,
supportsDynamicModes: false,
supportsMcpServers: false,
supportsReasoningStream: false,
supportsToolInvocations: false,
},
currentModeId: null,
availableModes: [],
pendingPermissions: [],
persistence: {
mock.triggerMessage(
wrapSessionMessage({
type: "status",
payload: {
status: "agent_resumed",
requestId: request.message.requestId,
agentId: "agent-1",
timelineSize: 0,
agent: {
id: "agent-1",
provider: "custom-codex",
sessionId: "thread-1",
nativeHandle: "thread-1",
cwd: "/tmp/repo",
model: null,
features: [],
thinkingOptionId: null,
effectiveThinkingOptionId: null,
createdAt: "2026-04-30T00:00:00.000Z",
updatedAt: "2026-04-30T00:00:00.000Z",
lastUserMessageAt: null,
status: "idle",
capabilities: {
supportsStreaming: false,
supportsSessionPersistence: false,
supportsDynamicModes: false,
supportsMcpServers: false,
supportsReasoningStream: false,
supportsToolInvocations: false,
},
currentModeId: null,
availableModes: [],
pendingPermissions: [],
persistence: {
provider: "custom-codex",
sessionId: "thread-1",
nativeHandle: "thread-1",
},
title: null,
labels: {},
requiresAttention: false,
attentionReason: null,
},
title: null,
labels: {},
requiresAttention: false,
attentionReason: null,
},
},
}),
);
}),
);
await expect(promise).resolves.toMatchObject({
id: "agent-1",
provider: "custom-codex",
});
});
await expect(promise).resolves.toMatchObject({
id: "agent-1",
provider: "custom-codex",
});
},
);
test("uses server-provided dictation finish timeout budget", async () => {
useHeartbeatClock();
+5 -2
View File
@@ -3212,8 +3212,11 @@ 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.");
if (input.modeId) {
await this.whenConnected();
if (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({