























@@ -0,0 +1,152 @@
1+import type { AgentMessage } from "@mariozechner/pi-agent-core";
2+import { describe, expect, it, vi } from "vitest";
3+import type { ContextEngine } from "../../context-engine/types.js";
4+import { OPENCLAW_RUNTIME_CONTEXT_CUSTOM_TYPE } from "../internal-runtime-context.js";
5+import {
6+assembleHarnessContextEngine,
7+finalizeHarnessContextEngineTurn,
8+} from "./context-engine-lifecycle.js";
9+10+function textMessage(role: "user" | "assistant", text: string, timestamp: number): AgentMessage {
11+return {
12+ role,
13+content: [{ type: "text", text }],
14+ timestamp,
15+} as AgentMessage;
16+}
17+18+function runtimeContextMessage(content: string, timestamp: number): AgentMessage {
19+return {
20+role: "custom",
21+customType: OPENCLAW_RUNTIME_CONTEXT_CUSTOM_TYPE,
22+ content,
23+display: false,
24+details: { source: "openclaw-runtime-context" },
25+ timestamp,
26+} as AgentMessage;
27+}
28+29+function createContextEngine(overrides: Partial<ContextEngine> = {}): ContextEngine {
30+return {
31+info: { id: "test", name: "Test context engine" },
32+ingest: vi.fn(async () => ({ ingested: true })),
33+assemble: vi.fn(async (params) => ({
34+messages: params.messages,
35+estimatedTokens: 0,
36+})),
37+compact: vi.fn(async () => ({ ok: true, compacted: false })),
38+ ...overrides,
39+};
40+}
41+42+const sessionParams = {
43+sessionIdUsed: "session-1",
44+sessionId: "session-1",
45+sessionKey: "agent:main",
46+sessionFile: "sessions/main.jsonl",
47+};
48+49+describe("harness context engine lifecycle", () => {
50+it("keeps hidden runtime-context custom messages out of assemble hooks", async () => {
51+const visibleUser = textMessage("user", "visible ask", 1);
52+const hiddenRuntimeContext = runtimeContextMessage("hidden runtime context", 2);
53+const visibleAssistant = textMessage("assistant", "visible answer", 3);
54+const assemble = vi.fn(async (params: Parameters<ContextEngine["assemble"]>[0]) => ({
55+messages: params.messages,
56+estimatedTokens: 0,
57+}));
58+59+await assembleHarnessContextEngine({
60+contextEngine: createContextEngine({ assemble }),
61+sessionId: sessionParams.sessionId,
62+sessionKey: sessionParams.sessionKey,
63+messages: [visibleUser, hiddenRuntimeContext, visibleAssistant],
64+modelId: "gpt-test",
65+});
66+67+expect(assemble).toHaveBeenCalledWith(
68+expect.objectContaining({
69+messages: [visibleUser, visibleAssistant],
70+}),
71+);
72+});
73+74+it("keeps hidden runtime-context custom messages out of afterTurn hooks", async () => {
75+const beforePromptUser = textMessage("user", "old ask", 1);
76+const beforePromptRuntimeContext = runtimeContextMessage("old hidden context", 2);
77+const beforePromptAssistant = textMessage("assistant", "old answer", 3);
78+const turnUser = textMessage("user", "new ask", 4);
79+const turnRuntimeContext = runtimeContextMessage("new hidden context", 5);
80+const turnAssistant = textMessage("assistant", "new answer", 6);
81+const afterTurn = vi.fn(async () => {});
82+83+await finalizeHarnessContextEngineTurn({
84+contextEngine: createContextEngine({ afterTurn }),
85+promptError: false,
86+aborted: false,
87+yieldAborted: false,
88+sessionIdUsed: sessionParams.sessionIdUsed,
89+sessionKey: sessionParams.sessionKey,
90+sessionFile: sessionParams.sessionFile,
91+messagesSnapshot: [
92+beforePromptUser,
93+beforePromptRuntimeContext,
94+beforePromptAssistant,
95+turnUser,
96+turnRuntimeContext,
97+turnAssistant,
98+],
99+prePromptMessageCount: 3,
100+tokenBudget: 2048,
101+runtimeContext: {},
102+runMaintenance: async () => undefined,
103+warn: () => {},
104+});
105+106+expect(afterTurn).toHaveBeenCalledWith(
107+expect.objectContaining({
108+messages: [beforePromptUser, beforePromptAssistant, turnUser, turnAssistant],
109+prePromptMessageCount: 2,
110+}),
111+);
112+});
113+114+it("keeps hidden runtime-context custom messages out of ingestBatch fallbacks", async () => {
115+const beforePromptUser = textMessage("user", "old ask", 1);
116+const beforePromptRuntimeContext = runtimeContextMessage("old hidden context", 2);
117+const beforePromptAssistant = textMessage("assistant", "old answer", 3);
118+const turnUser = textMessage("user", "new ask", 4);
119+const turnRuntimeContext = runtimeContextMessage("new hidden context", 5);
120+const turnAssistant = textMessage("assistant", "new answer", 6);
121+const ingestBatch = vi.fn(async () => ({ ingestedCount: 2 }));
122+123+await finalizeHarnessContextEngineTurn({
124+contextEngine: createContextEngine({ ingestBatch }),
125+promptError: false,
126+aborted: false,
127+yieldAborted: false,
128+sessionIdUsed: sessionParams.sessionIdUsed,
129+sessionKey: sessionParams.sessionKey,
130+sessionFile: sessionParams.sessionFile,
131+messagesSnapshot: [
132+beforePromptUser,
133+beforePromptRuntimeContext,
134+beforePromptAssistant,
135+turnUser,
136+turnRuntimeContext,
137+turnAssistant,
138+],
139+prePromptMessageCount: 3,
140+tokenBudget: 2048,
141+runtimeContext: {},
142+runMaintenance: async () => undefined,
143+warn: () => {},
144+});
145+146+expect(ingestBatch).toHaveBeenCalledWith(
147+expect.objectContaining({
148+messages: [turnUser, turnAssistant],
149+}),
150+);
151+});
152+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。