v2.2.2-xml-placeholder-reject: drop placeholder XML tool calls at parse time

Reject qwen3.6 spurious <invoke> tails with path "..." or empty args before
they enter toolCalls, preventing duplicate assistant answers. Dropped blocks
append to flushed text; four new xml-parser tests. DEFERRED-WORK §6 for
console.debug → pino cleanup.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 16:22:43 +00:00
parent 314adaae48
commit 31e1b32be1
5 changed files with 91 additions and 2 deletions

View File

@@ -270,6 +270,44 @@ describe('extractToolCallBlocks (v1.13.16 — unified extraction)', () => {
expect(result.flushed).toBe(input);
expect(result.remaining).toBe('');
});
describe('placeholder arg rejection (qwen3.6 answer-then-spurious-tools)', () => {
it('rejects <invoke> with path "..." — 0 calls, block in flushed', () => {
const block = '<invoke name="view_file"><parameter name="path">...</parameter></invoke>';
const result = extractToolCallBlocks(`Answer text.\n${block}`);
expect(result.calls).toEqual([]);
expect(result.flushed).toContain('Answer text.');
expect(result.flushed).toContain(block);
expect(result.remaining).toBe('');
});
it('rejects <invoke> with empty path — 0 calls, block in flushed', () => {
const block = '<invoke name="view_file"><parameter name="path"></parameter></invoke>';
const result = extractToolCallBlocks(block);
expect(result.calls).toEqual([]);
expect(result.flushed).toBe(block);
expect(result.remaining).toBe('');
});
it('rejects <invoke> with path "<path>" — 0 calls', () => {
const block = '<invoke name="view_file"><parameter name="path"><path></parameter></invoke>';
const result = extractToolCallBlocks(block);
expect(result.calls).toEqual([]);
expect(result.flushed).toBe(block);
});
it('returns 1 valid call and flushes placeholder block when mixed in same buffer', () => {
const valid =
'<invoke name="view_file"><parameter name="path">/opt/boocode/README.md</parameter></invoke>';
const placeholder =
'<invoke name="view_file"><parameter name="path">...</parameter></invoke>';
const result = extractToolCallBlocks(`${valid} tail ${placeholder}`);
expect(result.calls).toEqual([{ name: 'view_file', args: { path: '/opt/boocode/README.md' } }]);
expect(result.flushed).toContain('tail');
expect(result.flushed).toContain(placeholder);
expect(result.remaining).toBe('');
});
});
});
describe('levenshtein', () => {