diff --git a/src/agents/model-selection.e2e.test.ts b/src/agents/model-selection.e2e.test.ts index 6e7546d201..6638d5720b 100644 --- a/src/agents/model-selection.e2e.test.ts +++ b/src/agents/model-selection.e2e.test.ts @@ -142,7 +142,7 @@ describe("model-selection", () => { const cfg: Partial = { agents: { defaults: { - model: "claude-3-5-sonnet", + model: { primary: "claude-3-5-sonnet" }, }, }, }; diff --git a/src/agents/pi-embedded-runner.e2e.test.ts b/src/agents/pi-embedded-runner.e2e.test.ts index 08024ea29d..2255af5589 100644 --- a/src/agents/pi-embedded-runner.e2e.test.ts +++ b/src/agents/pi-embedded-runner.e2e.test.ts @@ -76,7 +76,7 @@ vi.mock("@mariozechner/pi-ai", async () => { if (model.id === "mock-throw") { throw new Error("transport failed"); } - const stream = new actual.AssistantMessageEventStream(); + const stream = actual.createAssistantMessageEventStream(); queueMicrotask(() => { stream.push({ type: "done", @@ -98,6 +98,7 @@ let tempRoot: string | undefined; let agentDir: string; let workspaceDir: string; let sessionCounter = 0; +let runCounter = 0; beforeAll(async () => { vi.useRealTimers(); @@ -145,6 +146,7 @@ const nextSessionFile = () => { sessionCounter += 1; return path.join(workspaceDir, `session-${sessionCounter}.jsonl`); }; +const nextRunId = (prefix = "run-embedded-test") => `${prefix}-${++runCounter}`; const testSessionKey = "agent:test:embedded"; const immediateEnqueue = async (task: () => Promise) => task(); @@ -156,6 +158,7 @@ const runWithOrphanedSingleUserMessage = async (text: string) => { sessionManager.appendMessage({ role: "user", content: [{ type: "text", text }], + timestamp: Date.now(), }); const cfg = makeOpenAiConfig(["mock-1"]); @@ -171,6 +174,7 @@ const runWithOrphanedSingleUserMessage = async (text: string) => { model: "mock-1", timeoutMs: 5_000, agentDir, + runId: nextRunId("orphaned-user"), enqueue: immediateEnqueue, }); }; @@ -216,6 +220,7 @@ const runDefaultEmbeddedTurn = async (sessionFile: string, prompt: string) => { model: "mock-1", timeoutMs: 5_000, agentDir, + runId: nextRunId("default-turn"), enqueue: immediateEnqueue, }); }; @@ -259,6 +264,7 @@ describe("runEmbeddedPiAgent", () => { model: "definitely-not-a-model", timeoutMs: 1, agentDir, + runId: nextRunId("unknown-model"), enqueue: immediateEnqueue, }), ).rejects.toThrow(/Unknown model:/); @@ -366,9 +372,10 @@ describe("runEmbeddedPiAgent", () => { model: "mock-error", timeoutMs: 5_000, agentDir, + runId: nextRunId("prompt-error"), enqueue: immediateEnqueue, }); - expect(result.payloads[0]?.isError).toBe(true); + expect(result.payloads?.[0]?.isError).toBe(true); const messages = await readSessionMessages(sessionFile); const userIndex = messages.findIndex( @@ -393,9 +400,10 @@ describe("runEmbeddedPiAgent", () => { model: "mock-throw", timeoutMs: 5_000, agentDir, + runId: nextRunId("transport-error"), enqueue: immediateEnqueue, }); - expect(result.payloads[0]?.isError).toBe(true); + expect(result.payloads?.[0]?.isError).toBe(true); const entries = await readSessionEntries(sessionFile); const promptErrorEntry = entries.find( @@ -417,6 +425,7 @@ describe("runEmbeddedPiAgent", () => { sessionManager.appendMessage({ role: "user", content: [{ type: "text", text: "seed user" }], + timestamp: Date.now(), }); sessionManager.appendMessage({ role: "assistant", @@ -481,6 +490,7 @@ describe("runEmbeddedPiAgent", () => { model: "mock-1", timeoutMs: 5_000, agentDir, + runId: nextRunId("turn-first"), enqueue: immediateEnqueue, }); @@ -495,6 +505,7 @@ describe("runEmbeddedPiAgent", () => { model: "mock-1", timeoutMs: 5_000, agentDir, + runId: nextRunId("turn-second"), enqueue: immediateEnqueue, }); diff --git a/src/agents/pi-embedded-runner.limithistoryturns.e2e.test.ts b/src/agents/pi-embedded-runner.limithistoryturns.e2e.test.ts index 37fd3f09ec..e9cbbc5e80 100644 --- a/src/agents/pi-embedded-runner.limithistoryturns.e2e.test.ts +++ b/src/agents/pi-embedded-runner.limithistoryturns.e2e.test.ts @@ -3,11 +3,68 @@ import { describe, expect, it } from "vitest"; import { limitHistoryTurns } from "./pi-embedded-runner.js"; describe("limitHistoryTurns", () => { + const mockUsage = { + input: 1, + output: 1, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 2, + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + total: 0, + }, + } as const; + + const userMessage = (text: string): AgentMessage => + ({ + role: "user", + content: [{ type: "text", text }], + timestamp: Date.now(), + }) as AgentMessage; + + const assistantTextMessage = (text: string): AgentMessage => + ({ + role: "assistant", + content: [{ type: "text", text }], + stopReason: "stop", + api: "openai-responses", + provider: "openai", + model: "mock-1", + usage: mockUsage, + timestamp: Date.now(), + }) as AgentMessage; + + const assistantToolCallMessage = (id: string): AgentMessage => + ({ + role: "assistant", + content: [{ type: "toolCall", id, name: "exec", arguments: {} }], + stopReason: "stop", + api: "openai-responses", + provider: "openai", + model: "mock-1", + usage: mockUsage, + timestamp: Date.now(), + }) as AgentMessage; + + const firstText = (message: AgentMessage): string | undefined => { + if (!("content" in message)) { + return undefined; + } + const content = message.content; + if (typeof content === "string") { + return content; + } + const first = content[0]; + return first?.type === "text" ? first.text : undefined; + }; + const makeMessages = (roles: ("user" | "assistant")[]): AgentMessage[] => - roles.map((role, i) => ({ - role, - content: [{ type: "text", text: `message ${i}` }], - })); + roles.map((role, i) => + role === "user" ? userMessage(`message ${i}`) : assistantTextMessage(`message ${i}`), + ); it("returns all messages when limit is undefined", () => { const messages = makeMessages(["user", "assistant", "user", "assistant"]); @@ -37,15 +94,15 @@ describe("limitHistoryTurns", () => { const messages = makeMessages(["user", "assistant", "user", "assistant", "user", "assistant"]); const limited = limitHistoryTurns(messages, 2); expect(limited.length).toBe(4); - expect(limited[0].content).toEqual([{ type: "text", text: "message 2" }]); + expect(firstText(limited[0])).toBe("message 2"); }); it("handles single user turn limit", () => { const messages = makeMessages(["user", "assistant", "user", "assistant", "user", "assistant"]); const limited = limitHistoryTurns(messages, 1); expect(limited.length).toBe(2); - expect(limited[0].content).toEqual([{ type: "text", text: "message 4" }]); - expect(limited[1].content).toEqual([{ type: "text", text: "message 5" }]); + expect(firstText(limited[0])).toBe("message 4"); + expect(firstText(limited[1])).toBe("message 5"); }); it("handles messages with multiple assistant responses per user turn", () => { @@ -58,16 +115,13 @@ describe("limitHistoryTurns", () => { it("preserves message content integrity", () => { const messages: AgentMessage[] = [ - { role: "user", content: [{ type: "text", text: "first" }] }, - { - role: "assistant", - content: [{ type: "toolCall", id: "1", name: "exec", arguments: {} }], - }, - { role: "user", content: [{ type: "text", text: "second" }] }, - { role: "assistant", content: [{ type: "text", text: "response" }] }, + userMessage("first"), + assistantToolCallMessage("1"), + userMessage("second"), + assistantTextMessage("response"), ]; const limited = limitHistoryTurns(messages, 1); - expect(limited[0].content).toEqual([{ type: "text", text: "second" }]); - expect(limited[1].content).toEqual([{ type: "text", text: "response" }]); + expect(firstText(limited[0])).toBe("second"); + expect(firstText(limited[1])).toBe("response"); }); }); diff --git a/src/agents/subagent-registry.persistence.e2e.test.ts b/src/agents/subagent-registry.persistence.e2e.test.ts index 5a97f03be9..d6fafbcfb1 100644 --- a/src/agents/subagent-registry.persistence.e2e.test.ts +++ b/src/agents/subagent-registry.persistence.e2e.test.ts @@ -26,7 +26,7 @@ vi.mock("../infra/agent-events.js", () => ({ const announceSpy = vi.fn(async () => true); vi.mock("./subagent-announce.js", () => ({ - runSubagentAnnounceFlow: (...args: unknown[]) => announceSpy(...args), + runSubagentAnnounceFlow: announceSpy, })); describe("subagent registry persistence", () => { diff --git a/src/agents/subagent-registry.steer-restart.test.ts b/src/agents/subagent-registry.steer-restart.test.ts index b8aebb3ec7..776fa3faff 100644 --- a/src/agents/subagent-registry.steer-restart.test.ts +++ b/src/agents/subagent-registry.steer-restart.test.ts @@ -30,7 +30,7 @@ vi.mock("../config/config.js", () => ({ const announceSpy = vi.fn(async () => true); vi.mock("./subagent-announce.js", () => ({ - runSubagentAnnounceFlow: (...args: unknown[]) => announceSpy(...args), + runSubagentAnnounceFlow: announceSpy, })); vi.mock("./subagent-registry.store.js", () => ({ diff --git a/src/agents/tools/telegram-actions.e2e.test.ts b/src/agents/tools/telegram-actions.e2e.test.ts index f4fb510ef5..827cadb237 100644 --- a/src/agents/tools/telegram-actions.e2e.test.ts +++ b/src/agents/tools/telegram-actions.e2e.test.ts @@ -20,11 +20,11 @@ const deleteMessageTelegram = vi.fn(async () => ({ ok: true })); const originalToken = process.env.TELEGRAM_BOT_TOKEN; vi.mock("../../telegram/send.js", () => ({ - reactMessageTelegram: (...args: unknown[]) => reactMessageTelegram(...args), - sendMessageTelegram: (...args: unknown[]) => sendMessageTelegram(...args), - sendStickerTelegram: (...args: unknown[]) => sendStickerTelegram(...args), - sendPollTelegram: (...args: unknown[]) => sendPollTelegram(...args), - deleteMessageTelegram: (...args: unknown[]) => deleteMessageTelegram(...args), + reactMessageTelegram, + sendMessageTelegram, + sendStickerTelegram, + sendPollTelegram, + deleteMessageTelegram, })); describe("handleTelegramAction", () => { diff --git a/src/agents/tools/whatsapp-actions.e2e.test.ts b/src/agents/tools/whatsapp-actions.e2e.test.ts index 907c29e519..0cc2a544a1 100644 --- a/src/agents/tools/whatsapp-actions.e2e.test.ts +++ b/src/agents/tools/whatsapp-actions.e2e.test.ts @@ -6,8 +6,8 @@ const sendReactionWhatsApp = vi.fn(async () => undefined); const sendPollWhatsApp = vi.fn(async () => ({ messageId: "poll-1", toJid: "jid-1" })); vi.mock("../../web/outbound.js", () => ({ - sendReactionWhatsApp: (...args: unknown[]) => sendReactionWhatsApp(...args), - sendPollWhatsApp: (...args: unknown[]) => sendPollWhatsApp(...args), + sendReactionWhatsApp, + sendPollWhatsApp, })); const enabledConfig = {