import path from "node:path"; import { afterEach, expect, it, vi } from "vitest"; import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js"; import { loadTranscriptEvents } from "../../config/sessions/session-accessor.js"; import { applyAssistantDeliveryDirectives } from "../../config/sessions/transcript-assistant-delivery.js"; import { sanitizeChatHistoryMessages } from "../../gateway/chat-display-projection.sanitize.js"; import type { AssistantMessage } from "../../llm/types.js"; import { upsertSessionEntry } from "../../plugin-sdk/session-store-runtime.js"; import { onInternalSessionTranscriptUpdate, type InternalSessionTranscriptUpdate, } from "../../sessions/transcript-events.js"; import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js"; import { isIntermediateAssistantTranscriptMessage } from "../embedded-agent-runner/message-visibility.js"; import { persistCliAssistantTranscript } from "./cli-run-transcript.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); afterEach(() => closeOpenClawAgentDatabasesForTest()); it.each([ { kind: "completed", yielded: undefined, stopReason: "stop" }, { kind: "yielded", yielded: true, stopReason: "stop" }, { kind: "interrupted", yielded: undefined, stopReason: "aborted" }, { kind: "interrupted after yielding", yielded: true, stopReason: "aborted" }, ] as const)( "prepares the $kind CLI assistant before its first transcript publication", async ({ yielded, stopReason }) => { const root = tempDirs.make("openclaw-cli-media-transcript-"); const target = { agentId: "main", sessionId: "cli-media-session", sessionKey: "agent:main:cli-media", storePath: path.join(root, "agents", "main", "agent", "openclaw-agent.sqlite"), }; await upsertSessionEntry({ ...target, entry: { sessionId: target.sessionId, updatedAt: Date.now() }, }); const sourceText = "Artifacts ready\nMEDIA:./artifact.json"; const prepareAssistantTranscriptMessage = vi.fn((message: AssistantMessage) => applyAssistantDeliveryDirectives(message, { managedMediaUrls: ["./artifact.json"] }), ); const updates: InternalSessionTranscriptUpdate[] = []; const unsubscribe = onInternalSessionTranscriptUpdate((update) => updates.push(update)); try { const result = await persistCliAssistantTranscript({ runParams: { ...target, sessionFile: `sqlite://agents/main/${target.sessionId}`, workspaceDir: root, prompt: "make an artifact", provider: "claude-cli", runId: "cli-media-run", timeoutMs: 1_000, persistAssistantTranscript: true, prepareAssistantTranscriptMessage, }, text: sourceText, modelId: "claude-sonnet-4-6", stopReason, yielded, }); expect(result.owned).toBe(true); expect(prepareAssistantTranscriptMessage).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ content: [{ type: "text", text: sourceText }] }), sourceText, ); expect(updates).toHaveLength(1); expect(updates[0]?.message).toMatchObject({ content: [{ type: "text", text: sourceText }], idempotencyKey: result.idempotencyKey, openclawDelivery: { mediaUrls: ["./artifact.json"] }, }); const messages = (await loadTranscriptEvents(target)).flatMap((event) => typeof event === "object" && event !== null && "message" in event ? [event.message] : [], ); expect(messages).toHaveLength(1); expect(isIntermediateAssistantTranscriptMessage(messages[0])).toBe( yielded === true && stopReason === "stop", ); expect(messages[0]).toMatchObject({ stopReason }); if (yielded && stopReason === "stop") { expect(messages[0]).toMatchObject({ openclawStreamFallback: { replacementText: sourceText, source: "segment", itemId: "cli-media-run", }, }); } else { expect(messages[0]).not.toHaveProperty("openclawStreamFallback"); } expect(sanitizeChatHistoryMessages(messages)).toMatchObject([ { content: [{ type: "text", text: "Artifacts ready" }] }, ]); } finally { unsubscribe(); } }, );