fix(core): reject truncated subagent write_file calls - #3505
Conversation
Propagate MAX_TOKENS truncation from subagent responses into tool requests and reject truncated edit calls before schema validation can surface misleading missing-parameter errors. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
When a streaming response hits MAX_TOKENS and is retried, accumulated state variables (functionCalls, wasOutputTruncated, roundText, etc.) were not cleared. This caused a successful retry to inherit the stale wasOutputTruncated=true flag from the failed attempt, incorrectly rejecting all Edit tool calls with a truncation error. Reset all per-attempt state on retry events, matching the existing behaviour in turn.ts. Add a regression test covering the truncated-then-retried-successfully scenario. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ — gpt-5.4 via Qwen Code /review
|
Took this for a thorough verification run before merge — local test suite (5896 tests) is green, typecheck/build clean, both new tests pass. One small coverage suggestion before merge if you have a moment: The existing test Without it('should discard pre-retry function calls so only the retried call executes', async () => {
const writeFileToolDef: FunctionDeclaration = {
name: WriteFileTool.Name,
description: 'Writes a file',
parameters: { type: Type.OBJECT, properties: {} },
};
const { config } = await createMockConfig({
getFunctionDeclarationsFiltered: vi
.fn()
.mockReturnValue([writeFileToolDef]),
getTool: vi.fn().mockImplementation((name: string) => {
if (name === WriteFileTool.Name) {
return new WriteFileTool(config);
}
return undefined;
}),
});
const toolConfig: ToolConfig = { tools: [WriteFileTool.Name] };
const toolResultEvents: AgentToolResultEvent[] = [];
const eventEmitter = new AgentEventEmitter();
eventEmitter.on(AgentEventType.TOOL_RESULT, (event: unknown) => {
toolResultEvents.push(event as AgentToolResultEvent);
});
// Round 1: emit a (would-be) tool call, then a retry, then a different
// tool call. The retry handler must reset the accumulated functionCalls
// array — otherwise BOTH calls execute and the pre-retry one (which
// was supposed to be discarded) leaks into tool execution.
let callCount = 0;
mockSendMessageStream.mockImplementation(async () => {
callCount++;
if (callCount === 1) {
return (async function* () {
yield {
type: 'chunk',
value: {
functionCalls: [
{
id: 'call_pre_retry',
name: WriteFileTool.Name,
args: {
file_path: '/tmp/pre-retry.txt',
content: 'should-not-run',
},
},
],
},
};
yield { type: 'retry' };
yield {
type: 'chunk',
value: {
functionCalls: [
{
id: 'call_post_retry',
name: WriteFileTool.Name,
args: {
file_path: '/tmp/post-retry.txt',
content: 'should-run',
},
},
],
},
};
yield {
type: 'chunk',
value: {
candidates: [
{ finishReason: 'STOP', content: { parts: [] } },
],
},
};
})();
}
return (async function* () {
yield {
type: 'chunk',
value: {
candidates: [
{
finishReason: 'STOP',
content: { parts: [{ text: 'done' }] },
},
],
},
};
})();
});
const scope = await AgentHeadless.create(
'test-agent',
config,
promptConfig,
defaultModelConfig,
defaultRunConfig,
toolConfig,
eventEmitter,
);
await scope.execute(new ContextState());
const writeResults = toolResultEvents.filter(
(event) => event.name === WriteFileTool.Name,
);
// Exactly one write_file invocation should execute — the retried one.
expect(writeResults).toHaveLength(1);
expect(writeResults[0].callId).toBe('call_post_retry');
// The discarded call must never have produced a TOOL_RESULT event.
expect(
toolResultEvents.some((e) => e.callId === 'call_pre_retry'),
).toBe(false);
});Reverse-validated: with 合入前完整跑了一遍验证,全包测试 5896 个用例 + typecheck/build 都干净,没有回归。提一个补测试的建议: 现有的 如果没有 修复本身很扎实,把截断检查前移到 schema 校验之前是对的。 |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(core): reject truncated subagent write_file calls Propagate MAX_TOKENS truncation from subagent responses into tool requests and reject truncated edit calls before schema validation can surface misleading missing-parameter errors. * fix(core): reset per-attempt stream state on retry in agent-core When a streaming response hits MAX_TOKENS and is retried, accumulated state variables (functionCalls, wasOutputTruncated, roundText, etc.) were not cleared. This caused a successful retry to inherit the stale wasOutputTruncated=true flag from the failed attempt, incorrectly rejecting all Edit tool calls with a truncation error. Reset all per-attempt state on retry events, matching the existing behaviour in turn.ts. Add a regression test covering the truncated-then-retried-successfully scenario. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…3505, #3467) (#113) * fix: backport upstream trivial bug fixes (#3499, #3630, #3320) Cherry-picked from QwenLM/qwen-code: - #3499 fix(core): use empty string instead of null for reasoning-only assistant content. Some OpenAI-compatible providers (e.g. Ollama qwen3.5:9b) reject content: null with HTTP 400 when reasoning_content is also present. Tool-call-only messages keep null per OpenAI spec. - #3630 fix(telemetry): switch FileExporter.serialize from JSON.stringify to safeJsonStringify. OTel ReadableSpans hold a BatchSpanProcessor back-reference that forms a cycle and crashed --telemetry-outfile users. - #3320 fix(core): cap chokidar depth at 2 in SkillManager and skip .git / special file types. Prevents FD exhaustion when a skill dir contains node_modules etc., which silently broke node-pty I/O. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(core): scope StreamingToolCallParser per-stream (#3525) Backports upstream PR #3525 + extends the per-stream context to also cover our fork's <think>-tag parser state. Bug: every caller of Config.getContentGenerator() — foreground turns, fork subagents, run_in_background subagents, ACP concurrent Agent calls (after #3463) — shared a single OpenAIContentConverter, which held the StreamingToolCallParser as an instance field. Concurrent streams corrupted each other's tool-call buffers, surfacing as NO_RESPONSE_TEXT. Fix: - New ConverterStreamContext interface holds toolCallParser, thinkBuffer, inThinkTag — one per stream. - createStreamContext() factory replaces resetStreamingToolCalls(). - convertOpenAIChunkToGemini(chunk, ctx) and processThinkChunk(chunk, ctx) thread the context through every parser/think-buffer access. - ContentGenerationPipeline.processStreamWithLogging creates one context at stream entry. The error path no longer manually resets — the context is GC'd when the generator unwinds. Our protoInternal recovery-note logic is preserved on the new shape. Note: upstream's follow-up #3550 (full stateless converter refactor) is deferred — it's hygiene without a functional bug; #3525 alone fixes the concurrency race. Tests: - New createStreamContext describe replaces resetStreamingToolCalls suite - Streaming <think> tests use a per-test context - pipeline.test.ts mock updated to match the new API - pipeline.concurrent.test.ts (from upstream commit 38edd9dc5) drives two real concurrent streams and asserts neither corrupts the other's tool-call output (positive control: pre-fix, this test fails with exactly the user-reported bug shape). Refs upstream #3516, #3525. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(core): strip thinking blocks from history on model switch (#3304) When switching models mid-session, reasoning_content fields from thinking-capable models leaked into API requests sent to the new provider, causing 422 errors on strict OpenAI-compatible endpoints. Call stripThoughtsFromHistory() in handleModelChange() so thought parts are removed before the next request is built for the new model. * fix(core): reject truncated subagent write_file calls (#3505) Backport of upstream #3505. Propagates MAX_TOKENS truncation from subagent responses into tool requests and rejects truncated edit calls before schema validation can surface misleading missing-parameter errors. Adapted to our fork's coreToolScheduler.ts which already had the truncation rejection block — kept both, dropped the unused clearRetryCountsForTool() call (we don't have that retry-counter machinery yet). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(core): prevent malformed permission rules from becoming tool-wide catch-alls (#3467) Backport of upstream #3467. A permission rule with unbalanced parens was silently parsed with specifier: undefined, causing matchesRule to treat it as a catch-all. For deny rules this blocked all commands; for allow rules a typo could silently auto-approve everything. - Adds an invalid flag to PermissionRule - parseRule marks unbalanced-paren rules as invalid - matchesRule short-circuits invalid rules to never match - parseRules / addSession*Rule / addPersistentRule warn on malformed input - listRules filters invalid rules from /permissions UI Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(core): preserve reasoning_content during session resume and active sessions (GH#3579) * test(config): drop fork-incompatible #3304 strip-thoughts test The test from upstream #3304 backport assumed an in-place qwen-oauth model switch path that our fork doesn't have; the source-side fix in config.ts (stripThoughtsFromHistory call in handleModelChange) is preserved. Coverage will be re-added when the fork's switch flow stabilizes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Automaker <automaker@localhost> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: tanzhenxin <tanzhenxing1987@gmail.com> Co-authored-by: Yuchen Fu <fuyuchen0904@163.com>
#115) * fix: backport upstream trivial bug fixes (#3499, #3630, #3320) Cherry-picked from QwenLM/qwen-code: - #3499 fix(core): use empty string instead of null for reasoning-only assistant content. Some OpenAI-compatible providers (e.g. Ollama qwen3.5:9b) reject content: null with HTTP 400 when reasoning_content is also present. Tool-call-only messages keep null per OpenAI spec. - #3630 fix(telemetry): switch FileExporter.serialize from JSON.stringify to safeJsonStringify. OTel ReadableSpans hold a BatchSpanProcessor back-reference that forms a cycle and crashed --telemetry-outfile users. - #3320 fix(core): cap chokidar depth at 2 in SkillManager and skip .git / special file types. Prevents FD exhaustion when a skill dir contains node_modules etc., which silently broke node-pty I/O. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(core): scope StreamingToolCallParser per-stream (#3525) Backports upstream PR #3525 + extends the per-stream context to also cover our fork's <think>-tag parser state. Bug: every caller of Config.getContentGenerator() — foreground turns, fork subagents, run_in_background subagents, ACP concurrent Agent calls (after #3463) — shared a single OpenAIContentConverter, which held the StreamingToolCallParser as an instance field. Concurrent streams corrupted each other's tool-call buffers, surfacing as NO_RESPONSE_TEXT. Fix: - New ConverterStreamContext interface holds toolCallParser, thinkBuffer, inThinkTag — one per stream. - createStreamContext() factory replaces resetStreamingToolCalls(). - convertOpenAIChunkToGemini(chunk, ctx) and processThinkChunk(chunk, ctx) thread the context through every parser/think-buffer access. - ContentGenerationPipeline.processStreamWithLogging creates one context at stream entry. The error path no longer manually resets — the context is GC'd when the generator unwinds. Our protoInternal recovery-note logic is preserved on the new shape. Note: upstream's follow-up #3550 (full stateless converter refactor) is deferred — it's hygiene without a functional bug; #3525 alone fixes the concurrency race. Tests: - New createStreamContext describe replaces resetStreamingToolCalls suite - Streaming <think> tests use a per-test context - pipeline.test.ts mock updated to match the new API - pipeline.concurrent.test.ts (from upstream commit 38edd9dc5) drives two real concurrent streams and asserts neither corrupts the other's tool-call output (positive control: pre-fix, this test fails with exactly the user-reported bug shape). Refs upstream #3516, #3525. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(core): strip thinking blocks from history on model switch (#3304) When switching models mid-session, reasoning_content fields from thinking-capable models leaked into API requests sent to the new provider, causing 422 errors on strict OpenAI-compatible endpoints. Call stripThoughtsFromHistory() in handleModelChange() so thought parts are removed before the next request is built for the new model. * fix(core): reject truncated subagent write_file calls (#3505) Backport of upstream #3505. Propagates MAX_TOKENS truncation from subagent responses into tool requests and rejects truncated edit calls before schema validation can surface misleading missing-parameter errors. Adapted to our fork's coreToolScheduler.ts which already had the truncation rejection block — kept both, dropped the unused clearRetryCountsForTool() call (we don't have that retry-counter machinery yet). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(core): prevent malformed permission rules from becoming tool-wide catch-alls (#3467) Backport of upstream #3467. A permission rule with unbalanced parens was silently parsed with specifier: undefined, causing matchesRule to treat it as a catch-all. For deny rules this blocked all commands; for allow rules a typo could silently auto-approve everything. - Adds an invalid flag to PermissionRule - parseRule marks unbalanced-paren rules as invalid - matchesRule short-circuits invalid rules to never match - parseRules / addSession*Rule / addPersistentRule warn on malformed input - listRules filters invalid rules from /permissions UI Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(core): preserve reasoning_content during session resume and active sessions (GH#3579) * test(config): drop fork-incompatible #3304 strip-thoughts test The test from upstream #3304 backport assumed an in-place qwen-oauth model switch path that our fork doesn't have; the source-side fix in config.ts (stripThoughtsFromHistory call in handleModelChange) is preserved. Coverage will be re-added when the fork's switch flow stabilizes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(acp): run Agent tool calls concurrently + graceful degrade (#3463) Backport of upstream #3463. When the model returns multiple Agent tool calls in a single turn, ACP Session was executing them sequentially in a for-loop, multiplying latency by sub-agent count. - Add private runToolCalls() helper that mirrors coreToolScheduler's partition logic: consecutive Agent calls form a parallel batch (safe because sub-agents have no shared mutable state); other tools form sequential batches. - Replace 2 for-loops in Session.ts with runToolCalls() calls. - Switch the AgentTool eventEmitter guard from key-presence check to truthy check (commit 651979cdf) — the key-presence check passed for { eventEmitter: undefined } and crashed inside SubAgentTracker.setup. Note: upstream replaced 3 for-loops; our fork only had 2 in those code paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(acp): support SSE and HTTP MCP servers in ACP mode In ACP mode, the Mcp server list sent by the IDE client can include SSE (type: "sse") and HTTP (type: "http") transports, but the previous implementation only handled stdio servers via toStdioServer(). Non-stdio servers were silently skipped (continue), so any SSE/HTTP-configured MCP server would never be registered. Changes: - Add toSseServer() helper: detects type=="sse" servers and maps them to MCPServerConfig(url=..., headers=...) - Add toHttpServer() helper: detects type=="http" servers and maps them to MCPServerConfig(httpUrl=..., headers=...) - Refactor newSessionConfig() loop to handle all three transport types - Declare mcpCapabilities: { sse: true, http: true } in agentCapabilities so IDE clients know this agent supports these transports without needing a transparent proxy - Export the three helper functions for unit testing Tests: - Unit tests for toStdioServer / toSseServer / toHttpServer helpers (type discrimination, mutual exclusion) - Integration-style tests for QwenAgent.initialize() mcpCapabilities - Integration-style tests for newSession() with SSE/HTTP MCP servers, verifying MCPServerConfig is constructed with the correct arguments (url vs httpUrl, headers passthrough, empty-headers → undefined) Fixes #3472 --------- Co-authored-by: Automaker <automaker@localhost> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: tanzhenxin <tanzhenxing1987@gmail.com> Co-authored-by: Yuchen Fu <fuyuchen0904@163.com> Co-authored-by: LaZzyMan <zeusdream7@gmail.com>
* fix: backport upstream trivial bug fixes (#3499, #3630, #3320)
Cherry-picked from QwenLM/qwen-code:
- #3499 fix(core): use empty string instead of null for reasoning-only
assistant content. Some OpenAI-compatible providers (e.g. Ollama
qwen3.5:9b) reject content: null with HTTP 400 when reasoning_content is
also present. Tool-call-only messages keep null per OpenAI spec.
- #3630 fix(telemetry): switch FileExporter.serialize from JSON.stringify
to safeJsonStringify. OTel ReadableSpans hold a BatchSpanProcessor
back-reference that forms a cycle and crashed --telemetry-outfile users.
- #3320 fix(core): cap chokidar depth at 2 in SkillManager and skip
.git / special file types. Prevents FD exhaustion when a skill dir
contains node_modules etc., which silently broke node-pty I/O.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(core): scope StreamingToolCallParser per-stream (#3525)
Backports upstream PR #3525 + extends the per-stream context to also
cover our fork's <think>-tag parser state.
Bug: every caller of Config.getContentGenerator() — foreground turns,
fork subagents, run_in_background subagents, ACP concurrent Agent calls
(after #3463) — shared a single OpenAIContentConverter, which held the
StreamingToolCallParser as an instance field. Concurrent streams
corrupted each other's tool-call buffers, surfacing as NO_RESPONSE_TEXT.
Fix:
- New ConverterStreamContext interface holds toolCallParser, thinkBuffer,
inThinkTag — one per stream.
- createStreamContext() factory replaces resetStreamingToolCalls().
- convertOpenAIChunkToGemini(chunk, ctx) and processThinkChunk(chunk, ctx)
thread the context through every parser/think-buffer access.
- ContentGenerationPipeline.processStreamWithLogging creates one context
at stream entry. The error path no longer manually resets — the
context is GC'd when the generator unwinds.
Our protoInternal recovery-note logic is preserved on the new shape.
Note: upstream's follow-up #3550 (full stateless converter refactor) is
deferred — it's hygiene without a functional bug; #3525 alone fixes
the concurrency race.
Tests:
- New createStreamContext describe replaces resetStreamingToolCalls suite
- Streaming <think> tests use a per-test context
- pipeline.test.ts mock updated to match the new API
- pipeline.concurrent.test.ts (from upstream commit 38edd9dc5)
drives two real concurrent streams and asserts neither corrupts the
other's tool-call output (positive control: pre-fix, this test fails
with exactly the user-reported bug shape).
Refs upstream #3516, #3525.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(core): strip thinking blocks from history on model switch (#3304)
When switching models mid-session, reasoning_content fields from
thinking-capable models leaked into API requests sent to the new
provider, causing 422 errors on strict OpenAI-compatible endpoints.
Call stripThoughtsFromHistory() in handleModelChange() so thought parts
are removed before the next request is built for the new model.
* fix(core): reject truncated subagent write_file calls (#3505)
Backport of upstream #3505. Propagates MAX_TOKENS truncation from
subagent responses into tool requests and rejects truncated edit calls
before schema validation can surface misleading missing-parameter errors.
Adapted to our fork's coreToolScheduler.ts which already had the
truncation rejection block — kept both, dropped the unused
clearRetryCountsForTool() call (we don't have that retry-counter
machinery yet).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(core): prevent malformed permission rules from becoming tool-wide catch-alls (#3467)
Backport of upstream #3467. A permission rule with unbalanced parens
was silently parsed with specifier: undefined, causing matchesRule to
treat it as a catch-all. For deny rules this blocked all commands; for
allow rules a typo could silently auto-approve everything.
- Adds an invalid flag to PermissionRule
- parseRule marks unbalanced-paren rules as invalid
- matchesRule short-circuits invalid rules to never match
- parseRules / addSession*Rule / addPersistentRule warn on malformed input
- listRules filters invalid rules from /permissions UI
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(core): preserve reasoning_content during session resume and active sessions (GH#3579)
* test(config): drop fork-incompatible #3304 strip-thoughts test
The test from upstream #3304 backport assumed an in-place qwen-oauth
model switch path that our fork doesn't have; the source-side fix in
config.ts (stripThoughtsFromHistory call in handleModelChange) is
preserved. Coverage will be re-added when the fork's switch flow
stabilizes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(acp): run Agent tool calls concurrently + graceful degrade (#3463)
Backport of upstream #3463. When the model returns multiple Agent tool
calls in a single turn, ACP Session was executing them sequentially in
a for-loop, multiplying latency by sub-agent count.
- Add private runToolCalls() helper that mirrors coreToolScheduler's
partition logic: consecutive Agent calls form a parallel batch (safe
because sub-agents have no shared mutable state); other tools form
sequential batches.
- Replace 2 for-loops in Session.ts with runToolCalls() calls.
- Switch the AgentTool eventEmitter guard from key-presence check to
truthy check (commit 651979cdf) — the key-presence check passed for
{ eventEmitter: undefined } and crashed inside SubAgentTracker.setup.
Note: upstream replaced 3 for-loops; our fork only had 2 in those code
paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(acp): support SSE and HTTP MCP servers in ACP mode
In ACP mode, the Mcp server list sent by the IDE client can include
SSE (type: "sse") and HTTP (type: "http") transports, but the previous
implementation only handled stdio servers via toStdioServer(). Non-stdio
servers were silently skipped (continue), so any SSE/HTTP-configured
MCP server would never be registered.
Changes:
- Add toSseServer() helper: detects type=="sse" servers and maps them
to MCPServerConfig(url=..., headers=...)
- Add toHttpServer() helper: detects type=="http" servers and maps them
to MCPServerConfig(httpUrl=..., headers=...)
- Refactor newSessionConfig() loop to handle all three transport types
- Declare mcpCapabilities: { sse: true, http: true } in agentCapabilities
so IDE clients know this agent supports these transports without needing
a transparent proxy
- Export the three helper functions for unit testing
Tests:
- Unit tests for toStdioServer / toSseServer / toHttpServer helpers
(type discrimination, mutual exclusion)
- Integration-style tests for QwenAgent.initialize() mcpCapabilities
- Integration-style tests for newSession() with SSE/HTTP MCP servers,
verifying MCPServerConfig is constructed with the correct arguments
(url vs httpUrl, headers passthrough, empty-headers → undefined)
Fixes #3472
* fix(openai): when samplingParams is set, pass it through verbatim
Previously pipeline.ts always hardcoded max_tokens as the output-token parameter name on the OpenAI-compatible path, falling back from samplingParams.max_tokens to request.config.maxOutputTokens to provider defaults. This broke GPT-5 / o-series on OpenAI and Azure OpenAI, which require max_completion_tokens and reject max_tokens with a 400 error.
Fix: when the user provides samplingParams explicitly, treat it as the complete source of truth for the wire shape and pass its keys through verbatim. No client-injected defaults, no request fallbacks, no hardcoded parameter names. The user describes what the provider wants; the client trusts them.
When samplingParams is absent, the historical default behavior (request fallback through temperature/top_p/.../max_tokens plus provider defaults) is preserved unchanged — existing users see no difference.
Concretely, users can now set any of:
samplingParams: { max_tokens: 4096 } # GPT-4 / Qwen / DeepSeek
samplingParams: { max_completion_tokens: 4096 } # GPT-5 / o-series
samplingParams: { reasoning_effort: 'medium' } # future knobs
without waiting for a qwen-code release that adds model-specific branches.
Signed-off-by: Gordon Lam (SH) <yeelam@microsoft.com>
---------
Signed-off-by: Gordon Lam (SH) <yeelam@microsoft.com>
Co-authored-by: Automaker <automaker@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: tanzhenxin <tanzhenxing1987@gmail.com>
Co-authored-by: Yuchen Fu <fuyuchen0904@163.com>
Co-authored-by: LaZzyMan <zeusdream7@gmail.com>
Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
* fix(core): reject truncated subagent write_file calls Propagate MAX_TOKENS truncation from subagent responses into tool requests and reject truncated edit calls before schema validation can surface misleading missing-parameter errors. * fix(core): reset per-attempt stream state on retry in agent-core When a streaming response hits MAX_TOKENS and is retried, accumulated state variables (functionCalls, wasOutputTruncated, roundText, etc.) were not cleared. This caused a successful retry to inherit the stale wasOutputTruncated=true flag from the failed attempt, incorrectly rejecting all Edit tool calls with a truncation error. Reset all per-attempt state on retry events, matching the existing behaviour in turn.ts. Add a regression test covering the truncated-then-retried-successfully scenario. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Summary
Fixes #3286
Verification