mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-25 00:26:31 +00:00
7151 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
22bb5e8b9f
|
feat(core): require an explicit user opt-in before the model launches a workflow (#9806)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* feat(core): require an explicit user opt-in before the model launches a workflow
The Workflow tool description carried judgement heuristics for orchestrating
well, but no rule about when not to orchestrate at all. Read on its own the
prose is encouragement, and a run that can dispatch up to the per-run agent
cap is a large spend to enter on inference rather than on a request.
Prepend a gate above the existing guidance: do not call the tool unless the
user asked for multi-agent orchestration, with the five forms that count as
asking under this project's own triggers -- the `workflow` keyword, the
user's own words, a skill or slash command, a named saved workflow reached
through `workflow('<name>')` or `scriptPath`, and a resume. Upstream's
`ultracode` marker is deliberately not among them: it does not exist here,
and naming it would enumerate a trigger no user can pull.
The fallback path is the load-bearing half. Without an offer-and-ask route
the model reads a refusal rule as "refuse", and a user who would have said
yes never gets asked, so the text ends by telling it to say what a workflow
would fan out over and let the user decide.
The agent cap is interpolated from `DEFAULT_MAX_AGENTS_PER_RUN` rather than
pasted, matching the rest of the description.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDuRWMhKbqEYZXbbLhn3en
* chore: keep agent-core.ts as it is on main
The merge commit's pre-commit hook ran prettier over every file the merge
staged, not just the ones this branch changes, and reformatted a type union
in agent-core.ts. main's copy does not satisfy the repo's pinned prettier
3.6.1, so the hook produced a real diff in a file this PR has no business
touching. Restore main's bytes to keep the PR scoped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDuRWMhKbqEYZXbbLhn3en
* test(core): pin workflow opt-in contract
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
|
||
|
|
bf123a375a
|
fix(goal): converge the three continuation prompts on one guarded contract (#9834)
* refactor(goal): render Goal continuation prompts from one core renderer The prompt sent when `runtime.finishTurn` schedules another Goal turn was assembled independently in three hosts: the TUI's inline array in `useGeminiStream`, and a `buildGoalContinuationParts` in each of the ACP session and the non-interactive CLI. Three copies of the same four shared lines have already drifted -- the TUI carries the anti-spoofing guard lines but no objective, while ACP and non-interactive carry the runtime continuation context but no guard lines. Upcoming work adds further variants (an "objective was edited" announcement and a budget wind-down prompt). With the text living in three places, every new variant means three edits, which is precisely how the current drift was produced. This moves assembly into `packages/core/src/goals/goal-continuation-prompt.ts`, where a variant is a case in one function and the shared prefix exists once. The two `buildGoalContinuationParts` helpers keep their names and signatures and simply delegate. This is a pure refactor: no prompt text changes. Each host still emits a byte-identical string to the one it emitted before. The existing drift is preserved deliberately and is left for a separate, behavior-changing follow-up. The new unit test pins the complete rendered string for both variants with and without verifier feedback, so any future edit to a line surfaces as a test diff; the existing host tests pass unmodified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(goal): tighten continuation renderer contract * test(goal): cover verifier feedback hosts * refactor(goal): hoist Goal continuation parts builder into core (#9581) * fix(goal): converge the three continuation prompts on one guarded contract Every automatic Goal turn now renders the same prompt in every host: the runtime-supplied goalId, revision and objective as an escaped JSON data block, framed as untrusted task data, under both anti-spoofing guard lines, followed by a line stating the block supersedes any earlier objective in the conversation. Before this change the drift ran the wrong way. ACP and non-interactive interpolated the raw objective into a synthetic user-role turn carrying neither guard line; the TUI carried both guard lines but dropped the objective, so the host that guarded most gave up information and the two that guarded least were the exposed ones. None of the three escaped the objective, so objective text shaped like a tag could break out of the surrounding prompt. The prompt input collapses to a single flat shape, so the variant discriminant and its unreachable-default arm are gone. `<`, `>` and `&` are escaped inside the serialized JSON so an objective cannot close the data block or open one of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
ac109c9dd3
|
fix(core): relax uniqueItems in function schemas (#9869)
* fix(core): relax uniqueItems in function schemas * fix(core): preserve schema map keys when relaxing tools --------- Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
02f5214939
|
test(cli): skip unreadable-ledger run-ledger test under root (#9913)
* test(cli): skip unreadable-ledger run-ledger test under root The test simulates an unreadable ledger with chmod 000, but root bypasses permission bits, so the read succeeds and the append goes through, failing the test on every root run. Skip it there since the property is untestable without DAC enforcement. Fixes #9909 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): extend unreadable-ledger run-ledger skip to win32 --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
1fffa5108d
|
fix(acp-bridge): Disable permission timeout by default (#9933)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
npm cache producer / Save npm cache (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* fix(acp-bridge): disable permission timeout by default Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore: regenerate settings schema Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(acp-bridge): fix stale timeout comment Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
f7b936630f
|
fix(core): reject malformed Anthropic tool arguments (#9013)
* fix(core): reject malformed Anthropic tool arguments Validate streamed tool input as protocol JSON and release tool calls atomically only after a trustworthy message stop. This prevents truncated arguments or open parallel tool blocks from leaking executable calls into retry consumers. * fix(core): preserve max-token recovery for tool truncation * fix(core): preserve interrupted Anthropic tool calls * fix(core): recover empty max-token responses * refactor(core): share stream retry predicates * Merge main into tlysanhuo/fix-anthropic-tool-json --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: yiliang114 <effortyiliang@gmail.com> |
||
|
|
95bdd46241
|
fix(config): accept output.format "stream-json" in the settings schema (#8966)
* fix(config): accept output.format "stream-json" in the settings schema The runtime already reads and honors output.format: "stream-json" from settings.json (normalizeOutputFormat -> OutputFormat.STREAM_JSON), and it is a documented --output-format choice, but the settings schema listed only text and json. The VS Code companion applies that schema to every .qwen/settings.json, so it flagged a valid, working config as invalid. Add stream-json to the source schema and regenerate the shipped settings.schema.json. Same schema/runtime drift class as #8752. Closes #8965 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(config): bind output.format schema values to OutputFormat and document stream-json Apply the review's non-blocking suggestions: - Schema options now use the OutputFormat enum constants the runtime's normalizeOutputFormat accepts, so the settings schema cannot silently drift from core. - The full enum is pinned in the test (toEqual, sibling-test pattern) instead of a toContain probe. - The format description — schema, regenerated VS Code schema, and the settings reference table — now notes that stream-json makes runs started with a prompt non-interactive (headless), and the docs table lists stream-json as a possible value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01US2APQw84vvQZZ4pZaKtzn * test(cli): add OutputFormat to core mock factories that reach settingsSchema settingsSchema.ts now reads OutputFormat at module load, so the two test files that mock @qwen-code/qwen-code-core with a hand-built factory and transitively import it need the enum in the mock, matching how they already mock ApprovalMode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(config): derive the output.format test pin from the enum and document the argv-only gates Address the round-2 review: - The test pins the schema options against Object.values(OutputFormat), so a format added in core fails the test until the schema and the regenerated JSON follow; the schema comment now states exactly that instead of overpromising drift protection from the binding alone. - The description, regenerated schema, and docs table note that flags validated at argv parse time (--include-partial-messages, --input-format stream-json) still require the explicit --output-format stream-json flag, since those yargs checks run before settings are loaded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(cli): cover settings-driven stream-json output and name the flag in the docs note Address the round-3 suggestions: a config test now exercises output.format stream-json arriving from settings through loadCliConfig, and the docs table names the --output-format stream-json flag the argv-time checks require, matching the schema description. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(cli): pin argv-over-settings output format precedence with differing values The existing precedence test used the same value on both sides, so an inverted merge passed the suite. The new case sets settings stream-json against argv text and asserts text wins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(test): match the enum-pin comment to the order-sensitive assertion Apply the maintainer review nits: the comment now says array-derived, order included, which is what toEqual checks, and the precedence test drops a comment that restated its name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2dbe806204
|
docs(sdk): fix query timeout example signature (#9867)
Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c216584e01
|
docs(core): fix goal judge timeout unit (#9861)
Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> |
||
|
|
703bb7a5cf
|
docs(core): fix read file paging default comment (#9863)
Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
24db7f6ef2
|
feat(review): say when the approach, not the patch, is the open question (#9340)
* feat(review): say when the approach, not the patch, is the open question Every finding /review emits is anchored to a `file:line` in the current diff. That is what a finding is — and it means a review can report where an approach leaks, but never that a different approach would retire all of the leaks at once. Measured: one change to `extractAndStripMeta` took three attempts across two PRs. #9097 (3 rounds, 18 findings) added a timeout to the vm call; #9136 (6 rounds, 56 findings) moved the walk inside the vm and ended up spawning a child process per call, growing 228 -> 920 source diff lines. #9325 landed it in one commit by not evaluating the literal at all. All 74 findings were individually correct, and every one of them went away with the mechanism. The signal was already there and filed as the wrong kind of thing: `did not converge within the reverse-audit round cap` appeared four times across the two PRs, as a coverage gap — "we did not finish looking" — rather than as a conclusion about the change. Nothing was responsible for reading it as "stop patching". Add one advisory paragraph, and one clause on the terminal verdict line, when a non-Approve round is past the round threshold AND its source diff has grown at least 3x since the review first measured it. This round's round-cap stop rides along as corroborating text when present; it is never a trigger on its own. It is deliberately not a finding. Findings are what the autofix loop consumes, and that loop patching each finding in turn is the pattern being interrupted — a finding here would be fixed rather than read. It addresses the human deciding what happens next, so it is a body paragraph and a verdict-line clause, it adds no cap, and it never moves the event. The baseline is a baseline, not the previous round's size: 228 -> 920 across six rounds is ~1.3x per round, which no per-round delta would notice, but 4.0x cumulatively. `Ledger.src0` records the first measurement and is carried forward unchanged, so a diff that later shrinks cannot rewrite its own baseline. It is the one marker field that survives truncation — the ruling that withholds an anchor from a partial finding list does not extend to a measurement of the diff. Known limits, documented rather than papered over: it cannot see across pull requests, so the three-attempt shape that motivated it would have fired only on a second forgeable persisted counter; and it is retroactively blank, staying silent until a PR has posted two rounds after this ships. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(review): suppress approach signal for downgraded approvals * fix(review): measure approach growth over full diff * fix(review): validate approach signal evidence * fix(review): pin approach-signal boundaries and validator coverage Round-5 review findings: boundary tests for the round threshold, growth factor, and source-diff floor; the round-cap corroborating clause and its zh rendering; src0 survival through the pr-context persist seam and the incremental marker carry-forward; artifact validator refusal/absence tests for approachSignal; design doc firing list names the pre-cap verdict. * fix(review): clamp the approach signal's round at the ledger cap (R9-1) The signal computed its displayed round with an unclamped `prevRound + 1` while the ledger marker stamp and the deferred-suggestions clause both clamp with `Math.min(prevRound + 1, LEDGER_MAX_ROUND)`. `parseLedger` accepts `round == LEDGER_MAX_ROUND`, so a side file at the cap is representable and carries forward: one composed body announced "⚠️ Round 10001" beside a marker stamping `"round":10000`, and the terminal verdict line printed 10001 too — the doc comment in this same diff claims all three consumers cannot disagree "at the cap included". The new test pins the cap for the third consumer, mirroring the existing deferred-clause cap test; mutation-verified that reverting the clamp turns it red with `round: 10001`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
d128998779
|
fix(core): accept quiet post-tool-result completions after retry exhaustion (#9196)
* fix(core): accept quiet post-tool-result completions after retry exhaustion (#9026) Models that legitimately end a turn silently after a tool result (valid finish reason, no visible content) trip the NO_TOOL_RESULT_PROGRESS guard; the #7039 retry budget gives them four identical retries and then the error aborts the whole run — the dominant failure mode across ~390 headless rollouts on GPT/Gemini-family models (291/96 hits vs 0 on Qwen). Keep the retry-first behavior and, once the budget is exhausted, accept the quiet completion instead of re-throwing: the MAX_TOKENS variant stays fatal, a placeholder keeps user/model alternation well-formed when the accepted turn produced nothing, and the JSONL record carries the same text as history. Explicit per-attempt one-shot flag; continuation streams share the same acceptance through the shared retry wrapper. * fix(core): scope quiet completion retry flag per attempt * test(core): pin quiet completion retry edges * fix(core): close round-1 findings on quiet-completion acceptance (#9026) - consume the one-shot acceptance where the retry outcome is rescheduled, not where the attempt starts: re-arm the flag in the rate-limit / transport-replay / transport-continuation / reactive-compression continue branches whenever either invalid-stream budget is exhausted, and arm on any budget-exhausting invalid-stream type (the accept gate independently requires a tool-result continuation with no visible progress and a non-MAX_TOKENS/SAFETY/RECITATION finish) - keep SAFETY/RECITATION-blocked quiet turns fatal: content-filtered continuations (OpenAI content_filter / Anthropic mapped to SAFETY) must not end silently with no user-visible signal - record accepted quiet turns from one source: hoist acceptedTurnParts and build the JSONL record from the same parts history keeps, so an accepted turn whose only parts are non-text (inlineData/fileData) no longer desyncs transcript from history on --resume - fatal-MAX_TOKENS test: attach the rejection assertion before advancing fake timers so the file's test command no longer exits 1 on an unhandled rejection - regression tests: armed attempt surviving a transport replay, a 429, and a mixed final error type; inlineData-only accepted turn recording; SAFETY quiet turns staying fatal * fix(core): close round-3 findings on quiet-completion acceptance (#9026) R1-1: the invalid-stream retry branch now rearms the one-shot quiet acceptance like the other four rescheduling paths, so an armed attempt that fails with PROTOCOL_TAG_LEAK no longer strands the acceptance. Arming is keyed to the transient bucket only: quiet completions surface as a transient-type error, so a tag-leak-only budget exhaustion can no longer arm prematurely and bypass the #7039 retry-first invariant. R2: the accept gate keeps the whole content-filter finish-reason family fatal (SAFETY/RECITATION/BLOCKLIST/PROHIBITED_CONTENT/SPII and the IMAGE_* variants), mirroring mapGeminiFinishReasonToOpenAI's content_filter grouping; the constants move into genai-compat's FinishReason alongside MAX_TOKENS. Also drops the dead contentText placeholder write (acceptedTurnParts is the single source for the JSONL record and history push), parameterizes expectStreamExhaustion so the fatal tests reuse it (clearing the vitest/valid-expect lint failures), hoists chatWithRecorder to the shared scope, pins RECITATION via it.each, and adds regression tests for the tag-leak rearm, tag-leak-only no-arm, and the armed-continuation rearm site. * fix(core): keep IMAGE_OTHER fatal in the quiet-completion gate (#9026) mapGeminiFinishReasonToOpenAI also groups IMAGE_OTHER into content_filter; include it in CONTENT_FILTER_FINISH_REASONS so a native-route IMAGE_OTHER block cannot be silently accepted either. * fix(core): map Anthropic refusal stop_reason into the content-filter family (#9026) R6-1: mapAnthropicFinishReasonToGemini had no entry for Anthropic's refusal stop_reason, so it fell through to FINISH_REASON_UNSPECIFIED — defined (passes the NO_FINISH_REASON throw), not MAX_TOKENS, and not in CONTENT_FILTER_FINISH_REASONS. Once the retry budget was spent, the armed attempt therefore accepted a provider refusal as a quiet "(empty content)" completion, masking the safety decision the gate was written to keep fatal. Map refusal to SAFETY alongside content_filter so the existing content-filter family keeps it fatal. Regression tests: converter refusal-to-SAFETY mapping (mutation- verified) and a geminiChat armed-attempt test asserting NO_TOOL_RESULT_PROGRESS exhaustion with no placeholder turn recorded. * fix(core): fail closed on unknown quiet finish reasons --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> |
||
|
|
d1cfd87683
|
feat(review): promote language-pitfall and wrapper/proxy checks out of Agent 1a (#9805)
* feat(review): promote language-pitfall and wrapper/proxy checks out of Agent 1a (#9788) Split the two checks folded into Agent 1a's line-by-line brief into dedicated Step 3A roles at high effort: Agent 1d (language-pitfall scan, always) and Agent 1e (wrapper/proxy routing, rostered when the plan's wrapperSignal is true — a capture-time vocabulary heuristic that fails safe: only an explicit false keeps it out, so version-skewed plans still owe the check). The roster, check-coverage and agent-prompt all read the gate from the plan, so a run that skips either agent is named. Briefs, SKILL.md, and the user-facing code-review doc updated; 1a keeps its walk minus the two clauses. * fix(review): address round-1 feedback on the 1d/1e split (#9805) * fix(review): address round-2 feedback on the 1d/1e split (#9805) * fix(review): address round-3 feedback on the 1d/1e split (#9805) * fix(review): address round-4 feedback on the 1d/1e split (#9805) * fix(review): address round-5 feedback on the 1d/1e split (#9805) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
37cedea5b2
|
feat(computer-use): replace built-in tools with bundled skill (#9856) | ||
|
|
3892ca32ca
|
fix(core): map OpenAI-compatible finish_reason case-insensitively (#9884)
OpenAI-compatible gateways that front Gemini backends can return the Gemini-native finish_reason spelling in uppercase (`STOP`, `MAX_TOKENS`). The previous case-sensitive lookup mapped these to FINISH_REASON_UNSPECIFIED, silently disabling MAX_TOKENS truncation recovery. Fold the reason to lowercase before lookup and add an explicit `max_tokens` alias for the Gemini-native spelling. Resolves #9882 |
||
|
|
6a21c436d6
|
test(core): align lazy generator vertex test (#9888)
Use an interface-supported method to exercise lazy Vertex initialization after the content generator interface was narrowed. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> |
||
|
|
b5aec6691e
|
fix(serve): Canonicalize Live task bridge session IDs (#9819)
* fix(serve): Canonicalize Live task bridge session IDs Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(serve): Pin canonical Live owner lookup Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): harden mixed-case live session routing Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
43d46be912
|
refactor(core): shrink the content generator interface (#9676)
* refactor(core): shrink content generator interface * refactor(core): remove orphaned request-tokenizer estimator cluster Removing countTokens from both providers deleted the last production consumers of RequestTokenEstimator. Delete the orphaned cluster: requestTokenizer.ts (330), imageTokenizer.ts (534), types.ts (36), the directory barrel (11), and both test files (608 lines). Also drop the inert vi.mock of requestTokenizer.js left in client.test.ts and the stale dimension-extractor cross-reference in review/lib/assets.ts. textTokenizer.ts and supportedImageFormats.ts stay: converter.ts, pdf.ts, and fileUtils.ts still consume them and the core barrel re-exports them. * docs(design): sync lazy-google-genai-loading record with shrunk interface countTokens and useSummarizedThinking no longer exist on ContentGenerator, so the design record for the lazy-wrapper architecture must not keep advertising them: list the three remaining shared async operations, drop the useSummarizedThinking sentence and the summarized-thinking item from the consumer audit and Verification section, and add a dated note recording the interface shrink from PR #9676. * ci: record cd-cua-driver.yml size growth in .size-baseline Same latent main-side violation as fixed in #9682: #9587 grew the workflow without a baseline update; record the new size as the check message directs (precedent #9747). * docs: finish scrubbing tokenizer references after estimator-cluster removal Follow-up to 0ee17632c7/1871bb5b81 (review round 2): - supportedImageFormats.ts header and getSupportedImageFormatsString doc no longer describe a tokenizer decode/metadata-extraction stage; the list is now documented as the vision-input acceptance list, with token accounting noted as the flat DEFAULT_IMAGE_TOKEN_ESTIMATE. - web-shell-image-drag-and-drop.md's BMP rationale no longer claims ImageTokenizer parses BMP dimensions; dated sync note added stating BMP support rests on SUPPORTED_IMAGE_MIME_TYPES plus converter passthrough since PR #9676. * docs: drop tokenizer from the BMP test-plan line Follow-up to 18f08c0924: the test plan still required converter/tokenizer focused tests for image paths; the image-tokenizer estimator cluster was removed in PR #9676 (text tokenizer is unaffected and out of scope here). |
||
|
|
e0d933b23e
|
refactor(core): make derived Config ownership explicit (#8100)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
SDK Python / Classify PR (push) Waiting to run
SDK Python / SDK Python (3.10) (push) Blocked by required conditions
SDK Python / SDK Python (3.11) (push) Blocked by required conditions
SDK Python / SDK Python (3.12) (push) Blocked by required conditions
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* refactor(core): define derived config ownership * docs(core): align derived config ownership scope |
||
|
|
27285a5243
|
refactor: centralize approval mode contracts (#9796)
* refactor: centralize approval mode contracts * fix: align Python SDK import grouping * test: restore approval mode exports in CLI mocks * fix: close approval mode drift gaps * test(cli): preserve core exports in serve mocks * fix: close approval mode review gaps * test(cli): complete permission request fixture * test(sdk): match approval mode route * test(approval): close review coverage gaps * test(sdk): cover approval mode global scope path |
||
|
|
a60cbbc54a
|
refactor(core): make utils/ a leaf layer (#9778)
* refactor(core): make utils/ a leaf layer Eliminate every runtime (value) upward import from packages/core/src/utils production modules so utils/ can become a leaf layer with no runtime dependency on the rest of core. Two mechanisms, no behavior change: - Relocate domain-coupled modules out of utils/ into their owning module (agents, config, core, memory, services, tools), and move generic constants/types that live elsewhere into utils/. All `git mv` moves keep history; every import that pointed at a moved file is rewritten. - Extract the remaining value imports as small leaf modules inside utils/ (AuthType, isTool, ToolErrorType, DEFAULT_QWEN_MODEL) and re-export them from their original owners so cross-package consumers are unaffected. doesToolInvocationMatch moves into shell-utils, its only production consumer. Only type-only imports now cross the utils/ boundary. The two deferred inversions in debugLogger (Storage, getTraceContext) are stateful and left for a follow-up. * chore(core): enforce utils/ leaf layer with lint rule Add architecture/no-core-utils-upward-import, which flags runtime (value) imports that leave packages/core/src/utils. Type-only imports, sibling utils imports, and external package specifiers stay allowed; the two deferred debugLogger inversions (config/storage, telemetry/trace-context) are carried on an explicit allowlist. Enable the rule as an error on core sources and cover it with Linter-based tests. * fix(core): restore iconv-lite tree-shaking for sync-file-encoding The utils leaf-layer refactor moved sync-file-encoding from utils/ to services/, but the esbuild tree-shake plugin still matched the old ./utils/ specifier, so its sideEffects:false marker no longer applied and the ACP startup closure regained a static iconv-lite import. Point the onResolve filter at the new ./services/ path. * fix(ci): catch stale integration imports earlier * fix(core): close utils boundary review gaps * fix(core): close self-reference boundary gaps * ci: re-trigger after self-hosted runner checkout EACCES |
||
|
|
5b3830b46c
|
feat(vscode-ide-companion): adopt WebShell transcript as the default timeline (#9719)
* feat(vscode-ide-companion): reuse WebShell transcript UI behind experimental flag
Bridge ACP session/update notifications into the shared SDK daemon transcript reducer and render the result with the WebShell transcript component, gated on qwen-code.experimental.webShellTranscript (default off).
The WebShell renderer and its heavy transitive dependencies (echarts, mermaid, shiki, codemirror, katex) are lazily loaded via esbuild code splitting, so the default configuration keeps the ~700KB webview bundle unchanged.
* fix(vscode-ide-companion): grant wasm-unsafe-eval for shiki WASM when WebShell transcript enabled
* feat(vscode-ide-companion): adopt WebShell transcript as default timeline
Drop the experimental flag and the legacy MessageList renderer. The companion timeline now always renders through the shared WebShell transcript component, fed by ACP session/update notifications via the SDK daemon transcript reducer (lazy loaded through esbuild code splitting).
The flag-gated wiring is removed: the qwen-code.experimental.webShellTranscript setting, the conditional CSP/body attribute in WebViewContent, and the legacy MessageList path in App.tsx (~850 lines). The webview CSP now grants wasm-unsafe-eval unconditionally for Shiki's Oniguruma WASM.
* fix(vscode-ide-companion): reset WebShell transcript state on session switch
The experimental useAcpTranscript hook only consumed transcriptUpdate
messages, so its reducer state survived session boundaries. When the
extension switched sessions it kept the webview mounted and replayed the
newly-selected session through ACP, causing the previous session's blocks
to merge with the new replay (e.g. user text "alpha" from session A leaked
into session B as "alphabeta").
Reset both the reducer state and the rendered blocks on the same
boundaries the legacy message flow uses: qwenSessionSwitched (sent before
the ACP replay of the selected session) and conversationCleared (new
session). Adds a regression test that replays two sessions with a switch
between them.
* fix(vscode-ide-companion): harden WebShell transcript session boundaries
- reset the transcript on `conversationLoaded` too, closing the same
cross-session leak the previous commit fixed for `qwenSessionSwitched`
and `conversationCleared` (agent reconnect posts only this boundary)
- track the active session id and drop late `transcriptUpdate` frames
whose `sessionId` no longer matches, so a previous session's trailing
frames cannot contaminate the next session's timeline
- seed the transcript from cached messages carried by
`qwenSessionSwitched` so offline restores and load-failure fallbacks
render their history instead of a blank timeline
- dispatch `assistant.done` on `streamEnd`/`sessionLoadComplete` so the
final assistant/thought block of a turn (or history replay) does not
stay `streaming: true` forever
* fix(vscode-ide-companion): adopt live ACP session id after load-failure fallback
* fix(vscode-ide-companion): echo user prompt into WebShell transcript
* fix(vscode-ide-companion): keep WebShell transcript expanded and clear of the composer
* fix(vscode-ide-companion): surface local error and interrupt notices in the transcript area
* fix(vscode-ide-companion): restore file-link opening from the WebShell transcript
* fix(vscode-ide-companion): restore contributed copy commands for the WebShell transcript
* fix(vscode-ide-companion): add localOnly marker to TextMessage state type
* fix(vscode-ide-companion): restore /insight progress card and report link in the transcript UI
* fix(vscode-ide-companion): finalize in-flight tools on timeout and pin session-switch seeding guard
Map streamEnd reasons timeout/session_expired onto the reducer's error reason so abandoned mid-tool turns no longer spin forever (ceuI). Add qwenSessionSwitched cases with no messages field and an empty cache array; the no-messages case fails when the seeding guard is forced true, pinning its false side (ceuN).
* fix(vscode-ide-companion): remove unreachable editMessage backend and dead submit options
The user-message edit/rewind UI was dropped in the WebShell-transcript migration, leaving editTargetTurnIndex/onSubmitted options in useMessageSubmit and the full editMessage/rewind flow in SessionMessageHandler unreachable. Remove the dead options, the editMessage dispatch case, the rewind/snapshot flow with its recovery branches, and their tests (R1-8 direction b).
* fix(vscode-ide-companion): drop write-only loadingMessage bookkeeping
The waiting-message renderer was removed with the WebShell transcript migration and the user prompt is echoed into the timeline at send time (
|
||
|
|
9ef32c5f3c
|
fix(core): make team shutdown a leader-only tool (#9401)
* fix(core): make team shutdown a leader-only tool send_message carried an optional single-value enum `type: ['shutdown_request']` described as "structured message type for control flow". Models filled it while composing an ordinary report; the call was then rejected leader-only and the report content was discarded, leaving the teammate retrying a report the leader never received. Split control from content at the tool boundary rather than validating the field harder. `type` is removed from send_message entirely, and shutdown becomes request_shutdown, which createToolRegistry skips for subagent-context registries — so a teammate has no declaration for it and cannot emit the call at all, instead of emitting one and being refused. The mailbox wire format is unchanged: sendStructuredMessage still writes `type: 'shutdown_request'` with `from: LEADER_NAME`. Only the tool surface moved. Fixes #9276 * fix(ui): cover request_shutdown in tool display-name drift guards The new leader-only request_shutdown tool landed without entries in the drift-guarded display surfaces, breaking CI: - web-shell TOOL_DISPLAY_NAMES (toolFormatting.drift.test.ts) and the zh badge label in client/i18n.tsx (toolFormatting.test.ts parity) - cli toolDisplayName locale entries enforced by i18n/index.test.ts and check-i18n key parity (en identity / zh / zh-TW) * fix(core): close the leader-only hole the review found, and register the display name Three criticals from review, all real. The leader-only guarantee did not hold. "Enforced by absence" covers a registry that was *built* with forSubAgent, but runSingleDispatch's workflow fast path hands a subagent the parent leader's registry untouched — and that one does contain request_shutdown. So a workflow subagent could request a shutdown as the leader. The runtime guard now uses isSubagentLikeExecutionContext rather than isTeammate, which covers every subagent-like context instead of only a teammate identity. RequestShutdown had no toolDisplayName locale entry, failing the CLI i18n guard, and no entry in web-shell's manually synced TOOL_DISPLAY_NAMES, failing that package's drift guard. Both added, matching the locales that carry SendMessage. And the suggestions: a config test asserting the tool is present in a leader registry and absent from a forSubAgent one — the property everything rests on, which nothing checked; the deliberate 'ask' permission default pinned; the empty and whitespace recipient guard covered, since the schema admits both; and the dead requestShutdown mock left in send-message.test.ts's helper type removed. Verified: 534 config tests, 30 across request-shutdown and send-message. * fix(core): exclude request_shutdown from subagent and teammate tool sets * fix(i18n): translate the RequestShutdown tool display name in Catalan The entry was left as raw English while every sibling team tool is translated. --------- Co-authored-by: yiliang114 <jinjing.zzj@gmail.com> |
||
|
|
b2d0687213
|
feat(serve): add --open-with-auth (#9738)
* docs(serve): propose ephemeral auth for --open Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): address ephemeral auth review Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): clarify asset pre-check boundary Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): centralize token selection plan Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): make ephemeral auth opt in Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): align ineligible-browser handling with manual-URL fallback Browser-launch eligibility is a heuristic with common false negatives, so it is no longer a hard pre-listen gate: an ineligible environment warns (naming the tripped signal), starts the daemon, and prints the fragment-bearing manual URL, matching the launch-failure recovery. Also pin the generation breadcrumb with planned test assertions. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * feat(serve): add opt-in ephemeral auth for --open Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * feat(serve): replace ephemeral auth with --open-with-auth Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): clarify temporary token storage Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(serve): clarify ephemeral token persistence Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9738) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9738) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
dbf7382c8f
|
fix(memory): scan uncapped when selecting forget candidates (#9530)
* fix(memory): scan uncapped when selecting forget candidates Recall moved to the uncapped scanner in #8716; forget did not. A document ranked past the 200-document cap could be recalled and injected into the prompt but never forgotten. Forget now scans uncapped, so its candidate universe matches recall's. The model-selection prompt renders every candidate, so it gets its own bound of 400: literal query matches first, then the most recently modified remainder. The heuristic fallback keeps scanning the full uncapped list. Indexer, status, and extraction stay capped on purpose, and the two design docs that recorded forget as capped now say otherwise. Refs: #9378 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(memory): give each scope its own share of the forget prompt Review round 1. The 400-candidate bound ranked both scopes into one recency budget, so a store whose project entries are all newer than its user entries seated no user memory at all. The capped scanners this replaced ran per scope, so each scope always had seats. That made an old user entry unselectable by the model while recall could still inject it, which is the same asymmetry the PR set out to close. Each scope now keeps a 200-candidate quota and whatever a smaller scope leaves is handed to the other. Within a scope, literal query matches rank first and both groups are ordered newest first, so truncation is deterministic instead of scan-order, and the bound logs when it drops candidates. Also from review: the query normalisation and match predicate are now shared with selectByHeuristic so the two cannot drift; the user scan gets the best-effort guard recall.ts and extractionAgentPlanner.ts already carry; and the docstring and design docs no longer claim an unconditional guarantee the bound does not provide. Three tests, each verified against the mutation it is meant to catch: global ranking drops the user ids, an ascending sort drops the newest filler, and handing the fallback the bounded list returns 400 of 450 matches. Refs: #9378 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(memory): bound the unconfirmed forget path and drop the silent scan guard Review round 2, all suggestions. MemoryManager.forget passed limit: MAX_SAFE_INTEGER and deletes without confirmation. With an uncapped scan and a heuristic fallback that substring matches the whole store, a one-character query matched nearly every entry in both scopes, where the capped scanners had held that same failure to one scan's worth of candidates. The limit is now the prompt bound, restoring the old ceiling. Round 1 added a best-effort catch on the user scan. That was wrong on two counts: scan.ts caps after reading and ordering the whole tree, so uncapping adds no read exposure to justify it, and swallowing the failure made forget report "no entries matched" for a scope it never read, then act on that answer by deleting. Reverted, with a comment saying why forget differs from recall here: a missed injection is recoverable, a missed deletion is not. normalizeForgetQuery now delegates to normalizeSummary so query matching and the post-selection re-match cannot drift apart, and one design-doc sentence no longer implies only semantic matches fall off the bound. Two tests, each verified against its mutation: the quota split is now exercised with both scopes over quota, where dropping it to 150 seats 250 project entries instead of 200; and the delete ceiling fails at 401 removals if the unbounded limit comes back. Refs: #9378 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(memory): split forget's deletion seats per scope, and decouple the ceiling Review round 3. The deletion ceiling added last round truncated the heuristic fallback in candidate order, and listIndexedForgetCandidates pushes every user entry ahead of every project entry. With 450 matching user entries and 50 matching project ones and the side query down, forget deleted 400 user entries, zero project ones, and reported success. That is the reachability asymmetry this PR exists to remove, moved into the delete path. The per-scope allocation the model prompt already used is now shared with the heuristic, so each scope keeps its share of the limit and a smaller scope's unused seats go to the other. The ceiling is also its own constant now rather than an alias of the prompt bound. Resizing the model prompt is a cost decision and resizing this is a blast-radius decision; sharing one constant let the first silently widen the second. Two tests, each checked against its mutation: the 450-user/50-project shape returns zero project matches under a plain slice, and oldest-first ranking inside a scope drops that scope's newest entry from the prompt. Refs: #9378 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(memory): pin the forget split at a small limit and the heuristic's own order Cross-review found both new tests mutation-survivable. Every case used a 400 limit, so hard-coding a 200 per-scope quota instead of deriving it from the budget still passed, and the recency case let the side query succeed, so it pinned the model prompt's ranking rather than selectByHeuristic's own comparator. One case at limit 5 with the side query failing covers both: it asserts the 3/2 split, which only holds if the quota comes from the budget, and that each scope contributes its newest entry, which fails if the comparator is reversed. Both mutants verified failing. Refs: #9378 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(memory): share the forget recency comparator and log a bound deletion Review round 4, both suggestions. The mtime comparator was the last thing the model path and the heuristic path each typed for themselves, after this branch had already hoisted the query normaliser, the match predicate and the per-scope allocator so the two could not drift. Each site has its own test, so a one-sided ordering change would have updated its own test, passed CI, and left the sibling stale. Now one definition. The deletion cap also bound silently. The prompt bound warns when it truncates; the path that actually deletes did not, so a forget that removed 400 of 500 matches reported success and left no record of why recall kept injecting the rest. It now says so. No test for the new warning: it is a debug log line, and asserting on it would pin the wording rather than the behaviour. Refs: #9378 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
c7c4dc80e0
|
feat(core): add the output-style layer to the system prompt (#9565)
* feat(core): add the output-style layer to the system prompt
Introduces output styles: a named prompt section that changes how the
agent reports its work, selected per session. This is the core half —
the registry, the four built-in styles, and the prompt wiring. Nothing
selects a style yet; the picker, the settings key, and the per-turn
reminder follow separately.
The style lands at the end of the stable `base` layer: after the mandates
it refines, and still ahead of every context/volatile layer, so the
prompt prefix stays cacheable for the whole session.
Two precedence rules, both following what the file already does for
QWEN_SYSTEM_IDENTITY_MD:
- A QWEN_SYSTEM_MD override wins. That file is a full, user-owned prompt;
layering our section onto it would defeat the override.
- The QWEN_WRITE_SYSTEM_MD dump stays a pure base prompt. A style sits on
top of a base, so baking it into the dump would apply it twice once
that file is fed back through QWEN_SYSTEM_MD.
`keepCodingInstructions: false` lets a style replace the base outright,
for styles that are not about software engineering at all. All four
built-ins keep it true — Proactive in particular changes how much you
plan and ask, not what you are allowed to do, and says so in its prompt
so it does not read as a second permission knob next to ApprovalMode.
Subagents and arena runs deliberately do not inherit the main session's
style: it would multiply Explanatory's insight blocks across every child,
and skew an arena comparison that is supposed to isolate the model.
* fix(core): narrow keepCodingInstructions to the section it names
`keepCodingInstructions: false` replaced the whole base prompt, which
took the safety rules down with the workflow guidance: a non-coding
style lost `# Executing actions with care`, the mandates, the tool
guidance and the tone section along with the part it meant to drop.
It now omits exactly one section — the software-engineering workflow
guidance, split out as `getSoftwareEngineeringTasksSection()`. Every
other section stays under every style. A style adjusts how work is
reported; it never switches off the rules for taking risky actions.
Two smaller corrections in the same area:
The identity sentence now points at the style when one is active
("responding according to your Output Style below") instead of claiming
the agent specializes in software engineering under a style that says
otherwise. A `QWEN_SYSTEM_IDENTITY_MD` override is still inserted
verbatim — that wording is distributor-owned and not ours to rewrite.
The style section is headed `# Output Style: <name>` rather than
`# <name> Style Active`. The heading is the contract a custom style file
will rely on once user and project styles load: the file body becomes
the prompt verbatim and the heading is what names it.
The per-turn reminder is now every style's, not just the two that spell
one out. `turnReminder` overrides the generic wording rather than
deciding whether a reminder exists at all — an Explanatory session
drifts back to terse answers as readily as a Concise one does.
`getOutputStyleTurnReminder()` renders the line the injection site will
use.
Because the style now feeds the base prompt rather than only being
appended to it, the QWEN_WRITE_SYSTEM_MD dump builds its own unstyled
copy, preserving the invariant that the dump is a reusable base.
The prompts.test.ts snapshots are unchanged, which is the evidence that
a session with no style selected still gets a byte-identical prompt.
* test(core): pin the output-style layer where review round 1 found it unpinned
Two coverage gaps from the r1 review, both confirmed by mutation probes:
R1-1 (prompts.test.ts): the `QWEN_SYSTEM_IDENTITY_MD` override combined with
an active output style was pinned by nothing — the identity-override tests
pass no style and the `outputStyle parameter` tests never stub the override.
Skipping the style append under an override kept all 105 tests green while
silently dropping the user's style for every deployment shipping an identity
override. The new case asserts the override text wins verbatim, the styled
identity sentence is skipped, and `# Output Style: Concise` still lands.
R1-2 (contextCommand.test.ts): the `config.getOutputStyle()` forwarding into
`getCoreSystemPrompt` was never exercised with a style active — every config
mock returned `undefined`. Deleting the argument kept the suite at 16/16
while `/context` undercounted system-prompt tokens by the style section. The
new case bills a `Concise` style and asserts the estimate grows by that
section.
Mutation verification: skipping the style append under an identity override
fails only the new prompts case (1 failed | 105 passed); deleting the
`config.getOutputStyle()` argument fails only the new contextCommand case
(1 failed | 16 passed).
* fix(core): align output style prompt consumers
* test(core): pin Learning output style modes
* test(core): pin Learning style in ACP prompts
* fix(core): align output-style doc comment and Concise description
---------
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
|
||
|
|
f241c19ace
|
fix(core): support per-provider stream idle timeout (#9795) | ||
|
|
717ad101e2
|
feat(web-shell): add async submit preparation (#9802)
* feat(web-shell): add async submit preparation * fix(web-shell): keep failed-prompt retries out of the daemon retry path The failed-prompt retry reused `retry: true` only to skip prepareSubmit, but the flag is also forwarded to the daemon, whose retry branch skips recording the user message — the retried turn ran and was answered, yet its user prompt never reached the transcript. Skip preparation with an internal skipPrepareSubmit flag instead; the Ctrl+Y turn-error retry keeps `retry: true`, where the daemon semantics are correct. Also classify the retry payload from the prepared prompt so a host rewrite that changes slash-ness still arms the turn-error retry. Adds focused coverage for the resolvePreparedSubmit fallbacks, preparation-time staleness cancellation on both paths, prepareSubmit rejection, and the queued empty-prepared guard. * fix(web-shell): disarm stale retry state after slash-prepared submits * test(web-shell): pin slash-prepared disarm clears with attachment and stash variants Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
4f53c7d160
|
fix(core): route debug logs through sessionIdContext before global session (#9538)
* fix(core): route debug logs through sessionIdContext before global session In daemon/ACP mode one process hosts many sessions. Config creation overwrites the process-wide debug log session, so logs for session A were written to session B's file after B's Config was created. ACP sessions already wrap execution in sessionIdContext.run(sessionId). Make debugLogger.getActiveSession() consult that context before falling back to the global session, while keeping runWithDebugLogSession / runWithoutDebugLogSession overrides first. Fixes #9535. * fix(core): bind sessionIdContext in ACP control dispatch and refresh latest alias - Wrap AcpAgent.extMethod dispatch in sessionIdContext.run(sessionId, ...) when a sessionId parameter is present, so control-plane handlers like qwen/control/session/recap route debug logs to the targeted session. - Make debugLogger.getActiveSession() consult sessionIdContext before falling back to the process-wide session. - Refresh the 'latest' debug-log alias when the active writing session changes, so multi-session daemons don't leave it stale. - Add regression tests for control-plane dispatch binding and alias refresh. Relates to #9535. * refactor(core): harden debug logger alias and ACP session context binding - Gate AcpAgent.extMethod sessionIdContext.run on this.sessions.has() to avoid binding unsanitized caller-supplied strings. - Serialize latest-debug-alias updates via a module-level promise chain and key the dedup marker by debug-directory + sessionId. - Extract doUpdateLatestDebugLogAlias so marker/alias bookkeeping is centralized in updateLatestDebugLogAlias. - Strengthen regression tests: assert the run callback actually dispatches, and add a negative case for alias dedup. Relates to #9535. * fix(cli): use Config session id for async-context binding and cover review findings - Bind sessionIdContext to session.getConfig().getSessionId() in AcpAgent.extMethod so the context spelling matches Session.ts and avoids splitting legacy sessions across upper/lower-case debug log files (R3-1). - Wrap top-level session handlers (cancel, setSessionMode, unstable_setSessionModel, setSessionConfigOption) with sessionIdContext.run (R2-4). - Only bind extMethod dispatch for session-scoped methods, preventing global operations from being framed with a leaked session context (R4-1). - Add regression tests for Config-spelling binding, global-method allowlist, and serialized latest-alias updates (R3-4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
01416830c9
|
docs(vscode): correct minimum VS Code version (#9852)
Align the companion README requirement with the extension manifest so users do not try unsupported VS Code releases. Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
4d3f9ff571
|
fix(core): allow hook-bounced workflow reapproval (#9547)
* fix(core): allow hook-bounced workflow reapproval * fix(core): release settled workflow approval sources * fix(core): reject stale workflow approvals Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): retry failed approval delivery Record approval incarnations only after synchronous event delivery succeeds, preserving retry recovery while pinning scheduler bounce identity. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): log approval delivery failures Expose failed approval event delivery while preserving retry-on-update behavior and consolidate the shared approval test setup. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): stabilize approval event delivery Share responder state across retries, reject stale incarnations, and bound automatic redelivery so transient listener failures cannot stall or duplicate approvals. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): isolate approval delivery per listener Retrying a partial approval delivery re-emitted the event to every listener, so a single failing listener both blocked healthy ones from ever seeing the request and re-delivered duplicates to listeners that already succeeded (forwardApproval/createApprovalHandler have no callId dedup). Push the isolation down to the emitter: iterate rawListeners, catch per listener, and retry only the listeners that actually threw. Surface retry exhaustion via console.error so it is visible outside debug sessions, and drop the dead awaitingByCallId guard (a same-tick snapshot compared against itself could never differ). Cover thrower-before-healthy, healthy-before-thrower, exhaustion reporting, and both clearApprovalDeliveries teardown paths (abort and all-tool-calls-complete). --------- Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> |
||
|
|
014b903bf5
|
fix(daemon): Bound conditional-close refusal holds (#9820)
* fix(daemon): Bound active-work close refusal holds Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9820) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
3fd059368b
|
fix(core): invalidate token counts recorded for a switched model route (#9506)
* fix(core): invalidate token counts recorded for a switched model route `/model` switches rebuild the content generator but keep the same GeminiChat instance, so API-reported prompt/output token counts from the previous route survived and anchored admission, output clamping, and compression decisions for a different serialization (#9454). Attribute the counts to the route that produced them (Config.getModelRouteIdentity) and invalidate them on a route change so all safety decisions fall back to the history-walk estimate. Closes #9454 * fix(core): keep token counts scoped to producing route * fix(core): close round-2 route-scoping findings (#9454) - C1: client.test.ts session-token-limit gate test now seeds the chat mock's getLastPromptTokenCount (the gate's new source) instead of relying on the telemetry stub alone. - C2: goal-turn-integration.test.ts passes the routeKey positional added to processStreamResponse and widens the local cast. - S1: fix stale rationale comment — no session token-limit gate reads the telemetry mirror anymore; UI context counters and compression banners do. - S2: pin tryCompress's entry invalidation with a focused test; manual /compress reaches it without sendMessageStream's entry reset. - S3: direct config tests for getModelRouteIdentity — call stability, model@<sha-prefix> shape, and the guard keeping the registry baseUrl out of non-active model identities. - R2-3: sendMessageStream now resolves the request route first and invalidates counts against IT (deriving the key from the actual model param on the non-exact branch), so an active-route count can no longer anchor an exact `\0` route's output clamp; regression test added. * fix(core): close round-3 route-scoping findings (#9454) - R3-1: hard-rescue rollback now restores tokenCountsRouteKey alongside the counts; tryCompress re-stamps the key to the active route mid-rescue, which left the resurrected request-route count riding the active key past the next entry invalidation (regression test: active-route read after failed rescue must not inherit the override count). - R3-2(2): invalidation zeroes the telemetry cached-content mirror together with the prompt mirror so /context stops rendering a foreign cached count beside a zeroed prompt count; the mirror is documented as best-effort display state between a switch and the next guarded read (R3-2(1)). - R3-4: collapse the requestRouteKey ternary into one getModelRouteIdentity call (the non-exact arm passed exactly the default parameter value). - R3-5: resolve the active-route default lazily after the zero-count fast path instead of eagerly in the default parameter. - R1-3: add the missing compressFast route-invalidation test (third entrypoint; mutation-verified). * fix(core): preserve route-scoped token guards (#9454) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): resolve full-turn route selectors at the session token limit gate (#9454) * fix(core): stamp fallback-served token counts under the request route (#9454) * fix(core): retain route-scoped token counts across route crossings (#9454) * fix(core): keep route-scoped token counts consistent through compression (#9454) * fix(core): keep rescued output counts and compression stamps route-scoped (#9454) --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
aac9606f78
|
fix(cli): skip terminal redraw optimizer on WSL/ConPTY (#7897)
* fix(cli): skip terminal redraw optimizer on WSL/ConPTY and enable sync output on Windows Terminal (#7634) The streaming text repetition bug on WSL + Windows Terminal is caused by the terminal redraw optimizer batching cursor-up sequences, which ConPTY processes differently from individual per-line erases. The cursor lands at the wrong row, causing each new frame to overlap remnants of the previous one. Two fixes: 1. Skip the redraw optimizer when WSL (WSL_DISTRO_NAME / WSL_INTEROP) or Windows Terminal (WT_SESSION) is detected, falling back to Ink's original per-line erase sequences that ConPTY handles correctly. 2. Enable synchronized output (DEC mode 2026) for Windows Terminal, which has supported it since v1.6, making frame updates atomic and masking any residual cursor positioning issues. Fixes #7634 * fix: add WSL_INTEROP test and clear env vars in beforeEach to fix test fragility Address review feedback on #7897: - P1: clear WSL/Windows Terminal env vars in beforeEach so existing optimizer tests don't silently break when run inside WSL - P2: add dedicated test for WSL_INTEROP detection * review: address wenshao feedback on #7897 - Accept injectable env in installTerminalRedrawOptimizer (matches sibling terminalSupportsSynchronizedOutput), eliminating the need for beforeEach env-stubbing in test files - Add QWEN_CODE_LEGACY_ERASE_LINES=0 as a force-on escape hatch for WSL/Windows Terminal users whose terminals handle the batched sequences correctly - Collapse three near-identical WSL/WT skip tests into it.each - Correct Windows Terminal DEC 2026 support version: v1.18, not v1.6 - Move WT_SESSION check above the TERM declaration in terminalSupportsSynchronizedOutput so the term isn't declared before its only consumer - Add a table case asserting TMUX guard still wins over WT_SESSION - Pass explicit empty env to installTerminalRedrawOptimizer in the synchronizedOutput composition test so it doesn't depend on the runner's environment * fix(cli): narrow optimizer skip to WSL only, drop WT_SESSION Per wenshao's review: WT_SESSION is set on the Windows side and is not propagated into WSL shells without WSLENV, so it can never be the env var that fires for #7634. Remove WT_SESSION from the optimizer skip (WSL_DISTRO_NAME + WSL_INTEROP remain) and from the synchronized-output allowlist. The synchronized-output change for Windows Terminal belongs in its own PR once confirmed; bundling it into a WSL bug fix mixed two independent behavior changes. Also correct the comment: 'WSL or Windows Terminal' -> 'WSSL only', and remove the WT_SESSION test cases from both test files. * fix(cli): clean up WT_SESSION comment residue and pin its exclusion Per review: the drop of the WT_SESSION skip left stale comments and no test pinning the deliberate exclusion. Fix the force-enable comment (WSL only, not Windows Terminal), complete the truncated WT_SESSION rationale, and add a test asserting WT_SESSION alone does NOT trigger the skip (it is not propagated into WSL shells). Also stub QWEN_CODE_LEGACY_ERASE_LINES in beforeEach so the suite is isolated from a host that has the flag set. * refactor(cli): extract shared isWsl(env) into terminal-env util WSL detection was inlined in terminalRedrawOptimizer (this PR) and duplicated as a private helper in voice-availability. Extract a single isWsl(env) into ui/utils/terminal-env.ts and use it from both sites so the marker set cannot drift. Requested by maintainer in #7897 reviews. * fix(ui): add license header and gate WSL_INTEROP in voice preflight Round-3 review: terminal-env.ts shipped without the @license header every sibling carries; and the voice-side isWsl migration was inert under the test probe because voice-availability.test.ts only exercised the WSL_DISTRO_NAME marker. Add the header and cover WSL_INTEROP via it.each. #7897 * docs(ui): note the separate core-side WSL check in terminal-env Round-4 review: the extraction comment claimed the marker set cannot drift, but ripgrepUtils.wslTimeout() in packages/core keeps its own narrower WSL_INTEROP-only check because core cannot import from cli. Document the exception so a future maintainer greps both sites. #7897 * docs(cli): document QWEN_CODE_LEGACY_ERASE_LINES escape hatch Round-5 review (R5-1): isWsl(env) relies solely on env markers, which env-scrubbing launchers (sudo, env -i) strip - so the #7634 skip never fires in those contexts. Document the launch-time =1 fallback and note it must be passed at launch because sudo drops the flag too. Also closes the round-2 R2-3 gap (the flag was previously undocumented). #7897 * refactor(cli): move isWsl to core and apply maintainer review polish wenshao's manual review suggested moving the shared WSL marker check to packages/core so cli can import it (core cannot import from cli), while ripgrepUtils.wslTimeout() keeps its deliberately narrower predicate. Also: - Sharpen the ConPTY divergence comment with the concrete sequences the optimizer emits (CSI 1 B cursor-down, CSI n A multi-count) that Ink's native erase path never does. - Replace the beforeEach vi.stubEnv test fixture with explicit empty-env arguments (truer 'not on WSL' fixture, no host-env dependency). - Note the env parameter exists for testability. - Trim the moved file's doc block to durable facts and tighten the docs row wording. #7897 * test(cli): pin the env default-parameter seam in redraw optimizer Round-7 review: the production call path (installTerminalRedrawOptimizer with no env arg) was never exercised - every test passed env explicitly, so a mutation to the = process.env default (e.g. = {}) would pass green while silently disabling the WSL skip and =1 escape hatch in production. Add a hermetic test that stubs WSL_DISTRO_NAME and asserts the no-arg call skips the optimizer. #7897 * test(cli): restore afterEach env cleanup for default-seam test Round-8 review: placing vi.unstubAllEnvs() as the last statement in the default-seam test body meant a failing expect (the exact regression the test pins) would skip the cleanup and leak WSL_DISTRO_NAME=Ubuntu into process.env for the rest of the file. Move the cleanup back into the describe-level afterEach so it runs even on assertion failure. #7897 * test(cli): close the two minor coverage gaps from chiga0's review Maintainer chiga0 approved the PR but noted two minor test gaps: - The default-seam test only stubbed WSL_DISTRO_NAME, so a host-set QWEN_CODE_LEGACY_ERASE_LINES=1 would pass it for the wrong reason; stub the flag too so the assertion depends only on the WSL marker. - The tri-state flag has no case for a non-standard truthy value; add one pinning that 'garbage' falls through to the platform default (WSL skip). #7897 * docs(cli): correct 'only path' claim in ConPTY divergence comment Round-11 review: the comment asserted the optimizer is the ONLY path emitting CSI 1 B / CSI n A, but the repo's patched ink build also emits both sequence classes on its cursor-positioning path (buildCursorSuffix / buildReturnToBottom, reachable via BaseTextInput.setCursorPosition). Skipping the optimizer on WSL does not remove these from interactive input. Narrow the claim to the per-frame erase-and-redraw path. #7897 |
||
|
|
a8b822f5d2
|
feat(web-shell): expose agent task changes (#9637)
* feat(web-shell): expose agent task changes * fix(web-shell): deduplicate agent task callbacks * fix(web-shell): ignore agent task telemetry churn * fix(web-shell): skip immutable prompts in task fingerprint * fix(web-shell): type agent task fingerprint |
||
|
|
5cff52c6e8
|
fix(goal): count catalog previews in the unit their budget is written in (#9835)
Evidence catalog previews were cut to 240 characters while the budget they feed is 24,000 bytes. In UTF-8 those units differ by up to four times, so the guard held only for ASCII. A legal 32-claim checkpoint of Chinese claims serialized to roughly 29kB — over the cap on its own, before a single new record had been scanned — which marked the window truncated, and `truncated` switched compaction off. The one state compaction exists to resolve was the one state it refused to run in, so the Goal was stopped as `usage_limited`, the only status the reducer refuses to resume, with nothing left to salvage. An English Goal never reached that state; a Chinese one could not avoid it. Previews are now capped to 240 UTF-8 bytes on a code point boundary at the two points a catalog entry is built. Nothing changes for ASCII, where the two units already agreed; a CJK preview is shorter than before, which is the cost of the cap actually holding. With it, a full checkpoint is bounded well inside the catalog budget for every script, so a window can no longer start out truncated. A truncated window now compresses rather than stopping the Goal. Overflow means the budget is full and the newest evidence that did fit is exactly what a checkpoint folds into claims; the older evidence left behind is already covered by the previous checkpoint. Only a window that captured nothing at all has nothing to salvage, and that is the sole remaining path to `usage_limited` here. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
674afdf1a7
|
feat(core): show what a workflow will do, and scope the grant that approves it (#9837)
A user approving a workflow was shown `Confirm WorkflowTool` over `Run a workflow script (4127 chars)` — a character count standing in for arbitrary model-authored JavaScript that can fan out to the per-run agent cap, provision git worktrees and spend an uncapped token budget. The asymmetry was visible inside one run: the subagent approvals that same workflow bubbles up each got a full dialog. Override getConfirmationDetails to show what the script says it will do, read from `export const meta`: the workflow's name and description, its declared phases, the resolved args, and a bounded excerpt of the source. Meta is obtained through `extractAndStripMeta`, which parses rather than evaluates, so nothing model-authored runs before the user has approved anything. It throws on a malformed literal, so the call is wrapped: a script with a broken meta block stays approvable-or-rejectable rather than taking the dialog down with it. Everything displayed goes through `stripAnsiAndControl` first. The screen ships with the preview rather than after it, because until now nothing was displayed and so nothing could be spoofed — a preview without it is what would open the hole. Single-line fields are flattened, which is what we want for a `meta.name` spanning three lines. The script excerpt is sanitized per line instead: `\n` is a C0 control character, so the naive call would collapse the script into one unreadable line. Scope the grant on the same object. An inline `script` is fresh source every time, so it can never be pre-approved: `hideAlwaysAllow` removes the option, and an empty `permissionRules` stops `injectPermissionRulesIfMissing` from supplying the bare tool name, which `buildPermissionRules` documents as matching every invocation of the tool. A `scriptPath` names a file the user chose, so it stays pre-approvable but scoped to that path via a `key:value` param matcher. The rule is built with the same helpers the matcher uses, and the test asserts it behaviourally -- a rule that reads plausibly but never matches would make "always allow" silently do nothing. Finally, move the token-cost warning ahead of the spend it warns about. It previously appeared only on the success path, i.e. after the run, and never at all when the run failed. Claude-Session: https://claude.ai/code/session_01MDuRWMhKbqEYZXbbLhn3en Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a2e458deef
|
feat(auth): add Kimi (Moonshot AI) as a built-in third-party provider (#9814)
Adds a Moonshot preset to the /auth Third-party Providers menu, offering the international and China API endpoints and seeding the current Kimi model catalog. Moonshot speaks the OpenAI protocol, so this is a declarative preset with no new mechanism and no change to the provider type. Model metadata follows Moonshot's published capabilities. K3 is marked thinking-mandatory: its API exposes a reasoning-effort knob but no way to turn thinking off, so a disable shape must never reach the wire. The two code models and K2.6 keep thinking toggleable, and all four accept image and video input, which the K2.6 guide states explicitly. Registers the new credential env key everywhere a provider key has to appear: the no-AK CI gate and its pinned assertion list, and the telemetry provider mapping, both by env key and by request hostname so Kimi traffic is attributed rather than reported as unknown. The three first-run docs that enumerate built-in providers are brought back into agreement, which also picks up entries that were already stale. Closes #9197 |
||
|
|
747dbf00c2
|
feat(web-shell): compact agent activity summaries (#9657)
* feat(web-shell): compact agent activity summaries * fix(web-shell): preserve merged todo timing * fix(web-shell): address compact summary review * test(web-shell): cover compact review edge cases * fix(web-shell): make the folded thought header row fully clickable * fix(web-shell): guard folded thought toggle against popover clicks (#9657) * test(web-shell): cover thinking header hit area and parallel-agent thoughts (#9657) --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
03bcfe44b5
|
refactor(review): split SKILL.md into a core body plus verdict-gated reference files (#9804)
* refactor(review): split SKILL.md into core body plus verdict-gated references (#9787) The bundled review skill's SKILL.md (381,984 bytes, ~95k tokens) was injected whole on every run, although large stretches are conditional territory a given run never touches. Split it into a core body plus reference files the orchestrator reads on demand, gated on the parse-args verdict it already holds: - references/posting.md — whole Step 7 (authorisation gate, presubmit, anchors, submit, 422/head-drift recovery, publish-assets). Loaded only when posting is live (comment.effective or a same-session post request; PR + high only). Its compose-state field list relocates verbatim to Step 6's Verdict section, because a report-only run still writes that state for compose-review without ever loading posting.md. - references/persistence.md — whole Step 8 (tail batching, report, artifact registration, incremental cache). Loaded before Step 8 on every run except cross-repo lightweight mode. - references/aone.md — the self-contained Aone blocks of Step 1 (clone and two-host rules, a1-backed surface, the five submit failure shapes, dedup shape notes). Loaded before match-remote when the host/meta says Aone. The split moves whole steps; incident-backed rules stay with the step they guard. The write prohibition and the posting gates remain in the injected core so they bind runs that never load a file. No enterprise.md: the GHE host notes are sentences woven into universal paragraphs, and extracting them would strip rules from steps that remain in core. Injected prompt: 381,125 -> 304,427 body bytes. Typical non-posting runs (local/file/PR, any effort) save ~58 KB (~15%); lightweight runs ~77 KB (~20%); posting runs load posting.md back and save only the Aone block. The issue's "roughly a third" estimate is unreachable under its own whole-step guardrail — Steps 1 and 6 dominate the core and interleaving forbids fragmenting them; Step 5 / Step 3C effort-gated splits are the natural follow-up. Drive-by, verified against #9627's revert-guard test and the a1 implementation: three stale sentences still claiming comment-status "has no Aone backing" are aligned with the a1-backed behavior that landed in #9627. Tests: SKILL.test.ts revert guards now govern the full corpus (SKILL.md + references), with new pins for the gates, the core-retained invariants and the no-duplication invariant; run-skill-parity reads the corpus oracle; bundled-skills integration pins the shipped reference files. Verified by build + bundle, all review-skill suites, and a real-model E2E run of the split skill (verdict-gated reads observed: persistence.md loaded before Step 8, posting.md and aone.md correctly skipped). * fix(review): drop uninterpolated template tokens from skill references (#9804) * test(review): guard stems oracle by persistence.md, pin gate clauses to bullets (#9804) * fix(review): close Step 7 reference doc gaps from reverse audit (#9804) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
a7128d30c3
|
fix(core): isolate fork cache readers by session (#9471)
* fix(core): isolate fork cache readers by session * fix(core): skip foreign cache extraction * perf(core): skip foreign cache before memory IO * test(core): provide session id in extraction fixture --------- Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com> |
||
|
|
deed96e733
|
fix(core): avoid ghost shutdown pending state (#9550)
* fix(core): avoid ghost shutdown pending state * fix(core): gate assignments during shutdown delivery Keep teammates out of task assignment while shutdown delivery is pending, with rollback on mailbox failure. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): preserve concurrent shutdown state Track in-flight and delivered shutdown requests independently so a failed retry cannot reopen assignment gates owned by another request. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): settle resolved shutdown writes Keep response resolution separate from mailbox delivery so late writes cannot re-arm a completed shutdown generation. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): separate shutdown marker ownership Track delivered requests independently from test-only markers so failed writes clean up without dropping pending successful deliveries. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): track shutdown response ownership Represent delivered shutdown requests as reservable tokens so concurrent writes and responses conserve pending state without stale generations or duplicate aborts. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com * fix(core): deliver every stacked shutdown request A busy teammate that received two shutdown requests only ever saw the first: one idle flush read all unread shutdown messages but enqueued only shutdowns[0], leaving the rest marked-read and dropped. Each write still minted a delivered token, so the un-delivered request's token could never be consumed by a response and hasShutdownWork stayed true forever, permanently gating task assignment for a live teammate. Enqueue every shutdown returned by a single flush so token count and delivered-request count stay balanced. Add coverage for stacked delivery, markShutdownRequested idempotence, the responsesInFlight gating window, and abort-on-approve when a MESSAGE_SENT listener throws. Extract the repeated gated-mailbox-write scaffolding into one helper and reset the shared mailbox mocks per test. * chore(core): log shutdown ledger state transitions Add debug logging at the shutdown state-machine transitions (token mint, response settlement, ledger drain) so a stuck shutdown-pending teammate can be diagnosed from logs without reading private fields. Addresses reviewer suggestion on #9550. --------- Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com> |
||
|
|
75fb40d832
|
refactor(goal): render Goal continuation prompts from one core renderer (#9581)
* refactor(goal): render Goal continuation prompts from one core renderer The prompt sent when `runtime.finishTurn` schedules another Goal turn was assembled independently in three hosts: the TUI's inline array in `useGeminiStream`, and a `buildGoalContinuationParts` in each of the ACP session and the non-interactive CLI. Three copies of the same four shared lines have already drifted -- the TUI carries the anti-spoofing guard lines but no objective, while ACP and non-interactive carry the runtime continuation context but no guard lines. Upcoming work adds further variants (an "objective was edited" announcement and a budget wind-down prompt). With the text living in three places, every new variant means three edits, which is precisely how the current drift was produced. This moves assembly into `packages/core/src/goals/goal-continuation-prompt.ts`, where a variant is a case in one function and the shared prefix exists once. The two `buildGoalContinuationParts` helpers keep their names and signatures and simply delegate. This is a pure refactor: no prompt text changes. Each host still emits a byte-identical string to the one it emitted before. The existing drift is preserved deliberately and is left for a separate, behavior-changing follow-up. The new unit test pins the complete rendered string for both variants with and without verifier feedback, so any future edit to a line surfaces as a test diff; the existing host tests pass unmodified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(goal): tighten continuation renderer contract * test(goal): cover verifier feedback hosts * refactor(goal): hoist Goal continuation parts builder into core (#9581) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
00461ab275
|
fix(core): mark agent launch failures as failed tool calls (#9519)
* fix(core): mark agent launch failures as failed tool calls The subagent-not-found path and failWorktreeProvisioning() returned a ToolResult with llmContent and a failed returnDisplay but no error field. The scheduler records a failure only when error is set (coreToolScheduler keys off toolResult.error), so a launch that never ran was counted as a successful agent call: no error-formatted model response, no failure-path hooks, and optimistic success telemetry. Set error consistently with buildSpawnBlockedResult in the same file, which already documents this convention. The full guidance text (the available-subagents list, the specific provisioning reason) goes into error.message because the failure path forwards only that to the model. returnDisplay is unchanged, so terminal output is identical. Fixes #9509 * fix(core): mark agent launch failures * test(core): cover preserved agent worktree errors * test(core): keep preserved worktree error test foreground * test(core): fail subagent via creation seam in preserved-worktree test |
||
|
|
20e076f8f3
|
fix(core): surface nested sub-agent approvals under background parents (#9793)
* fix(core): surface nested sub-agent approvals under background parents A tool call needing confirmation inside a nested sub-agent (launched by a background agent or fork) was neither surfaced nor denied: the prompt- avoidance policy was stamped on an Object.create wrapper while the rebuilt tool registry binds to agentConfig, so nested schedulers resolved Config.prototype's false and believed they could prompt; and the nested invocation's emitter was never bridged, so TOOL_WAITING_APPROVAL fired with no listener and the call waited forever - the enclosing agent hung silently. - Stamp getShouldAvoidPermissionPrompts on agentConfig itself (launch and resume paths) so nested launches inherit the real policy through their config prototype chains: hang-forever becomes an explicit deny when bubbling is off. - Bridge nested foreground launches' approval events onto the nearest backgrounded running ancestor's Background-tasks entry (walking the registry's parentAgentId lineage), so they park where the ancestor's own approvals go and the user can answer them from the dialog. - Mark bridged approvals with the nested runtime's subagentId (declared by the bridge caller via nestedSource - runtime ids and registry ids use different suffixes so comparing them cannot work) and show the waiter in the Background tasks dialog. Fixes #9782 * chore(i18n): add 'from nested agent' locale entries * fix(core): harden nested sub-agent approval bridging (#9793) * fix(core): tighten nested approval bridging per review round 2 (#9793) - addPendingApproval returns a discriminated result ('parked' | 'duplicate' | 'unavailable') mirroring the workflow registry's parkPendingApproval, so the bridge drops re-emitted events without re-running the dedup scan on the caller side - log a debug line when a re-emitted approval event is dropped, so "approval never appeared" sessions can tell a drop from a lost event - extract stampBackgroundPromptPolicy next to createApprovalModeOverride; both the launch and resume paths now share one stamp + rationale - gate the nested approval bridge on the inherited prompt-avoidance policy like the sibling bridges, instead of wiring a dead subscription under auto-denying ancestors - refresh two JSDoc examples that still cited the deleted Object.create wrapper * fix(core): key parked background approvals on runtime identity (#9793) Generated tool-call ids (`call_qwen_N`) are only unique per conversation, so two nested sub-agents whose first id-less call needs confirmation both arrive under the same callId on their shared background ancestor entry. The parked-approval queue keyed identity on callId alone: the second runtime's prompt was dropped as a "duplicate" with nothing in the dialog to answer, and a resolution or TOOL_RESULT for one runtime could clear another runtime's parked prompt. Key identity on (subagentId, callId) — the same composite the workflow run registry already uses — across addPendingApproval's duplicate predicate, resolvePendingApproval, and clearPendingApproval. Own approvals stay unstamped, so same-call re-emissions from the entry's own runtime still dedupe while different runtimes never collide. * fix(core): attribute nested approval logs and pin composite-key dedup (#9793) * fix(core): attribute remaining approval failures and pin own-vs-nested collision tests (#9793) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
95b2cf4cb1
|
fix(review): pin the verified git identity across the residue probe (#9557) (#9742)
* fix(review): pin the verified git identity across the residue probe (#9557) `worktreeResidue` verified a tree's identity once and then ran five commands that each re-discovered the repository from `cwd`, through the same `.git` file the check had read — a file writable by anything running as this user. Two halves, and they close different windows: - **The gate now requires the gitfile's admin entry to point BACK at this tree.** A repository whose `core.worktree` names this path answers `--show-toplevel` with this path, so the old gate saw itself while every command after it measured the plant's index — which already holds the contamination as committed content, so `status` comes back clean for a tree with a mutant in it. A planted standalone repo has no admin entry to round-trip; `scratch-tree` gates its own reset on exactly this. - **The verified `--git-dir`/`--work-tree` are pinned onto every later spawn**, so a swap that lands AFTER the gate cannot redirect them either. The reason recorded against doing this — that an explicit pin changes what `ls-files` resolves, so it needs its own round — was measured and is wrong. Across a standalone checkout, a linked worktree, a superproject with an initialised submodule, and a worktree reached through a symlinked ancestor, all five commands return **byte-identical** output pinned and unpinned. What was right about it is that it needed measuring, which is what this is. The backpointer half is pinned by a test that goes red without it. The pin half is not: it closes a check-then-use window the suite cannot open deterministically (the swap has to land between two spawns). Its evidence is a shell repro — with the identity captured, then the gitfile swapped, discovery reports a clean tree while the pinned commands still report ` M a.ts` and the untracked probe file. Saying that rather than implying a pin that is not there. * fix(review): refuse symlink redirects and forged admin entries in the residue probe (#9742) * fix(review): fail closed on unanchored clean verdicts and steered boundaries (#9742) The residue probe's identity gate answered CLEAN for shapes it cannot vouch for. Four closes, all fail-closed: - A repository answering for a path it does not contain — the common dir no literal ancestor of the tree path — is refused before the symlink walk. That is the shape a forge uses to steer the walk's own stop boundary so it fires before the planted link is lstat'd, and the walk's bound escaping to the filesystem root misdiagnosed refusals (and reddened the suite on hosts whose tmpdir resolves through a symlink). - A clean measurement no fetched-head record anchored is refused: a forged pair answers clean too, so an unanchored empty status certifies whichever index the gitfile names. Dirty reports still name paths — a forge answers clean, never dirty. - The agent-prompt wiring fails closed when the plan's fetchedSha is absent or malformed instead of degrading to an unpinned probe: every worktree-mode fetch writes the field. - A dangling admin-entry backpointer is "does not point back", not the outer catch's "not a git worktree". Comments now say what the pin actually closes (post-gate identity redirect, pre-planted shapes) and what it does not (in-window state presentation by an active writer — cost-raising, not closure; the structural close is the sandbox boundary, #9556). Witnessed: every guard flips its focused tests when removed (mutation probes), including new ones for the case-fold, the unreadable-HEAD arm, the sha handover seam, and the sha-less production caller. Fixtures realpath their roots so the suite holds under any tmpdir spelling. * fix(review): anchor the scratch-tree residue check and name unmeasured reasons (#9742) Three closes on the round-4 findings, all inside the residue probe's own footprint: - The sha-less production caller flattened the tampering signal the probe exists to protect: the no-record refusal fired on 100% of clean runs, so a note that fires always is a note nobody reads. scratch-tree now takes --fetched-sha, agent-prompt welds the plan's record into the verifier's command, and a healthy shared tree measures clean again — a forged pair is refused at the pin with a distinguishable reason instead. - The unmeasured renderers blamed `git status` for refusals where it never failed (the gates run before it; the no-record tail runs after it answered CLEAN), sending triagers to debug a git environment with nothing wrong. All three renderings now name a reason. - The ancestor-symlink walk had no witness: both sibling shapes refuse at earlier gates, and deleting the arm shipped green (measured). A shape that passes the leaf, the self-equality and the boundary gate leaves only the walk to refuse it. * test(review): pin the fetched-sha CLI contract and the weld continuation (#9742) * test(review): share the forge-territory fixture across the redirect residue tests (#9742) * fix(review): admit healthy trees spelled through links above the root (#9742) * fix(review): canonicalise both spellings the containment gate compares (#9742) * fix(review): admit healthy layouts and refuse unpinned or drifting trees (#9742) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
3a9d2d37f8
|
docs(agent): clarify parameter preconditions (#9580)
* docs(agent): clarify parameter preconditions * test(agent): cover read-only preconditions * docs(agent): clarify nested background downgrade * docs(agent): align working-dir background guidance * docs(agent): clarify teammate worktree execution --------- Co-authored-by: tao943 <278275162+tao943@users.noreply.github.com> |
||
|
|
5cb946b55b
|
feat(scheduled-tasks): allow creating a task with an existing session (#9361)
* feat(scheduled-tasks): allow creating a task with an existing session POST /scheduled-tasks and the workspace-qualified endpoint now accept an optional `sessionId`. When provided, the task binds to that existing session instead of minting a dedicated one. The session is validated up front: it must be live in the target workspace, idle, not archived, and not already bound to another scheduled task (checked both in a best-effort pre-read and authoritatively under the cron write lock). A failed create never tears down a caller-provided session (only route-minted sessions roll back); after a successful create the session follows the regular scheduled-task session lifecycle. Omitting `sessionId` keeps the dedicated-session behavior unchanged. Closes #8906 * fix(serve): harden scheduled-task session binding per bot review round 1 Four fixes inside the route, each pinned by a test: - Move the caller-session ⏰ rename to after the cron write commits, so a failed create (over-cap/duplicate 409, write 500, generation rollback) never leaves the caller's pre-existing session permanently renamed with no owning task (nothing restores the prior display name). - On SessionNotFoundError, consult SessionService.getSessionLocation so an archived session — removed from the live map by archiving — still gets the documented 409 session_archived instead of a bare 404. - canonicalizeWorkspace re-throws non-ENOENT filesystem errors (EACCES/EIO/ ELOOP/ESTALE); surface those as a retryable 500 scheduled_tasks_session_failed with a stderr log instead of a misleading 400 session_workspace_mismatch. - Parse sessionId with parseCallerSuppliedSessionId, the parser every other caller-supplied-session-id surface uses: UUID grammar, case-normalized, length-bounded (no unbounded echo in error bodies/stderr), and duplicate-binding equality per session rather than per spelling. New tests: disk-backed archived fallback (runtime harness), ELOOP 500, generic lookup-failure 500 with no side effects, over-cap rejection on the reuse path, concurrent-create single-bind invariant (updateCronTasks serializes writers; deleting the under-write-lock check flips the second response to 201), null→mint, and padded/mixed-case normalization. Stub session ids migrate to valid UUIDs to match the shared grammar. * fix(serve): classify persisted-but-not-live sessions in task binding probe The scheduled-task binding disk probe only special-cased 'archived'; 'active' and 'conflict' locations fell through to a 404 that misreported existing resumable sessions as nonexistent (routine after daemon restarts, when only task-bound sessions are rehydrated). Answer 409 session_not_live / session_conflict for on-disk states and reserve 404 for genuinely absent ids; add the findSessionIdIgnoringCase fallback for legacy uppercase-spelled session files (mirrors session-id-admission). Also drop the dead isArchived switch the bridge never populates, dedup the repeated rename / lookup-failure bodies behind shared closures, align the invalid_session_id message with the sibling caller-id surfaces, and pin the new behavior plus the post-commit rename-failure invariant with tests. * fix(serve): gate scheduled-task delete teardown on session ownership Persist whether a task's bound session was minted by the task (sessionOwnedByTask on DurableCronTask) and only close it on DELETE when the task owns it — a caller-provided session pre-existed the task and must survive its deletion. Tasks written before the marker keep today's teardown (their bound sessions were always task-minted), and the keepalive stamps ownership when it binds a freshly minted session. Also stop mapping real filesystem failures in the persisted-session probe to 404 session_not_found: the probe helpers rethrow non-ENOENT errors (EACCES/EIO/ESTALE), which now surface as a retryable 500 scheduled_tasks_session_failed with a stderr log, matching the sibling canonicalizeWorkspace catch in the same block. Keepalive naming now uses the same payload as the route (task.name ?? task.prompt), so the post-restart sweep no longer clobbers the route's ⏰ name on bound sessions (matters now that caller-provided sessions are named by the route too). * fix(serve): close session-binding races in scheduled-task create/reuse R4-1: re-validate a caller-provided session under the cron write lock; archive/delete tears the session out of the live map before its cron hook runs, so a session that left the map between validation and commit is now rejected with 409 session_not_live instead of binding a 201-returned task to an archived/deleted session. R4-2: the in-lock duplicate-binding check now covers just-minted sessions too (boundSessionId, not only providedSessionId) and runs before the cap check; the alreadyBound branch no longer rolls the session back, since a committed owner task means a concurrent reuse-create won the race and owns the session. R4-3 (narrowed, not closed): DELETE re-reads the cron file right before closeSession and skips teardown when a surviving task references the session; the residual re-read-to-close window needs session-scoped serialization shared with the bind path (follow-up). R4-4: keepalive bind writes also bail when any committed task already references the just-minted session, mirroring the route's in-lock check. R4-5/R4-6: add the missing discriminating tests (mint-site naming, sessionOwnedByTask validation); both mutation-verified. * fix(serve): keepalive must not tear down a session a committed task owns The round-14 review caught a regression in the duplicate-reference bail: it routed the "a committed task already references the just-minted session" case into the orphan rollback. In production wiring cleanupSession is deleteDaemonSessionIfOrphan, whose requireZeroAttaches passes for a just-minted session, and whose persisted removal cascades removeTasksForSessions — so the rollback killed the race-winning task's live session AND deleted its committed task from the cron file. The two no-write bail reasons are now distinguishable: the committed-reference check runs first and, when it fires, keepalive logs and continues without cleanup — the session is left to its owner (mirroring the route's symmetric alreadyBound branch, which performs no rollback for exactly this reason) and this task stays unbound on disk for the next tick to retry with a fresh session. The original bail (task no longer bindable) still rolls the orphan back, unchanged. Also pin three load-bearing behaviors that had no coverage: the duplicate-check-before-cap-check ordering at the cap boundary (session_already_bound, never max_tasks_reached with rollback), the DELETE pre-close re-read failure fallback (still closes the owned session), and the under-lock re-validation generic-error branch (500, never coerced to session_not_live). All three mutation-verified. * fix(serve): serialize scheduled-task session teardown with reuse-create binding (#9415) * fix(serve): extend scheduled-task session teardown serialization to rollback and keepalive sites (#9415 R6) * fix(scheduled-tasks): narrow existing session reuse * fix(scheduled-tasks): restore conversation-bound tasks * fix(scheduled-tasks): restore conversation runtime tasks * fix(scheduled-tasks): honor the session-management gate on the primary surface * test(scheduled-tasks): isolate the Conversations runtime ownership record The boot-restore test passed no liveDiscoveryStableBaseDir, so runQwenServe resolved it to ~/.qwen and built the Conversations-runtime ownership on the machine-global record. A concurrent live owner under the same HOME (another vitest worker, a shared-runner CI job, a developer's qwen serve) failed the boot with 'The Conversations runtime is owned by another daemon.' Point the test at a temp stable base, matching the four daemon boots in run-qwen-serve-live.test.ts. * test(scheduled-tasks): cover the ambiguous session-owner rejection path --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
2855149d47
|
fix(core): detect long verbatim repetition loops in content and reasoning streams (#9668)
* fix(core): detect long verbatim repetition loops in content and reasoning streams The chunk-hash content loop rule only treats repeated 50-char chunks as a loop when their occurrences cluster within 1.5 chunk lengths (75 chars), so a verbatim-repeated unit longer than that (the ~300-char analysis block chanted in issue #1775) never fires. Add a long-period rule: five equally spaced occurrences of an identical chunk mark a candidate period, and the spanned region is verified to be exactly periodic with that stride before halting. Raise the content history window so long units stay observable. Also route thought text into the content-repetition detectors when the structured thought check does not fire: OpenAI-compatible providers stream reasoning as thought parts that getResponseText filters out of Content events, so chants in the thinking stage never reached the chunk-hash rules. * fix(core): isolate reasoning deltas from the content channel's markdown state Route thought-sourced text through an append-and-analyze-only entry point instead of checkContentLoop. Reasoning text is raw chain-of-thought, never rendered markdown: an unbalanced code fence in a thought used to flip the shared inCodeBlock parity — which nothing clears mid-turn — silently disabling visible-content chant detection for the rest of the turn, and list/heading-shaped thought deltas reset the shared history, erasing already-accumulated content evidence when a provider interleaves thought and content parts. * fix(core): grow the periodic-rule verified region with the repetition count The long-period rule only inspected the last five occurrences, pinning the verified region at 4 x stride + 50 chars: units of ~76-237 chars fell in a gap between the clustered rule's 75-char bound and the 1000-char region floor at any repetition count, and units of ~1 KB or more could never fit five occurrences into the 4000-char history window at all. Extend the candidate run backwards over the longest equally-spaced suffix of occurrences so the verified region grows with the repetition count, and once the history saturates accept a shorter run (>= 3 occurrences) when the entire retained region is verified periodic back to the history start, so earlier occurrences truncated out of the window cannot hide a chant. Also correct the constants' comments describing the rule's domains. * test(core): cover post-truncation chant detection after a long varied turn Add the realistic #1775 shape that had no positive coverage: a long varied turn filling the history window, then a ~700-char chant streamed as misaligned deltas. Asserts detection at exactly the fifth in-window occurrence, pinning MAX_HISTORY_LENGTH, truncateAndUpdate's index adjustment, and the long-unit case together — a shrunken window would fire early via the truncated-run path once the filler flushes, and a broken index adjustment would never fire. * fix(cli): widen chanting halt label to cover reasoning-stream repetitions Reasoning-stream chants fire CHANTING_IDENTICAL_SENTENCES via checkReasoningContentLoop, but getResponseText filters reasoning out of visible output, so the headless label 'repeated the same sentence in its output' sends users looking for a repetition that is never rendered. Widen the label to 'output or reasoning' and add a headless-path regression test asserting the wording. * refactor(core): share the append/truncate/analyze tail across loop channels checkReasoningContentLoop duplicated the streamContentHistory append, truncateAndUpdate, analyzeContentChunksForLoop tail of checkContentLoop, leaving the history contract in two copies that a future fix could let drift. Extract the tail into appendToContentHistoryAndAnalyze and call it from both entry points. * perf(core): compare periodic regions in place instead of slicing history isRegionPeriodicWithStride sliced up to ~4 KB of history per invocation. Near-periodic chants fail verification repeatedly while their occurrence runs persist, so once a run reaches length 5 the check fires on up to every streamed character -- a probe measured ~136 MB of transient copies over one 49k-char stream. Index the existing string directly instead; comparison semantics are unchanged. * fix(core): reset stream-content loop state on retry replays and model fallback A replay (non-continuation) retry re-streams the failed attempt's content and reasoning through the chunk detectors — the #7832 transport-replay gate admits thought-only cuts, and with deterministic decoding the re-stream is verbatim. The Retry case in addAndCheckHeuristicLoops cleared only the tool-call counters, so the accumulated identical copies could fire CHANTING_IDENTICAL_SENTENCES mid-way through an otherwise healthy attempt. Continuation retries (isContinuation) keep the delivered text and append new output, so their state stays. ModelFallback had no case at all: the fallback model restarts from scratch, so mirror the replay resets for it. A genuine chant simply re-accumulates after the restart. * perf(core): defer content-history truncation with a hysteresis slack Once streamContentHistory saturates, truncateAndUpdate walked the whole contentStats map on every streamed event — Θ(window) entries in steady state, since the stride-1 sliding window hashes every position (~385 µs/event at window 4000 vs ~12 µs pre-saturation). With high-frequency small reasoning deltas now routed through the path, healthy long-thinking turns paid thousands of events of synchronous CPU. Trim only when the length exceeds MAX_HISTORY_LENGTH by a TRUNCATION_SLACK margin (1000 chars), slicing back to exactly MAX_HISTORY_LENGTH, so the index-rebase walk is amortized over appended chars. The change is behavior-neutral: the detection rules now always operate on the logical window of the last MAX_HISTORY_LENGTH chars — occurrences the window has passed are dropped at lookup (the exact set a per-event trim would have removed) and the periodic rule's escape valve verifies from the window start, i.e. exactly the content a fully-trimmed history retains. Tests pin pre-change fire offsets across saturation and multiple trims, plus the deferred-trim mechanics. * feat(core): log a chanting-region excerpt on loop halt for debug A reasoning-channel halt exits headless runs with empty stdout and only the loop-type label on stderr; neither the LoopDetected event (loop_type + prompt_id only), telemetry, nor any log carried an excerpt of what repeated, leaving no way to tell a true repetition from a detector misfire without instrumenting a repro. Capture one period of the matched region (the span between the last two occurrences, capped at 80 chars) when the chanting detector fires and emit it through the config debug logger at the firing site. The LoopDetected event contract is deliberately unchanged. * fix(core): preserve subagent continuation retries * test(core): cover plain subagent retry forwarding * fix(core): omit plain retry continuation flag |
||
|
|
b2edb80a57
|
fix(cli): probe microphone permission on recording start, not voice warmup (#8912)
* fix(cli): probe microphone permission on recording start, not voice warmup Voice warmup called recorder.microphoneStatus() as soon as the input prompt mounted with voice dictation configured. On macOS an undetermined TCC status maps to 'prompt', so every startup appended a "Voice dictation needs microphone access" notice to the chat history, including for users who never record. warmupVoice now only preloads the recorder backend. The permission probe and its 'denied'/'prompt' notices move to a checkMicrophonePermission callback that useVoiceInput invokes from startRecording, so the notice reaches only users who are actually trying to dictate. The dedup ref moves up to Composer and reaches InputPrompt as an optional prop, matching clipboardUnavailableShownRef. A per-instance ref reset on every InputPrompt remount, which is what produced the duplicate notice. Fixes #8877 * fix(cli): hold voice mic-permission dedup in AppContainer, not Composer Dialogs (tool approvals, auth, settings) swap Composer out of the layout, so a ref held in Composer reset on every dialog round trip and the notice could repeat on the next recording. The ref now lives in AppContainer, which owns dialogsVisible and never unmounts, and reaches InputPrompt through uiState like mainControlsRef. Also from review: delegate setupRecorder to setupRecorderWith in the InputPrompt tests, cover the prompt->denied status transition (re-warns as an error), and assert Composer forwards the session ref with stable identity across input-active toggles. |