166b065ad4
Add in-place session renewal, searchable model selection, clearer progress feedback, tool-result previews, and per-session presentation state. Keep commands, errors, loading, and lifecycle indicators attached to their owning runtime so parallel tabs cannot interfere.
314 lines
6.9 KiB
JavaScript
314 lines
6.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import {
|
|
MAX_FRAME_BYTES,
|
|
ProtocolError,
|
|
parseRequestFrame,
|
|
validateEvent,
|
|
} from "../src/protocol/index.js";
|
|
|
|
const request = (value) =>
|
|
JSON.stringify({ version: "v1", id: "request-1", ...value });
|
|
|
|
test("accepts a command scoped to its selected agent", () => {
|
|
assert.deepEqual(
|
|
parseRequestFrame(
|
|
request({
|
|
op: "prompt",
|
|
agentId: "agent-main",
|
|
payload: { message: "implement the protocol" },
|
|
}),
|
|
),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "prompt",
|
|
agentId: "agent-main",
|
|
payload: { message: "implement the protocol" },
|
|
},
|
|
);
|
|
});
|
|
|
|
test("accepts an agent transcript request without a synthetic cursor", () => {
|
|
assert.deepEqual(
|
|
parseRequestFrame(request({ op: "get_transcript", agentId: "agent-main" })),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "get_transcript",
|
|
agentId: "agent-main",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("accepts bridge-managed directory and session operations", () => {
|
|
assert.deepEqual(parseRequestFrame(request({ op: "list_directories" })), {
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "list_directories",
|
|
});
|
|
assert.deepEqual(
|
|
parseRequestFrame(
|
|
request({
|
|
op: "forget_directory",
|
|
payload: { worktreePath: "/projects/feature" },
|
|
}),
|
|
),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "forget_directory",
|
|
payload: { worktreePath: "/projects/feature" },
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseRequestFrame(request({ op: "list_sessions", agentId: "agent-main" })),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "list_sessions",
|
|
agentId: "agent-main",
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseRequestFrame(request({ op: "new_session", agentId: "agent-main" })),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "new_session",
|
|
agentId: "agent-main",
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseRequestFrame(request({ op: "renew_session", agentId: "agent-main" })),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "renew_session",
|
|
agentId: "agent-main",
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseRequestFrame(
|
|
request({
|
|
op: "switch_session",
|
|
agentId: "agent-main",
|
|
payload: { sessionPath: "/sessions/session.jsonl" },
|
|
}),
|
|
),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "switch_session",
|
|
agentId: "agent-main",
|
|
payload: { sessionPath: "/sessions/session.jsonl" },
|
|
},
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
parseRequestFrame(
|
|
request({
|
|
op: "switch_session",
|
|
agentId: "agent-main",
|
|
payload: { sessionPath: "relative.jsonl" },
|
|
}),
|
|
),
|
|
/must be absolute/,
|
|
);
|
|
});
|
|
|
|
test("accepts additive multi-session runtime operations and numeric workspace cursors", () => {
|
|
const worktreePath = "/home/alex/project";
|
|
const sessionPath = "/home/alex/project/sessions/one.jsonl";
|
|
for (const value of [
|
|
{ op: "get_workspace" },
|
|
{ op: "get_workspace_summary" },
|
|
{ op: "create_session_runtime", payload: { worktreePath } },
|
|
{ op: "open_session_runtime", payload: { worktreePath, sessionPath } },
|
|
{ op: "close_session_runtime", payload: { runtimeId: "runtime-1" } },
|
|
{ op: "list_directory_sessions", payload: { worktreePath } },
|
|
{
|
|
op: "get_session_runtime_snapshot",
|
|
payload: { runtimeId: "runtime-1" },
|
|
},
|
|
{ op: "subscribe_workspace", payload: { cursor: 42 } },
|
|
])
|
|
assert.equal(parseRequestFrame(request(value)).op, value.op);
|
|
assert.throws(
|
|
() =>
|
|
parseRequestFrame(
|
|
request({
|
|
op: "subscribe_workspace",
|
|
payload: { cursor: "opaque" },
|
|
}),
|
|
),
|
|
/cursor must be a non-negative integer/,
|
|
);
|
|
});
|
|
|
|
test("accepts native session naming and manual compaction requests", () => {
|
|
assert.deepEqual(
|
|
parseRequestFrame(
|
|
request({
|
|
op: "set_session_name",
|
|
agentId: "agent-main",
|
|
payload: { name: "Release UI" },
|
|
}),
|
|
),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "set_session_name",
|
|
agentId: "agent-main",
|
|
payload: { name: "Release UI" },
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseRequestFrame(request({ op: "compact", agentId: "agent-main" })),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "compact",
|
|
agentId: "agent-main",
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
parseRequestFrame(
|
|
request({
|
|
op: "compact",
|
|
agentId: "agent-main",
|
|
payload: { customInstructions: "Preserve implementation details" },
|
|
}),
|
|
),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "compact",
|
|
agentId: "agent-main",
|
|
payload: { customInstructions: "Preserve implementation details" },
|
|
},
|
|
);
|
|
for (const invalidRequest of [
|
|
request({
|
|
op: "set_session_name",
|
|
agentId: "agent-main",
|
|
payload: { name: " " },
|
|
}),
|
|
request({
|
|
op: "set_session_name",
|
|
agentId: "agent-main",
|
|
payload: { name: "Valid", extra: true },
|
|
}),
|
|
request({
|
|
op: "compact",
|
|
agentId: "agent-main",
|
|
payload: { customInstructions: " " },
|
|
}),
|
|
request({ op: "compact", agentId: "agent-main", payload: { extra: true } }),
|
|
])
|
|
assert.throws(() => parseRequestFrame(invalidRequest), ProtocolError);
|
|
});
|
|
|
|
test("accepts an agent session-statistics request", () => {
|
|
assert.deepEqual(
|
|
parseRequestFrame(
|
|
request({ op: "get_session_stats", agentId: "agent-main" }),
|
|
),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "get_session_stats",
|
|
agentId: "agent-main",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("accepts a default composer submit request", () => {
|
|
assert.deepEqual(
|
|
parseRequestFrame(
|
|
request({
|
|
op: "submit_prompt",
|
|
agentId: "agent-main",
|
|
payload: { message: "Continue" },
|
|
}),
|
|
),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "submit_prompt",
|
|
agentId: "agent-main",
|
|
payload: { message: "Continue" },
|
|
},
|
|
);
|
|
});
|
|
|
|
test("accepts a selected agent get_commands request", () => {
|
|
assert.deepEqual(
|
|
parseRequestFrame(request({ op: "get_commands", agentId: "agent-main" })),
|
|
{
|
|
version: "v1",
|
|
id: "request-1",
|
|
op: "get_commands",
|
|
agentId: "agent-main",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("rejects malformed, unknown, and oversized frames", () => {
|
|
assert.throws(() => parseRequestFrame("not json"), ProtocolError);
|
|
assert.throws(
|
|
() => parseRequestFrame(request({ op: "teleport" })),
|
|
/not supported/,
|
|
);
|
|
assert.throws(
|
|
() => parseRequestFrame("x".repeat(MAX_FRAME_BYTES + 1)),
|
|
/frame exceeds/,
|
|
);
|
|
});
|
|
|
|
test("rejects cross-worktree routing fields on agent commands", () => {
|
|
assert.throws(
|
|
() =>
|
|
parseRequestFrame(
|
|
request({
|
|
op: "prompt",
|
|
agentId: "agent-main",
|
|
payload: { message: "wrong worktree", worktreePath: "/other" },
|
|
}),
|
|
),
|
|
/not supported/,
|
|
);
|
|
});
|
|
|
|
test("requires explicit absolute worktree selection", () => {
|
|
assert.throws(
|
|
() =>
|
|
parseRequestFrame(
|
|
request({
|
|
op: "select_agent",
|
|
payload: { worktreePath: "relative/project" },
|
|
}),
|
|
),
|
|
/must be absolute/,
|
|
);
|
|
});
|
|
|
|
test("normalizes sequenced bridge events", () => {
|
|
assert.deepEqual(
|
|
validateEvent({
|
|
version: "v1",
|
|
seq: 2,
|
|
type: "extension_ui_request",
|
|
agentId: "agent-main",
|
|
data: { id: "extension-1", method: "confirm" },
|
|
}),
|
|
{
|
|
version: "v1",
|
|
seq: 2,
|
|
type: "extension_ui_request",
|
|
agentId: "agent-main",
|
|
data: { id: "extension-1", method: "confirm" },
|
|
},
|
|
);
|
|
});
|