import { EventId, ProjectId, ProviderInstanceId, ThreadId, TurnId, type OrchestrationEvent, type OrchestrationThread, type OrchestrationThreadActivity,} from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test"; import { buildThreadFeed, type ThreadFeedActivity } from "../../mobile/src/lib/threadActivity.ts";import { deriveLatestContextWindowSnapshot } from "../../web/src/lib/contextWindow.ts";import { deriveWorkLogEntries } from "../../web/src/session-logic.ts";import { projectActivityEvent, projectActivityPayload, projectThreadDetailSnapshot,} from "../src/orchestration/ActivityPayloadProjection.ts"; function makeActivity( id: string, itemType: string, data: Record<string, unknown>,): OrchestrationThreadActivity { return { id: EventId.make(id), tone: "tool", kind: "tool.completed", summary: `Completed ${itemType}`, payload: { itemType, title: itemType, detail: `${itemType} detail`, status: "completed", requestKind: "command", data, }, turnId: TurnId.make(`turn-${id}`), createdAt: "2026-07-27T00:00:00.000Z", };} function makeThread(activities: ReadonlyArray<OrchestrationThreadActivity>): OrchestrationThread { return { id: ThreadId.make("thread-projection"), projectId: ProjectId.make("project-projection"), title: "Activity projection", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4", }, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, latestTurn: null, createdAt: "2026-07-27T00:00:00.000Z", updatedAt: "2026-07-27T00:00:00.000Z", archivedAt: null, settledOverride: null, settledAt: null, deletedAt: null, messages: [], proposedPlans: [], activities, checkpoints: [], session: null, };} const fixtures = [ makeActivity("command", "command_execution", { item: { command: ["bash", "-lc", "pnpm test"], input: { command: "fallback input", ignored: "input bulk" }, result: { command: "fallback result", aggregatedOutput: "x".repeat(10_000) }, commandActions: [{ type: "unknown", output: "y".repeat(5_000) }], }, command: "fallback data", kind: "execute", toolCallId: "tool-command", rawOutput: { content: "\n```\nfirst useful line\nsecond line", stdout: "unused stdout", ignored: "raw bulk", }, ignored: "top-level bulk", }), makeActivity("file-change", "file_change", { item: { changes: [ { oldPath: "src/old.ts", newPath: "src/new.ts", patch: "large patch".repeat(1_000) }, { filePath: "src/second.ts" }, ], }, ignored: "top-level bulk", }), makeActivity("dynamic", "dynamic_tool_call", { toolCallId: "tool-dynamic", rawOutput: { stdout: "dynamic summary\nlong output".repeat(1_000), }, ignored: "top-level bulk", }), makeActivity("collab", "collab_agent_tool_call", { kind: "delegate", rawOutput: { content: "``` \n```", stdout: "must not be used when content is present", }, ignored: "top-level bulk", }), makeActivity("mcp", "mcp_tool_call", { item: { server: "repository", tool: "search", arguments: { query: "activity projection" }, aggregatedOutput: "mcp payload remains available", }, ignored: "MCP data is rendered verbatim", }), makeActivity("search", "web_search", { rawOutput: { totalFiles: 42, truncated: true, content: "ignored because totalFiles wins", }, ignored: "top-level bulk", }), makeActivity("image", "image_view", { ignored: "top-level bulk", }),] satisfies ReadonlyArray<OrchestrationThreadActivity>; describe("projectActivityPayload", () => { function comparableActivity(activity: ThreadFeedActivity) { return { ...activity, fullDetail: activity.getFullDetail(), copyText: activity.getCopyText(), getFullDetail: undefined, getCopyText: undefined, }; } function comparableThreadFeed(activities: ReadonlyArray<OrchestrationThreadActivity>) { return buildThreadFeed(makeThread(activities)).map((entry) => entry.type === "activity-group" ? { ...entry, activities: entry.activities.map(comparableActivity), } : entry, ); } it("drops unread bulk while retaining command, file, tool, and summary inputs", () => { const projected = projectActivityPayload(fixtures[0]!); expect(projected.payload).toEqual({ itemType: "command_execution", title: "command_execution", detail: "command_execution detail", status: "completed", requestKind: "command", data: { item: { command: ["bash", "-lc", "pnpm test"], input: { command: "fallback input" }, result: { command: "fallback result" }, }, command: "fallback data", toolCallId: "tool-command", kind: "execute", rawOutput: { content: "first useful line" }, }, }); expect(projectActivityPayload(fixtures[1]!).payload).toMatchObject({ data: { files: [{ path: "src/new.ts" }, { path: "src/old.ts" }, { path: "src/second.ts" }], }, }); }); it("passes MCP tool data through unchanged", () => { expect(projectActivityPayload(fixtures[4]!)).toBe(fixtures[4]); }); it("keeps current web and mobile derived output identical for every tool item type", () => { for (const activity of fixtures) { const projected = projectActivityPayload(activity); expect(deriveWorkLogEntries([projected])).toEqual(deriveWorkLogEntries([activity])); expect(comparableThreadFeed([projected])).toEqual(comparableThreadFeed([activity])); } }); it("projects snapshot and event transports without mutating their sources", () => { const activity = fixtures[0]!; const thread = makeThread([activity]); const snapshot = { snapshotSequence: 7, thread }; const projectedSnapshot = projectThreadDetailSnapshot(snapshot); expect(projectedSnapshot.thread.activities[0]).not.toBe(activity); expect(snapshot.thread.activities[0]).toBe(activity); const event = { sequence: 8, eventId: EventId.make("event-activity"), aggregateKind: "thread", aggregateId: thread.id, occurredAt: "2026-07-27T00:00:01.000Z", commandId: null, causationEventId: null, correlationId: null, metadata: {}, type: "thread.activity-appended", payload: { threadId: thread.id, activity, }, } satisfies Extract<OrchestrationEvent, { type: "thread.activity-appended" }>; const projectedEvent = projectActivityEvent(event); expect(projectedEvent).not.toBe(event); expect( projectedEvent.type === "thread.activity-appended" ? projectedEvent.payload.activity : undefined, ).toEqual(projectActivityPayload(activity)); expect(event.payload.activity).toBe(activity); });}); describe("context-window snapshot dedup", () => { function makeContextWindowActivity( id: string, usedTokens: number, turn = `turn-${id}`, ): OrchestrationThreadActivity { return { id: EventId.make(id), tone: "info", kind: "context-window.updated", summary: "Context window updated", payload: { usedTokens, maxTokens: 200_000 }, turnId: TurnId.make(turn), createdAt: "2026-07-27T00:00:00.000Z", }; } it("keeps only the latest context-window activity per turn in snapshots", () => { const stale1 = makeContextWindowActivity("ctx-1", 1_000, "turn-a"); const latestA = makeContextWindowActivity("ctx-2", 2_000, "turn-a"); const latestB = makeContextWindowActivity("ctx-3", 3_000, "turn-b"); const tool = fixtures[0]!; const projected = projectThreadDetailSnapshot({ snapshotSequence: 7, thread: makeThread([stale1, tool, latestA, latestB]), }); expect(projected.thread.activities.map((activity) => activity.id)).toEqual([ tool.id, latestA.id, latestB.id, ]); // The retained rows keep their payloads untouched — the tool-data // projection only rewrites payloads with a `data` record. expect(projected.thread.activities[2]?.payload).toEqual(latestB.payload); }); it("still resolves a meter value after the client reverts the newest turn", () => { // A live thread.reverted makes the client drop all activities from // discarded turns; each surviving turn must keep a usable row. const olderTurn = makeContextWindowActivity("ctx-old", 1_500, "turn-kept"); const revertedTurn = makeContextWindowActivity("ctx-new", 9_000, "turn-reverted"); const projected = projectThreadDetailSnapshot({ snapshotSequence: 7, thread: makeThread([olderTurn, revertedTurn]), }); const afterRevert = projected.thread.activities.filter( (activity) => activity.turnId === TurnId.make("turn-kept"), ); expect(deriveLatestContextWindowSnapshot(afterRevert)).toEqual( deriveLatestContextWindowSnapshot([olderTurn]), ); }); it("matches what the web client derives from the full history", () => { const activities = [ makeContextWindowActivity("ctx-1", 1_000), makeContextWindowActivity("ctx-2", 2_000), ]; const projected = projectThreadDetailSnapshot({ snapshotSequence: 7, thread: makeThread(activities), }); expect(deriveLatestContextWindowSnapshot(projected.thread.activities)).toEqual( deriveLatestContextWindowSnapshot(activities), ); }); it("does not let a malformed row shadow an earlier valid row in the same turn", () => { const valid = makeContextWindowActivity("ctx-valid", 5_000, "turn-a"); const malformed: OrchestrationThreadActivity = { ...makeContextWindowActivity("ctx-broken", 0, "turn-a"), payload: { usedTokens: null }, }; const projected = projectThreadDetailSnapshot({ snapshotSequence: 7, thread: makeThread([valid, malformed]), }); // The malformed row passes through, the valid row survives, and the // client's backward walk resolves the same value as with full history. expect(projected.thread.activities.map((activity) => activity.id)).toEqual([ valid.id, malformed.id, ]); expect(deriveLatestContextWindowSnapshot(projected.thread.activities)).toEqual( deriveLatestContextWindowSnapshot([valid, malformed]), ); }); it("leaves snapshots without context-window activities untouched", () => { const projected = projectThreadDetailSnapshot({ snapshotSequence: 7, thread: makeThread([fixtures[4]!]), }); expect(projected.thread.activities).toEqual([fixtures[4]]); }); it("does not filter live activity-appended events", () => { const activity = makeContextWindowActivity("ctx-live", 4_000); const event = { sequence: 9, eventId: EventId.make("event-ctx"), aggregateKind: "thread", aggregateId: ThreadId.make("thread-projection"), occurredAt: "2026-07-27T00:00:02.000Z", commandId: null, causationEventId: null, correlationId: null, metadata: {}, type: "thread.activity-appended", payload: { threadId: ThreadId.make("thread-projection"), activity, }, } satisfies Extract<OrchestrationEvent, { type: "thread.activity-appended" }>; const projected = projectActivityEvent(event); expect( projected.type === "thread.activity-appended" ? projected.payload.activity : undefined, ).toEqual(activity); });});
import {
EventId,
ProjectId,
ProviderInstanceId,
ThreadId,
TurnId,
type OrchestrationEvent,
type OrchestrationThread,
type OrchestrationThreadActivity,
} from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";
import { buildThreadFeed, type ThreadFeedActivity } from "../../mobile/src/lib/threadActivity.ts";
import { deriveLatestContextWindowSnapshot } from "../../web/src/lib/contextWindow.ts";
import { deriveWorkLogEntries } from "../../web/src/session-logic.ts";
import {
projectActivityEvent,
projectActivityPayload,
projectThreadDetailSnapshot,
} from "../src/orchestration/ActivityPayloadProjection.ts";
function makeActivity(
id: string,
itemType: string,
data: Record<string, unknown>,
): OrchestrationThreadActivity {
return {
id: EventId.make(id),
tone: "tool",
kind: "tool.completed",
summary: `Completed ${itemType}`,
payload: {
itemType,
title: itemType,
detail: `${itemType} detail`,
status: "completed",
requestKind: "command",
data,
},
turnId: TurnId.make(`turn-${id}`),
createdAt: "2026-07-27T00:00:00.000Z",
};
}
function makeThread(activities: ReadonlyArray<OrchestrationThreadActivity>): OrchestrationThread {
return {
id: ThreadId.make("thread-projection"),
projectId: ProjectId.make("project-projection"),
title: "Activity projection",
modelSelection: {
instanceId: ProviderInstanceId.make("codex"),
model: "gpt-5.4",
},
runtimeMode: "full-access",
interactionMode: "default",
branch: null,
worktreePath: null,
latestTurn: null,
createdAt: "2026-07-27T00:00:00.000Z",
updatedAt: "2026-07-27T00:00:00.000Z",
archivedAt: null,
settledOverride: null,
settledAt: null,
deletedAt: null,
messages: [],
proposedPlans: [],
activities,
checkpoints: [],
session: null,
};
}
const fixtures = [
makeActivity("command", "command_execution", {
item: {
command: ["bash", "-lc", "pnpm test"],
input: { command: "fallback input", ignored: "input bulk" },
result: { command: "fallback result", aggregatedOutput: "x".repeat(10_000) },
commandActions: [{ type: "unknown", output: "y".repeat(5_000) }],
},
command: "fallback data",
kind: "execute",
toolCallId: "tool-command",
rawOutput: {
content: "\n```\nfirst useful line\nsecond line",
stdout: "unused stdout",
ignored: "raw bulk",
},
ignored: "top-level bulk",
}),
makeActivity("file-change", "file_change", {
item: {
changes: [
{ oldPath: "src/old.ts", newPath: "src/new.ts", patch: "large patch".repeat(1_000) },
{ filePath: "src/second.ts" },
],
},
ignored: "top-level bulk",
}),
makeActivity("dynamic", "dynamic_tool_call", {
toolCallId: "tool-dynamic",
rawOutput: {
stdout: "dynamic summary\nlong output".repeat(1_000),
},
ignored: "top-level bulk",
}),
makeActivity("collab", "collab_agent_tool_call", {
kind: "delegate",
rawOutput: {
content: "``` \n```",
stdout: "must not be used when content is present",
},
ignored: "top-level bulk",
}),
makeActivity("mcp", "mcp_tool_call", {
item: {
server: "repository",
tool: "search",
arguments: { query: "activity projection" },
aggregatedOutput: "mcp payload remains available",
},
ignored: "MCP data is rendered verbatim",
}),
makeActivity("search", "web_search", {
rawOutput: {
totalFiles: 42,
truncated: true,
content: "ignored because totalFiles wins",
},
ignored: "top-level bulk",
}),
makeActivity("image", "image_view", {
ignored: "top-level bulk",
}),
] satisfies ReadonlyArray<OrchestrationThreadActivity>;
describe("projectActivityPayload", () => {
function comparableActivity(activity: ThreadFeedActivity) {
return {
...activity,
fullDetail: activity.getFullDetail(),
copyText: activity.getCopyText(),
getFullDetail: undefined,
getCopyText: undefined,
};
}
function comparableThreadFeed(activities: ReadonlyArray<OrchestrationThreadActivity>) {
return buildThreadFeed(makeThread(activities)).map((entry) =>
entry.type === "activity-group"
? {
...entry,
activities: entry.activities.map(comparableActivity),
}
: entry,
);
}
it("drops unread bulk while retaining command, file, tool, and summary inputs", () => {
const projected = projectActivityPayload(fixtures[0]!);
expect(projected.payload).toEqual({
itemType: "command_execution",
title: "command_execution",
detail: "command_execution detail",
status: "completed",
requestKind: "command",
data: {
item: {
command: ["bash", "-lc", "pnpm test"],
input: { command: "fallback input" },
result: { command: "fallback result" },
},
command: "fallback data",
toolCallId: "tool-command",
kind: "execute",
rawOutput: { content: "first useful line" },
},
});
expect(projectActivityPayload(fixtures[1]!).payload).toMatchObject({
data: {
files: [{ path: "src/new.ts" }, { path: "src/old.ts" }, { path: "src/second.ts" }],
},
});
});
it("passes MCP tool data through unchanged", () => {
expect(projectActivityPayload(fixtures[4]!)).toBe(fixtures[4]);
});
it("keeps current web and mobile derived output identical for every tool item type", () => {
for (const activity of fixtures) {
const projected = projectActivityPayload(activity);
expect(deriveWorkLogEntries([projected])).toEqual(deriveWorkLogEntries([activity]));
expect(comparableThreadFeed([projected])).toEqual(comparableThreadFeed([activity]));
}
});
it("projects snapshot and event transports without mutating their sources", () => {
const activity = fixtures[0]!;
const thread = makeThread([activity]);
const snapshot = { snapshotSequence: 7, thread };
const projectedSnapshot = projectThreadDetailSnapshot(snapshot);
expect(projectedSnapshot.thread.activities[0]).not.toBe(activity);
expect(snapshot.thread.activities[0]).toBe(activity);
const event = {
sequence: 8,
eventId: EventId.make("event-activity"),
aggregateKind: "thread",
aggregateId: thread.id,
occurredAt: "2026-07-27T00:00:01.000Z",
commandId: null,
causationEventId: null,
correlationId: null,
metadata: {},
type: "thread.activity-appended",
payload: {
threadId: thread.id,
activity,
},
} satisfies Extract<OrchestrationEvent, { type: "thread.activity-appended" }>;
const projectedEvent = projectActivityEvent(event);
expect(projectedEvent).not.toBe(event);
expect(
projectedEvent.type === "thread.activity-appended"
? projectedEvent.payload.activity
: undefined,
).toEqual(projectActivityPayload(activity));
expect(event.payload.activity).toBe(activity);
});
});
describe("context-window snapshot dedup", () => {
function makeContextWindowActivity(
id: string,
usedTokens: number,
turn = `turn-${id}`,
): OrchestrationThreadActivity {
return {
id: EventId.make(id),
tone: "info",
kind: "context-window.updated",
summary: "Context window updated",
payload: { usedTokens, maxTokens: 200_000 },
turnId: TurnId.make(turn),
createdAt: "2026-07-27T00:00:00.000Z",
};
}
it("keeps only the latest context-window activity per turn in snapshots", () => {
const stale1 = makeContextWindowActivity("ctx-1", 1_000, "turn-a");
const latestA = makeContextWindowActivity("ctx-2", 2_000, "turn-a");
const latestB = makeContextWindowActivity("ctx-3", 3_000, "turn-b");
const tool = fixtures[0]!;
const projected = projectThreadDetailSnapshot({
snapshotSequence: 7,
thread: makeThread([stale1, tool, latestA, latestB]),
});
expect(projected.thread.activities.map((activity) => activity.id)).toEqual([
tool.id,
latestA.id,
latestB.id,
]);
// The retained rows keep their payloads untouched — the tool-data
// projection only rewrites payloads with a `data` record.
expect(projected.thread.activities[2]?.payload).toEqual(latestB.payload);
});
it("still resolves a meter value after the client reverts the newest turn", () => {
// A live thread.reverted makes the client drop all activities from
// discarded turns; each surviving turn must keep a usable row.
const olderTurn = makeContextWindowActivity("ctx-old", 1_500, "turn-kept");
const revertedTurn = makeContextWindowActivity("ctx-new", 9_000, "turn-reverted");
const projected = projectThreadDetailSnapshot({
snapshotSequence: 7,
thread: makeThread([olderTurn, revertedTurn]),
});
const afterRevert = projected.thread.activities.filter(
(activity) => activity.turnId === TurnId.make("turn-kept"),
);
expect(deriveLatestContextWindowSnapshot(afterRevert)).toEqual(
deriveLatestContextWindowSnapshot([olderTurn]),
);
});
it("matches what the web client derives from the full history", () => {
const activities = [
makeContextWindowActivity("ctx-1", 1_000),
makeContextWindowActivity("ctx-2", 2_000),
];
const projected = projectThreadDetailSnapshot({
snapshotSequence: 7,
thread: makeThread(activities),
});
expect(deriveLatestContextWindowSnapshot(projected.thread.activities)).toEqual(
deriveLatestContextWindowSnapshot(activities),
);
});
it("does not let a malformed row shadow an earlier valid row in the same turn", () => {
const valid = makeContextWindowActivity("ctx-valid", 5_000, "turn-a");
const malformed: OrchestrationThreadActivity = {
...makeContextWindowActivity("ctx-broken", 0, "turn-a"),
payload: { usedTokens: null },
};
const projected = projectThreadDetailSnapshot({
snapshotSequence: 7,
thread: makeThread([valid, malformed]),
});
// The malformed row passes through, the valid row survives, and the
// client's backward walk resolves the same value as with full history.
expect(projected.thread.activities.map((activity) => activity.id)).toEqual([
valid.id,
malformed.id,
]);
expect(deriveLatestContextWindowSnapshot(projected.thread.activities)).toEqual(
deriveLatestContextWindowSnapshot([valid, malformed]),
);
});
it("leaves snapshots without context-window activities untouched", () => {
const projected = projectThreadDetailSnapshot({
snapshotSequence: 7,
thread: makeThread([fixtures[4]!]),
});
expect(projected.thread.activities).toEqual([fixtures[4]]);
});
it("does not filter live activity-appended events", () => {
const activity = makeContextWindowActivity("ctx-live", 4_000);
const event = {
sequence: 9,
eventId: EventId.make("event-ctx"),
aggregateKind: "thread",
aggregateId: ThreadId.make("thread-projection"),
occurredAt: "2026-07-27T00:00:02.000Z",
commandId: null,
causationEventId: null,
correlationId: null,
metadata: {},
type: "thread.activity-appended",
payload: {
threadId: ThreadId.make("thread-projection"),
activity,
},
} satisfies Extract<OrchestrationEvent, { type: "thread.activity-appended" }>;
const projected = projectActivityEvent(event);
expect(
projected.type === "thread.activity-appended" ? projected.payload.activity : undefined,
).toEqual(activity);
});
});