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