Files
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

227 lines
6.1 KiB
TypeScript

import { renderHook } from "@testing-library/react";
import type {
AgentActivityMessage,
EngineQueuedPrompt,
PendingSubmitIntentRecord,
PendingActivationIntentRecord
} from "@tutti-os/agent-activity-core";
import { describe, expect, it } from "vitest";
import { useAgentGUIActiveMessages } from "./useAgentGUIActiveMessages";
type NewPendingActivationIntentRecord = Extract<
PendingActivationIntentRecord,
{ mode: "new" }
>;
describe("useAgentGUIActiveMessages", () => {
it("does not rematerialize an authoritatively retracted initial prompt", () => {
const storedMessage = message("replacement", "replacement-submit");
const { result } = renderHook(() =>
useAgentGUIActiveMessages({
activeConversationId: "session-1",
activePendingActivation: activation({ initialPromptRetracted: true }),
activePendingSubmits: [],
activeQueuedPrompts: [],
currentUserId: "user-1",
storedMessages: [storedMessage],
workspaceId: "workspace-1"
})
);
expect(result.current.activeMessages).toEqual([storedMessage]);
});
it("keeps an unretracted initial prompt optimistic until canonical confirmation", () => {
const { result } = renderHook(() =>
useAgentGUIActiveMessages({
activeConversationId: "session-1",
activePendingActivation: activation({
initialPromptRetracted: false
}),
activePendingSubmits: [],
activeQueuedPrompts: [],
currentUserId: "user-1",
storedMessages: [],
workspaceId: "workspace-1"
})
);
expect(result.current.activeMessages).toHaveLength(1);
expect(result.current.activeMessages[0]?.payload).toMatchObject({
__agentGuiOptimisticPrompt: true,
clientSubmitId: "initial-submit"
});
});
it("keeps a dequeued optimistic image prompt after the durable assistant response", () => {
const durableUserMessage = message("user-a", "submit-a", {
occurredAtUnixMs: 100,
sequence: 1,
turnId: "turn-a"
});
const durableAssistantMessage = message("assistant-a", "", {
occurredAtUnixMs: 300,
payload: { text: "Assistant A" },
role: "assistant",
sequence: 2,
turnId: "turn-a",
version: 2
});
const pendingImageSubmit = pendingSubmit();
const queuedImageSubmit = queuedPrompt();
const { result, rerender } = renderHook(
({
activeQueuedPrompts,
storedMessages
}: {
activeQueuedPrompts: readonly EngineQueuedPrompt[];
storedMessages: readonly AgentActivityMessage[];
}) =>
useAgentGUIActiveMessages({
activeConversationId: "session-1",
activePendingActivation: null,
activePendingSubmits: [pendingImageSubmit],
activeQueuedPrompts,
currentUserId: "user-1",
storedMessages,
workspaceId: "workspace-1"
}),
{
initialProps: {
activeQueuedPrompts: [queuedImageSubmit],
storedMessages: [durableUserMessage, durableAssistantMessage]
}
}
);
expect(timelineEventIds(result.current.activeTimelineItems)).toEqual([
"user-a",
"assistant-a"
]);
rerender({
activeQueuedPrompts: [],
storedMessages: [durableUserMessage, durableAssistantMessage]
});
expect(timelineEventIds(result.current.activeTimelineItems)).toEqual([
"user-a",
"assistant-a",
"client-submit:user:submit-b"
]);
rerender({
activeQueuedPrompts: [],
storedMessages: [
durableUserMessage,
durableAssistantMessage,
message("client-submit:user:submit-b", "submit-b", {
occurredAtUnixMs: 400,
payload: {
clientSubmitId: "submit-b",
content: pendingImageSubmit.content,
text: "[Image]"
},
sequence: 3,
turnId: "turn-b",
version: 3
})
]
});
expect(timelineEventIds(result.current.activeTimelineItems)).toEqual([
"user-a",
"assistant-a",
"client-submit:user:submit-b"
]);
});
});
function activation(
patch: Partial<NewPendingActivationIntentRecord>
): NewPendingActivationIntentRecord {
return {
agentSessionId: "session-1",
agentTargetId: "target-1",
clientSubmitId: "initial-submit",
commandOutcome: "succeeded",
commandSettledAtUnixMs: 2,
content: [{ type: "text", text: "old prompt" }],
cwd: "/workspace",
errorCode: null,
errorMessage: null,
expiresAtUnixMs: 120_000,
initialPromptRetracted: false,
initialTurnExpected: true,
lastObservedStage: "confirmed",
mode: "new",
requestedAtUnixMs: 1,
requestId: "activation-1",
snapshotObservedAtUnixMs: 2,
snapshotOutcome: "matched",
status: "confirmed",
title: "Session",
workspaceId: "workspace-1",
...patch
};
}
function message(
messageId: string,
clientSubmitId: string,
patch: Partial<AgentActivityMessage> = {}
): AgentActivityMessage {
return {
agentSessionId: "session-1",
kind: "text",
messageId,
occurredAtUnixMs: 2,
payload: { clientSubmitId, text: "replacement prompt" },
role: "user",
turnId: "replacement-turn",
version: 1,
workspaceId: "workspace-1",
...patch
};
}
function pendingSubmit(): PendingSubmitIntentRecord {
return {
acceptedSessionVersion: null,
agentSessionId: "session-1",
clientSubmitId: "submit-b",
content: [
{
type: "image",
data: "aW1hZ2UtYQ==",
mimeType: "image/png",
name: "image-a.png"
}
],
errorCode: null,
errorMessage: null,
errorReason: null,
expiresAtUnixMs: 120_000,
requestedAtUnixMs: 200,
status: "requested",
turnId: null,
workspaceId: "workspace-1"
};
}
function queuedPrompt(): EngineQueuedPrompt {
const pending = pendingSubmit();
return {
clientSubmitId: pending.clientSubmitId,
content: pending.content,
createdAtUnixMs: pending.requestedAtUnixMs,
id: "queued-b"
};
}
function timelineEventIds(
items: ReturnType<typeof useAgentGUIActiveMessages>["activeTimelineItems"]
): string[] {
return items.map((item) => item.eventId);
}