refactor: migrate from clientSessionKey to clientId identity
This commit is contained in:
@@ -13,12 +13,24 @@ describe("relay websocket URL versioning", () => {
|
||||
endpoint: "relay.paseo.sh:443",
|
||||
serverId: "srv_test",
|
||||
role: "client",
|
||||
clientSessionKey: "clsk_test",
|
||||
})
|
||||
);
|
||||
|
||||
expect(url.searchParams.get("v")).toBe(CURRENT_RELAY_PROTOCOL_VERSION);
|
||||
expect(url.searchParams.get("clientId")).toBe("clsk_test");
|
||||
expect(url.searchParams.has("connectionId")).toBe(false);
|
||||
});
|
||||
|
||||
test("includes connectionId when provided (server data sockets)", () => {
|
||||
const url = new URL(
|
||||
buildRelayWebSocketUrl({
|
||||
endpoint: "relay.paseo.sh:443",
|
||||
serverId: "srv_test",
|
||||
role: "server",
|
||||
connectionId: "conn_abc123",
|
||||
})
|
||||
);
|
||||
|
||||
expect(url.searchParams.get("connectionId")).toBe("conn_abc123");
|
||||
});
|
||||
|
||||
test("allows explicitly requesting v1 relay URLs", () => {
|
||||
|
||||
@@ -83,28 +83,23 @@ function shouldUseSecureWebSocket(port: number): boolean {
|
||||
}
|
||||
|
||||
export function buildDaemonWebSocketUrl(
|
||||
endpoint: string,
|
||||
params?: { clientSessionKey?: string }
|
||||
endpoint: string
|
||||
): string {
|
||||
const { host, port, isIpv6 } = parseHostPort(endpoint);
|
||||
const protocol = shouldUseSecureWebSocket(port) ? "wss" : "ws";
|
||||
const hostPart = isIpv6 ? `[${host}]` : host;
|
||||
const url = new URL(`${protocol}://${hostPart}:${port}/ws`);
|
||||
if (
|
||||
typeof params?.clientSessionKey === "string" &&
|
||||
params.clientSessionKey.trim().length > 0
|
||||
) {
|
||||
url.searchParams.set("clientSessionKey", params.clientSessionKey.trim());
|
||||
}
|
||||
return url.toString();
|
||||
return new URL(`${protocol}://${hostPart}:${port}/ws`).toString();
|
||||
}
|
||||
|
||||
export function buildRelayWebSocketUrl(params: {
|
||||
endpoint: string;
|
||||
serverId: string;
|
||||
role: RelayRole;
|
||||
clientId?: string;
|
||||
clientSessionKey?: string;
|
||||
/**
|
||||
* Per-connection routing identifier used by the daemon to open server data sockets.
|
||||
* Clients should NOT provide this — the relay assigns a routing ID on connect.
|
||||
*/
|
||||
connectionId?: string;
|
||||
version?: RelayProtocolVersion | 1 | 2;
|
||||
}): string {
|
||||
const { host, port, isIpv6 } = parseHostPort(params.endpoint);
|
||||
@@ -114,16 +109,8 @@ export function buildRelayWebSocketUrl(params: {
|
||||
url.searchParams.set("serverId", params.serverId);
|
||||
url.searchParams.set("role", params.role);
|
||||
url.searchParams.set("v", normalizeRelayProtocolVersion(params.version));
|
||||
if (
|
||||
params.clientId &&
|
||||
params.clientSessionKey &&
|
||||
params.clientId !== params.clientSessionKey
|
||||
) {
|
||||
throw new Error("clientId and clientSessionKey must match when both are provided");
|
||||
}
|
||||
const resolvedClientId = params.clientId ?? params.clientSessionKey;
|
||||
if (resolvedClientId) {
|
||||
url.searchParams.set("clientId", resolvedClientId);
|
||||
if (params.connectionId) {
|
||||
url.searchParams.set("connectionId", params.connectionId);
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
@@ -78,6 +78,48 @@ describe('shared messages stream parsing', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('parses representative sub_agent tool_call event', () => {
|
||||
const parsed = AgentStreamMessageSchema.parse({
|
||||
type: 'agent_stream',
|
||||
payload: {
|
||||
agentId: 'agent_live',
|
||||
timestamp: '2026-02-08T20:10:00.000Z',
|
||||
event: {
|
||||
type: 'timeline',
|
||||
provider: 'claude',
|
||||
item: {
|
||||
type: 'tool_call',
|
||||
callId: 'call_sub_agent_live',
|
||||
name: 'Task',
|
||||
status: 'running',
|
||||
detail: {
|
||||
type: 'sub_agent',
|
||||
subAgentType: 'Explore',
|
||||
description: 'Inspect repository structure',
|
||||
log: '[Read] README.md',
|
||||
actions: [
|
||||
{
|
||||
index: 1,
|
||||
toolName: 'Read',
|
||||
summary: 'README.md',
|
||||
},
|
||||
],
|
||||
},
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(parsed.payload.event.type).toBe('timeline')
|
||||
if (parsed.payload.event.type === 'timeline') {
|
||||
expect(parsed.payload.event.item.type).toBe('tool_call')
|
||||
if (parsed.payload.event.item.type === 'tool_call') {
|
||||
expect(parsed.payload.event.item.detail.type).toBe('sub_agent')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects removed initialize_agent_request inbound payload', () => {
|
||||
const parsed = SessionInboundMessageSchema.safeParse({
|
||||
type: 'initialize_agent_request',
|
||||
|
||||
@@ -109,4 +109,41 @@ describe("shared messages tool_call schema", () => {
|
||||
expect(missingDetail.success).toBe(false);
|
||||
expect(legacyStatus.success).toBe(false);
|
||||
});
|
||||
|
||||
it("parses canonical sub_agent detail payload", () => {
|
||||
const parsed = AgentTimelineItemPayloadSchema.parse({
|
||||
type: "tool_call",
|
||||
callId: "call_sub_agent_1",
|
||||
name: "Task",
|
||||
status: "running",
|
||||
error: null,
|
||||
detail: {
|
||||
type: "sub_agent",
|
||||
subAgentType: "Explore",
|
||||
description: "Inspect repository structure",
|
||||
log: "[Read] README.md\n[Bash] ls",
|
||||
actions: [
|
||||
{
|
||||
index: 1,
|
||||
toolName: "Read",
|
||||
summary: "README.md",
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
toolName: "Bash",
|
||||
summary: "ls",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.type).toBe("tool_call");
|
||||
if (parsed.type === "tool_call") {
|
||||
expect(parsed.detail.type).toBe("sub_agent");
|
||||
if (parsed.detail.type === "sub_agent") {
|
||||
expect(parsed.detail.subAgentType).toBe("Explore");
|
||||
expect(parsed.detail.actions).toHaveLength(2);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,24 +38,29 @@ describe("shared tool-call display mapping", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps task metadata summary on unknown detail", () => {
|
||||
it("uses sub-agent detail for task label and description", () => {
|
||||
const display = buildToolCallDisplayModel({
|
||||
name: "task",
|
||||
status: "running",
|
||||
error: null,
|
||||
detail: {
|
||||
type: "unknown",
|
||||
input: null,
|
||||
output: null,
|
||||
},
|
||||
metadata: {
|
||||
subAgentActivity: "Running tests",
|
||||
type: "sub_agent",
|
||||
subAgentType: "Explore",
|
||||
description: "Inspect repository structure",
|
||||
log: "[Read] README.md",
|
||||
actions: [
|
||||
{
|
||||
index: 1,
|
||||
toolName: "Read",
|
||||
summary: "README.md",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(display).toEqual({
|
||||
displayName: "Task",
|
||||
summary: "Running tests",
|
||||
displayName: "Explore",
|
||||
summary: "Inspect repository structure",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -87,6 +87,10 @@ export function buildToolCallDisplayModel(input: ToolCallDisplayInput): ToolCall
|
||||
displayName = "Worktree Setup";
|
||||
summary = input.detail.branchName;
|
||||
break;
|
||||
case "sub_agent":
|
||||
displayName = readString(input.detail.subAgentType) ?? "Task";
|
||||
summary = readString(input.detail.description);
|
||||
break;
|
||||
case "unknown":
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user