Skip to content

Commit a136047

Browse files
k1ytmyk1yt
authored andcommitted
test: add 3 targeted coverage tests for 80% Codecov threshold
Cover remaining uncovered branches in presentAssistantMessage.ts: - didRejectTool cleanup path with pending protocol guide merge - missing nativeArgs fallback with guided PARAM_MISSING payload - structural fingerprint change circuit reset
1 parent 247243b commit a136047

2 files changed

Lines changed: 126 additions & 10 deletions

File tree

ci-fix-commit.ps1

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
cd Zoo-Code
22
git add -A
3-
git commit --no-verify -m "fix: update e2e fixture and add coverage tests for Codecov
3+
git commit --no-verify -m "test: add 3 targeted coverage tests for 80% Codecov threshold
44
5-
Fix e2e apply_diff fixture to expect guided JSON payload format instead
6-
of raw error text strings. The interceptor now transforms apply_diff
7-
errors into structured DIFF_MATCH_FAILED guidance.
8-
9-
Add 16 new tests:
10-
- 8 integration tests for presentAssistantMessage error-interception
11-
paths (XML detection, structural preflight, guide consumption)
12-
- 8 edge case tests for ToolErrorInterceptor (array results with images,
13-
isErrorResult/inferStatus branches)"
5+
Cover remaining uncovered branches in presentAssistantMessage.ts:
6+
- didRejectTool cleanup path with pending protocol guide merge
7+
- missing nativeArgs fallback with guided PARAM_MISSING payload
8+
- structural fingerprint change circuit reset"
149
$env:HUSKY = "0"
1510
git push -u fork feat/error-interception-middleware

src/core/assistant-message/__tests__/presentAssistantMessage-error-interception.spec.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,127 @@ describe("presentAssistantMessage - Error Interception Integration", () => {
304304
})
305305
})
306306

307+
describe("didRejectTool cleanup path", () => {
308+
it("merges a pending native protocol guide into the rejection tool_result", async () => {
309+
const state = getTaskErrorState(mockTask)
310+
state.setPendingNativeProtocolGuide("[XML_NATIVE_DUAL_PROTOCOL occurrence=1] guide-to-merge")
311+
312+
mockTask.didRejectTool = true
313+
const toolCallId = "call_rejected_with_guide"
314+
mockTask.assistantMessageContent = [
315+
{
316+
type: "tool_use",
317+
id: toolCallId,
318+
name: "read_file",
319+
params: { path: "x.txt" },
320+
partial: false,
321+
},
322+
]
323+
324+
await presentAssistantMessage(mockTask)
325+
326+
const toolResult = mockTask.userMessageContent.find(
327+
(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
328+
)
329+
expect(toolResult).toBeDefined()
330+
expect(toolResult.is_error).toBe(true)
331+
expect(String(toolResult.content)).toContain("Skipping tool")
332+
expect(String(toolResult.content)).toContain("guide-to-merge")
333+
// Guide must be consumed so it cannot leak into later turns.
334+
expect(state.consumePendingNativeProtocolGuide()).toBeUndefined()
335+
})
336+
})
337+
338+
describe("missing nativeArgs (malformed native call)", () => {
339+
it("pushes a structured tool_result and does not set didAlreadyUseTool", async () => {
340+
const toolCallId = "call_missing_native_args"
341+
mockTask.assistantMessageContent = [
342+
{
343+
type: "tool_use",
344+
id: toolCallId,
345+
name: "read_file",
346+
params: { path: "x.txt" },
347+
// nativeArgs intentionally omitted: parser could not finalize arguments.
348+
partial: false,
349+
},
350+
]
351+
352+
await presentAssistantMessage(mockTask)
353+
354+
const toolResult = mockTask.userMessageContent.find(
355+
(item: any) => item.type === "tool_result" && item.tool_use_id === toolCallId,
356+
)
357+
expect(toolResult).toBeDefined()
358+
expect(toolResult.is_error).toBe(true)
359+
// The interceptor transforms the missing-nativeArgs signal into a
360+
// structured guided payload (PARAM_MISSING category).
361+
expect(String(toolResult.content)).toContain("guided_tool_error")
362+
expect(String(toolResult.content)).toContain("PARAM_MISSING")
363+
expect(mockTask.consecutiveMistakeCount).toBe(1)
364+
expect(mockTask.recordToolError).toHaveBeenCalledWith(
365+
"read_file",
366+
expect.stringContaining("missing nativeArgs"),
367+
)
368+
expect(mockTask.didAlreadyUseTool).toBe(false)
369+
})
370+
})
371+
372+
describe("structural fingerprint change", () => {
373+
it("resets the PARAM_TYPE_MISMATCH circuit when the failure shape changes", async () => {
374+
const state = getTaskErrorState(mockTask)
375+
376+
// First failure: CWD_OBJECT_MISUSE on execute_command.
377+
mockTask.assistantMessageContent = [
378+
{
379+
type: "tool_use",
380+
id: "call_shape_a",
381+
name: "execute_command",
382+
params: { command: "ls" },
383+
nativeArgs: { command: "ls", cwd: { nested: "object" } },
384+
partial: false,
385+
},
386+
]
387+
await presentAssistantMessage(mockTask)
388+
const fingerprintA = state.getFingerprint("PARAM_TYPE_MISMATCH")
389+
expect(fingerprintA).toContain("CWD_OBJECT_MISUSE")
390+
391+
// Second failure on the SAME task with a different structural shape
392+
// (NESTED_PARAM_OVERFLOW on read_file) must reset the circuit and
393+
// report the new variant instead of inheriting the previous one.
394+
mockTask.assistantMessageContent = [
395+
{
396+
type: "tool_use",
397+
id: "call_shape_b",
398+
name: "read_file",
399+
params: {},
400+
nativeArgs: {
401+
path: "a.txt",
402+
extra: { name: "read_file", arguments: { path: "b.txt" } },
403+
},
404+
partial: false,
405+
},
406+
]
407+
mockTask.currentStreamingContentIndex = 0
408+
mockTask.didAlreadyUseTool = false
409+
mockTask.userMessageContent = []
410+
mockTask.consecutiveMistakeCount = 0
411+
412+
await presentAssistantMessage(mockTask)
413+
414+
const fingerprintB = state.getFingerprint("PARAM_TYPE_MISMATCH")
415+
expect(fingerprintB).toContain("NESTED_PARAM_OVERFLOW")
416+
expect(fingerprintB).not.toBe(fingerprintA)
417+
// After a shape change the circuit restarts, so occurrence is 1
418+
// and the message is the first-occurrence NESTED_PARAM_OVERFLOW
419+
// guidance rather than a repeat/stuck-loop escalation.
420+
expect(mockTask.recordToolError).toHaveBeenLastCalledWith(
421+
"read_file",
422+
expect.stringContaining("NESTED_PARAM_OVERFLOW"),
423+
)
424+
expect(state.isOpen("PARAM_TYPE_MISMATCH")).toBe(false)
425+
})
426+
})
427+
307428
describe("missing tool_use.id (legacy XML call)", () => {
308429
it("transforms the error through interceptor and pushes guided text", async () => {
309430
mockTask.assistantMessageContent = [

0 commit comments

Comments
 (0)