summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-05 01:35:50 +0900
committerAdam Malczewski <[email protected]>2026-06-05 01:35:50 +0900
commit94dd5334b0277f3cf3b0588150a6615af86a32b3 (patch)
tree53b1b327790ae43bd3b7cbabe555b832f3e27248
parent977ca522736bba53172e010494de5ac59fdb2a4a (diff)
downloaddispatch-94dd5334b0277f3cf3b0588150a6615af86a32b3.tar.gz
dispatch-94dd5334b0277f3cf3b0588150a6615af86a32b3.zip
refactor(kernel): rename tabId → conversationId across contracts + consumers (218 tests)
Step 4 of the post-MVP backlog: resolve the last vocab drift. The canonical term for a thread of turns is `conversationId` (GLOSSARY), but `AgentEvent` variants and `RunTurnInput` still used the legacy `tabId` from the old frontend "tab" concept, with session-orchestrator bridging `conversationId → tabId`. Atomic, type-driven rename across the full 10-file consumer set: - contracts/events.ts: all 11 AgentEvent variants tabId → conversationId - contracts/runtime.ts: RunTurnInput.tabId → conversationId - runtime/{events,run-turn,dispatch}.ts: factory params, ctx field, locals - session-orchestrator: drop the redundant `tabId: conversationId` bridge line - transport-http: emit wiring; external /chat field + X-Conversation-Id header unchanged (already canonical) — only the emitted NDJSON event field flips - tests (run-turn, app, logic): inputs + assertions now use conversationId Pure rename, zero behavior change: typecheck clean, 218 tests pass (unchanged count), biome clean, `grep tabId packages/` → zero matches. Verified live: multi-turn curl emits conversationId-keyed NDJSON and threads history correctly. GLOSSARY drift note removed. Closes the post-MVP backlog (Steps 1–4).
-rw-r--r--GLOSSARY.md13
-rw-r--r--packages/kernel/src/contracts/events.ts24
-rw-r--r--packages/kernel/src/contracts/runtime.ts6
-rw-r--r--packages/kernel/src/runtime/dispatch.ts15
-rw-r--r--packages/kernel/src/runtime/events.ts34
-rw-r--r--packages/kernel/src/runtime/run-turn.test.ts38
-rw-r--r--packages/kernel/src/runtime/run-turn.ts39
-rw-r--r--packages/session-orchestrator/src/orchestrator.ts2
-rw-r--r--packages/transport-http/src/app.test.ts10
-rw-r--r--packages/transport-http/src/app.ts2
-rw-r--r--packages/transport-http/src/logic.test.ts4
-rw-r--r--tasks.md32
12 files changed, 138 insertions, 81 deletions
diff --git a/GLOSSARY.md b/GLOSSARY.md
index 8ead590..dade3c2 100644
--- a/GLOSSARY.md
+++ b/GLOSSARY.md
@@ -29,12 +29,9 @@
| **provider** | An extension wrapping an LLM backend (`stream(messages, tools)`), provider-agnostic to the kernel. | — |
| **AgentEvent** | An outward event the runtime emits during a turn (text-delta, tool-call, usage, done, etc.). Carries `conversationId` + `turnId`. | — |
-## Known vocabulary drift (tracked, not yet fixed)
+## Known vocabulary drift
-- **`tabId` in `AgentEvent`s and `runTurn` input.** The events contract
- (`packages/kernel/src/contracts/events.ts`) and `RunTurnInput` still use
- **`tabId`** where the canonical term is now **`conversationId`**. The
- session-orchestrator maps `conversationId → tabId` as a bridge. This is a
- CONTRACT change (kernel + every consumer) — to be done as a scoped fan-out via
- `lsp references` (see HANDOFF.md), not a silent edit. Until then, `tabId` in
- emitted events == the `conversationId`.
+- _None._ The former `tabId` drift in `AgentEvent`s and `RunTurnInput` was fully
+ resolved (renamed to `conversationId` across contracts + every consumer; see
+ tasks.md Step 4). `tabId` is retained only in the "Aliases to avoid" column
+ above so it is never reintroduced.
diff --git a/packages/kernel/src/contracts/events.ts b/packages/kernel/src/contracts/events.ts
index ce1dae4..74e23fd 100644
--- a/packages/kernel/src/contracts/events.ts
+++ b/packages/kernel/src/contracts/events.ts
@@ -25,24 +25,24 @@ export type AgentEvent =
| TurnDoneEvent
| TurnSealedEvent;
-/** Status change for a tab / session (e.g. idle → running). */
+/** Status change for a conversation (e.g. idle → running). */
export interface StatusEvent {
readonly type: "status";
- readonly tabId: string;
+ readonly conversationId: string;
readonly status: string;
}
/** A turn has begun. */
export interface TurnStartEvent {
readonly type: "turn-start";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
}
/** Incremental text content from the model during a turn. */
export interface TurnTextDeltaEvent {
readonly type: "text-delta";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
readonly delta: string;
}
@@ -50,7 +50,7 @@ export interface TurnTextDeltaEvent {
/** Incremental reasoning / thinking content during a turn. */
export interface TurnReasoningDeltaEvent {
readonly type: "reasoning-delta";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
readonly delta: string;
}
@@ -58,7 +58,7 @@ export interface TurnReasoningDeltaEvent {
/** The model has requested a tool to be run. */
export interface TurnToolCallEvent {
readonly type: "tool-call";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
readonly toolCallId: string;
readonly toolName: string;
@@ -68,7 +68,7 @@ export interface TurnToolCallEvent {
/** A tool has completed execution. */
export interface TurnToolResultEvent {
readonly type: "tool-result";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
readonly toolCallId: string;
readonly toolName: string;
@@ -79,7 +79,7 @@ export interface TurnToolResultEvent {
/** Streaming output from a tool execution (e.g. shell stdout/stderr). */
export interface TurnToolOutputEvent {
readonly type: "tool-output";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
readonly toolCallId: string;
readonly data: string;
@@ -89,7 +89,7 @@ export interface TurnToolOutputEvent {
/** Token usage for the current step or turn. */
export interface TurnUsageEvent {
readonly type: "usage";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
readonly usage: Usage;
}
@@ -97,7 +97,7 @@ export interface TurnUsageEvent {
/** An error occurred during the turn. */
export interface TurnErrorEvent {
readonly type: "error";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
readonly message: string;
readonly code?: string;
@@ -106,7 +106,7 @@ export interface TurnErrorEvent {
/** The turn has completed (model finished generating). */
export interface TurnDoneEvent {
readonly type: "done";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
readonly reason: string;
}
@@ -117,6 +117,6 @@ export interface TurnDoneEvent {
*/
export interface TurnSealedEvent {
readonly type: "turn-sealed";
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
}
diff --git a/packages/kernel/src/contracts/runtime.ts b/packages/kernel/src/contracts/runtime.ts
index ff994af..68c0444 100644
--- a/packages/kernel/src/contracts/runtime.ts
+++ b/packages/kernel/src/contracts/runtime.ts
@@ -57,10 +57,10 @@ export interface RunTurnInput {
/**
* Identifiers used to attribute every emitted `AgentEvent`. The kernel does
- * not generate these — the session-orchestrator owns turn/tab identity and
- * passes them in, so events are traceable to their conversation.
+ * not generate these — the session-orchestrator owns turn/conversation identity
+ * and passes them in, so events are traceable to their conversation.
*/
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
/**
diff --git a/packages/kernel/src/runtime/dispatch.ts b/packages/kernel/src/runtime/dispatch.ts
index c6c5f8e..626b333 100644
--- a/packages/kernel/src/runtime/dispatch.ts
+++ b/packages/kernel/src/runtime/dispatch.ts
@@ -13,7 +13,7 @@ export async function executeToolCall(
tool: ToolContract | undefined,
signal: AbortSignal,
emit: EventEmitter,
- tabId: string,
+ conversationId: string,
turnId: string,
): Promise<ToolResult> {
if (tool === undefined) {
@@ -26,7 +26,7 @@ export async function executeToolCall(
toolCallId: call.id,
signal,
onOutput: (data, stream) => {
- emit(toolOutputEvent(tabId, turnId, call.id, data, stream));
+ emit(toolOutputEvent(conversationId, turnId, call.id, data, stream));
},
};
try {
@@ -48,7 +48,7 @@ export function createStepDispatcher(
policy: ToolDispatchPolicy,
signal: AbortSignal,
emit: EventEmitter,
- tabId: string,
+ conversationId: string,
turnId: string,
): StepDispatcher {
let activeCount = 0;
@@ -78,7 +78,14 @@ export function createStepDispatcher(
}
async function runAndResolve(entry: QueueEntry): Promise<void> {
- const result = await executeToolCall(entry.call, entry.tool, signal, emit, tabId, turnId);
+ const result = await executeToolCall(
+ entry.call,
+ entry.tool,
+ signal,
+ emit,
+ conversationId,
+ turnId,
+ );
activeCount--;
if (entry.tool?.concurrencySafe === false) unsafeRunning = false;
entry.resolve(result);
diff --git a/packages/kernel/src/runtime/events.ts b/packages/kernel/src/runtime/events.ts
index 62218be..2a92008 100644
--- a/packages/kernel/src/runtime/events.ts
+++ b/packages/kernel/src/runtime/events.ts
@@ -1,57 +1,61 @@
import type { AgentEvent } from "../contracts/events.js";
import type { Usage } from "../contracts/provider.js";
-export function textDeltaEvent(tabId: string, turnId: string, delta: string): AgentEvent {
- return { type: "text-delta", tabId, turnId, delta };
+export function textDeltaEvent(conversationId: string, turnId: string, delta: string): AgentEvent {
+ return { type: "text-delta", conversationId, turnId, delta };
}
-export function reasoningDeltaEvent(tabId: string, turnId: string, delta: string): AgentEvent {
- return { type: "reasoning-delta", tabId, turnId, delta };
+export function reasoningDeltaEvent(
+ conversationId: string,
+ turnId: string,
+ delta: string,
+): AgentEvent {
+ return { type: "reasoning-delta", conversationId, turnId, delta };
}
export function toolCallEvent(
- tabId: string,
+ conversationId: string,
turnId: string,
toolCallId: string,
toolName: string,
input: unknown,
): AgentEvent {
- return { type: "tool-call", tabId, turnId, toolCallId, toolName, input };
+ return { type: "tool-call", conversationId, turnId, toolCallId, toolName, input };
}
export function toolResultEvent(
- tabId: string,
+ conversationId: string,
turnId: string,
toolCallId: string,
toolName: string,
content: string,
isError: boolean,
): AgentEvent {
- return { type: "tool-result", tabId, turnId, toolCallId, toolName, content, isError };
+ return { type: "tool-result", conversationId, turnId, toolCallId, toolName, content, isError };
}
export function toolOutputEvent(
- tabId: string,
+ conversationId: string,
turnId: string,
toolCallId: string,
data: string,
stream: "stdout" | "stderr",
): AgentEvent {
- return { type: "tool-output", tabId, turnId, toolCallId, data, stream };
+ return { type: "tool-output", conversationId, turnId, toolCallId, data, stream };
}
-export function usageEvent(tabId: string, turnId: string, usage: Usage): AgentEvent {
- return { type: "usage", tabId, turnId, usage };
+export function usageEvent(conversationId: string, turnId: string, usage: Usage): AgentEvent {
+ return { type: "usage", conversationId, turnId, usage };
}
export function errorEvent(
- tabId: string,
+ conversationId: string,
turnId: string,
message: string,
code?: string,
): AgentEvent {
if (code !== undefined) {
- return { type: "error", tabId, turnId, message, code };
+ return { type: "error", conversationId, turnId, message, code };
}
- return { type: "error", tabId, turnId, message };
+ return { type: "error", conversationId, turnId, message };
}
diff --git a/packages/kernel/src/runtime/run-turn.test.ts b/packages/kernel/src/runtime/run-turn.test.ts
index 1ea6406..696a385 100644
--- a/packages/kernel/src/runtime/run-turn.test.ts
+++ b/packages/kernel/src/runtime/run-turn.test.ts
@@ -52,7 +52,7 @@ const userMessage: ChatMessage = {
};
describe("runTurn", () => {
- it("emits events with the tabId and turnId from input", async () => {
+ it("emits events with the conversationId and turnId from input", async () => {
const provider = createFakeProvider([
[
{ type: "text-delta", delta: "hi" },
@@ -68,14 +68,14 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "conv-42",
+ conversationId: "conv-42",
turnId: "turn-99",
emit,
});
expect(events.length).toBeGreaterThan(0);
for (const event of events) {
- expect(event.tabId).toBe("conv-42");
+ expect(event.conversationId).toBe("conv-42");
if (event.type !== "status") {
expect(event.turnId).toBe("turn-99");
}
@@ -100,7 +100,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit,
});
@@ -143,7 +143,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [tool],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit,
});
@@ -201,7 +201,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [tool],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
});
@@ -250,7 +250,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [toolA, toolB],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
});
@@ -294,7 +294,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [toolA, toolB],
dispatch: { maxConcurrent: 2, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
});
@@ -352,7 +352,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [toolA, toolB, toolC],
dispatch: { maxConcurrent: 0, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
});
@@ -413,7 +413,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [tool],
dispatch: { maxConcurrent: 1, eager: true },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
});
@@ -464,7 +464,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [tool],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
});
@@ -513,7 +513,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [tool],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit,
signal: ac.signal,
@@ -545,7 +545,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
signal: ac.signal,
@@ -583,7 +583,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [tool],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit,
});
@@ -657,7 +657,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [unsafeTool, safeTool],
dispatch: { maxConcurrent: 5, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
});
@@ -691,7 +691,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit,
});
@@ -724,7 +724,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit,
});
@@ -759,7 +759,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [tool],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit: () => {},
});
@@ -797,7 +797,7 @@ describe("runTurn", () => {
messages: [userMessage],
tools: [tool],
dispatch: { maxConcurrent: 1, eager: false },
- tabId: "tab-test",
+ conversationId: "tab-test",
turnId: "turn-test",
emit,
});
diff --git a/packages/kernel/src/runtime/run-turn.ts b/packages/kernel/src/runtime/run-turn.ts
index 919491a..9421d86 100644
--- a/packages/kernel/src/runtime/run-turn.ts
+++ b/packages/kernel/src/runtime/run-turn.ts
@@ -74,7 +74,7 @@ interface StepContext {
readonly dispatch: RunTurnInput["dispatch"];
readonly emit: EventEmitter;
readonly signal: AbortSignal;
- readonly tabId: string;
+ readonly conversationId: string;
readonly turnId: string;
}
@@ -96,11 +96,11 @@ function processEvent(
switch (event.type) {
case "text-delta":
appendTextDelta(chunks, event.delta);
- ctx.emit(textDeltaEvent(ctx.tabId, ctx.turnId, event.delta));
+ ctx.emit(textDeltaEvent(ctx.conversationId, ctx.turnId, event.delta));
break;
case "reasoning-delta":
appendThinkingDelta(chunks, event.delta);
- ctx.emit(reasoningDeltaEvent(ctx.tabId, ctx.turnId, event.delta));
+ ctx.emit(reasoningDeltaEvent(ctx.conversationId, ctx.turnId, event.delta));
break;
case "tool-call": {
const call: ToolCall = {
@@ -115,14 +115,22 @@ function processEvent(
toolName: event.toolName,
input: event.input,
});
- ctx.emit(toolCallEvent(ctx.tabId, ctx.turnId, event.toolCallId, event.toolName, event.input));
+ ctx.emit(
+ toolCallEvent(
+ ctx.conversationId,
+ ctx.turnId,
+ event.toolCallId,
+ event.toolName,
+ event.input,
+ ),
+ );
if (ctx.dispatch.eager) {
dispatcher.submit(call);
}
break;
}
case "usage":
- ctx.emit(usageEvent(ctx.tabId, ctx.turnId, event.usage));
+ ctx.emit(usageEvent(ctx.conversationId, ctx.turnId, event.usage));
break;
case "finish":
break;
@@ -132,7 +140,7 @@ function processEvent(
} else {
chunks.push({ type: "error", message: event.message });
}
- ctx.emit(errorEvent(ctx.tabId, ctx.turnId, event.message, event.code));
+ ctx.emit(errorEvent(ctx.conversationId, ctx.turnId, event.message, event.code));
break;
}
}
@@ -148,7 +156,7 @@ async function executeStep(ctx: StepContext): Promise<StepResult> {
ctx.dispatch,
ctx.signal,
ctx.emit,
- ctx.tabId,
+ ctx.conversationId,
ctx.turnId,
);
@@ -167,7 +175,7 @@ async function executeStep(ctx: StepContext): Promise<StepResult> {
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
chunks.push({ type: "error", message });
- ctx.emit(errorEvent(ctx.tabId, ctx.turnId, message));
+ ctx.emit(errorEvent(ctx.conversationId, ctx.turnId, message));
finishReason = "error";
}
@@ -184,7 +192,16 @@ async function executeStep(ctx: StepContext): Promise<StepResult> {
const result = results.get(call.id);
if (result !== undefined) {
const isError = result.isError ?? false;
- ctx.emit(toolResultEvent(ctx.tabId, ctx.turnId, call.id, call.name, result.content, isError));
+ ctx.emit(
+ toolResultEvent(
+ ctx.conversationId,
+ ctx.turnId,
+ call.id,
+ call.name,
+ result.content,
+ isError,
+ ),
+ );
toolMessages.push({
role: "tool",
chunks: [
@@ -217,7 +234,7 @@ export async function runTurn(input: RunTurnInput): Promise<RunTurnResult> {
toolMap.set(tool.name, tool);
}
- const tabId = input.tabId;
+ const conversationId = input.conversationId;
const turnId = input.turnId;
const signal = input.signal ?? new AbortController().signal;
@@ -235,7 +252,7 @@ export async function runTurn(input: RunTurnInput): Promise<RunTurnResult> {
dispatch: input.dispatch,
emit: input.emit,
signal,
- tabId,
+ conversationId,
turnId,
});
diff --git a/packages/session-orchestrator/src/orchestrator.ts b/packages/session-orchestrator/src/orchestrator.ts
index f209d2d..302404e 100644
--- a/packages/session-orchestrator/src/orchestrator.ts
+++ b/packages/session-orchestrator/src/orchestrator.ts
@@ -48,7 +48,7 @@ export function createSessionOrchestrator(deps: SessionOrchestratorDeps): Sessio
tools,
dispatch,
emit: onEvent,
- tabId: conversationId,
+ conversationId,
turnId,
...(signal !== undefined ? { signal } : {}),
});
diff --git a/packages/transport-http/src/app.test.ts b/packages/transport-http/src/app.test.ts
index 0125952..9763605 100644
--- a/packages/transport-http/src/app.test.ts
+++ b/packages/transport-http/src/app.test.ts
@@ -66,10 +66,10 @@ describe("POST /chat", () => {
it("streams events as NDJSON", async () => {
const events: AgentEvent[] = [
- { type: "turn-start", tabId: "tab1", turnId: "turn1" },
- { type: "text-delta", tabId: "tab1", turnId: "turn1", delta: "Hello" },
- { type: "text-delta", tabId: "tab1", turnId: "turn1", delta: " world" },
- { type: "done", tabId: "tab1", turnId: "turn1", reason: "stop" },
+ { type: "turn-start", conversationId: "tab1", turnId: "turn1" },
+ { type: "text-delta", conversationId: "tab1", turnId: "turn1", delta: "Hello" },
+ { type: "text-delta", conversationId: "tab1", turnId: "turn1", delta: " world" },
+ { type: "done", conversationId: "tab1", turnId: "turn1", reason: "stop" },
];
const app = createApp({ orchestrator: createFakeOrchestrator(events) });
@@ -98,7 +98,7 @@ describe("POST /chat", () => {
it("generates conversationId when not provided", async () => {
const app = createApp({
orchestrator: createFakeOrchestrator([
- { type: "done", tabId: "tab1", turnId: "turn1", reason: "stop" },
+ { type: "done", conversationId: "tab1", turnId: "turn1", reason: "stop" },
]),
generateId: () => "generated-uuid",
});
diff --git a/packages/transport-http/src/app.ts b/packages/transport-http/src/app.ts
index 92553b7..e1e954c 100644
--- a/packages/transport-http/src/app.ts
+++ b/packages/transport-http/src/app.ts
@@ -48,7 +48,7 @@ export function createApp(opts: CreateServerOptions): Hono {
.catch((err) => {
events.push({
type: "error",
- tabId: conversationId,
+ conversationId,
turnId: "",
message: err instanceof Error ? err.message : String(err),
});
diff --git a/packages/transport-http/src/logic.test.ts b/packages/transport-http/src/logic.test.ts
index 4a77643..a0f3fe6 100644
--- a/packages/transport-http/src/logic.test.ts
+++ b/packages/transport-http/src/logic.test.ts
@@ -79,7 +79,7 @@ describe("serializeEventLine", () => {
it("serializes an event as JSON followed by newline", () => {
const event: AgentEvent = {
type: "text-delta",
- tabId: "tab1",
+ conversationId: "tab1",
turnId: "turn1",
delta: "hello",
};
@@ -90,7 +90,7 @@ describe("serializeEventLine", () => {
it("serializes a done event", () => {
const event: AgentEvent = {
type: "done",
- tabId: "tab1",
+ conversationId: "tab1",
turnId: "turn1",
reason: "stop",
};
diff --git a/tasks.md b/tasks.md
index 3777b23..a212fe5 100644
--- a/tasks.md
+++ b/tasks.md
@@ -122,3 +122,35 @@ HostDeps.storageFactory). (3) host-bin wiring (orchestrator): deleted the
now-unused `HostAPI` import. typecheck clean, **218 tests pass**, biome clean.
Live boot: all 7 extensions activate, curl returns real responses. Summons:
prompts/step3-{kernel-host,storage-sqlite}.md (mimo-v2.5-pro, parallel, disjoint).
+
+### Step 4 — Vocab drift: tabId → conversationId [x] DONE (verified)
+Interlocked contract rename (every consumer breaks at once) → ONE coordinated
+multi-file owner-agent (mimo-v2.5-pro, output→reports/step4-*.run.log) owns the
+full 10-file set; orchestrator updates GLOSSARY + tasks separately.
+Files: contracts/{events,runtime}.ts, runtime/{events,run-turn,dispatch,run-turn.test}.ts,
+session-orchestrator/orchestrator.ts, transport-http/{app,app.test,logic.test}.ts.
+External `/chat` field is ALREADY conversationId — unchanged; only the internal
+field + emitted NDJSON event field flip tabId→conversationId. prompts/step4-rename-conversationid.md.
+
+**Step 4 RESULT:** done + verified. The agent renamed `tabId` → `conversationId`
+across all 10 files in one atomic change: all 11 `AgentEvent` variants
+(`contracts/events.ts`) + `RunTurnInput` (`contracts/runtime.ts`), the runtime
+event factories/`run-turn`/`dispatch`, the session-orchestrator bridge (dropped
+the redundant `tabId: conversationId` line), transport-http emit wiring, and all
+three test files (inputs + assertions). Doc comments updated ("turn/tab identity"
+→ "turn/conversation identity"; StatusEvent "tab / session" → "conversation").
+Orchestrator re-verified independently: **typecheck clean (EXIT 0)**, **218 tests
+pass** (unchanged count — pure rename, no behavior change), **biome clean**, and
+`grep tabId packages/` → **zero matches**. Agent stayed in-lane (exactly the 10
+owned files). External `/chat` request field + `X-Conversation-Id` response header
+untouched (already canonical); emitted NDJSON events now carry `conversationId`.
+GLOSSARY drift note removed. Report: reports/step4-rename-conversationid.md.
+
+---
+
+## 🏁 Post-MVP backlog COMPLETE (Steps 1–4 all done + verified)
+All four ordered HANDOFF steps landed: auth→provider seam wired, first live tool
+extension (read_file), hygiene CRs (getHostAPI + manifest honesty), and the
+tabId→conversationId vocab rename. typecheck + biome clean; 218 tests green.
+Remaining work is the parked design decisions (persistent waking agents, etc.) —
+non-blocking. See HANDOFF.md "Open design decisions still parked".