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
196 lines
5.8 KiB
TypeScript
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 ");
|
|
});
|
|
});
|