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, ): 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): 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; describe("projectActivityPayload", () => { function comparableActivity(activity: ThreadFeedActivity) { return { ...activity, fullDetail: activity.getFullDetail(), copyText: activity.getCopyText(), getFullDetail: undefined, getCopyText: undefined, }; } function comparableThreadFeed(activities: ReadonlyArray) { 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; 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; const projected = projectActivityEvent(event); expect( projected.type === "thread.activity-appended" ? projected.payload.activity : undefined, ).toEqual(activity); }); });