



























@@ -113,6 +113,67 @@ describe("harness context engine lifecycle", () => {
113113expect(afterTurnParams?.prePromptMessageCount).toBe(2);
114114});
115115116+describe("assembleHarnessContextEngine result validation", () => {
117+// Regression for #75541: plugins that return a malformed assemble result
118+// previously poisoned activeSession.messages with `undefined`, which then
119+// crashed downstream with "Cannot read properties of undefined (reading
120+// 'length')". The harness wrapper now throws a descriptive error so the
121+// runner's existing assemble try/catch can log the engine id and fall
122+// back to the pipeline messages instead of corrupting session state.
123+const visibleUser = textMessage("user", "ping", 1);
124+125+async function runAssembleWithEngineResult(result: unknown) {
126+return assembleHarnessContextEngine({
127+contextEngine: createContextEngine({
128+info: { id: "broken-engine", name: "Broken engine" },
129+assemble: vi.fn(async () => result as never),
130+}),
131+sessionId: sessionParams.sessionId,
132+sessionKey: sessionParams.sessionKey,
133+messages: [visibleUser],
134+modelId: "gpt-test",
135+});
136+}
137+138+it("passes through a well-formed AssembleResult unchanged", async () => {
139+const wellFormed = { messages: [visibleUser], estimatedTokens: 0 };
140+await expect(runAssembleWithEngineResult(wellFormed)).resolves.toBe(wellFormed);
141+});
142+143+it("rejects an undefined assemble result with the engine id", async () => {
144+await expect(runAssembleWithEngineResult(undefined)).rejects.toThrow(
145+/context engine "broken-engine"[\s\S]*messages/,
146+);
147+});
148+149+it("rejects a null assemble result with the engine id", async () => {
150+await expect(runAssembleWithEngineResult(null)).rejects.toThrow(
151+/context engine "broken-engine"[\s\S]*messages/,
152+);
153+});
154+155+it("rejects an assemble result missing the messages array (lossless-claw shape)", async () => {
156+// Mirrors the malformed shape reported in #75541, where the plugin
157+// returned an object that satisfied the truthy `if (!assembled)` guard
158+// but omitted the required `messages` field.
159+await expect(runAssembleWithEngineResult({ estimatedTokens: 0 })).rejects.toThrow(
160+/assemble\(\) returned an invalid result[\s\S]*messages of type undefined/,
161+);
162+});
163+164+it("rejects an assemble result whose messages field is not an array", async () => {
165+await expect(
166+runAssembleWithEngineResult({ messages: "all of them", estimatedTokens: 0 }),
167+).rejects.toThrow(/messages of type string/);
168+});
169+170+it("rejects an assemble result whose messages field is null", async () => {
171+await expect(
172+runAssembleWithEngineResult({ messages: null, estimatedTokens: 0 }),
173+).rejects.toThrow(/messages of type null/);
174+});
175+});
176+116177it("keeps hidden runtime-context custom messages out of ingestBatch fallbacks", async () => {
117178const beforePromptUser = textMessage("user", "old ask", 1);
118179const beforePromptRuntimeContext = runtimeContextMessage("old hidden context", 2);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。