mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-07 07:45:54 +00:00
* fix(core): cap a streaming response's total lifetime The stream inactivity watchdog resets on every chunk, so a drip-fed stream — a gateway trickling keep-alive chunks, or a model crawling through an oversized single message — kept it alive forever while the message never completed: in issue #8597's CI review runs that meant 2.5-4.5 hours of silence ending in the outer timeout's kill. Add a per-request lifetime cap (default 15 min) that does not reset on chunk arrival. Tripping it throws a retryable ETIMEDOUT, so the existing transport-continuation recovery resumes a healthy generation the cap happened to cut. Config field streamMaxLifetimeMs; env knob QWEN_STREAM_MAX_LIFETIME_MS; 0 disables. * fix(review): carry findings lists as digest-named files, not inline agent-prompt folded the cumulative findings list into every printed verify/reverse-audit block. On a 12-14-auditor round that made the launch one 65-82 KB assistant message — the oversized single message whose stream generation never completed in #8597 — and cost a 5-10 minute paged relay through the orchestrator's context before every round. The list now goes where the brief already goes: on disk, named by the same findings digest that keys the record; the block carries a read_file pointer. The delivery guarantee is unchanged — a launch that drops the read matches no record — and the retirement scheduler's echo guard reads the list back from the file the prompt names, failing toward auditing when it is gone. * fix: address review feedback on stream guards and findings files * fix: address review feedback on findings delivery and stream guards The delivery floor counted only the brief's read receipt, so an instruction-skipping verifier could open its brief, skip the one instructed read of the findings file its block points at, and clear the gate having never seen the list it ruled on. The floor now extracts the findings pointer from the recorded prompt and requires a successful read of it — a new findings-unread delivery shape, with gap texts for the verify, reverse-audit, and combined steps. A failed findings-file write also speaks on stderr instead of silently pointing a whole round at a missing file. Stream guards: consult the lifetime deadline at the top of the loop, not only when the timer wins the race — a pre-buffered chunk resolves next() as a microtask and beats setTimeout(…, 0) every time. Merge the two ETIMEDOUT bypass blocks into one rule. New tests pin the idle-off + lifetime-on branch, the both-guards-0 disable (config and env), and the buffered-source shape. Also reword the prose sites that still described the inlined list. * fix(review): name the findings file in the verify/reverse-audit briefs The two briefs still told the agent the findings were "listed in the message that launched you", but since the list moved behind a read_file pointer they find only a pointer there. The agent reads its brief first (the block mandates it), so the stale text sent it looking for finding blocks in the launch message — the exact findings-unread shape the new delivery floor gates on. Point both briefs at the .findings.md file and the required read instead. * fix(review): address round-3 review — empty-round brief, guard hardening The b796708 brief fix stated unconditionally that the launch message points at a findings file, but an early reverse-audit round on a clean review names no file (the empty branch prints '## Nothing is confirmed yet' with no pointer) — the auditors were being sent to read a path their prompt does not contain. The reverse-audit brief now says the file is named when there is a list, and that an early round has none. Also from the round-3 review: - writeFindingsFile's failure diagnostic now uses writeStderrLineSafe: the catch exists to keep the build alive, so it must not throw out of it on EPIPE (qwen … | head). - The retirement echo-guard resolves the findings pointer from the CLI's own record (never the orchestrator's pasted copy), confined to the plan's record dir, memoized per round — an out-of-bounds or unreadable file degrades to the prompt, failing toward auditing. - Document the delivery floor's deliberate weakening: it proves the findings file was opened, not paged to completion (coverage.ts), and drop the last stale 'record folds the findings in' comment. - withStreamInactivityTimeout returns the source untouched when both guards are disabled, so the invariant survives a caller refactor (setTimeout(Infinity) would otherwise clamp to ~1ms). - settings.md + the config comment now say the stream guards are OpenAI-compatible-only, and that the 15-minute cap bounds a stream whose idle timeout was raised above it (raise or disable the cap to keep a longer window). * fix(review): address round-4 review — memo hygiene, read-visibility, naming The retirement echo-guard memoized a failed read's per-record fallback under the round's shared findings pointer, so one chunk's launch prompt would serve as a sibling chunk's findings list. Memoize only successful reads; a miss falls back to THIS record's own prompt, uncached. The findings block now prints the list's line count, so an agent whose read_file truncates can see it saw a fraction rather than the whole confirmed list — a visibility aid the delivery floor (which proves the file was opened, not paged) does not provide. Also from the round-4 review: rename withStreamInactivityTimeout to withStreamGuards (it now enforces two guards), correct the constants comment (the cap is measured from the stream's first iteration, not its first byte), and give the two stream-guard knobs a dedicated settings.md entry instead of burying them in the timeout paragraph. * fix(review): address round-5 review — deterministic record walk, accurate line count The cross-contamination regression test only discriminated under one readdir order: readRecordedPrompts walked the record dir in filesystem (filename-hash) order, so which chunk's fallback poisoned the shared memo depended on the walk. Sort the directory listing — a deterministic walk removes that whole class of order-sensitivity from a module that reasons per-record, not just from this test (a temporary revert of the memo fix confirms the test now fails against the buggy shape). findingsSection printed the line count of the TRIMMED findings body while writeFindingsFile writes the untrimmed content, so a list with leading/trailing blank lines got a label smaller than the file the agent actually reads — precisely the under-reading the count exists to make visible. Count the untrimmed content. * fix(core,review): charge the lifetime cap on upstream-wait, not delivery time Round-2's top-of-loop deadline check made the lifetime cap measure end-to-end delivery time: a healthy upstream that finished and buffered its chunks was cut for the CONSUMER's slowness (a paused IDE client, a big render), and a stream whose terminal done resolved at the boundary was converted into a retry. The cap is now charged on accumulated upstream-wait — the time the loop is blocked in await it.next() — so a buffered, already-complete stream always completes and only real upstream latency counts; the drip-fed never-completing stream spends exactly that time waiting, so #8597's shape is still caught. Also from the round-6 review: - Hoist the stream-guard error branch above the thinking-tag check: a drip-fed gateway cutting mid-<think> surfaced the guard's ETIMEDOUT as a PROTOCOL_TAG_LEAK and burned the tag-leak retry budget. - The findings line count drops the trailing newline's empty segment, so a 12-line list is not advertised as 13. - settings.md: the stream guards are env/config-only (no settings.json key); document that streamIdleTimeoutMs: 0 embedders now also need streamMaxLifetimeMs: 0 to fully opt out. - constants: a functionCall already streamed (the tool-heavy common case) recovers as a visible classified error, not a continuation. - coverage.ts: drop the stale 'four shapes' counts after Delivery grew a fifth; pipeline: dedupe the instanceof in the guard debug log; and correct the both-guards-off test's comment to pin the outcome, not the caller mechanism. * fix(core,review): monotonic guard clock, findings-write fallback, read-only floor Round-6 follow-ups the previous commit left open: - The stream guards accounted on Date.now(): an NTP step forward (or a long sleep) killed a healthy generation on the next iteration, and a backward step silently disabled the lifetime cap — the hang #8597 exists to bound. All guard accounting is now performance.now(); the setTimeout it races is monotonic too, so the two agree. Vitest fakes performance alongside the timers, and a new test A/B-verified against the wall-clock shape (it fails on Date.now() accounting). - A failed findings-file write returned the path anyway, pointing a whole 12-14-agent round at a file that does not exist — every agent burned its round, then the delivery floor failed it. writeFindingsFile now returns null on failure and findingsSection falls back to inlining the list (the pre-#8597 shape): the recorded prompt carries the list, the delivery check compares it verbatim, and the build stays alive. - The findings delivery floor matched the path in ANY successful tool call's serialized args, so a search_file_content or list_directory that merely named the file cleared it without reading a line. The transcript parser now records read_file calls apart, and the floor counts only those — a mention is not an open. * fix(core,review): round-7 leftovers — upstream-wait wording, key helper, test mock The round-6 error message and class doc still said 'total lifetime cap' and promised the continuation recovery unconditionally, and the wrap-site comment still read 'aborts at maxLifetimeMs from stream start' — all three now describe the upstream-wait semantics the guard actually implements, and the message names the wall clock separately so the two numbers reconcile. The round suffix baked into findings-role record keys and the findings file name was derived independently in three sites (findingsFileFor, runAllChunks, the single build) — a change to how a round is spelled would update two of three and silently fork the artifacts; roundPartOf spells it once. The writeFindingsFile test's module mock replaced all of stdioHelpers (three stubs, missing the other exports) and asserted mock.calls[0][0] with no mock reset — it now spreads importOriginal, clears mocks in beforeEach, and matches the stderr lines with stringContaining. * fix(review,core): round-3 follow-ups — conditional findings briefs, JSDoc placement, log labels The verify/reverse-audit briefs told the agent its findings live in the .findings.md file unconditionally, on the exact path (writeFindingsFile returning null -> findingsSection inlining the list) this PR added to work WITHOUT one; both now say 'when the message points at a findings file, read it; on the rare write-failure fallback the list is inlined in the message, read it there.' roundPartOf had been inserted between findingsFileFor's JSDoc and the function, orphaning the comment that records the one-file-per-round and null-means-inline contracts; the helper now sits above it. The guard debug log labelled the wall clock 'streamLifetimeMs' beside the cap; it is now 'wallClockMs' so the two numbers reconcile the same way the error message does. * fix(review): anchor the findings-pointer extraction to its emitted shape findingsPointerOf matched the FIRST read_file(file_path="….findings.md") anywhere in the recorded prompt. On the write-failure inline fallback the findings list occupies exactly the position the pointer would sit in, and a finding entry there can itself quote a read_file pointer of its own (a finding about this pipeline, which the harness produces when it reviews this repo). The loose match then extracted the quotation as the pointer and the readers diverged: coverage demanded a read of a path no agent was told to read (a spurious findings-unread on a run that is already degraded), and retirement, worse, confined-and-read an earlier round's file and flipped a just-filed finding to an echo, retiring a chunk that had just reported — the one direction the module's header commits to never failing. A quoted pointer inside a findings entry is indented or embedded in prose, so anchoring to a standalone read_file line removes it; the happy-path pointer is alone on its own line inside its fence. A test drives an inlined list containing a pointer-shaped line (fails under the old loose regex). --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|---|---|---|
| .. | ||
| scripts | ||
| src | ||
| vendor | ||
| index.ts | ||
| package.json | ||
| test-setup.ts | ||
| tsconfig.json | ||
| vitest.config.ts | ||