multica/src/agent/subagent/announce.test.ts

254 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from "vitest";
import { buildSubagentSystemPrompt, formatAnnouncementMessage, formatCoalescedAnnouncementMessage } from "./announce.js";
import type { FormatAnnouncementParams } from "./announce.js";
import type { SubagentRunRecord } from "./types.js";
describe("buildSubagentSystemPrompt", () => {
it("includes task and session context", () => {
const prompt = buildSubagentSystemPrompt({
requesterSessionId: "parent-123",
childSessionId: "child-456",
task: "Analyze the auth module for security issues",
});
expect(prompt).toContain("## Subagent Rules");
expect(prompt).toContain("Analyze the auth module for security issues");
expect(prompt).toContain("parent-123");
expect(prompt).toContain("child-456");
expect(prompt).toContain("Do NOT spawn nested subagents");
expect(prompt).toContain("## Safety");
});
it("includes label when provided", () => {
const prompt = buildSubagentSystemPrompt({
requesterSessionId: "parent-123",
childSessionId: "child-456",
label: "Security Audit",
task: "Check for vulnerabilities",
});
expect(prompt).toContain('Label: "Security Audit"');
});
it("omits label line when not provided", () => {
const prompt = buildSubagentSystemPrompt({
requesterSessionId: "parent-123",
childSessionId: "child-456",
task: "Do something",
});
expect(prompt).not.toContain("Label:");
});
});
describe("formatAnnouncementMessage", () => {
const baseParams: FormatAnnouncementParams = {
runId: "run-1",
childSessionId: "child-456",
requesterSessionId: "parent-123",
task: "Analyze code",
label: "Code Analysis",
cleanup: "delete",
outcome: { status: "ok" },
startedAt: 1000000,
endedAt: 1030000,
};
it("formats successful completion", () => {
const msg = formatAnnouncementMessage({
...baseParams,
findings: "Found 3 issues in the auth module.",
});
expect(msg).toContain('"Code Analysis" just completed successfully');
expect(msg).toContain("Found 3 issues in the auth module.");
expect(msg).toContain("runtime 30s");
expect(msg).toContain("session child-456");
});
it("formats error outcome", () => {
const msg = formatAnnouncementMessage({
...baseParams,
outcome: { status: "error", error: "API key expired" },
});
expect(msg).toContain("failed: API key expired");
});
it("formats timeout outcome", () => {
const msg = formatAnnouncementMessage({
...baseParams,
outcome: { status: "timeout" },
});
expect(msg).toContain("timed out");
});
it("shows (no output) when findings is not provided", () => {
const msg = formatAnnouncementMessage(baseParams);
expect(msg).toContain("(no output)");
});
it("uses task text when label is not provided", () => {
const paramsNoLabel: FormatAnnouncementParams = {
...baseParams,
label: undefined,
};
const msg = formatAnnouncementMessage(paramsNoLabel);
expect(msg).toContain('"Analyze code"');
});
it("formats runtime for minutes", () => {
const msg = formatAnnouncementMessage({
...baseParams,
startedAt: 1000000,
endedAt: 1150000, // 150 seconds = 2m30s
});
expect(msg).toContain("runtime 2m30s");
});
it("formats runtime for hours", () => {
const msg = formatAnnouncementMessage({
...baseParams,
startedAt: 1000000,
endedAt: 4600000, // 3600 seconds = 1h
});
expect(msg).toContain("runtime 1h");
});
it("includes summarization instruction", () => {
const msg = formatAnnouncementMessage(baseParams);
expect(msg).toContain("Summarize this naturally for the user");
expect(msg).toContain("NO_REPLY");
});
});
describe("formatCoalescedAnnouncementMessage", () => {
function makeRecord(overrides: Partial<SubagentRunRecord> = {}): SubagentRunRecord {
return {
runId: "run-1",
childSessionId: "child-1",
requesterSessionId: "parent-1",
task: "Default task",
cleanup: "delete",
createdAt: 1000000,
startedAt: 1000000,
endedAt: 1030000,
outcome: { status: "ok" },
findings: "Some findings",
findingsCaptured: true,
announced: false,
...overrides,
};
}
it("delegates to formatAnnouncementMessage for a single record", () => {
const record = makeRecord({ label: "Code Analysis" });
const coalesced = formatCoalescedAnnouncementMessage([record]);
const direct = formatAnnouncementMessage({
runId: record.runId,
childSessionId: record.childSessionId,
requesterSessionId: record.requesterSessionId,
task: record.task,
label: record.label,
cleanup: record.cleanup,
outcome: record.outcome,
startedAt: record.startedAt,
endedAt: record.endedAt,
findings: record.findings,
});
expect(coalesced).toBe(direct);
});
it("formats multiple records with all task findings and stats", () => {
const records = [
makeRecord({
runId: "run-1",
childSessionId: "child-1",
label: "Task A",
findings: "Found issue A",
startedAt: 1000000,
endedAt: 1030000,
}),
makeRecord({
runId: "run-2",
childSessionId: "child-2",
label: "Task B",
findings: "Found issue B",
startedAt: 1000000,
endedAt: 1045000, // 45 seconds
}),
];
const msg = formatCoalescedAnnouncementMessage(records);
expect(msg).toContain("All 2 background tasks have completed");
expect(msg).toContain('Task 1: "Task A"');
expect(msg).toContain("Found issue A");
expect(msg).toContain('Task 2: "Task B"');
expect(msg).toContain("Found issue B");
expect(msg).toContain("Total wall time: 45s");
expect(msg).toContain("2 succeeded, 0 failed");
});
it("reports mixed outcomes correctly", () => {
const records = [
makeRecord({ runId: "run-1", label: "OK Task", outcome: { status: "ok" } }),
makeRecord({ runId: "run-2", label: "Failed Task", outcome: { status: "error", error: "crash" } }),
makeRecord({ runId: "run-3", label: "Timeout Task", outcome: { status: "timeout" } }),
];
const msg = formatCoalescedAnnouncementMessage(records);
expect(msg).toContain("completed successfully");
expect(msg).toContain("failed: crash");
expect(msg).toContain("timed out");
expect(msg).toContain("1 succeeded, 2 failed");
});
it("shows (no output) for missing findings", () => {
const records = [
makeRecord({ runId: "run-1", findings: undefined }),
makeRecord({ runId: "run-2", findings: "Has output" }),
];
const msg = formatCoalescedAnnouncementMessage(records);
expect(msg).toContain("(no output)");
expect(msg).toContain("Has output");
});
it("includes combined summary instruction for multi-record", () => {
const records = [
makeRecord({ runId: "run-1" }),
makeRecord({ runId: "run-2" }),
];
const msg = formatCoalescedAnnouncementMessage(records);
expect(msg).toContain("MUST include findings from every task item above");
expect(msg).toContain("NO_REPLY");
});
it("includes raw findings for every task in coalesced payload", () => {
const records = [
makeRecord({ runId: "run-1", label: "南京天气", findings: "南京12°C" }),
makeRecord({ runId: "run-2", label: "上海天气", findings: "上海多云9°C" }),
];
const msg = formatCoalescedAnnouncementMessage(records);
expect(msg).toContain("Raw findings from each task (MUST cover all items):");
expect(msg).toContain("[1] 南京天气:");
expect(msg).toContain("南京12°C");
expect(msg).toContain("[2] 上海天气:");
expect(msg).toContain("上海多云9°C");
expect(msg).toContain("MUST include findings from every task item above");
});
});