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 { 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); }); });