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

244 lines
7.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
appendAgentGUIComposerConnector,
appendAgentGUIComposerFiles,
appendAgentGUIComposerPrompt,
resolveAgentGUIComposerAppendRequest,
type AgentGUIComposerAppendRequest
} from "./useAgentGUIComposerAppendRequest";
import {
agentComposerDraftFiles,
agentComposerDraftConnectors,
agentComposerDraftPrompt,
agentComposerDraftToPromptContent,
buildAgentComposerDraft
} from "../model/agentComposerDraft";
const existingFile = {
id: "existing",
name: "existing.txt",
path: "/tmp/existing.txt"
};
const archivedContextFile = {
id: "archived-context-1",
mimeType: "text/plain",
name: "context.txt",
path: "/tmp/context.txt",
sizeBytes: 42
};
describe("appendAgentGUIComposerConnector", () => {
it("selects a connector semantically without changing the typed prompt", () => {
const draft = buildAgentComposerDraft({ prompt: "Summarize my workspace" });
const next = appendAgentGUIComposerConnector(draft, "notion");
expect(agentComposerDraftPrompt(next)).toBe("Summarize my workspace");
expect(agentComposerDraftConnectors(next)).toEqual([
{ connectorKey: "notion" }
]);
expect(appendAgentGUIComposerConnector(next, "notion")).toBe(next);
expect(
agentComposerDraftToPromptContent({ draft: next, skills: [] })
).toEqual([
{ type: "text", text: "Summarize my workspace" },
{ type: "connector", connectorKey: "notion" }
]);
});
});
describe("appendAgentGUIComposerFiles", () => {
it("preserves the public files contract and appends landed attachments", () => {
const draft = buildAgentComposerDraft({
files: [existingFile],
prompt: "Fix this element"
});
const next = appendAgentGUIComposerFiles(draft, [archivedContextFile]);
expect(agentComposerDraftPrompt(next)).toBe("Fix this element");
expect(agentComposerDraftFiles(next)).toEqual([
existingFile,
archivedContextFile
]);
});
it("deduplicates appended attachments by id", () => {
const draft = buildAgentComposerDraft({
files: [archivedContextFile],
prompt: ""
});
expect(
agentComposerDraftFiles(
appendAgentGUIComposerFiles(draft, [archivedContextFile])
)
).toEqual([archivedContextFile]);
});
});
describe("appendAgentGUIComposerPrompt", () => {
it("appends a host-provided inline mention without creating an attachment", () => {
const draft = buildAgentComposerDraft({ prompt: "Fix this" });
const mention =
"[@a](mention://browser-element/browser-element%3A1?path=%2Ftmp%2Felement.txt&tag=a&workspaceId=workspace-1)";
const next = appendAgentGUIComposerPrompt(draft, mention);
expect(agentComposerDraftPrompt(next)).toBe(`Fix this ${mention} `);
expect(agentComposerDraftFiles(next)).toHaveLength(0);
expect(appendAgentGUIComposerPrompt(next, mention)).toBe(next);
});
it("keeps multiple browser element mentions in one text-only message", () => {
const first =
"[@a](mention://browser-element/browser-element%3A1?path=%2Ftmp%2Fa.txt&tag=a&workspaceId=workspace-1)";
const second =
"[@div](mention://browser-element/browser-element%3A2?path=%2Ftmp%2Fdiv.txt&tag=div&workspaceId=workspace-1)";
const withFirst = appendAgentGUIComposerPrompt(
buildAgentComposerDraft({ prompt: "Review these" }),
first
);
const withBoth = appendAgentGUIComposerPrompt(withFirst, second);
expect(agentComposerDraftPrompt(withBoth)).toBe(
`Review these ${first} ${second} `
);
expect(
agentComposerDraftToPromptContent({ draft: withBoth, skills: [] })
).toEqual([
{
type: "text",
text: `Review these ${first} ${second}`
}
]);
expect(agentComposerDraftFiles(withBoth)).toHaveLength(0);
});
});
describe("resolveAgentGUIComposerAppendRequest", () => {
it("routes a connector selection to the exact active draft once", () => {
const request: AgentGUIComposerAppendRequest = {
agentSessionId: "session-1",
connectorKey: "notion",
sequence: 9
};
const resolved = resolveAgentGUIComposerAppendRequest({
activeConversationId: "session-1",
draftByScopeKey: {
"session:session-1": buildAgentComposerDraft({ prompt: "Keep me" })
},
handledSequence: null,
request
});
expect(agentComposerDraftPrompt(resolved!.nextDraft)).toBe("Keep me");
expect(agentComposerDraftConnectors(resolved!.nextDraft)).toEqual([
{ connectorKey: "notion" }
]);
expect(
resolveAgentGUIComposerAppendRequest({
activeConversationId: "session-1",
draftByScopeKey: { "session:session-1": resolved!.nextDraft },
handledSequence: 9,
request
})
).toBeNull();
});
it("waits for the exact target session before appending a routed prompt", () => {
const request: AgentGUIComposerAppendRequest = {
agentSessionId: "source-session",
prompt: "Modify the managed issue",
sequence: 8
};
expect(
resolveAgentGUIComposerAppendRequest({
activeConversationId: "other-session",
draftByScopeKey: {
"session:other-session": buildAgentComposerDraft({
prompt: "Keep this draft"
})
},
handledSequence: null,
request
})
).toBeNull();
const resolved = resolveAgentGUIComposerAppendRequest({
activeConversationId: "source-session",
draftByScopeKey: {
"session:source-session": buildAgentComposerDraft({
prompt: "Existing source draft"
})
},
handledSequence: null,
request
});
expect(agentComposerDraftPrompt(resolved!.nextDraft)).toBe(
"Existing source draft Modify the managed issue "
);
});
it("keeps legacy external file requests as draft attachments", () => {
const request: AgentGUIComposerAppendRequest = {
files: [archivedContextFile],
sequence: 6
};
const resolved = resolveAgentGUIComposerAppendRequest({
activeConversationId: "session-1",
draftByScopeKey: {
"session:session-1": buildAgentComposerDraft({ prompt: "Keep me" })
},
handledSequence: null,
request
});
expect(agentComposerDraftPrompt(resolved!.nextDraft)).toBe("Keep me");
expect(agentComposerDraftFiles(resolved!.nextDraft)).toEqual([
archivedContextFile
]);
});
it("resolves each request sequence once against the active draft scope", () => {
const initialDraft = buildAgentComposerDraft({ prompt: "Keep me" });
const browserElementMention =
"[@a](mention://browser-element/browser-element%3A1?path=%2Ftmp%2Fbrowser-element.txt&tag=a&workspaceId=workspace-1)";
const request: AgentGUIComposerAppendRequest = {
prompt: browserElementMention,
sequence: 7
};
const resolved = resolveAgentGUIComposerAppendRequest({
activeConversationId: "session-1",
draftByScopeKey: { "session:session-1": initialDraft },
handledSequence: null,
request
});
expect(resolved?.draftKey).toBe("session:session-1");
expect(agentComposerDraftPrompt(resolved!.nextDraft)).toContain(
"mention://browser-element/"
);
expect(agentComposerDraftFiles(resolved!.nextDraft)).toHaveLength(0);
expect(
agentComposerDraftToPromptContent({
draft: resolved!.nextDraft,
skills: []
})
).toEqual([
expect.objectContaining({
type: "text",
text: expect.stringContaining("mention://browser-element/")
})
]);
expect(
resolveAgentGUIComposerAppendRequest({
activeConversationId: "session-1",
draftByScopeKey: { "session:session-1": resolved!.nextDraft },
handledSequence: 7,
request
})
).toBeNull();
});
});