源码快照 @ 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:
+352
@@ -0,0 +1,352 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
normalizeAgentActivitySession,
|
||||
type AgentActivityMessage,
|
||||
type AgentActivitySession
|
||||
} from "@tutti-os/agent-activity-core";
|
||||
import type { WorkspaceAgentActivityCard } from "../../workspaceAgentActivityListViewModel";
|
||||
import { projectWorkspaceAgentMessagesToConversationVM } from "./workspaceAgentMessageProjection";
|
||||
import { projectAgentCollaborationVM } from "./agentCollaborationProjection";
|
||||
import type { AgentMessageRowVM } from "../contracts/agentMessageRowVM";
|
||||
|
||||
describe("projectAgentCollaborationVM", () => {
|
||||
it("projects the full typed payload", () => {
|
||||
const vm = projectAgentCollaborationVM({
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
status: "completed",
|
||||
payload: {
|
||||
runId: "run-1",
|
||||
mode: "consult",
|
||||
status: "completed",
|
||||
triggerSource: "user",
|
||||
triggerReason: "composer_consult",
|
||||
modelPlanId: "plan-1",
|
||||
model: "kimi-k2",
|
||||
contextScope: "summary",
|
||||
resultText: "Consider a version-key guard.",
|
||||
durationMs: 5200,
|
||||
usage: { inputTokens: 812, outputTokens: 96 },
|
||||
adoption: "pending"
|
||||
}
|
||||
});
|
||||
|
||||
expect(vm).toEqual({
|
||||
kind: "collaboration",
|
||||
runId: "run-1",
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
mode: "consult",
|
||||
status: "completed",
|
||||
triggerSource: "user",
|
||||
triggerReason: "composer_consult",
|
||||
targetSessionId: null,
|
||||
targetAgentTargetId: null,
|
||||
modelPlanId: "plan-1",
|
||||
modelPlanName: null,
|
||||
model: "kimi-k2",
|
||||
contextScope: "summary",
|
||||
resultText: "Consider a version-key guard.",
|
||||
failureReason: null,
|
||||
durationMs: 5200,
|
||||
usage: { inputTokens: 812, outputTokens: 96 },
|
||||
adoption: "pending"
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the item status and rejects payloads without a run identity", () => {
|
||||
const running = projectAgentCollaborationVM({
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
status: "running",
|
||||
payload: { runId: "run-2", mode: "consult" }
|
||||
});
|
||||
expect(running?.status).toBe("running");
|
||||
expect(running?.adoption).toBe("not_applicable");
|
||||
expect(running?.usage).toBeNull();
|
||||
|
||||
expect(
|
||||
projectAgentCollaborationVM({
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
status: "running",
|
||||
payload: { mode: "consult" }
|
||||
})
|
||||
).toBeNull();
|
||||
expect(
|
||||
projectAgentCollaborationVM({
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
status: "running",
|
||||
payload: { runId: "run-3" }
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("collaboration message kind projection", () => {
|
||||
it("projects a running consult without result text into a collaboration row", () => {
|
||||
const conversation = projectWorkspaceAgentMessagesToConversationVM({
|
||||
activity: activity(),
|
||||
session: session({ effectiveStatus: "working" }),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
messages: [
|
||||
message({
|
||||
messageId: "user-1",
|
||||
id: 1,
|
||||
version: 1,
|
||||
role: "user",
|
||||
kind: "text",
|
||||
payload: { text: "Please double-check the plan." }
|
||||
}),
|
||||
collaborationMessage({
|
||||
id: 2,
|
||||
version: 2,
|
||||
status: "running",
|
||||
payload: collaborationPayload({ status: "running" })
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const row = collaborationRow(conversation.rows);
|
||||
expect(row).not.toBeNull();
|
||||
const content = row?.messages[0];
|
||||
expect(content?.contentKind).toBe("collaboration");
|
||||
expect(content?.collaboration).toMatchObject({
|
||||
runId: "run-1",
|
||||
mode: "consult",
|
||||
status: "running",
|
||||
triggerSource: "user",
|
||||
modelPlanId: "plan-1",
|
||||
model: "kimi-k2"
|
||||
});
|
||||
expect(content?.collaboration?.resultText).toBeNull();
|
||||
});
|
||||
|
||||
it("updates the same collaboration row in place on status transitions", () => {
|
||||
const conversation = projectWorkspaceAgentMessagesToConversationVM({
|
||||
activity: activity(),
|
||||
session: session(),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
messages: [
|
||||
message({
|
||||
messageId: "user-1",
|
||||
id: 1,
|
||||
version: 1,
|
||||
role: "user",
|
||||
kind: "text",
|
||||
payload: { text: "Please double-check the plan." }
|
||||
}),
|
||||
collaborationMessage({
|
||||
id: 2,
|
||||
version: 2,
|
||||
status: "running",
|
||||
payload: collaborationPayload({ status: "running" })
|
||||
}),
|
||||
collaborationMessage({
|
||||
id: 3,
|
||||
version: 3,
|
||||
status: "completed",
|
||||
payload: collaborationPayload({
|
||||
status: "completed",
|
||||
resultText: "Looks right; watch the merge key.",
|
||||
durationMs: 4100,
|
||||
usage: { inputTokens: 900, outputTokens: 120 },
|
||||
adoption: "pending"
|
||||
})
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const rows = conversation.rows.filter(
|
||||
(row): row is AgentMessageRowVM =>
|
||||
row.kind === "message" &&
|
||||
row.messages.some((content) => content.contentKind === "collaboration")
|
||||
);
|
||||
expect(rows).toHaveLength(1);
|
||||
const content = rows[0]?.messages[0];
|
||||
expect(content?.collaboration).toMatchObject({
|
||||
runId: "run-1",
|
||||
status: "completed",
|
||||
resultText: "Looks right; watch the merge key.",
|
||||
durationMs: 4100,
|
||||
usage: { inputTokens: 900, outputTokens: 120 },
|
||||
adoption: "pending"
|
||||
});
|
||||
// Collaboration cards own their copy affordance; no transcript copy text.
|
||||
expect(content?.copyText ?? null).toBeNull();
|
||||
});
|
||||
|
||||
it("does not merge collaboration rows into adjacent assistant prose", () => {
|
||||
const conversation = projectWorkspaceAgentMessagesToConversationVM({
|
||||
activity: activity(),
|
||||
session: session(),
|
||||
workspaceRoot: "/workspace/demo",
|
||||
messages: [
|
||||
message({
|
||||
messageId: "assistant-1",
|
||||
id: 1,
|
||||
version: 1,
|
||||
role: "assistant",
|
||||
kind: "text",
|
||||
payload: { text: "Starting a consult." }
|
||||
}),
|
||||
collaborationMessage({
|
||||
id: 2,
|
||||
version: 2,
|
||||
status: "completed",
|
||||
payload: collaborationPayload({
|
||||
status: "completed",
|
||||
resultText: "Advice text."
|
||||
})
|
||||
}),
|
||||
message({
|
||||
messageId: "assistant-2",
|
||||
id: 3,
|
||||
version: 3,
|
||||
role: "assistant",
|
||||
kind: "text",
|
||||
payload: { text: "Continuing after the consult." }
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const messageRows = conversation.rows.filter(
|
||||
(row): row is AgentMessageRowVM => row.kind === "message"
|
||||
);
|
||||
expect(messageRows).toHaveLength(3);
|
||||
expect(
|
||||
messageRows.map((row) =>
|
||||
row.messages.some((content) => content.contentKind === "collaboration")
|
||||
)
|
||||
).toEqual([false, true, false]);
|
||||
});
|
||||
});
|
||||
|
||||
function collaborationRow(
|
||||
rows: readonly { kind: string }[]
|
||||
): AgentMessageRowVM | null {
|
||||
for (const row of rows) {
|
||||
if (
|
||||
row.kind === "message" &&
|
||||
(row as AgentMessageRowVM).messages.some(
|
||||
(content) => content.contentKind === "collaboration"
|
||||
)
|
||||
) {
|
||||
return row as AgentMessageRowVM;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collaborationPayload(
|
||||
overrides: Record<string, unknown> = {}
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
runId: "run-1",
|
||||
mode: "consult",
|
||||
status: "running",
|
||||
triggerSource: "user",
|
||||
triggerReason: "composer_consult",
|
||||
modelPlanId: "plan-1",
|
||||
model: "kimi-k2",
|
||||
adoption: "not_applicable",
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
function collaborationMessage(
|
||||
overrides: Partial<AgentActivityMessage> & { id?: number }
|
||||
): AgentActivityMessage {
|
||||
return message({
|
||||
messageId: "collab:run-1",
|
||||
turnId: "turn-1",
|
||||
role: "assistant",
|
||||
kind: "collaboration",
|
||||
...overrides
|
||||
});
|
||||
}
|
||||
|
||||
function message(
|
||||
overrides: Partial<AgentActivityMessage> & { id?: number }
|
||||
): AgentActivityMessage {
|
||||
const { id: legacyStorageId, ...canonical } = overrides;
|
||||
const version = overrides.version ?? legacyStorageId ?? 1;
|
||||
return {
|
||||
agentSessionId: "session-1",
|
||||
messageId: "message-1",
|
||||
version,
|
||||
turnId: "turn-1",
|
||||
role: "assistant",
|
||||
kind: "text",
|
||||
payload: {},
|
||||
occurredAtUnixMs: version,
|
||||
...canonical
|
||||
};
|
||||
}
|
||||
|
||||
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, turnPhase, ...canonical } = overrides;
|
||||
const phase = turnPhase ?? effectiveStatus;
|
||||
const hasActiveTurn =
|
||||
phase === "submitted" ||
|
||||
phase === "running" ||
|
||||
phase === "working" ||
|
||||
phase === "waiting" ||
|
||||
phase === "settling";
|
||||
return normalizeAgentActivitySession({
|
||||
activeTurnId: null,
|
||||
latestTurnInteractions: [],
|
||||
pendingInteractions: [],
|
||||
workspaceId: "room-1",
|
||||
agentSessionId: "session-1",
|
||||
userId: "user-1",
|
||||
provider: "codex",
|
||||
providerSessionId: "provider-session-1",
|
||||
cwd: "/workspace/demo",
|
||||
title: "Codex",
|
||||
createdAtUnixMs: 1,
|
||||
updatedAtUnixMs: 10,
|
||||
...(hasActiveTurn
|
||||
? {
|
||||
activeTurn: {
|
||||
agentSessionId: "session-1",
|
||||
origin: "user_prompt",
|
||||
outcome: null,
|
||||
phase: phase === "working" ? "running" : phase,
|
||||
settledAtUnixMs: null,
|
||||
startedAtUnixMs: 1,
|
||||
turnId: "turn-1",
|
||||
updatedAtUnixMs: 10
|
||||
},
|
||||
activeTurnId: "turn-1"
|
||||
}
|
||||
: {}),
|
||||
...canonical
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user