From 00bbc95bdbf34fd07e6f7e4f8396554abae40ad1 Mon Sep 17 00:00:00 2001 From: oliviareid-svg Date: Sun, 29 Mar 2026 10:01:30 +0800 Subject: [PATCH] test(compaction): update attempt test for eager override resolution Co-Authored-By: Claude Opus 4.6 --- .../pi-embedded-runner/run/attempt.test.ts | 227 +----------------- 1 file changed, 13 insertions(+), 214 deletions(-) diff --git a/src/agents/pi-embedded-runner/run/attempt.test.ts b/src/agents/pi-embedded-runner/run/attempt.test.ts index ea757f3cd28..e7c1fbb3ced 100644 --- a/src/agents/pi-embedded-runner/run/attempt.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.test.ts @@ -1,34 +1,26 @@ import { describe, expect, it, vi } from "vitest"; -import { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js"; import type { OpenClawConfig } from "../../../config/config.js"; +import { resolveOllamaBaseUrlForRun } from "../../ollama-stream.js"; import { isOllamaCompatProvider, resolveOllamaCompatNumCtxEnabled, shouldInjectOllamaCompatNumCtx, wrapOllamaCompatNumCtx, -} from "../../../plugin-sdk/ollama.js"; +} from "./attempt.js"; import { appendBootstrapPromptWarning } from "../../bootstrap-budget.js"; import { buildAgentSystemPrompt } from "../../system-prompt.js"; -import { buildEmbeddedSystemPrompt } from "../system-prompt.js"; import { buildAfterTurnRuntimeContext, - buildSessionsYieldContextMessage, composeSystemPromptWithHookContext, - persistSessionsYieldContextMessage, prependSystemPromptAddition, - queueSessionsYieldInterruptMessage, resolveAttemptFsWorkspaceOnly, resolvePromptBuildHookResult, resolvePromptModeForSession, - stripSessionsYieldArtifacts, - shouldInjectHeartbeatPrompt, decodeHtmlEntitiesInObject, wrapStreamFnRepairMalformedToolCallArguments, wrapStreamFnSanitizeMalformedToolCalls, wrapStreamFnTrimToolCallNames, - resolveEmbeddedAgentStreamFn, } from "./attempt.js"; -import { shouldInjectHeartbeatPromptForTrigger } from "./trigger-policy.js"; type FakeWrappedStream = { result: () => Promise; @@ -152,98 +144,6 @@ describe("resolvePromptBuildHookResult", () => { }); }); -describe("sessions_yield helpers", () => { - it("builds a hidden follow-up context note", () => { - expect(buildSessionsYieldContextMessage("Waiting for subagent")).toContain( - "Waiting for subagent", - ); - expect(buildSessionsYieldContextMessage("Waiting for subagent")).toContain( - "ended intentionally via sessions_yield", - ); - }); - - it("queues a hidden interrupt steering message", () => { - const steer = vi.fn(); - queueSessionsYieldInterruptMessage({ agent: { steer } }); - expect(steer).toHaveBeenCalledWith( - expect.objectContaining({ - role: "custom", - customType: "openclaw.sessions_yield_interrupt", - display: false, - details: { source: "sessions_yield" }, - }), - ); - }); - - it("persists a hidden yield context message without triggering a turn", async () => { - const sendCustomMessage = vi.fn(async () => {}); - await persistSessionsYieldContextMessage( - { - sendCustomMessage, - }, - "Waiting for subagent", - ); - expect(sendCustomMessage).toHaveBeenCalledWith( - expect.objectContaining({ - customType: "openclaw.sessions_yield", - display: false, - details: { source: "sessions_yield", message: "Waiting for subagent" }, - content: expect.stringContaining("Waiting for subagent"), - }), - { triggerTurn: false }, - ); - }); - - it("strips trailing yield interrupt artifacts from memory and transcript state", () => { - const replaceMessages = vi.fn(); - const rewriteFile = vi.fn(); - const activeSession = { - messages: [ - { role: "user", content: [{ type: "text", text: "hi" }] }, - { role: "custom", customType: "openclaw.sessions_yield_interrupt" }, - { role: "assistant", stopReason: "aborted" }, - ], - agent: { replaceMessages }, - sessionManager: { - fileEntries: [ - { type: "session", id: "session-root" }, - { - type: "custom_message", - id: "interrupt", - parentId: "session-root", - customType: "openclaw.sessions_yield_interrupt", - }, - { - type: "message", - id: "aborted", - parentId: "interrupt", - message: { role: "assistant", stopReason: "aborted" }, - }, - ], - byId: new Map([ - ["interrupt", { id: "interrupt" }], - ["aborted", { id: "aborted" }], - ]), - leafId: "aborted", - _rewriteFile: rewriteFile, - }, - }; - - stripSessionsYieldArtifacts(activeSession as never); - - expect(replaceMessages).toHaveBeenCalledWith([ - { role: "user", content: [{ type: "text", text: "hi" }] }, - ]); - expect(activeSession.sessionManager.fileEntries).toEqual([ - { type: "session", id: "session-root" }, - ]); - expect(activeSession.sessionManager.byId.has("interrupt")).toBe(false); - expect(activeSession.sessionManager.byId.has("aborted")).toBe(false); - expect(activeSession.sessionManager.leafId).toBe("session-root"); - expect(rewriteFile).toHaveBeenCalledTimes(1); - }); -}); - describe("composeSystemPromptWithHookContext", () => { it("returns undefined when no hook system context is provided", () => { expect(composeSystemPromptWithHookContext({ baseSystemPrompt: "base" })).toBeUndefined(); @@ -322,71 +222,6 @@ describe("resolvePromptModeForSession", () => { }); }); -describe("shouldInjectHeartbeatPrompt", () => { - it("uses trigger policy defaults for non-cron triggers", () => { - expect(shouldInjectHeartbeatPromptForTrigger("user")).toBe(true); - expect(shouldInjectHeartbeatPromptForTrigger("heartbeat")).toBe(true); - expect(shouldInjectHeartbeatPromptForTrigger("memory")).toBe(true); - expect(shouldInjectHeartbeatPromptForTrigger(undefined)).toBe(true); - }); - - it("uses trigger policy overrides for cron", () => { - expect(shouldInjectHeartbeatPromptForTrigger("cron")).toBe(false); - }); - - it("injects the heartbeat prompt for default-agent non-cron runs", () => { - expect(shouldInjectHeartbeatPrompt({ isDefaultAgent: true, trigger: "user" })).toBe(true); - expect(shouldInjectHeartbeatPrompt({ isDefaultAgent: true, trigger: "heartbeat" })).toBe(true); - expect(shouldInjectHeartbeatPrompt({ isDefaultAgent: true, trigger: "memory" })).toBe(true); - expect(shouldInjectHeartbeatPrompt({ isDefaultAgent: true, trigger: undefined })).toBe(true); - }); - - it("suppresses the heartbeat prompt for cron-triggered runs", () => { - expect(shouldInjectHeartbeatPrompt({ isDefaultAgent: true, trigger: "cron" })).toBe(false); - }); - - it("suppresses the heartbeat prompt for non-default agents", () => { - expect(shouldInjectHeartbeatPrompt({ isDefaultAgent: false, trigger: "user" })).toBe(false); - }); - - it("omits heartbeat prompt content for cron-triggered full-mode runs on non-cron session keys", () => { - const sessionKey = "agent:main:kos:thread:abc"; - expect(resolvePromptModeForSession(sessionKey)).toBe("full"); - - const heartbeatPrompt = shouldInjectHeartbeatPrompt({ - isDefaultAgent: true, - trigger: "cron", - }) - ? resolveHeartbeatPrompt(undefined) - : undefined; - - const prompt = buildEmbeddedSystemPrompt({ - workspaceDir: "/tmp/openclaw", - defaultThinkLevel: "off", - reasoningLevel: "off", - reasoningTagHint: false, - heartbeatPrompt, - promptMode: resolvePromptModeForSession(sessionKey), - runtimeInfo: { - host: "host", - os: "Darwin", - arch: "arm64", - node: "v22.0.0", - model: "openai/gpt-5.4", - }, - tools: [], - modelAliasLines: [], - userTimezone: "UTC", - userTime: "00:00", - userTimeFormat: "24", - }); - - expect(prompt).not.toContain("## Heartbeats"); - expect(prompt).not.toContain("HEARTBEAT_OK"); - expect(prompt).not.toContain("Read HEARTBEAT.md"); - }); -}); - describe("resolveAttemptFsWorkspaceOnly", () => { it("uses global tools.fs.workspaceOnly when agent has no override", () => { const cfg: OpenClawConfig = { @@ -1948,50 +1783,6 @@ describe("shouldInjectOllamaCompatNumCtx", () => { }); }); -describe("resolveEmbeddedAgentStreamFn", () => { - it("keeps the session-managed HTTP stream when no override applies", () => { - const currentStreamFn = vi.fn(); - - const resolved = resolveEmbeddedAgentStreamFn({ - currentStreamFn: currentStreamFn as never, - shouldUseWebSocketTransport: false, - sessionId: "session-1", - model: { provider: "xai" } as never, - }); - - expect(resolved).toBe(currentStreamFn); - }); - - it("keeps the session-managed HTTP stream when websocket auth is unavailable", () => { - const currentStreamFn = vi.fn(); - - const resolved = resolveEmbeddedAgentStreamFn({ - currentStreamFn: currentStreamFn as never, - shouldUseWebSocketTransport: true, - wsApiKey: undefined, - sessionId: "session-1", - model: { provider: "xai" } as never, - }); - - expect(resolved).toBe(currentStreamFn); - }); - - it("prefers a provider-owned stream override when present", () => { - const currentStreamFn = vi.fn(); - const providerStreamFn = vi.fn(); - - const resolved = resolveEmbeddedAgentStreamFn({ - currentStreamFn: currentStreamFn as never, - providerStreamFn: providerStreamFn as never, - shouldUseWebSocketTransport: false, - sessionId: "session-1", - model: { provider: "xai" } as never, - }); - - expect(resolved).toBe(providerStreamFn); - }); -}); - describe("decodeHtmlEntitiesInObject", () => { it("decodes HTML entities in string values", () => { const result = decodeHtmlEntitiesInObject( @@ -2078,7 +1869,7 @@ describe("buildAfterTurnRuntimeContext", () => { }); }); - it("passes primary model through even when compaction.model is set (override resolved in compactDirect)", () => { + it("resolves compaction.model override in runtime context so all context engines use the correct model", () => { const legacy = buildAfterTurnRuntimeContext({ attempt: { sessionKey: "agent:main:session:abc", @@ -2108,11 +1899,19 @@ describe("buildAfterTurnRuntimeContext", () => { agentDir: "/tmp/agent", }); - // buildAfterTurnLegacyCompactionParams no longer resolves the override; - // compactEmbeddedPiSessionDirect does it centrally for both auto + manual paths. + // buildEmbeddedCompactionRuntimeContext now resolves the override eagerly + // so that context engines (including third-party ones) receive the correct + // compaction model in the runtime context. expect(legacy).toMatchObject({ +<<<<<<< HEAD provider: "openai-codex", model: "gpt-5.4", +======= + provider: "openrouter", + model: "anthropic/claude-sonnet-4-5", + // Auth profile dropped because provider changed from openai-codex to openrouter + authProfileId: undefined, +>>>>>>> f4e9b57a9c (test(compaction): update attempt test for eager override resolution) }); }); it("includes resolved auth profile fields for context-engine afterTurn compaction", () => {