Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/controller/agentGuiController.interactiveHelpers.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

271 lines
6.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { AgentActivityInteraction } from "@tutti-os/agent-activity-core";
import type { AgentHostUserProjectsApi } from "../../../host/agentHostApi";
import { normalizeAgentApprovalOptions } from "../../../shared/agentConversation/projection/agentApprovalProjection";
import { projectConversationRailSectionsByExactKey } from "../model/agentGuiConversationRail";
import {
areAgentGUIUserProjectsEqual,
interactiveApprovalFromInteraction,
interactivePromptFromInteraction,
readAgentGUIUserProjectSnapshot,
upsertAgentGUIUserProject
} from "./agentGuiController.interactiveHelpers";
describe("interactiveApprovalFromInteraction", () => {
it("projects the normalized file-edit approval purpose", () => {
const interaction: AgentActivityInteraction = {
agentSessionId: "session-1",
createdAtUnixMs: 1,
input: {
callId: "call-1",
options: [{ optionId: "allow", name: "Allow", kind: "allow_once" }]
},
kind: "approval",
metadata: { approvalPurpose: "edit-files" },
requestId: "request-1",
status: "pending",
toolName: "Approval",
turnId: "turn-1",
updatedAtUnixMs: 1
};
expect(interactiveApprovalFromInteraction(interaction)).toMatchObject({
agentSessionId: "session-1",
approvalPurpose: "edit-files",
requestId: "request-1",
turnId: "turn-1"
});
});
it("uses the shared approval-option projection", () => {
expect(
normalizeAgentApprovalOptions([
{
description: "Apply this once",
optionId: "allow-once",
title: "Allow once"
}
])
).toEqual([
{
description: "Apply this once",
id: "allow-once",
kind: "",
label: "Allow once"
}
]);
});
});
describe("interactivePromptFromInteraction", () => {
it("preserves runtime plan options and the keep-planning option id", () => {
const interaction: AgentActivityInteraction = {
agentSessionId: "session-1",
createdAtUnixMs: 1,
input: {
options: [
{
description: "Auto-approve edits",
id: "acceptEdits",
name: "Accept edits"
},
{ id: "plan", name: "Keep planning" }
],
toolCall: { kind: "switch_mode" }
},
kind: "plan",
requestId: "request-1",
status: "pending",
toolName: "ExitPlanMode",
turnId: "turn-1",
updatedAtUnixMs: 1
};
expect(interactivePromptFromInteraction(interaction)).toEqual({
agentSessionId: "session-1",
keepPlanningOptionId: "plan",
kind: "exit-plan",
options: [
{
description: "Auto-approve edits",
id: "acceptEdits",
kind: "acceptEdits",
label: "Accept edits"
}
],
requestId: "request-1",
title: "ExitPlanMode",
turnId: "turn-1"
});
});
});
describe("AgentGUI user-project snapshot projection", () => {
it("preserves the ordered section templates from an already-loaded shared service", () => {
const api = {
service: {
getSnapshot: () => ({
error: null,
initialized: true,
isLoading: false,
projects: [
{
id: "beta",
label: "Beta",
path: "/workspace/beta",
pinnedAtUnixMs: 20,
sectionKey: "project:/workspace/beta"
},
{
id: "alpha",
label: "Alpha",
path: "/workspace/alpha",
pinnedAtUnixMs: 0,
sectionKey: "project:/workspace/alpha"
}
],
revision: 1
})
}
} as AgentHostUserProjectsApi;
const projects = readAgentGUIUserProjectSnapshot(api);
const sections = projectConversationRailSectionsByExactKey({
conversations: [],
labels: {
sectionConversations: "Conversations",
sectionPinned: "Pinned"
},
userProjects: projects
});
expect(projects.map((project) => project.sectionKey)).toEqual([
"project:/workspace/beta",
"project:/workspace/alpha"
]);
expect(sections.map((section) => section.id)).toEqual([
"project:/workspace/beta",
"project:/workspace/alpha",
"conversations"
]);
});
it("treats a section-key-only change as a new AgentGUI snapshot", () => {
expect(
areAgentGUIUserProjectsEqual(
[
{
id: "alpha",
label: "Alpha",
path: "/alpha",
pinnedAtUnixMs: 0
}
],
[
{
id: "alpha",
label: "Alpha",
path: "/alpha",
pinnedAtUnixMs: 0,
sectionKey: "project:/alpha"
}
]
)
).toBe(false);
});
it("treats Windows slash and case differences as the same project", () => {
expect(
areAgentGUIUserProjectsEqual(
[
{
id: "project",
label: "Project",
path: "C:\\Users\\Demo\\Repo",
pinnedAtUnixMs: 0
}
],
[
{
id: "project",
label: "Project",
path: "c:/users/demo/repo/",
pinnedAtUnixMs: 0
}
]
)
).toBe(true);
});
it("treats a pin-state-only change as a new AgentGUI snapshot", () => {
expect(
areAgentGUIUserProjectsEqual(
[
{
id: "alpha",
label: "Alpha",
path: "/alpha",
pinnedAtUnixMs: 0
}
],
[
{
id: "alpha",
label: "Alpha",
path: "/alpha",
pinnedAtUnixMs: 10
}
]
)
).toBe(false);
});
it.each(["createdAtUnixMs", "updatedAtUnixMs", "lastUsedAtUnixMs"] as const)(
"treats a %s-only change as a new AgentGUI snapshot",
(field) => {
expect(
areAgentGUIUserProjectsEqual(
[
{
id: "alpha",
label: "Alpha",
path: "/alpha",
pinnedAtUnixMs: 0,
[field]: 10
}
],
[
{
id: "alpha",
label: "Alpha",
path: "/alpha",
pinnedAtUnixMs: 0,
[field]: 20
}
]
)
).toBe(false);
}
);
it("keeps sectionKey when a use result is upserted", () => {
expect(
upsertAgentGUIUserProject([], {
id: "alpha",
label: "Alpha",
path: "/alpha",
pinnedAtUnixMs: 0,
sectionKey: "project:/alpha"
})
).toEqual([
{
id: "alpha",
label: "Alpha",
path: "/alpha",
pinnedAtUnixMs: 0,
sectionKey: "project:/alpha"
}
]);
});
});