源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
+786
@@ -0,0 +1,786 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
normalizeAgentActivitySession,
|
||||
type AgentActivitySession
|
||||
} from "@tutti-os/agent-activity-core";
|
||||
import type { WorkspaceAgentActivityTimelineItem } from "../../workspaceAgentTimelineTypes";
|
||||
import type { WorkspaceAgentActivityCard } from "../../workspaceAgentActivityListViewModel";
|
||||
import { buildWorkspaceAgentSessionDetailViewModel } from "../../workspaceAgentSessionDetailViewModel";
|
||||
import {
|
||||
buildCanonicalWorkspaceAgentDetailView,
|
||||
projectWorkspaceAgentTimelineToConversationVM
|
||||
} from "./workspaceAgentTimelineProjection";
|
||||
|
||||
describe("projectWorkspaceAgentTimelineToConversationVM", () => {
|
||||
it("groups adjacent tool calls and bridge thinking directly from timeline items", () => {
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM({
|
||||
activity: activity(),
|
||||
session: session(),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: timelineItems()
|
||||
});
|
||||
|
||||
expect(conversation.rows.map((row) => row.kind)).toEqual([
|
||||
"message",
|
||||
"message",
|
||||
"tool-group",
|
||||
"tool-group"
|
||||
]);
|
||||
|
||||
const groupedRows = conversation.rows.filter(
|
||||
(
|
||||
row
|
||||
): row is Extract<
|
||||
(typeof conversation.rows)[number],
|
||||
{ kind: "tool-group" }
|
||||
> => row.kind === "tool-group" && row.grouped
|
||||
);
|
||||
expect(groupedRows).toHaveLength(1);
|
||||
expect(groupedRows[0]?.entries.map((entry) => entry.kind)).toEqual([
|
||||
"tool-call",
|
||||
"thinking",
|
||||
"tool-call"
|
||||
]);
|
||||
expect(groupedRows[0]?.entries[1]?.kind).toBe("thinking");
|
||||
expect(
|
||||
groupedRows[0]?.entries[1]?.kind === "thinking"
|
||||
? groupedRows[0].entries[1].thinking.body
|
||||
: null
|
||||
).toBe("Need to inspect before editing.");
|
||||
|
||||
expect("pendingApproval" in conversation).toBe(false);
|
||||
expect("pendingInteractivePrompt" in conversation).toBe(false);
|
||||
|
||||
const summaryRow = conversation.rows.find(
|
||||
(
|
||||
row
|
||||
): row is Extract<
|
||||
(typeof conversation.rows)[number],
|
||||
{ kind: "turn-summary" }
|
||||
> => row.kind === "turn-summary"
|
||||
);
|
||||
expect(summaryRow).toBeUndefined();
|
||||
});
|
||||
|
||||
it("builds canonical detail turns directly from timeline items without the legacy detail builder", () => {
|
||||
const detail = buildCanonicalWorkspaceAgentDetailView({
|
||||
activity: activity(),
|
||||
session: session({ effectiveStatus: "working", turnPhase: "working" }),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: timelineItems()
|
||||
});
|
||||
|
||||
expect(detail.turns).toHaveLength(1);
|
||||
expect(detail.turns[0]?.rawAgentItems?.map((item) => item.kind)).toEqual([
|
||||
"message",
|
||||
"tool-calls",
|
||||
"thinking",
|
||||
"tool-calls",
|
||||
"tool-calls",
|
||||
"tool-calls"
|
||||
]);
|
||||
expect(detail.turns[0]?.agentItems.map((item) => item.kind)).toEqual([
|
||||
"message",
|
||||
"tool-calls",
|
||||
"thinking",
|
||||
"tool-calls",
|
||||
"tool-calls",
|
||||
"tool-calls"
|
||||
]);
|
||||
expect(detail.showProcessingIndicator).toBe(false);
|
||||
});
|
||||
|
||||
it("omits Claude synthetic interrupt messages from left detail conversation rows", () => {
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM({
|
||||
activity: activity(),
|
||||
session: session({
|
||||
provider: "claude-code",
|
||||
title: "[Request interrupted by user]"
|
||||
}),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: [
|
||||
{
|
||||
...timelineItems()[0]!,
|
||||
content: "[Request interrupted by user]"
|
||||
},
|
||||
{
|
||||
...timelineItems()[0]!,
|
||||
id: 11,
|
||||
eventId: "event-11",
|
||||
content: "[Request interrupted by user for tool use]",
|
||||
occurredAtUnixMs: 11,
|
||||
createdAtUnixMs: 11
|
||||
},
|
||||
{
|
||||
...timelineItems()[0]!,
|
||||
id: 12,
|
||||
eventId: "event-12",
|
||||
content: "继续正常请求",
|
||||
occurredAtUnixMs: 12,
|
||||
createdAtUnixMs: 12
|
||||
},
|
||||
timelineItems()[1]!
|
||||
]
|
||||
});
|
||||
|
||||
const userMessages = conversation.rows
|
||||
.filter(
|
||||
(
|
||||
row
|
||||
): row is Extract<
|
||||
(typeof conversation.rows)[number],
|
||||
{ kind: "message" }
|
||||
> => row.kind === "message" && row.speaker === "user"
|
||||
)
|
||||
.flatMap((row) => row.messages.map((message) => message.body));
|
||||
|
||||
expect(userMessages).toEqual(["继续正常请求"]);
|
||||
});
|
||||
|
||||
it("prefers durable sequence order over drifted timestamps", () => {
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM({
|
||||
activity: activity(),
|
||||
session: session(),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: timelineItems().map((item) => ({
|
||||
...item,
|
||||
occurredAtUnixMs:
|
||||
item.seq === 3
|
||||
? 300
|
||||
: item.seq === 4
|
||||
? 40
|
||||
: item.seq === 5
|
||||
? 50
|
||||
: item.seq,
|
||||
createdAtUnixMs:
|
||||
item.seq === 3
|
||||
? 300
|
||||
: item.seq === 4
|
||||
? 40
|
||||
: item.seq === 5
|
||||
? 50
|
||||
: item.seq
|
||||
}))
|
||||
});
|
||||
|
||||
expect(conversation.rows.map((row) => row.kind)).toEqual([
|
||||
"message",
|
||||
"message",
|
||||
"tool-group",
|
||||
"tool-group"
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps the legacy detail builder aligned with the canonical timeline projection helper", () => {
|
||||
const input = {
|
||||
activity: activity(),
|
||||
session: session({ effectiveStatus: "working", turnPhase: "working" }),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: timelineItems()
|
||||
};
|
||||
|
||||
const canonical = buildCanonicalWorkspaceAgentDetailView(input);
|
||||
const legacy = buildWorkspaceAgentSessionDetailViewModel(input);
|
||||
|
||||
expect(legacy).toEqual(canonical);
|
||||
});
|
||||
|
||||
it("respects avoidGroupingEdits when rebuilding rows directly from timeline items", () => {
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM(
|
||||
{
|
||||
activity: activity(),
|
||||
session: session({
|
||||
effectiveStatus: "completed",
|
||||
turnPhase: "completed"
|
||||
}),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: timelineItems()
|
||||
},
|
||||
{ avoidGroupingEdits: true }
|
||||
);
|
||||
|
||||
const groupedRows = conversation.rows.filter(
|
||||
(
|
||||
row
|
||||
): row is Extract<
|
||||
(typeof conversation.rows)[number],
|
||||
{ kind: "tool-group" }
|
||||
> => row.kind === "tool-group" && row.grouped
|
||||
);
|
||||
expect(groupedRows).toHaveLength(1);
|
||||
expect(groupedRows[0]?.calls.map((call) => call.id)).toEqual([
|
||||
"call:read-1"
|
||||
]);
|
||||
expect(
|
||||
conversation.rows.some(
|
||||
(
|
||||
row
|
||||
): row is Extract<
|
||||
(typeof conversation.rows)[number],
|
||||
{ kind: "message" }
|
||||
> =>
|
||||
row.kind === "message" &&
|
||||
row.speaker === "assistant" &&
|
||||
row.thinking.some(
|
||||
(thinking) => thinking.body === "Need to inspect before editing."
|
||||
)
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("projects transport notices without merging them into assistant content", () => {
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM({
|
||||
activity: activity(),
|
||||
session: session(),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: [
|
||||
timelineItems()[0]!,
|
||||
{
|
||||
id: 98,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 98,
|
||||
eventId: "notice:event-98",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "message.assistant",
|
||||
role: "assistant",
|
||||
status: "completed",
|
||||
payload: {
|
||||
kind: "agent_system_notice",
|
||||
noticeKind: "transport_retry",
|
||||
severity: "warning",
|
||||
title: "Codex connection interrupted. Reconnecting...",
|
||||
detail:
|
||||
"ResponseStreamDisconnected: websocket IO error: Broken pipe",
|
||||
retryable: true,
|
||||
content: "Codex connection interrupted. Reconnecting...",
|
||||
text: "Codex connection interrupted. Reconnecting..."
|
||||
},
|
||||
occurredAtUnixMs: 98,
|
||||
createdAtUnixMs: 98
|
||||
},
|
||||
{
|
||||
id: 99,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 99,
|
||||
eventId: "event-99",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "message.assistant",
|
||||
role: "assistant",
|
||||
status: "completed",
|
||||
content: "Done.",
|
||||
occurredAtUnixMs: 99,
|
||||
createdAtUnixMs: 99
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const assistantRows = conversation.rows.filter(
|
||||
(
|
||||
row
|
||||
): row is Extract<
|
||||
(typeof conversation.rows)[number],
|
||||
{ kind: "message" }
|
||||
> => row.kind === "message" && row.speaker === "assistant"
|
||||
);
|
||||
expect(assistantRows).toHaveLength(2);
|
||||
expect(assistantRows[0]?.messages[0]?.systemNotice).toEqual({
|
||||
noticeKind: "transport_retry",
|
||||
severity: "warning",
|
||||
title: "Codex connection interrupted. Reconnecting...",
|
||||
detail: "ResponseStreamDisconnected: websocket IO error: Broken pipe",
|
||||
retryable: true
|
||||
});
|
||||
expect(assistantRows[1]?.messages[0]?.body).toBe("Done.");
|
||||
});
|
||||
|
||||
it("maps review-process thinking timeline items to assistant message rows", () => {
|
||||
const detail = buildCanonicalWorkspaceAgentDetailView({
|
||||
activity: activity(),
|
||||
session: session(),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: [
|
||||
{
|
||||
id: 1,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-review",
|
||||
seq: 1,
|
||||
eventId: "event-1",
|
||||
actorType: "user",
|
||||
actorId: "user-1",
|
||||
itemType: "message.user",
|
||||
role: "user",
|
||||
content: "/review",
|
||||
occurredAtUnixMs: 1,
|
||||
createdAtUnixMs: 1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-review",
|
||||
seq: 2,
|
||||
eventId: "event-2",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "message.assistant_thinking",
|
||||
role: "assistant_thinking",
|
||||
status: "completed",
|
||||
content:
|
||||
"**Considering workspace registration order**\n\nInspecting the auth flow.",
|
||||
payload: { messageKind: "review-process" },
|
||||
occurredAtUnixMs: 2,
|
||||
createdAtUnixMs: 2
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
expect(detail.turns[0]?.agentItems).toEqual([
|
||||
expect.objectContaining({
|
||||
kind: "message",
|
||||
message: expect.objectContaining({
|
||||
body: "Inspecting the auth flow."
|
||||
})
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not append processing after a terminal assistant message even if the session patch is still working", () => {
|
||||
const completedReplyTimelineItems: WorkspaceAgentActivityTimelineItem[] = [
|
||||
timelineItems()[0]!,
|
||||
{
|
||||
id: 99,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 99,
|
||||
eventId: "event-99",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "message.assistant",
|
||||
role: "assistant",
|
||||
status: "completed",
|
||||
content: "Done.",
|
||||
occurredAtUnixMs: 99,
|
||||
createdAtUnixMs: 99
|
||||
}
|
||||
];
|
||||
|
||||
const detail = buildCanonicalWorkspaceAgentDetailView({
|
||||
activity: activity(),
|
||||
session: session({
|
||||
effectiveStatus: "working",
|
||||
turnPhase: "working"
|
||||
}),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: completedReplyTimelineItems
|
||||
});
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM({
|
||||
activity: activity(),
|
||||
session: session({
|
||||
effectiveStatus: "working",
|
||||
turnPhase: "working"
|
||||
}),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: completedReplyTimelineItems
|
||||
});
|
||||
|
||||
expect(detail.showProcessingIndicator).toBe(false);
|
||||
expect(conversation.rows.some((row) => row.kind === "processing")).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps processing after an interim assistant message while the turn lifecycle reports an active turn", () => {
|
||||
const interimReplyTimelineItems: WorkspaceAgentActivityTimelineItem[] = [
|
||||
timelineItems()[0]!,
|
||||
{
|
||||
id: 99,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 99,
|
||||
eventId: "event-99",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "message.assistant",
|
||||
role: "assistant",
|
||||
status: "completed",
|
||||
content: "I will now dispatch the sub-agents.",
|
||||
occurredAtUnixMs: 99,
|
||||
createdAtUnixMs: 99
|
||||
}
|
||||
];
|
||||
const workingSession = {
|
||||
...session({
|
||||
effectiveStatus: "working",
|
||||
turnPhase: "working"
|
||||
}),
|
||||
activeTurn: activeTurn("running")
|
||||
};
|
||||
|
||||
const detail = buildCanonicalWorkspaceAgentDetailView({
|
||||
activity: activity(),
|
||||
session: workingSession,
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: interimReplyTimelineItems
|
||||
});
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM({
|
||||
activity: activity(),
|
||||
session: workingSession,
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: interimReplyTimelineItems
|
||||
});
|
||||
|
||||
expect(detail.showProcessingIndicator).toBe(true);
|
||||
expect(conversation.rows.some((row) => row.kind === "processing")).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps processing when session status is stale ready but turn lifecycle is still running", () => {
|
||||
const completedToolTimelineItems = timelineItems().slice(0, 5);
|
||||
const staleReadySession = {
|
||||
...session({
|
||||
effectiveStatus: "ready",
|
||||
turnPhase: "idle"
|
||||
}),
|
||||
activeTurn: activeTurn("running")
|
||||
};
|
||||
|
||||
const detail = buildCanonicalWorkspaceAgentDetailView({
|
||||
activity: activity(),
|
||||
session: staleReadySession,
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: completedToolTimelineItems
|
||||
});
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM({
|
||||
activity: activity(),
|
||||
session: staleReadySession,
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: completedToolTimelineItems
|
||||
});
|
||||
|
||||
expect(detail.showProcessingIndicator).toBe(true);
|
||||
expect(conversation.rows.some((row) => row.kind === "processing")).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("does not append processing after a terminal assistant message once the turn lifecycle is settling", () => {
|
||||
const settlingReplyTimelineItems: WorkspaceAgentActivityTimelineItem[] = [
|
||||
timelineItems()[0]!,
|
||||
{
|
||||
id: 99,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 99,
|
||||
eventId: "event-99",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "message.assistant",
|
||||
role: "assistant",
|
||||
status: "completed",
|
||||
content: "Done.",
|
||||
occurredAtUnixMs: 99,
|
||||
createdAtUnixMs: 99
|
||||
}
|
||||
];
|
||||
const settlingSession = {
|
||||
...session({
|
||||
effectiveStatus: "working",
|
||||
turnPhase: "working"
|
||||
}),
|
||||
activeTurn: activeTurn("settled")
|
||||
};
|
||||
|
||||
const detail = buildCanonicalWorkspaceAgentDetailView({
|
||||
activity: activity(),
|
||||
session: settlingSession,
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: settlingReplyTimelineItems
|
||||
});
|
||||
|
||||
expect(detail.showProcessingIndicator).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps processing after settle until authoritative terminal transcript content arrives", () => {
|
||||
const userOnlyTimelineItems: WorkspaceAgentActivityTimelineItem[] = [
|
||||
timelineItems()[0]!
|
||||
];
|
||||
const settledWithoutFinal = {
|
||||
...session({
|
||||
effectiveStatus: "working",
|
||||
turnPhase: "working"
|
||||
}),
|
||||
activeTurn: activeTurn("settled")
|
||||
};
|
||||
|
||||
const detail = buildCanonicalWorkspaceAgentDetailView({
|
||||
activity: activity(),
|
||||
session: settledWithoutFinal,
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: userOnlyTimelineItems
|
||||
});
|
||||
const conversation = projectWorkspaceAgentTimelineToConversationVM({
|
||||
activity: activity(),
|
||||
session: settledWithoutFinal,
|
||||
workspaceRoot: "/workspace/demo",
|
||||
timelineItems: userOnlyTimelineItems
|
||||
});
|
||||
|
||||
expect(detail.showProcessingIndicator).toBe(true);
|
||||
expect(conversation.rows.some((row) => row.kind === "processing")).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function activity(
|
||||
overrides: Partial<WorkspaceAgentActivityCard> = {}
|
||||
): WorkspaceAgentActivityCard {
|
||||
return {
|
||||
id: "activity-1",
|
||||
sessionId: "session-1",
|
||||
agentName: "Codex",
|
||||
agentProvider: "codex",
|
||||
status: "working",
|
||||
title: "Codex",
|
||||
latestActivitySummary: "Working",
|
||||
sortTimeUnixMs: 10,
|
||||
changedFiles: [],
|
||||
userId: "user-1",
|
||||
userName: "Taylor",
|
||||
userAvatarUrl: "",
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
function session(
|
||||
overrides: Partial<AgentActivitySession> & {
|
||||
effectiveStatus?: string;
|
||||
turnPhase?: string;
|
||||
} = {}
|
||||
): AgentActivitySession {
|
||||
const {
|
||||
effectiveStatus: _effectiveStatus,
|
||||
turnPhase: _turnPhase,
|
||||
...canonical
|
||||
} = overrides;
|
||||
return normalizeAgentActivitySession({
|
||||
...{
|
||||
activeTurnId: null,
|
||||
latestTurnInteractions: [],
|
||||
pendingInteractions: []
|
||||
},
|
||||
workspaceId: "workspace-1",
|
||||
agentSessionId: "session-1",
|
||||
userId: "user-1",
|
||||
provider: "codex",
|
||||
providerSessionId: "provider-session-1",
|
||||
cwd: "/workspace/demo",
|
||||
title: "Codex",
|
||||
createdAtUnixMs: 1,
|
||||
updatedAtUnixMs: 10,
|
||||
latestTurn: {
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
phase: "settled",
|
||||
origin: "user_prompt",
|
||||
outcome: "completed",
|
||||
startedAtUnixMs: 1,
|
||||
settledAtUnixMs: 10,
|
||||
updatedAtUnixMs: 10,
|
||||
fileChanges: {
|
||||
files: [
|
||||
{
|
||||
path: "/workspace/demo/src/App.tsx",
|
||||
change: "modified"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
...canonical
|
||||
});
|
||||
}
|
||||
|
||||
function activeTurn(phase: "running" | "settled") {
|
||||
return {
|
||||
agentSessionId: "session-1",
|
||||
origin: "user_prompt" as const,
|
||||
phase,
|
||||
startedAtUnixMs: 1,
|
||||
turnId: "turn-1",
|
||||
updatedAtUnixMs: 10
|
||||
};
|
||||
}
|
||||
|
||||
function timelineItems(): WorkspaceAgentActivityTimelineItem[] {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 1,
|
||||
eventId: "event-1",
|
||||
actorType: "user",
|
||||
actorId: "user-1",
|
||||
itemType: "message.user",
|
||||
role: "user",
|
||||
content: "Ship the patch",
|
||||
occurredAtUnixMs: 1,
|
||||
createdAtUnixMs: 1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 2,
|
||||
eventId: "event-2",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "message.assistant",
|
||||
role: "assistant",
|
||||
content: "On it",
|
||||
occurredAtUnixMs: 2,
|
||||
createdAtUnixMs: 2
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 3,
|
||||
eventId: "event-3",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "call.completed",
|
||||
role: "assistant",
|
||||
callType: "tool",
|
||||
callId: "read-1",
|
||||
name: "read_file",
|
||||
status: "completed",
|
||||
payload: {
|
||||
tool_state: {
|
||||
name: "read_file",
|
||||
input: { path: "/workspace/demo/README.md" },
|
||||
output: { text: "Read README.md" }
|
||||
}
|
||||
},
|
||||
occurredAtUnixMs: 3,
|
||||
createdAtUnixMs: 3
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 4,
|
||||
eventId: "event-4",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "message.assistant_thinking",
|
||||
role: "assistant_thinking",
|
||||
content: "Need to inspect before editing.",
|
||||
occurredAtUnixMs: 4,
|
||||
createdAtUnixMs: 4
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 5,
|
||||
eventId: "event-5",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "call.completed",
|
||||
role: "assistant",
|
||||
callType: "tool",
|
||||
callId: "edit-1",
|
||||
name: "edit_file",
|
||||
status: "completed",
|
||||
payload: {
|
||||
tool_state: {
|
||||
name: "edit_file",
|
||||
input: {
|
||||
file_path: "/workspace/demo/src/App.tsx",
|
||||
old_string: "old",
|
||||
new_string: "new"
|
||||
},
|
||||
output: {}
|
||||
}
|
||||
},
|
||||
occurredAtUnixMs: 5,
|
||||
createdAtUnixMs: 5
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 6,
|
||||
eventId: "event-6",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "approval.requested",
|
||||
role: "assistant",
|
||||
callType: "approval",
|
||||
callId: "approval-1",
|
||||
name: "Approval",
|
||||
status: "waiting_approval",
|
||||
payload: {
|
||||
input: {
|
||||
requestId: "approval-request-1",
|
||||
options: [
|
||||
{ id: "allow_once", label: "Allow once", kind: "allow_once" }
|
||||
]
|
||||
}
|
||||
},
|
||||
occurredAtUnixMs: 6,
|
||||
createdAtUnixMs: 6
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
turnId: "turn-1",
|
||||
seq: 7,
|
||||
eventId: "event-7",
|
||||
actorType: "agent",
|
||||
actorId: "codex",
|
||||
itemType: "interactive.requested",
|
||||
role: "assistant",
|
||||
callType: "interactive",
|
||||
callId: "ask-1",
|
||||
name: "AskUserQuestion",
|
||||
status: "waiting_input",
|
||||
payload: {
|
||||
input: {
|
||||
requestId: "ask-request-1",
|
||||
questions: [
|
||||
{
|
||||
id: "approach",
|
||||
header: "Approach",
|
||||
question: "Which direction should we take?",
|
||||
options: [
|
||||
{ label: "Use typed renderer", description: "Keep going" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
occurredAtUnixMs: 7,
|
||||
createdAtUnixMs: 7
|
||||
}
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user