Files
tutti/packages/agent/gui/agentSideConversationProjection.spec.ts
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

196 lines
5.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
createAgentActivityEphemeralConversationProjector,
type AgentActivityEphemeralConversationProjector
} from "@tutti-os/agent-activity-core";
import type { AgentSideUpdatedPayloadV1 } from "@tutti-os/event-protocol";
import { normalizeAgentSideConversationEvent } from "./agentSideConversationProjection";
import { projectAgentSideConversationVM } from "./agentSideConversationViewProjection";
function projector(): AgentActivityEphemeralConversationProjector {
return createAgentActivityEphemeralConversationProjector({
workspaceId: "workspace-1",
agentSessionId: "side-1",
sourceAgentSessionId: "source-1",
provider: "codex",
cwd: "/workspace",
occurredAtUnixMs: 100
});
}
function apply(
subject: AgentActivityEphemeralConversationProjector,
event: AgentSideUpdatedPayloadV1
) {
subject.apply(normalizeAgentSideConversationEvent(event));
}
describe("Agent Side conversation projection", () => {
it("routes Side messages through the shared conversation projection", () => {
const subject = projector();
apply(subject, {
workspaceId: "workspace-1",
sideAgentSessionId: "side-1",
sourceAgentSessionId: "source-1",
sequence: 1,
eventType: "state_patch",
data: {
currentPhase: "working",
turnLifecycle: { activeTurnId: "turn-1" },
turn: {
turnId: "turn-1",
activeTurnId: "turn-1",
phase: "running",
origin: "user_prompt",
startedAtUnixMs: 101
}
}
});
apply(subject, {
workspaceId: "workspace-1",
sideAgentSessionId: "side-1",
sourceAgentSessionId: "source-1",
sequence: 2,
eventType: "message_update",
data: {
messageId: "thinking-1",
turnId: "turn-1",
role: "assistant",
kind: "reasoning",
status: "completed",
payload: { text: "Inspecting the source turn." },
occurredAtUnixMs: 102
}
});
apply(subject, {
workspaceId: "workspace-1",
sideAgentSessionId: "side-1",
sourceAgentSessionId: "source-1",
sequence: 3,
eventType: "message_update",
data: {
messageId: "tool-1",
turnId: "turn-1",
role: "assistant",
kind: "tool_call",
status: "running",
callId: "call-1",
payload: {
toolName: "shell",
input: { command: "git status" }
},
occurredAtUnixMs: 103
}
});
apply(subject, {
workspaceId: "workspace-1",
sideAgentSessionId: "side-1",
sourceAgentSessionId: "source-1",
sequence: 4,
eventType: "message_delta",
data: {
messageId: "assistant-1",
turnId: "turn-1",
role: "assistant",
kind: "text",
content: { operation: "set", text: "The parent is still running." },
status: "completed",
occurredAtUnixMs: 104
}
});
const conversation = projectAgentSideConversationVM(subject.getSnapshot());
expect(conversation?.activity.sessionId).toBe("side-1");
expect(
conversation?.rows.some(
(row) =>
row.kind === "tool-group" &&
row.calls[0]?.payload?.toolName === "shell"
)
).toBe(true);
expect(
conversation?.rows.flatMap((row) =>
row.kind === "message" ? row.thinking.map((item) => item.body) : []
)
).toContain("Inspecting the source turn.");
expect(
conversation?.rows.flatMap((row) =>
row.kind === "message"
? row.messages.map((message) => message.body)
: []
)
).toContain("The parent is still running.");
});
it("preserves whitespace in streamed message and tool text", () => {
const messageDelta = normalizeAgentSideConversationEvent({
workspaceId: "workspace-1",
sideAgentSessionId: "side-1",
sourceAgentSessionId: "source-1",
sequence: 1,
eventType: "message_delta",
data: {
messageId: "assistant-1",
turnId: "turn-1",
role: "assistant",
kind: "text",
content: { operation: "append_text", text: " hello \n" },
occurredAtUnixMs: 101
}
});
expect(messageDelta.change.kind).toBe("message_delta");
if (messageDelta.change.kind !== "message_delta") return;
expect(messageDelta.change.data.content).toEqual({
operation: "append_text",
text: " hello \n"
});
const toolDelta = normalizeAgentSideConversationEvent({
workspaceId: "workspace-1",
sideAgentSessionId: "side-1",
sourceAgentSessionId: "source-1",
sequence: 2,
eventType: "message_delta",
data: {
messageId: "tool-1",
turnId: "turn-1",
role: "assistant",
kind: "tool_call",
toolOutput: {
operation: "append_text",
text: " output \n",
offsetBytes: 0
},
occurredAtUnixMs: 102
}
});
expect(toolDelta.change.kind).toBe("message_delta");
if (toolDelta.change.kind !== "message_delta") return;
expect(toolDelta.change.data.toolOutput).toEqual({
operation: "append_text",
text: " output \n",
offsetBytes: 0
});
const update = normalizeAgentSideConversationEvent({
workspaceId: "workspace-1",
sideAgentSessionId: "side-1",
sourceAgentSessionId: "source-1",
sequence: 3,
eventType: "message_update",
data: {
messageId: "assistant-1",
turnId: "turn-1",
role: "assistant",
kind: "text",
contentDelta: " trailing delta ",
payload: {},
occurredAtUnixMs: 103
}
});
expect(update.change.kind).toBe("message_update");
if (update.change.kind !== "message_update") return;
expect(update.change.message.payload.text).toBe(" trailing delta ");
});
});