Files
tutti/packages/agent/gui/agent-message-center/WorkspaceAgentMessageCenterCard.spec.tsx
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

340 lines
11 KiB
TypeScript

import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import {
createAgentSessionEngine,
normalizeAgentActivitySession
} from "@tutti-os/agent-activity-core";
import { createTestEngineCommandPort } from "../shared/testing/createTestAgentSessionEngine";
import {
messageCenterStackPreviewNodes,
messageCenterStackPreviewText,
WorkspaceAgentMessageCenterCard
} from "./WorkspaceAgentMessageCenterCard";
import {
buildWorkspaceAgentMessageCenterModelFromEngine,
selectWorkspaceAgentMessageCenterPresentation
} from "./workspaceAgentMessageCenterEngineModel";
import type { WorkspaceAgentMessageCenterItem } from "./workspaceAgentMessageCenterModel";
describe("messageCenterStackPreviewText", () => {
it("renders agent-session mention links as plain text instead of raw markdown", () => {
const text = messageCenterStackPreviewText(
item({
summary:
"[@查看昨天提交的代码](mention://agent-session/e8399a9c-da59-485c-b0bf-68c745d36867?workspaceId=ws-1)"
})
);
expect(text).not.toContain("mention://");
expect(text).not.toContain("[@");
expect(text).toContain("查看昨天提交的代码");
});
});
describe("messageCenterStackPreviewNodes", () => {
it("renders a session mention as a static chip with the session icon", () => {
const { container } = render(
<>
{messageCenterStackPreviewNodes(
item({
summary:
"[@查看昨天提交的代码](mention://agent-session/e8399a9c-da59-485c-b0bf-68c745d36867?workspaceId=ws-1)"
})
)}
</>
);
const chip = container.querySelector('[data-agent-mention-kind="session"]');
expect(chip).not.toBeNull();
expect(chip?.tagName).toBe("SPAN");
expect(chip?.textContent).toContain("查看昨天提交的代码");
expect(container.querySelector("a")).toBeNull();
expect(container.textContent).not.toContain("mention://");
});
it("renders workspace-issue and workspace-app mentions with their own icon", () => {
const { container } = render(
<>
{messageCenterStackPreviewNodes(
item({
summary:
"[@修一下这个 bug](mention://workspace-issue/issue-1?workspaceId=ws-1) [@AI 文档](mention://workspace-app/ai-doc?workspaceId=ws-1)"
})
)}
</>
);
expect(
container.querySelector('[data-agent-mention-kind="workspace-issue"]')
?.textContent
).toContain("修一下这个 bug");
expect(
container.querySelector('[data-agent-mention-kind="workspace-app"]')
?.textContent
).toContain("AI 文档");
});
});
describe("WorkspaceAgentMessageCenterCard hidden delegate sessions", () => {
// The Issue task card path: a Tutti Mode delegate run session is hidden
// (visible=false) from ambient surfaces, but the card allowlists its exact
// target session, and the resulting item must render an answerable prompt.
it("renders an answerable pending prompt for an allowlisted hidden delegate session", () => {
const engine = createAgentSessionEngine({
clock: { nowUnixMs: () => 1 },
commandPort: createTestEngineCommandPort({
execute: async () => ({})
}),
identity: { origin: "test", workspaceId: "workspace-1" },
scheduler: { schedule: () => ({ cancel() {} }) }
});
engine.dispatch({
type: "session/snapshotReceived",
sessions: [
normalizeAgentActivitySession({
activeTurnId: "turn-1",
latestTurnInteractions: [],
workspaceId: "workspace-1",
agentSessionId: "delegate-1",
provider: "codex",
cwd: "/workspace",
title: "Delegated task",
visible: false,
activeTurn: {
turnId: "turn-1",
agentSessionId: "delegate-1",
origin: "user_prompt",
phase: "waiting",
startedAtUnixMs: 10,
updatedAtUnixMs: 21
},
pendingInteractions: [
{
requestId: "request-approval",
agentSessionId: "delegate-1",
turnId: "turn-1",
kind: "approval",
status: "pending",
toolName: "Bash",
input: {
command: "pnpm test",
options: [{ optionId: "allow", label: "Allow" }]
},
createdAtUnixMs: 21,
updatedAtUnixMs: 21
}
]
})
]
});
const model = buildWorkspaceAgentMessageCenterModelFromEngine(
selectWorkspaceAgentMessageCenterPresentation(engine.getSnapshot()),
{ workspaceId: "workspace-1", sessionMessagesById: {} },
{ includeHiddenSessionIds: ["delegate-1"] }
);
const delegateItem = model.items.find(
(candidate) => candidate.agentSessionId === "delegate-1"
);
expect(delegateItem?.pendingPrompt).not.toBeNull();
render(
<WorkspaceAgentMessageCenterCard
item={delegateItem!}
isSubmitting={false}
onOpenChat={() => undefined}
onSubmitPrompt={() => undefined}
/>
);
expect(screen.getByRole("button", { name: "Allow" })).toBeTruthy();
});
});
describe("WorkspaceAgentMessageCenterCard prompt presentation", () => {
it("supports a full prompt without repeating the digest summary", () => {
const pendingItem = item({ summary: "Allow the complete command?" });
pendingItem.status = "waiting";
pendingItem.pendingInteractionTarget = {
agentSessionId: "codex-1",
turnId: "turn-1",
requestId: "request-1"
};
pendingItem.pendingPrompt = {
kind: "approval",
id: "approval:request-1",
turnId: "turn-1",
requestId: "request-1",
callId: "call-1",
title: "Run command",
toolName: "Bash",
status: "pending",
input: { command: "printf a && printf b" },
options: [{ id: "allow", label: "Allow", kind: "allow" }],
output: null,
occurredAtUnixMs: 1
};
const { container } = render(
<WorkspaceAgentMessageCenterCard
item={pendingItem}
isSubmitting={false}
promptVariant="full"
showSummaryWithPrompt={false}
onOpenChat={() => undefined}
onSubmitPrompt={() => undefined}
/>
);
expect(screen.getByText("printf a && printf b")).toBeTruthy();
expect(screen.queryByText("Allow the complete command?")).toBeNull();
expect(screen.getByRole("button", { name: "Allow" })).toBeTruthy();
expect(container.textContent).not.toContain("Allow the complete command?");
});
it("disables prompt controls separately from submitting and exposes the reason", () => {
const pendingItem = item({ summary: "Allow the complete command?" });
pendingItem.status = "waiting";
pendingItem.pendingInteractionTarget = {
agentSessionId: "codex-1",
turnId: "turn-1",
requestId: "request-1"
};
pendingItem.pendingPrompt = {
kind: "approval",
id: "approval:request-1",
turnId: "turn-1",
requestId: "request-1",
callId: "call-1",
title: "Run command",
toolName: "Bash",
status: "pending",
input: { command: "printf a" },
options: [{ id: "allow", label: "Allow", kind: "allow" }],
output: null,
occurredAtUnixMs: 1
};
render(
<WorkspaceAgentMessageCenterCard
item={pendingItem}
isSubmitting={false}
isInteractionDisabled
interactionDisabledReason="The shared Agent owner is offline"
onOpenChat={() => undefined}
onSubmitPrompt={() => undefined}
/>
);
expect(screen.getByRole("button", { name: "Allow" })).toBeDisabled();
expect(
screen.getByRole("group", {
name: "The shared Agent owner is offline"
})
).toHaveAttribute("aria-disabled", "true");
});
it("does not focus or render an empty description when the disabled reason is absent", () => {
const pendingItem = item({ summary: "Allow the complete command?" });
pendingItem.status = "waiting";
pendingItem.pendingInteractionTarget = {
agentSessionId: "codex-1",
turnId: "turn-1",
requestId: "request-1"
};
pendingItem.pendingPrompt = {
kind: "approval",
id: "approval:request-1",
turnId: "turn-1",
requestId: "request-1",
callId: "call-1",
title: "Run command",
toolName: "Bash",
status: "pending",
input: { command: "printf a" },
options: [{ id: "allow", label: "Allow", kind: "allow" }],
output: null,
occurredAtUnixMs: 1
};
const { container } = render(
<WorkspaceAgentMessageCenterCard
item={pendingItem}
isSubmitting={false}
isInteractionDisabled
onOpenChat={() => undefined}
onSubmitPrompt={() => undefined}
/>
);
expect(screen.getByRole("button", { name: "Allow" })).toBeDisabled();
const disabledGroup = container.querySelector<HTMLElement>(
'[data-agent-interaction-disabled="true"]'
);
expect(disabledGroup).not.toBeNull();
expect(disabledGroup).not.toHaveAttribute("aria-label");
expect(disabledGroup).not.toHaveAttribute("tabindex");
});
it("keeps the summary when a leaving or read-only card cannot render its prompt", () => {
const pendingItem = item({ summary: "Allow the complete command?" });
pendingItem.status = "waiting";
pendingItem.pendingPrompt = {
kind: "approval",
id: "approval:request-1",
turnId: "turn-1",
requestId: "request-1",
callId: "call-1",
title: "Run command",
toolName: "Bash",
status: "pending",
input: { command: "printf a && printf b" },
options: [{ id: "allow", label: "Allow", kind: "allow" }],
output: null,
occurredAtUnixMs: 1
};
render(
<WorkspaceAgentMessageCenterCard
interactive={false}
item={pendingItem}
isSubmitting={false}
promptVariant="full"
showSummaryWithPrompt={false}
onOpenChat={() => undefined}
onSubmitPrompt={() => undefined}
/>
);
expect(screen.getByText("Allow the complete command?")).toBeTruthy();
expect(screen.queryByText("printf a && printf b")).toBeNull();
});
});
function item(overrides: { summary: string }): WorkspaceAgentMessageCenterItem {
return {
id: "message-center-codex-1",
agentSessionId: "codex-1",
provider: "codex",
userId: null,
title: "codex-1",
identity: null,
cwd: "/workspace",
status: "working",
digest: {
primary: {
kind: "progress",
summary: overrides.summary,
occurredAtUnixMs: 1
}
},
lastAgentMessageSummary: "",
lastAgentMessageAtUnixMs: 1,
pendingInteractionTarget: null,
pendingPrompt: null,
needsAttentionKind: null,
needsAttentionSummary: null,
sortTimeUnixMs: 1
};
}