Commit graph

33 commits

Author SHA1 Message Date
haochen
008c0de746
docs(sdk): clarify empty core-tool allowlists across SDKs (#13949)
Co-authored-by: Zhangchenhao-111 <285911435+Zhangchenhao-111@users.noreply.github.com>
2026-10-12 13:19:59 +00:00
tanzhenxin
81582ca19d
fix(core): align Code Mode text with lazy tool discovery (#12990)
* fix(core): align Code Mode text with lazy tool discovery

Code Mode now discovers deferred schemas through top-level tool_search,
but several texts still described the old surface: the system prompt's
calling convention named tools by registered name instead of JavaScript
name, and the SDK docs, the bridge design doc and a few code comments said
Code Mode hides both bridge tools and inlines every deferred schema.

Refs #12973

* docs(core): scope the subagent bridge note to direct mode

The incomplete-bridge fallback reveals ordinary deferred tools, not every
summarized one, and Code Mode subagents do get tool_search beside exec.

* docs(core): drop the eager-demoted skill gap closed by #12898

prepareTools() no longer filters eager-demoted tools out of the Code Mode
allowlist, so a subagent whose tools.eager list omits skill can still load
one through exec. The docblock described that gap as open.

Refs #12809
2026-09-29 12:55:03 +00:00
易良
e39567eb56
fix(cli): honor --bare and --safe-mode in the cross-session messaging gate (#9845)
* feat(core): accept cross-session messages behind an inbound gate

Step two of QwenLM/qwen-code#8724, rebuilt on current main now that the
registry from step one has landed. A session can be reached by another
session on the same machine, and every message that arrives is gated
before the model can act on it. Off by default behind
`agents.crossSessionMessaging`.

Transport is one UNIX domain socket per session, NDJSON over the wire,
one frame per line. The socket directory is 0700 and the socket 0600,
and that is the whole access-control story: Node cannot read SO_PEERCRED
without a native addon, so a frame's claimed `from` is not
authenticated. Everything downstream assumes that.

The gate is why the transport and the policy land together. With an
explicit `agents.crossSessionInbound` the user decides; unset, the
policy follows approval-mode parity — a message auto-delivers only when
acting on it cannot do more than the sender could already have done
itself. Anything unreadable holds. Held messages are settled rather than
stranded: the buffer is bounded, shutdown expires the rest, and every
terminal outcome goes back to the sender as a control frame.

Content reaches the model inside a <cross_session_message> envelope with
the delimiter defanged in the body, so a peer cannot close the envelope
early and forge one attributed to the user. The envelope carries a fixed
notice that a peer holds none of the user's authority, and the auto-mode
classifier gains the matching rule. `/peers` lists and releases held
messages; without it, holding would be indistinguishable from dropping.

Landed only after four independent reviews, whose non-obvious findings
are worth naming because they were all live defects:

- A receiver in AUTO_EDIT auto-accepted peer messages and nothing
  reviewed what they caused. The old rationale — "every consequential
  action still faces its own gate" — is true of AUTO but false of
  AUTO_EDIT, where edit confirmations are approved outright and the
  classifier does not run. A peer could get a file written with no
  prompt, no classifier and no user. Receiver policy now turns on
  whether the mode reviews actions at all, not on whether it is YOLO.
- `server.unref()` does not cover accepted connections, so any peer that
  connected and lingered pinned the session open forever.
- `fs.mkdir(recursive)` and `fs.chmod` both follow a symlink, so another
  user could pre-create the world-writable `/tmp` fallback directory and
  redirect our 0700 chmod onto a directory of ours.
- A full listen backlog surfaces as EAGAIN on Linux, not EBUSY; the busy
  case was being read as dead on the primary platform.
- The envelope was escapable without markup: `escapeAttribute` handled
  `&<>"` but not newlines, so a crafted `fromName` could emit
  free-standing lines inside the opening tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(i18n): translate the /peers description in strict-parity locales

`Test (ubuntu-latest, Node 22.x)` has failed every run on this branch with

    FAIL src/i18n/mustTranslateKeys.test.ts
      > does not fall back to English for any built-in command description
        in strict-parity locale { code: 'zh-TW' | 'zh' }
      AssertionError: expected [ 'peers' ] to deeply equal []

`peersCommand` set `description` to a literal English string rather than
routing it through `t()`, so both strict-parity Chinese locales fell back
to English and the parity test — which exists to catch exactly this —
reported the `peers` path.

Four merges of current main were attempted against this check. None could
fix it: the untranslated description is introduced by this branch, so no
state of main contains the missing keys.

Use the `get description() { return t(...) }` form every other built-in
command uses, and add the key to `en.js`, `zh.js` and `zh-TW.js`. All
three locale files remain in sync at 1817 keys.

Verified both directions in a clean worktree install: with the change,
`mustTranslateKeys.test.ts` is 20/20; stashing it reproduces the CI
failure exactly, `expected [ 'peers' ] to deeply equal []`, twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VgTjRF91xANQh6SY9YGyCf

* fix(ipc): close Critical review findings in the cross-session inbox

- Dedup held msgIds case-insensitively, matching /peers resolution
- Print unique dash-stripped handles and resolve them in /peers
- Bound outbound sends: in-flight cap plus an absolute deadline
  (socket.setTimeout is an idle timer a dribbling peer resets)
- Defang whitespace-split and quote-glued envelope delimiter tokens
- Seed onHeldChange subscribers with the current held snapshot
- Skip POSIX-only socket-path tests on win32 like the sibling suites

* fix(cli): gate held-message announcements on the held set growing

Once the hold buffer is full, every further peer frame evicts the oldest
entry while arriving under a fresh id; announcing each of those appends a
history item and re-renders per frame — reintroducing one layer up the
unbounded growth the hold buffer's ceiling exists to prevent.

* fix(ipc): close round-6 Critical findings in the cross-session inbox

* fix(ipc): close round-7 Critical findings in the cross-session inbox

- defang envelope delimiters split by render-invisible separators the
  \s class misses, sharing flattenPeerLabel's strip class so the two
  cannot drift (R5-1)
- restore admission-failed queue submissions deferred until idle, the
  same recovery the /btw path uses, so a restored peer envelope cannot
  leak raw into the racing turn's mid-turn steer drain (R7-1)
- cap the accepted-message backlog symmetric to the held cap: the
  pre-wiring buffer and the post-wiring queue refuse once full, with an
  honest 'expired' receipt instead of unbounded socket-speed growth (R7-11)

* fix(ipc): close round-8 Critical findings in the cross-session inbox

- Refuse any msgId canonicalizing to `all` at the wire boundary and fold
  the /peers bulk keyword case, so a peer-chosen id can never alias the
  bulk action or trap a per-message decision (R8-1, R1-11, R1-27).
- Re-hold approved messages whose delivery fails instead of silently
  dropping them: decide() returns 'failed' and parks the entry back at
  its position, reevaluate() re-holds failed releases, and /peers reports
  the failure honestly (R8-2, R1-18).
- Drain accepted peer envelopes on a preprocessing-free path: the queue
  marks them peer, the drain submits them with the Teammate send type
  (which returns before slash/shell/@ handling), renders the one-line
  projection, and restores failed admissions as peer entries (R8-3).
- Close the envelope-defang class structurally: escape every `<` in peer
  content so no forged delimiter survives regardless of wedged invisibles
  or homoglyph spellings; the open-enumeration regex is deleted (R5-1).

Every guard is witnessed by new focused tests and verified with mutation
probes (each probe fails its witnesses when removed, passes restored).

* fix(ipc): close peer messaging lifecycle races

* feat(core): address local peer sessions from agent tools

* fix(core): make peer addresses unambiguous

* fix(core): reserve peer routing namespace

* fix(ipc): complete peer delivery feedback

* fix(ipc): harden peer session routing

* fix(cli): honor effective peer messaging gate

* fix(cli): report effective peer messaging state

* test(cli): teach the cross-session test doubles the new gate accessor

The interactive UI gates the peer inbox on
`config.isCrossSessionMessagingEnabled()` instead of the merged
`agents.crossSessionMessaging` setting, so every config double that
exercises `startInteractiveUI` must expose that accessor.

The shared `sessionRegistryConfigStub` in `llm.test.tsx` builds most of
those doubles, and the four `startInteractiveUI.test.tsx` cases that
assert the inbox binds need the enabled config rather than the default.

Fixes the 37 failing cli tests (`Tests 37 failed | 28548 passed`) in the
`Test (ubuntu-latest, Node 22.x)` lane; both files now pass 101/101.

* refactor(core): drop peer-receipts, superseded by peer-send on main

The send-side receipt correlation this module implemented shipped on main
as packages/core/src/ipc/peer-send.ts (#10158, step 3 of #8724), in a
strictly richer form: it canonicalizes message ids, models seven delivery
statuses with explicit legal-transition sets, bounds tracked sends at
MAX_TRACKED_SENDS, and reports the settled receipt with its age.

This copy had no production import site -- only its own test imported it --
so keeping it could only let it drift from the shipped version. Removing
both files leaves the branch as just the cross-session messaging gate
change it now describes.

No behavior change.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): gate every peer-inbox reader on --bare and --safe-mode

The only reader was isCrossSessionMessagingEnabled(settings.merged), which
cannot see the two session-level suppressions. A session started with --bare
or --safe-mode bound a UDS inbox and published its ipcPath into the
live-session registry, advertising itself as addressable from the modes that
exist to close cross-process surfaces.

On the daemon path it was worse than visible. sendToPeer reports `disabled`
only when the caller's own record lacks an ipcPath, so publishing one is also
what let a suppressed hosted session send -- while inbound was already
refused. An interactive TUI under the same flags bound nothing and sent
nothing; a hosted session did both.

Answer the question once, in the file whose doc comment already claims to be
the one place it is answered: crossSessionMessagingSuppression(config) reads
the two accessors Config already carries, and
isCrossSessionMessagingActive(settings, config) narrows the setting with it.
All four readers now ask that -- the interactive bind, the interactive
failure banner, /peers, and registerHostedSession.

/peers also stops hardcoding "--bare or --safe-mode" and names the cause
that actually fired, with the environment channel that reaches each, so a
session suppressed only by QWEN_CODE_SAFE_MODE recognises why.

Drops the ConfigParameters field and accessor an earlier shape of this branch
added. Config already stores both flags and loadCliConfig already passes them
in, so the parameter duplicated two booleans that were on the object handed
to every read site. Its fail-closed default was also what made the hosted
reader look unsafe to wire, which is why that reader was deferred rather than
fixed; reading the accessors removes both the parameter and the reason.

Each new case was mutation-verified and each mutation reverted: ignoring
config at the ACP gate, at the interactive bind, or at the banner branch
turns the corresponding cases red; deleting the /peers message arm turns both
of its cases red; dropping the getBareMode clause turns the bare-mode cases
red in three suites.

Refs #8724

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: record that --bare and --safe-mode suppress cross-session messaging

Five places stated the settings-only rule as a contract and would be wrong
after the gate change: the agents.crossSessionMessaging row, the two
--safe-mode inventories, the hosted-session registration paragraph, and the
SDK's inbox note. The row now carries the same "Ignored in --bare and
--safe-mode" clause its sibling general.outputStyle already has, and both
inventories list cross-session messaging alongside the other surfaces safe
mode closes.

The dated design record docs/design/2026-09-02-peer-inbox-reachability.md is
left as written -- it describes the state at its own date, and rewriting it
would falsify the record rather than update it.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): name both causes when a flag and the setting both turn messaging off

`/peers` answered with the startup suppression whenever one was in force, so
a session whose own `agents.crossSessionMessaging` was also off was told the
setting cannot matter, and was sent to a restart that leaves messaging off
with the surviving cause still unnamed. The scope remedy now leads and the
flag follows as a second cause when both fire; the single-cause text is
byte-identical to before.

Also qualify the SDK's universal reachability claim for the two flags in the
three copies that carry it (package README, the `endpoint.ts` file JSDoc and
the hand-maintained `docs/developers/sdk-typescript.md` mirror, whose inbox
bullet was still the pre-PR twin of the one this branch amended), and record
at the gate that an *inherited* `QWEN_CODE_SAFE_MODE` — unlike
`QWEN_CODE_SIMPLE`, which `spawnChannel.ts` scrubs from spawned `qwen --acp`
children — suppresses every session a daemon or editor hosts.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmujyzhfka1

---------

Co-authored-by: qqqys <qys177@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: yiliang114 <effortyliang@gmail.com>
2026-09-28 10:48:12 +00:00
Dragon
70edbf475e
feat(core): preserve prompt cache for deferred tools (#10410)
* feat(core): add deferred tool call bridge

* fix(core): update read-file image guidance regex to the two-step bridge wording (#10410)

* fix: resolve remaining merge conflicts from main merge (#10410)

* fix(core): address round-1 review for the deferred tool-call bridge (#10410)

Security/correctness:
- Re-check the execution allowlist against the resolved tool_call target in
  the scheduler (fork allowlist previously only saw the wrapper name).
- Skip the pending bridge TOOL_CALL start on abort so a cancelled call never
  emits a start event after its synthetic TOOL_RESULT.
- Route INVALID_TOOL_PARAMS bridge resolution errors through the
  validation-retry loop so repeated bad envelopes get the stop directive.
- Reject a hidden deferred target when tool_search is unregistered, and make
  the TOOL_NOT_REGISTERED remedy conditional on tool_search existing.
- Seed announcedMcpToolNames for eagerly revealed MCP tools so a later
  disconnect is still announced when the bridge is incomplete.
- buildDeferredToolsReminder returns null when the bridge is incomplete.

Docs (bridge-registered caveats, two-half reachability, DeepSeek removal,
schema regeneration, table cells, wording): settings.md, settingsSchema.ts +
regenerated settings.schema.json, sdk types.ts/README/sdk-typescript.md,
design doc, skills/types.ts, web-shell i18n.

Tests: allowlist re-check, abort ordering, retry directive, leader-only and
tool_search-absent rejections, revealed-tool exclusion, bridge availability
both directions, PM bridge gate, cancel-after-resolution rename, incomplete-
bridge reminder, MCP disconnect announcement, zoom hint wording.

* test(cli): model the complete bridge in the ACP tool_call routing test (#10410)

* test(integration): adapt sdk-mcp-server E2E to the deferred-tool bridge contract (#10410)

Under the ToolSearch + ToolCall bridge, tools fetched via tool_search
select: stay hidden from the model-facing declaration list so the
prompt-cache prefix remains stable (they are invoked through tool_call;
direct invocation by name still executes, as the resume test already
exercises). Flip the three fresh-session advertisement assertions from
'tool is advertised after select:' to 'tool remains hidden', matching
the contract documented in tool-search.ts. The resume-restoration test
is unchanged and already passes at the merge commit.

* fix(core): normalize the legacy deny gate for tool_call like the loop fallback (#10410)

The no-PermissionManager bridge deny gate matched deny entries exactly
(canonicalToolName is alias-resolution only — no lowercase/trim), but the
_schedule legacy-deny fallback it delegates to compares
excludedTool.toLowerCase().trim() === normalizedToolName. Config stores
permissions.deny entries verbatim, so an entry like 'Tool_Call' or
' tool_call ' slipped past the gate's exact compare: the envelope was
unwrapped and the fallback then checked the deny list only against the
resolved TARGET name — executing a call the same config denies when it
arrives as a direct tool_call (pre-diff behavior). Mirror the loop's
normalization on the gate so both paths deny identically (R2-1).

Adds three parameterized cases ('Tool_Call', ' tool_call ', 'TOOL_CALL')
extending the legacy-deny pin; removing the normalization turns all three
red while the exact-entry case stays green (verified). Full
coreToolScheduler.test.ts 395/395.

* fix(core): complete the bridge remedy list, eager-reveal disconnect tracking, and prune pinning (#10410)

Three fix-induced findings from the bridge rounds:

R1-15: the unreachable-tools warning's remedy clause now names
tools.disabled alongside deny rules and --exclude-tools, matching the
cause enumeration the PR documents in settingsSchema.ts; the warning test
pins the mention.

R1-28: an eager-reveal of an MCP tool (incomplete bridge) now also records
the name in announcedMcpToolNames. The seed alone only reaches that set via
rememberAnnouncedDeferredTools, which runs exclusively in startChat — so a
server registering after the initial startChat used to disconnect silently
and the model kept calling a dead server's tools. New test covers the
mid-session reveal, the disconnect announcement, and a reconnect/disconnect
flap announcing twice; removing the add turns it red (verified).

R1-18: new interleaved test pinning the batch-start prune as the only
mechanism that clears a tool_call-keyed retry counter across a successful
bridged execution (recording keys on the model-facing name, prune keys on
post-resolution names); disabling the prune fires RETRY LOOP DETECTED
prematurely and turns the test red (verified).

client.test.ts + coreToolScheduler.test.ts: 770/770 green.

* docs: qualify the bridge reachability claims to match the code (#10410)

Four fix-induced doc findings, applied together across all copies:

R1-12 (9 locations): 'the demoted tools are out of reach for that session'
is now qualified with the two escape hatches the code has — tools also
listed in tools.visible are declared upfront, and resumed sessions
re-declare demoted tools referenced by direct calls in the transcript
(consistent with client.ts's own 'would be false for this session'
comment).

R1-25 (settings.md tools.core/discoveryCommand/migration rows): 'stay
reachable through tool_search + tool_call' now carries the 'while both
bridge tools are registered' condition.

R1-9 (threshold, 3 copies): 'Set 0 to always keep deferred tools behind
the bridge' overstated — two reveal paths ignore the threshold; replaced
with the qualified wording naming resumed-history replay, tools.visible,
and the incomplete-bridge fallback.

R2-2 (4 locations): the unaffected/bypass lists now name tool_call next to
tool_search, matching isExemptFromEagerAllowList which exempts both
identically.

* test(integration): harden the bridge-contract E2E assertions (#10410)

R2-9: the flipped not.toContain declaration assertions now carry positive
controls on the same snapshot — the post-select list must contain both
alwaysLoad bridge tools (tool_search, tool_call) — so an emptied or
index-shifted declaration list can no longer pass them vacuously.

R2-15: the first test now scripts the fake model to invoke the selected
tools through tool_call envelopes (the documented post-select path),
exercising resolveDeferredToolCall end-to-end through query(); results are
paired by the model-facing name ('tool_call') with content checks, since
the scheduler keeps modelFacingName on the wire. Reverting the script to
direct-by-name calls would remove the suite's only end-to-end signal for a
broken invocation half.

typecheck -p integration-tests clean.

* chore(core): address wenshao verification notes on the bridge (#10410)

1. tool-search.ts:326 — Prettier-format the schemaBlocks map (slipped
   through CI because the format step runs --write, not --check).
2. tool-call.test.ts — pin the recursive-bridge guard by its message
   ('cannot invoke bridge tool'), not just errorType: the downstream
   isDeferredAndHidden rejection returns the same INVALID_TOOL_PARAMS, so
   the old errorType-only assertion stayed green with the guard deleted.
   Mutation check confirms the strengthened test now kills that mutant
   (2 failed with the guard removed, 12/12 intact).

* fix(core): enforce the subagent/teammate tool-exclusion set in the tool_call bridge (#10410)

R4-1 (review round 4): the tool_call bridge bypassed the subagent/teammate
tool-exclusion set. EXCLUDED_TOOLS_FOR_SUBAGENTS / EXCLUDED_TOOLS_FOR_TEAMMATES
were enforced only at declaration level (prepareTools isExcluded), but the
bridge makes invocation independent of declaration without carrying the
exclusion set over — resolveDeferredToolCall enforced only the plan-lifecycle
and leader-only policies, tool_search had no exclusion filter, and the
scheduler's post-unwrap re-checks never consulted the set. A wildcard/
general-purpose subagent (or teammate) could therefore discover (tool_search)
and execute (tool_call) control-plane tools it must not reach (team_delete,
cron_*, workflow, send_message, ...); the probe executed team_delete
end-to-end through the bridge from a subagent context. The merge base
rejected the same trigger (undeclared names fail the declaredToolNames gate),
so the bridge introduced the path.

Fix: enforce the same context-aware exclusion set in resolveDeferredToolCall,
after the plan-lifecycle/leader-only checks (so those keep their specific
messages). To avoid a circular import (agent-core imports coreToolScheduler,
which imports tool-call), the exclusion sets and getExcludedToolsForCurrentContext
moved from agent-core.ts to subagent-plan-tool-policy.ts (already imported by
tool-call.ts); agent-core.ts re-exports them for existing consumers. The check
is guarded by isSubagentLikeExecutionContext so the leader context is not gated.

Adds a mutation-checked test (rejects an exclusion-set target team_delete /
workflow bridged from a subagent context): removing the exclusion check turns
both red. tool-call.test.ts 14/14; tool-search + coreToolScheduler 447;
subagent-plan-tool-policy 6; agent-core + background-agent-resume 99; core
tsc --noEmit clean.

* fix: remove unused SUBAGENT_PLAN_LIFECYCLE_TOOLS import from agent-core (TS6133)

The exclusion sets that spread SUBAGENT_PLAN_LIFECYCLE_TOOLS moved to
subagent-plan-tool-policy.ts so the tool_call bridge can enforce them
without a circular import; the import in agent-core.ts was left behind
and tsc --build flags it as unused (TS6133).

* fix(core): address round-5 review on the tool_call bridge exclusion (#10410)

R4-1 follow-up (Critical): the flat exclusion denied AgentTool through the
bridge even while maxSubagentDepth permitted the spawn — prepareTools
depth-gates AgentTool (re-admits it while spawnBlockReason === null) but the
bridge consumed the raw set. Add the shared predicate
isToolExcludedForCurrentContext (subagent-plan-tool-policy.ts) that carries
the depth-gated re-admission, consumed by BOTH prepareTools and
resolveDeferredToolCall so the layers cannot drift; the scheduler threads the
real configured maxSubagentDepth through. Without the configured depth the
bridge fails closed on AgentTool (the raw-set floor). The predicate is
deliberately ungated so prepareTools keeps its frameless fail-closed
contract; the bridge and tool_search apply their own isSubagentLikeExecution
context gate, leaving the leader session untouched.

R5-1: mirror the exclusion on the discovery side — collectCandidates and the
select: blocked predicate now drop context-excluded tools, so subagents are
no longer advertised the schemas of tools they are forbidden to invoke
(schema leak + wasted turns). Leader re-inspection stays unrestricted.

R5-2: move the plan-lifecycle / leader-only / exclusion checks ahead of the
isDeferredAndHidden gate (relative order kept). Real excluded tools are
frequently not deferred (workflow/team_delete/todo_write shouldDefer=false,
enter_plan_mode constructed shouldDefer=false), so the old gate order
misrouted them into the factually-wrong 'already visible — call it directly'
INVALID_TOOL_PARAMS denial and the scheduler's malformed-envelope retry
counting.

R5-3..R5-6 test pins: SEND_MESSAGE subagent denial + teammate resolve/deny
cases discriminate the context-aware selector; the plan-tool denial pins the
dedicated message; allow-side cases pin the context gate (leader and
in-frame non-excluded resolution); the legacy alias case pins canonical-name
membership keying. Fixtures now use the real non-deferred shape.

Mutation-verified: nine mutants (check removed, selector swapped both ways,
plan check removed, envelope-name keying, flat AgentTool, bridge gate
removed, blanket in-frame denial, tool-search filter removed) all turn the
new tests red; suites green (tool-call 22, tool-search 56, policy 6,
agent-core 49, coreToolScheduler 396).

* fix(core): close the two round-5 deferred bridge items (#10410)

Case-insensitive resolution: tool_search's select: resolves requested names
case-insensitively, but the tool_call half resolved them exactly, so a schema
reviewed as e.g. Read_File was not callable through the bridge. The
invocation half now falls back to a case-insensitive match against the
registered names (exact-case names keep the fast path), and the fallback feeds
the recursive bridge guard, so casing cannot dodge it either.

Remedy pin: 'rejects an unknown deferred target' now pins the present-side
remedy ('Run tool_search again...'); the absent-side constant was already
pinned by the sibling case, so both remedy branches are mutation-checked.

Mutation-verified: removing the fallback turns 2 tests red; dropping the
remedy suffix turns 1 red; suite green (24).

* fix(core): address round-6 review — disallowedTools re-check and stale DeepSeek text (#10410)

R6-8 (Critical): the bridge resolved around the declaration list, so a
per-agent disallowedTools blocklist — enforced only at declaration level in
prepareTools() — did not stop a bridged call: a subagent configured with
disallowedTools: ['mcp__slack'] could tool_call mcp__slack__post_message to
execution, where the merge base synthesized 'Tool not found'. Fold the
blocklist into AgentCore.isToolExecutionAllowed with prepareTools' exact
match semantics (MCP server-level patterns via matchesMcpPattern, exact
match otherwise); the predicate already feeds both the direct-call gate and
the scheduler's bridge target re-check, so one fold closes both. The
scheduler's bridge denial message now covers allowlist and blocklist.
Mutation check: removing the fold turns the new agent-core test red.
Discovery-side mirror (tool_search candidates) is deliberately deferred:
it needs the blocklist threaded into the agent context frame across all
launch sites; invocation is fail-closed either way (ruled in the thread).

R6-1..R6-7: drop the stale 'automatic opt-out for DeepSeek models'
references from every string this PR writes (settingsSchema + generated
vscode schema, SDK types/README, user/dev docs) — this PR's config change
removes that opt-out, so the text must not keep citing it; also drop the
DeepSeek disable advice from the tools.toolSearch.enabled doc row
(round-6 deferred list item).

Triage follow-up (wenshao): the ACP frontend now threads the configured
maxSubagentDepth through resolveDeferredToolCall, matching the terminal
scheduler and tool_search so the depth-gated AgentTool re-admission is
consistent across all three frontends (omitting it failed closed, but the
corner case now agrees everywhere).

Verified: core typecheck clean; agent-core 50, tool-call 24, tool-search 56,
coreToolScheduler 396 — all green; blocklist mutant red.

* test(cli): give the Session test config mock getMaxSubagentDepth (#10410)

The ACP bridge now threads the configured depth through
resolveDeferredToolCall; the shared partial Config mock in Session.test.ts
cast 'as unknown as Config' lacked the method, so 'routes tool_call through
a hidden deferred tool in ACP' crashed with a TypeError. Mocks claiming the
Config interface should carry the methods the code under test calls —
verified the target test green locally.

* fix(docs/core): address round-7 review — deny-vs-remove semantics, tie-break parity, precedence pin (#10410)

R7-1 (Critical): the rewritten tools.core row claimed permissions.deny
'removes a tool outright', but deny never removes MCP tools from the
registry — ToolRegistry.registerTool gates only on disabledTools and
mcp-client registration never consults the PermissionManager. Restore the
split semantic (deny blocks calls; tools.disabled / per-server excludeTools
remove from the registry), matching the SDK README and the
permissions.deny row's own wording. The pasted migration-table row at :492
gets the same wording fix AND its structure repaired (it was a 5-cell
settings-table row inside the 3-column migration table; now a proper
Legacy/Equivalent/Notes row).

R7-14: the invocation half's case-insensitive fallback now uses last-match
like the discovery half's lowerIndex (Map keyed by lowercase name, last
registered variant wins), so both bridge halves resolve the same tool for a
case-variant request. Mutation check: first-match turns the new tie-break
test red.

R7-13: new test composing executionAllowedTools with disallowedTools — the
blocklist wins for an allowlisted-and-blocklisted bridged target. Mutation
check: removing the blocklist fold turns both blocklist tests red.

Verified: tool-call 25 + agent-core 51 green; core typecheck clean.

* fix: align deferred tool case resolution

* fix(core): preserve bridged tool identities

* chore(docs): minimize formatting churn

* fix(core): handle deferred tool edge cases

* fix(core): close deferred bridge gaps

* fix(core): fail closed on malformed bridged classifier entries, correct bridge docs (#10410)

- toAutoClassifierInput coerces a missing/non-string envelope name and
  fails closed on the name alone, so a malformed bridged history entry
  can no longer throw into the caller's raw-args fallback and leak
  unredacted arguments into the AUTO classifier prompt (R16-3).
- The prefix-stability guarantee no longer scopes mid-session
  declaration refreshes to resumed sessions: any tool-set refresh
  re-declares a still-hidden deferred tool that live history calls
  directly. Swept through settings.md, settingsSchema.ts (+ regenerated
  schema), i18n zh, SDK README/JSDoc, and the tool_search description
  (R16-2).
- SDK docs now state that the incomplete-bridge warning goes to the CLI
  child's stderr, which the SDK discards unless debug is enabled (R16-4).
- Correct the collectCandidates mutation comment: the keyword assertion
  pins the layered collectCandidates + returnSchemas filter pair, not
  the single filter (verify-report F2).

* fix(core): redact orphaned tool call envelopes

* docs(sub-agents): clarify deferred-bridge exception to the tools allowlist (#10410)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): preserve Goal provenance for bridged duplicates

* fix(core): guard canonical tool-name lookup

* fix(core): keep bridged envelope payload in loop-detection repeat key

- getLoopDetectionToolCall substituted {} when a bridged envelope's
  `arguments` was not a plain object (e.g. a JSON string awaiting
  SchemaValidator repair), collapsing every such call to the same
  target onto one repeat key; the always-on consecutive-identical
  guard then halted turns of distinct calls. Keep the envelope itself
  as the key material so distinct payloads stay distinct.
- Correct the deferred-tool refresh enumeration in all doc copies:
  subagent completion never triggers a declaration refresh; the real
  triggers are resume, MCP discovery, the first plan-mode entry in a
  session, and subagent definition changes. Regenerated
  settings.schema.json from the settings source.

* fix(core): bound nested tool_call classifier projection, correct toolSearch cache text (#10410)

* fix: address deferred bridge review findings

* feat(core): default tools.toolSearch.threshold to 0 (always-defer) (#10410)

The preload gate shipped on by default for one reason: revealing a
deferred tool rewrote the function-declaration list at the front of the
prompt prefix, so a single tool_search load busted the whole KV cache.
With a small deferred set the token savings were negligible, which made
deferral a net loss.

The tool_search + tool_call bridge removes that premise — a bridge
reveal never touches the declaration list — so always-defer is now
affordable and matches Claude Code's ENABLE_TOOL_SEARCH default. What
preloading still buys is the one tool_search round trip before a
deferred tool's first use: a per-tool one-off, not the whole-session
compounding cost a cache bust was.

The gate itself is unchanged — still session-start only, still one
budget over bundled and MCP deferred tools alike, still all-or-nothing —
so raising the threshold opts back in exactly as before. Safe mode and
bare mode already forced 0, so that path is untouched. Regenerated
settings.schema.json from the settings source and corrected both design
docs, whose cache-bust rationale the bridge supersedes.

* fix(core): preserve deferred fork tools

* fix(core): harden bridge history consumers against malformed envelopes

- microcompact: guard non-string tool names from unvalidated history, and
  record an unparseable-call sentinel so a reused call id fails the
  identity unanimity check instead of compacting on partial evidence
- goals: classify Goal bookkeeping provenance case-insensitively so a
  case-variant bridged get_goal/update_goal result cannot enter the
  evidence catalog as an external fact
- client: seed recentCompletedToolNames from the resolved bridge target
  on resume, matching what the live path records
- docs: record the missing presentation-mark/schema-fingerprint gate as a
  known limitation pending the #11321 scope decision

* test(core): raise code-mode waitFor deadlines past host boot time

The verification gate rejected the previous round on three failures that
never reached an assertion: the code-mode host spawns a tsx child whose
boot alone measures ~4.5s on the oversubscribed runners, while
exec-context-tools waited the vi.waitFor default (1s) for the first
nested dispatch and two scheduler tests waited 4s for nested dispatch /
awaiting_approval. Raise the three inner deadlines to 10s and the two
containing test timeouts to 20s. No assertion changes.

* fix(core): align bridge wording and gates with the two-half tool bridge (#10410)

- Reword the hidden-deferred-tool sentence (shared by the Workflow tool
  description, its failure hint, and the workflow-keyword reminder) to the
  bridge flow: review the schema with tool_search, then invoke with
  tool_call; the old 'reveal with ToolSearch first' wording instructed a
  mechanism tool_search no longer performs (R27-1).
- Gate the workflow-authoring route and the keyword reminder on BOTH
  bridge halves, so a tool_search-only session inlines the reference and
  drops the steering instead of pointing at a route that cannot invoke
  (R27-2).
- Enforce the registry's isToolDeclared capability gate in
  resolveDeferredToolCall, matching tool_search's select: refusal, so
  tool_call cannot execute a registered-but-undeclared target such as
  propose_goal outside a goal-proposal turn (R27-3).
- Carry the parent agent's per-agent disallowedTools blocklist through
  the agent ALS frame into a fork's execution allowlist and toolConfig,
  so the union with the live registry cannot re-admit a tool the parent
  was configured never to reach (R24-1).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): enforce configured tool allowlists across bridge

* fix(core): persist fork disallowedTools blocklist across background resume (#10410)

* fix(core): admit exec under CodeModeOnly when configured tools omit it (#10410)

In code mode the registry declares exec unconditionally, so folding a
finite toolConfig.tools list that omits exec into the execution
allowlist refused the only declared tool and degraded the agent to
text-only. Carve exec out in the executionAllowedTools === undefined
branch, mirroring the existing carve-out below; the blocklist check
still runs first, so disallowedTools: ['exec'] keeps winning.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): align deferred bridge policy and redaction

* fix(core): preserve deferred fork tool access

* fix(core): restore deferred bridge in containers

* fix(core): preserve deferred bridge policy and accounting

* fix(core): distinguish deferred bridge outcomes on resume

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
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 Code Autofix <qwen-code-autofix[bot]@users.noreply.github.com>
Co-authored-by: yiliang114 <jinjing.zzj@gmail.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-09-20 13:17:49 +00:00
qqqys
f2f10e7b39
feat(settings): turn cross-session messaging on by default (#11840)
* feat(settings): turn cross-session messaging on by default

The switch has been opt-in since the feature landed, which was the right
posture while the pieces that make a reachable session safe were still
arriving one pull request at a time: authenticated inboxes, receipts and
held-message expiry, review-class parity with tighten-only repository
settings, trusted controller tokens, admission control, the registry
contract, daemon-managed sessions and the SDK peer endpoint. With all of
that in place, off by default no longer protects anything. It only means
a voice front-end joining through the SDK cannot see a session whose
user never found the setting, and two sessions in one repository cannot
tell each other a build finished.

Flip the schema default to true. Merged settings carry only what some
scope actually wrote, so the schema default never reached the readers of
this key: every one of them saw `undefined` and compared it to `true`.
The four readers now ask one helper, which answers on for `true` and for
an unset key, and off for `false` and for anything it does not recognize
— a value the reader cannot interpret must not open a socket. The
workspace tighten-only ranking follows: unset now ranks with `true`, so a
repository's `false` is a tightening and is kept, and a workspace `true`
repeats the default and is dropped without a warning.

The words change with the default. `/peers` on a session with no inbox
distinguishes "turned off in settings" from "the inbox could not start"
instead of telling the user to enable a setting that is already on. On a
platform without an inbox transport, the startup failure line is shown
only to a user who turned the switch on by hand; a bind that failed where
it should have worked is still reported to everyone, because that session
is unreachable and this line is the only place its user learns it. The
controller-token hint, the send_message error, the settings reference, the
SDK guides and the messaging section of the commands page now say what
turning the feature off looks like.

Suites that stage a session through partial mocks turn the switch off in
their settings, or complete the config mock where registration now runs;
the daemon-session and interactive-startup suites are where unset-means-on
is exercised.

Part of #10925

* test(cli): turn messaging off in suites that stage startup or ACP sessions through partial mocks

Two suites the default flip reaches that the first commit missed. The
entry-point startup test counts exit-cleanup registrations, and with
messaging on the inbox arms a third. The ACP worktree-restore suite loads
sessions through an exhaustive core mock, and with messaging on each
loaded session registers itself through calls that mock does not stage.

Neither suite is about messaging, so both say off in their settings; the
on-by-default path is exercised in startInteractiveUI.test and the
daemon-session suite of acpAgent.test.

* refactor(cli): name the opted-in-by-hand check next to the enabled helper

The startup failure notice asks two questions three lines apart: whether
messaging is on, which the shared helper answers and an unset key answers
yes, and whether the user wrote `true` themselves, which decides whether
an unsupported platform is worth mentioning. The second read the raw
setting inline, next to a helper documented as the one place the first
question is answered, and looked like a missed migration. Bind it to a
name so the two questions read as two.

* fix(cli): address review on the default-on switch: scope-aware notices, no futile rebinds, honest docs

Review round one on the default flip. Thirteen suggestions, all taken.

Two readers now ask a question merged settings cannot answer, so both
move next to `isCrossSessionMessagingEnabled` in peerMessaging/enabled.ts:

- Whether a person opted in. The startup notice for a platform with no
  inbox transport is said only to someone who wrote `true`; it read the
  merged value, which also carries an operator's system-defaults file, so
  a fleet default counted as a hand opt-in. It now reads the user scope,
  and the workspace scope when it is trusted and active.
- Which scope turned messaging off. `/peers` told everyone their settings
  said `false` and to set it to true. Anything but `true` or unset reads
  as off, so it no longer states the value, and it names the scope with a
  remedy that works there: a workspace `false` outranks a user `true` (a
  workspace may only tighten), System overrides everything, and a user
  `true` does override system defaults.

`/peers` on a platform with no inbox transport now says so as
information. It was error-styled and repeated the failure's advice to
disable a setting most of those users never enabled.

An ACP process no longer retries the bind for every hosted session after
`unsupported_platform`. That refusal holds for every candidate path, so
the settled null is kept; every other cause still retries.

The design doc, in both languages, names the two senders delivered
without review that the default now reaches: a process the session
starts, which inherits the child token, and a same-user process that
claims the receiving session's review class, which nothing
authenticates. It records why they are documented rather than gated.
The setting's description, the settings reference and the VS Code schema
carry the same clause, and the Windows paragraph says what changes there.
`commands.md` no longer promises a warning for a workspace `true` that
merely repeats the default.

Tests pin what was unpinned: a daemon-hosted session with the key unset
registers; one bind attempt under `unsupported_platform` and a retry
under any other cause; the warning when a workspace `true` would reopen a
switch the user turned off; the operator-default startup case; the scoped
off messages. Negative assertions that stopped being able to fail are
repointed, and stale "off by default" test names and comments are gone.
2026-09-15 11:46:41 +00:00
qqqys
af4dece3a7
feat(sdk): add a peer endpoint so a program outside Qwen Code can join cross-session messaging (#11560)
* feat(sdk): add a peer endpoint so a program outside Qwen Code can join cross-session messaging

`@qwen-code/sdk/peer` is an implementation of the cross-session protocol
for a program that is not itself a Qwen Code session — a voice
front-end, a relay, a build watcher. `PeerEndpoint.start({ name })`
binds an inbox and publishes a session record, so the program is listed
by `qwen sessions ps` and by every session's `list_agents`; `list()` and
`send()` address sessions by name; `onMessage` receives what they send;
receipts are applied as state transitions and can be awaited.

It is written from the protocol page with Node's own modules alone and
shares no code with Qwen Code. That makes it the page's first
independent implementation, and a conformance suite runs it against
Qwen Code's own registry, inbox, directory and send path in both
directions: listing and reaching each other, frames parsed and answered
each way, a folded drop receipt settling a burst, a user-minted
controller token recognised and a forged one not, identical verdicts on
hostile lines, record files and names, and every published limit pinned
to Qwen Code's constants.

The endpoint takes the shared `<pid>.json` when it is free and a minted
`<pid>-<8 hex>.json` when it is held, never replacing a record it cannot
prove dead. Its inbox accepts only its own token, it never deletes
records or sockets other processes wrote, and trust comes only from a
controller token. On Linux it refuses to publish a record without a
start token and PID namespace.

Implementing the page found two rules it left implicit, now stated in
"Writing your own record": `procStart` and `pidNs` are required on
Linux, and a held `<pid>.json` means writing the minted name instead.

Separate Node-only subpath, kept out of the browser-bundled entries;
the build checks its size and that it carries no runtime dependency.

* fix(sdk): present the controller token only on sends marked controller

Review follow-ups on the peer endpoint.

The controller token was presented on every send, so any process that
published a record under a name the program then addressed read the
plaintext of a credential Qwen Code only ever stores hashed. A send now
presents it only when marked `controller: true`; unmarked sends — from an
endpoint holding a token too — present the recipient's own token. On a
marked send, two records for one session id and name are ambiguous rather
than collapsed to the newer, so a copied record cannot take the grant. A
malformed `controllerToken` (empty, no `qpc_` prefix) is rejected at start
with `invalid-controller-token`, as is a marked send without one.

Other fixes:
- Two `start()` calls in one process raced for `<pid>.json` and one
  record was lost; registrations inside a process now run one at a time.
- `send()` checks `closed` again after the directory read, so a send
  overlapping `close()` rejects instead of writing a dead reply address.
- `ambiguous.matches` holds only addresses that select one session.
- Receipt `reason` is flattened and bounded like every other label.
- The local concurrent-send ceiling is marked `local` and described as
  this program's limit, not as a busy peer.
- An explicit `socketPath` no longer re-permissions a directory the call
  did not create.
- `probePeerSocket` is renamed `probePeerSocketVerdict`, matching core;
  the status list is the single source of the status type.
- `flattenPeerLabel` matches control and format characters by Unicode
  category and caps in code points, in core as well, so the two
  implementations still agree.
- The build checks every emitted peer declaration, from a module with
  tests of its own; the surface test is compiled by the typecheck and
  matches imports as path segments.

Docs: `list_agents` and `send_message` need `agents.crossSessionMessaging`
on the receiving session; delivery without review also follows a matching
`fromMode`, and `agents.crossSessionInbound` outranks a controller grant;
a controller send to another endpoint is dropped unread; the endpoint's
inbox applies none of the protocol page's §6; the `socketPath` sibling
fallback; and the protocol page gives separate reasons for `pidNs` and
`procStart`. The design doc no longer says the default entry ships to
browsers.

Tests pin each of these, plus the paths the first round left unexercised:
ledger and answered-id eviction past their bounds, start rollback, the
Linux identity guard, registry directory tightening and read errors, the
over-long line with the default deadline, the symlinked and over-long
socket paths, prompt close, Windows, and a live PID with a mismatched
start token.
2026-09-10 22:13:39 +00:00
易良
f86098363d
test(core): pin tools.eager Object.prototype-key tolerance and correct related docs (#10479)
Some checks are pending
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
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
Update ECS Runner Qwen / Resolve version (push) Waiting to run
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-1 (push) Blocked by required conditions
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-4 (push) Blocked by required conditions
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-5 (push) Blocked by required conditions
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-2 (push) Blocked by required conditions
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-3 (push) Blocked by required conditions
* test(core): pin tools.eager Object.prototype-key tolerance and correct related docs

Item 1 of #10400 (a tools.eager entry named after an Object.prototype
key crashing PermissionManager.initialize) was already fixed before
#10098 merged: resolveToolName only reads own properties of the alias
table and the crashing unknown-entry loop was removed. Pin that contract
with regression tests so a reintroduction fails CI:

- initialize() tolerates constructor/toString/valueOf/... eager entries
- resolveToolName returns Object.prototype-keyed names unchanged
- config.test.ts: add the third LS x tools.eager cell (enabled and
  covered by the list -> registered eagerly)

Also correct the doc inaccuracies tracked in the issue:

- settings.md: permissions.deny blocks MCP tool calls at runtime but
  does not remove them from the registry (tools.disabled / the
  per-server excludeTools filter does)
- ToolSearch-absent caveat (5 copies): note the resumed-session history
  re-reveal and tools.visible carve-outs
- sdk-typescript docs + README: note the tools.eager exempt families

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): cover visible permission-deferred tools

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-31 17:46:54 +00:00
易良
36de6ebef9
fix(core): decouple permissions.allow from tool registration via tools.eager (#10075) (#10098)
* fix(core): decouple permissions.allow from tool registration via tools.eager (#10075)

`permissions.allow` was overloaded in #9829 to double as a registry-level
allowlist, so configuring it purely for auto-approval silently reshaped the
model's toolset — the #10075 report (`edit` / `write_file` vanishing in
0.22.1). #10082 softened the symptom (uncovered tools became `deferred`
rather than unregistered) but kept the conflation: one key still decided both
"don't ask me about this" and "don't send this schema".

Split the two jobs instead of trading them off:

- `permissions.allow` (and `ask`) go back to PURE auto-approval. They never
  remove, demote, or hide a tool. This is the actual fix for #10075.
- A dedicated `settings.tools.eager` allowlist now drives the eager/deferred
  split, preserving #9827: when set, only the named tools' schemas ride in
  the initial model request and everything else is deferred — still
  registered, still in `/tools`, still loadable via `tool_search`.

#10082's three-state registration machinery is kept as-is; only its input
changes, so no multi-layer deferred wiring is reverted. #9829's rule-parser
alias map is likewise untouched — the rollback is scoped to the one part of
that PR which caused the regression.

Also fixed along the way:

- Command-discovered tools (`tools.discoveryCommand`) were hard-DROPPED when
  the allowlist did not cover them, which reproduced #10075's silent
  disappearance for that registration path. They now defer like built-ins.
- `isLsToolEnabled()` no longer treats permission rules as a registration
  signal (reverting the #9829 side-effect).
- `/tools` marks deferred tools "(on demand)" with a footnote, so an active
  `tools.eager` list is visible instead of silently reshaping the toolset —
  the user-facing half of the original report.

Migration: users who set `permissions.allow` for schema-shrinking (llama.cpp,
Docker Model Runner) move that list to `tools.eager`; the shape and entries
are unchanged. `tools.core` no longer migrates to `permissions.allow`.

* fix(core): make an explicitly empty tools.eager list active, not ignored

`tools.eager: []` collapsed into "unset", so an empty list silently meant
"no restriction" — the opposite of the convention #10080 establishes for
the sibling `tools.core` key, where an explicit `[]` is an active-but-empty
allowlist and only `undefined` means unrestricted. Two knobs in the same
`tools.*` namespace reading empty-array backwards from one another is a
trap, and #10138 shows how expensive that divergence gets once transports
start dropping the empty case.

Activation is now `Array.isArray()`, matching #10080. An explicitly empty
`tools.eager` defers every non-exempt tool, which is a gentler answer to
the constrained-decoding failures behind #10065 than `tools.core: []`:
the eager request carries almost no tool schemas, but nothing is disabled
— every tool stays registered and reachable via `tool_search`.

Two supporting changes were needed to make the empty case reachable at all,
the same class of bug #10138 reports for coreTools:

- `Config.getEagerTools()` returns `readonly string[] | undefined` instead
  of collapsing an absent list to `[]`.
- The CLI guards with `Array.isArray(settings.tools?.eager)` before calling
  `normalizeDisabledToolList`, which maps `undefined` to `[]` and would
  otherwise erase the distinction on the way into core.

Malformed-only lists (`['', 'Bash(unbalanced']`) now leave the allowlist
active but matching nothing, so everything defers. Deferring more than
intended is recoverable — ToolSearch still reaches every tool — whereas
ignoring a configured list resends exactly the schemas the user asked to
keep out.

* docs: qualify schema-shrink advice and registration claims (#10075)

Address review findings R1-2, R1-4, R1-7, R1-13: state that coreTools
is the only allowlist-style registration gate (whole-tool deny/exclude
rules and tools.disabled also deregister), qualify the schema-shrink
advice to whole-tool rules with MCP tools exempt, and correct the
deprecated tools.core migration guidance (permissions.allow is pure
auto-approval and cannot reproduce the fail-closed allowlist
restriction).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): cover the two behaviours this PR changed but left unpinned

R1-17 (adapted) — the `#9827` test sweep removed the only guard that
`Config.initialize()` tolerates non-string entries in a settings array.
The path that guard protected is gone with `isLsToolEnabled`'s revert, so
it is re-pointed at the equivalent new surface: settings load performs no
element-type validation, so `tools.eager` holding `null` or a number must
be filtered rather than crash registry construction.

R1-18 — `isLsToolEnabled()`'s reverted behaviour had no test. The eight
deleted tests only covered the positive allowlist path that no longer
exists; nothing pinned the negative. Added a test asserting that
`permissions.allow` / `permissions.ask` coverage no longer opts
`list_directory` into registration, so only `tools.listDirectory.enabled`
or `coreTools` can open that gate.

The docs findings from the same review (R1-1/R1-2/R1-4/R1-5/R1-12) are
already fixed by e3e2fcebd1 on this branch, which also covers the
specifier-rule and MCP deny-exemption nuances; nothing further is needed
there.

Deliberately not addressed: R1-15/R1-16 concern an integration-test file
this revision does not touch. R1-19 (scheduler if/else ordering) and R1-22
(`removePersistentRule` coverage) are pre-existing gaps in files #10080
also edits, so adding tests there now maximises the conflict between the
two PRs for marginal gain — better done once both have landed.

* fix(core): complement review round — missing schema, stale CLI tests, shim delegation witnesses (#10075)

Complements e3e2fcebd / 2620d9dec (docs qualifications and the R1-17/R1-18
witnesses):

- Regenerate settings.schema.json (the tools.eager entry was missing, which
  failed the "Check settings schema is up-to-date" CI gate).
- Rewrite the stale loadCliConfig "registry allowlist wiring" describe:
  the PR removed Config.getRegistryAllowList(), leaving five assertions
  calling a method that no longer exists. Re-point them at the tools.eager
  wiring, including the explicitly-empty-list case.
- Restore shim delegation tests for isToolDisabledByCoreToolsAllowList and
  getToolRegistrationStatus on both scoped PermissionManager shims (R1-21).
- Re-add the coreTools x permissions.allow combination witness (R1-23) and
  the ask-never-affects-registration witness (R1-25).
- Freshen stale "permissions.allow registry allowlist" comments now that
  tools.eager owns the demotion (tool-registry.ts, agent-core.ts,
  tool-registry.test.ts, config.test.ts) and rename the integration-test
  describe that still carried the old framing.

* fix(test): align the #10075 integration suite with the decoupled semantics

The suite still encoded the intermediate demote-via-permissions.allow
design: it asserted uncovered tools stay OUT of the eager request under a
bare permissions.allow configuration. Under the final decoupling
permissions.allow never gates the registry, so without tools.eager the
uncovered tools ride in the eager request — the no-AK gate failed on
'excluded schemas' expectations this revision no longer satisfies.

Rewrite the two witnesses:
- bare permissions.allow: uncovered tools stay registered, ADVERTISED in
  the eager request, and execute through the normal approval flow.
- tools.eager set: unlisted tools are deferred (absent from the eager
  request) yet tool_search still discovers and loads them — the #9827
  schema-shrink now pinned to its real knob.

* fix(core): preserve deferred discovered tools across registries

* fix(cli): let tools.eager replace across scopes

* fix(core): preserve tools.eager without tool search

* fix(core): report the two states where tools.eager quietly changes shape

Neither state is wrong to be in — both are just invisible today, and an
invisible reshaping of the toolset is what #10075 was reported for.

- No ToolSearch. Holding demoted tools back (rather than revealing them)
  keeps the allowlist honest, but with `tools.toolSearch.enabled: false`,
  a `tool_search` deny rule, or the automatic DeepSeek opt-out there is
  nothing left to load them on demand: they are unreachable until restart
  while still listed in `/tools`. Warn once, naming the tools and the ways
  out.

- Unusable entries. A misspelt `tools.eager` entry is dropped per-entry and
  can narrow the eager set to nothing, deferring the whole toolset. Keeping
  the list active is right — ignoring it would resend the schemas the user
  asked to withhold — but nothing said which entries fell out. Log them,
  with how many survived.

* fix(cli): stop /tools promising a tool_search load that cannot happen

The "(on demand)" footnote tells the user the model "loads them via
tool_search when needed". When `tool_search` is not registered that is
false, and it is false in exactly the session where the distinction bites:
tools held back by `tools.eager` have no loading path left. Pass the
registry's answer through to the view and say so instead.

* docs: document the tools.eager/tool_search pairing, retire two stale claims

- settings.md, settingsSchema and the VS Code schema now state what happens
  when ToolSearch is off (schemas still withheld, nothing left to load them
  back) and that unusable entries are dropped with a warning while the rest
  of the list stays active. Both were discoverable only by reading the code.
- `preloadDeferredToolsWithinBudget` still credited the demotion to the
  `permissions.allow` registry allowlist; every other comment in that file
  moved to `tools.eager` when the key changed.
- `migrateLegacyPermissions` is dead code (no call site anywhere) whose
  `tools.core` → `permissions.allow` arm encodes exactly the conflation this
  PR removes. Say so at the definition, so wiring it up later cannot quietly
  delete a user's allowlist and replace it with a no-op.

* fix(core): resolve registration-gate PM through getPermissionManager so scoped agent shims participate (#10075)

registerLazy and registerImageGenerationTool read the permissionManager field, which on Object.create-derived configs (skill-review / managed-memory agent shims installed via deriveConfig) resolves through the prototype chain to the base manager — bypassing the getPermissionManager override. With an active tools.eager allowlist omitting the file tools, the base manager demoted them to deferred and prepareTools stripped them from the forked agent's explicit tool list.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(settings): scope the tools.core fail-closed promise to the core tool set (#10075)

isToolDisabledByCoreToolsAllowList only disables names in the hardcoded CORE_TOOLS set; built-ins outside it (agent, skill, plan-mode lifecycle, goal tools, task_stop, send_message, list_agents, tool_search, MCP) bypass the allowlist by design. The row claimed 'all tools not in the list are disabled'.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(sdk): caveat the tool_search loadability promise for sessions without ToolSearch (#10075)

When ToolSearch is not registered (tools.toolSearch.enabled: false, a tool_search deny rule, or the automatic DeepSeek opt-out), resolveDeferredToolsForReminder withholds every permission-deferred tool for the whole session. Mirror the settings-schema caveat in the coreTools and allowedTools JSDoc blocks.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): surface tools.eager warnings on the console in default runs (#10075)

The dropped-entries and unreachable-tools warnings were emitted through debugLogger.warn, which writes nothing unless QWEN_DEBUG_LOG_FILE is set — the CLI only auto-sets that in --debug mode, so both warnings were invisible in default runs while the docs promise them. Emit them via console.warn, matching the resolveCronRecurringMaxAgeDays precedent for operator-facing breadcrumbs, and re-pin the existing tests to the visible channel.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): exempt history-revealed tools from the tools.eager unreachable warning (#10075)

The withheld list feeding the 'unreachable until restart' warning filtered only on isPermissionDeferred. At both call sites revealDeferredToolsReferencedInHistory runs first and re-exposes resume-referenced tools regardless of deferral reason, so a history-revealed tool's schema IS in the declarations while the warning still declared it unreachable. Skip already-revealed tools when building the list and pin the behavior with a regression test.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): warn per tool when tools.eager withholds late-registered tools (#10075)

The unreachable warning used a boolean latch that froze the first withheld snapshot. A tool registered mid-session (e.g. image_gen via /model --image) joins the permission-deferred set after the latch fires, and every later setTools() re-run withholds it again with zero diagnostic. Track already-warned names in a set and warn on the delta, preserving the anti-spam intent while naming late arrivals. Regression test covers the growing-summary path.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): warn when tools.eager normalization drops entries (#10075)

normalizeDisabledToolList strips empty/whitespace-only and non-string entries before PermissionManager.initialize() sees the list, so the dropped-entries warning there can never fire for that class on the real CLI path. Worse, tools.eager: [""] collapses to [] — the active defer-everything allowlist — silently demoting every non-exempt eager-by-default tool with zero log signal. Warn at normalization so the collapse always leaves a signal; the fail-closed [] itself is unchanged. CLI-level test pins the warning and goes red if the guard is removed.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): warn when a tools.eager entry matches no registered tool (#10075)

The dropped-entries warning only fires for syntactically unusable entries; a valid-but-unknown name like tools.eager: ["read_flie"] passes parseRule and resolveToolName unchanged, lands in canonicalNames with no diagnostic, and activates an allowlist matching nothing — silently deferring every non-exempt eager-by-default tool, the exact typo class the warning's rationale names. Warn once per initialize() for entries the alias table cannot resolve, keeping the mcp__/computer_use__ discovery-name pass-through untouched. Tests pin the diagnostic for the typo class and the pass-through silence; removing the check turns them red.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): pin tools.eager gate ordering and no-promotion invariants (#10075)

Arm the surviving coreTools branch in 'cites a matching deny rule when one exists (#9827)' so the test pins the deny-over-coreTools if/else-if ordering with both gates armed — swapping the branches turns it red locally. Add the missing ToolNames.LS negative assertions to the eager-wiring registry test and a companion case with lsToolEnabled: true where the eager list omits LS, pinning that tools.eager demotes via registerPermissionDeferredFactory and never promotes a disabled-by-default tool into existence.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): give memory-scoped PM stub its registration-time surface (#10075)

The forkedAgent bound-tool isolation test stubbed the upstream
getPermissionManager override as a bare marker object. Now that
createToolRegistry resolves the permission manager through the getter
and consults getToolRegistrationStatus at registration time, the bare
marker throws inside registerLazy's guard and every bare-mode factory
is skipped, so ensureTool(Edit) resolved undefined in CI. Give the
stub the getToolRegistrationStatus surface that production
memory-scoped PMs always implement (MemoryScopedPermissionManager in
memory-scoped-agent-config.ts).

Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>

* fix(core): close tools.eager review gaps

---------

Co-authored-by: yiliang114 <jinjing.zzj@gmail.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
2026-08-28 15:27:34 +00:00
易良
ad8e09c663
fix(core): keep allowlist-uncovered tools registered as deferred instead of removing them (#10075) (#10082)
* fix(core): demote permissions.allow-uncovered tools to deferred instead of removing them (#10075)

* test(integration): add E2E regression guard for #10075 uncovered tools

* fix(cli): preserve deferred daemon tools

* fix(core): preserve deferred permission boundaries
2026-08-26 11:58:31 +00:00
Yu Zhang
652cd76781
docs: consolidate assorted documentation corrections (#9855)
* docs(zed): use real debug log env var (#9853)

* docs(headless): fix JSON jq examples (#9858)

* docs(cli): align status line default preset prose (#9864)

* docs(acp): update permission mediator status (#9866)

* docs(sdk): align query option reference (#9855)

---------

Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-08-26 08:52:13 +00:00
易良
f2e593c26b
fix(core): make permissions.allow restrict the tool schemas sent to the model (#9829)
* fix(core): make permissions.allow restrict the tool schemas sent to the model (#9827)

permissions.allow only auto-approved calls; it never gated tool
registration, so the outgoing tools array kept every built-in schema
even when an allowlist was configured — contradicting the settings
docs migration table ("unlisted tools are disabled at registry
level") and breaking backends like llama.cpp that compile all tool
schemas into a single grammar.

- Activate a registry-level allowlist when settings.permissions.allow
  has at least one valid rule: built-in tools not covered by any allow
  rule are no longer registered (absent from /tools and the API
  request). MCP tools and the structured_output contract stay exempt;
  session-granted rules ("always allow", skill allowedTools) extend
  membership but never activate the allowlist mid-session.
  --allowed-tools / SDK allowedTools / legacy tools.allowed keep their
  pure auto-approval semantics.
- Complete the rule alias map so the display names shown by /tools
  (SendMessage, UpdateGoal, ...) match in allow/deny rules.

* fix(core): honor permissions.allow in the list_directory opt-in gate (#9827)

isLsToolEnabled() only read tools.listDirectory.enabled and the coreTools
allowlist, so an explicitly allowlisted list_directory passed
PermissionManager.isToolEnabled() but was never registered — absent from
/tools and the model request, with calls failing TOOL_NOT_REGISTERED. This
broke the documented tools.core -> permissions.allow migration equivalence
for exactly this tool. Consult getRegistryAllowList() with the same
coverage semantics the registry gate uses (toolMatchesRuleToolName, so
Read / ListFiles / specifier forms all count).

* fix(core): keep plan-mode lifecycle tools registered under the allowlist (#9827)

The permissions.allow registry gate covered exit_plan_mode /
enter_plan_mode / ask_user_question, so the exact reporter configuration
unregistered them. The plan-mode system reminder still instructs the model
to present its plan by calling exit_plan_mode, whose schema is then never
sent, so the sanctioned plan flow cannot complete. Exempt the three
plan-mode lifecycle tools alongside structured_output (same synthetic-
system-tool class the CORE_TOOLS docstring names; deny rules still apply).

* docs(sdk): correct allowedTools registry-allowlist contract (#9827)

The JSDoc added for QueryOptions.allowedTools (and the coreTools block)
claimed the SDK allowedTools param activates the registry allowlist and
hides unlisted built-in schemas. It does not: ProcessTransport maps it to
the CLI --allowed-tools flag, and this PR's CLI wiring builds
registryAllowList only from settings.permissions.allow. Reword both JSDoc
blocks and the two hand-maintained SDK doc pages (sdk-typescript.md,
sdk-typescript/README.md) to the shipped contract: allowedTools stays a
pure auto-approval grant; only permissions.allow in settings.json
(requires restart) activates the registry allowlist.

* docs(settings): note plan-mode lifecycle exemption in the allowlist (#9827)

The permissions.allow registry-allowlist exemption list named only MCP
tools and the structured_output contract. Add the plan-mode lifecycle
tools (exit_plan_mode / enter_plan_mode / ask_user_question) exempted in
b8ba258c40 so the documented exemption set matches the gate.

* fix(core): exempt the computer_use__* family from the registry allowlist (#9827)

* fix(core): gate command-discovered tools through the registry allowlist (#9827)

* fix(core): make registry-allowlist membership monotonic within the session (#9827)

* fix(core): narrow the skill allowedTools grant contract to restart-scoped registration (#9827)

* fix(core): count ask rules toward registry-allowlist membership (#9827)

A tool covered only by a permissions.ask rule was silently deregistered
whenever the permissions.allow registry allowlist was active: allow
["ReadFile"] + ask ["Shell"] hid the whole shell family from the model,
so the documented "always require user confirmation" silently became
"tool unavailable" and the ask rule could never fire.

Ask rules express "this tool must stay usable, with confirmation", so
they now count toward registry membership (frozen at startup for the
same restart-scoped monotonicity as allow rules).

* docs(settings): note that ask rules keep tools registered under allowlist (#9827)

* test(cli): pin registry-allowlist strip in bare mode (#9827)

The wiring tests only covered the safe-mode half of
registryAllowList: bareMode || safeMode ? undefined : ... — a mutant
dropping the bareMode guard survived the suite and would activate the
allowlist from settings while bare mode strips those same rules from
the merged allow set, leaving the bare registry's minimal toolset
ungated. Mirror the safe-mode test for --bare.

* fix(core): attribute registry-allowlist misses to permissions.allow (#9827)

An allowlist-miss rejection surfaced as "Qwen Code requires permission
to use X, but that permission was declined" citing a deny rule that
does not exist (findMatchingDenyRule finds nothing) and never
mentioning permissions.allow. When no deny rule matched and the
registry allowlist is active, emit a distinct message pointing at the
real config knob.

* test(core): pin resolveToolName coverage of every ToolNames entry (#9827)

TOOL_NAME_ALIASES hand-maintains the canonical/display-name mappings
that tool-names.ts declares; nothing enforced the sync, so a tool added
to tool-names.ts without an alias entry would compile, pass every test,
and silently never match a permission rule — the exact #9827 bug class,
now with higher stakes since a missed entry also breaks allowlist
coverage. Walk every ToolNames/ToolDisplayNames pair and assert it
round-trips through resolveToolName.

* fix(core): expose isPermissionsAllowListActive on scoped PM shims (#9827)

* fix(core): honour ask-only list_directory coverage in the opt-in gate (#9827)

* docs: align registry-allowlist contract wording across docs and JSDoc (#9827)

* docs: scope settings.md removal and whole-tool-deny claims precisely (#9827)

* fix(core): count merged allow coverage in the list_directory opt-in gate (#9827)

isLsToolEnabled() scanned only the settings-sourced getRegistryAllowList() for allow coverage while PermissionManager.isToolEnabled() counts the merged allow set (settings + --allowed-tools + SDK allowedTools + legacy tools.allowed). Under an active allowlist, list_directory covered only by a merged rule passed isToolEnabled but was never offered to registerLazy — it vanished from /tools and the model request while calls failed TOOL_NOT_REGISTERED. Count the merged allow set for coverage (activation still requires a valid settings rule) and filter empty/whitespace-only entries from activation exactly like PermissionManager.initialize's parseRules does, so a degenerate [""] entry cannot activate the gate here while the permission system reports the allowlist inactive.

* test(core): pin activation source and merged-allow coverage of the list_directory gate (#9827)

Every existing isLsToolEnabled test fed the identical array as both allow and registryAllowList, so the settings-only vs merged-allow distinction was unpinned and the R4-1 divergence shipped uncovered. Add three cases shaped like the CLI wiring: coverage by a merged (non-settings) allow rule under an active allowlist registers the tool; merged-only coverage with no settings rule does not activate the allowlist; an empty settings entry ([""]) does not activate it either.

* fix(core): attribute scheduler denials to permissions.allow only for uncovered tools (#9827)

The allowlist-miss message fired for any disabled tool with no matching deny rule while the allowlist is active — including tools rejected by the legacy coreTools gate that ARE covered by an allow rule, where 'not covered by any permissions.allow rule' is wrong and the remediation a no-op. Expose isCoveredByAllowOrAskRule on PermissionManager and take the allowlist branch only when the tool is genuinely uncovered; covered tools fall back to the generic declined message. The optional call keeps scoped PermissionManager shims (installed via 'as unknown as PermissionManager') from throwing until they grow the delegation.

* test(core): pin the covered-tool fallback for scheduler denial messages (#9827)

Add a scheduler-level case where the allowlist is active, no deny rule matches, and the disabled tool IS covered by an allow rule (the legacy coreTools gate shape): the message must be the generic declined one, not the permissions.allow attribution. Also make the existing allowlist-miss stub explicit about coverage.

* fix(core): register request_shutdown in the permission rule alias map (#9827)

Merging origin/main brought ToolNames.REQUEST_SHUTDOWN (#9806) but no TOOL_NAME_ALIASES entry, which the resolveToolName exhaustiveness test added on this branch pins. Map request_shutdown / RequestShutdown so permission rules can address the tool.

* fix(core): guard the list_directory allowlist gate against non-string rules (#9827)

isLsToolEnabled()'s activation check and coverage scan called raw.trim() / parseRule(raw) directly while PermissionManager.initialize computes the same thing through parseRules, whose r && r.trim() filter skips falsy entries. Settings load performs no element-type validation (the schema declares only type: array), so a stray null in settings.permissions.allow/ask — or in the legacy tools.allowed key riding the merged coverage set — threw TypeError during createToolRegistry and crashed startup while PermissionManager.initialize tolerated the same settings file. Mirror the parseRules guard with a typeof check in both the activation check and the coverage predicate, and pin both arms (tolerated entries still activate/cover, a [null]-only list keeps the gate closed).

* fix(core): exempt task_stop from the permissions.allow registry gate (#9827)

task_stop satisfies the PR's own two written exemption criteria but was missing from the set: it is shouldDefer=true (task-stop.ts), the exact deferred-schema property the computer_use__* exemption cites, and it is advertised to the model by a registered tool's copy — run_shell_command's schema says to use task_stop to stop a background command (and not to use broad process-name kills), and the background-promotion result instructs task_stop({ task_id }) verbatim. Under the reporter configuration the suite pins, run_shell_command stays listed while task_stop was gated out, so the sanctioned stop flow failed. Add the exemption and pin it next to the plan-mode exemption tests, including that a whole-tool deny rule still wins via the existing evaluate pass.

* fix(core): keep shim denials on the pre-#9827 message when coverage is unknown (#9827)

The optional isCoveredByAllowOrAskRule call's : true fallback routed shim-mediated rejections of COVERED tools into the new allowlist-attribution message, contradicting the comment above it ('they keep the pre-#9827 message meanwhile'). Both production shims (memory-scoped-agent-config.ts, skillReviewAgentPlanner.ts) Pick a partial interface without isCoveredByAllowOrAskRule, so for them the ternary always took the allowlist arm — telling the user a covered tool 'is not covered by any permissions.allow rule' when a different gate (e.g. the legacy coreTools allowlist) rejected it. Flip the fallback to false so unknown coverage stays on the pre-#9827 declined message, and update the shim test to pin that message instead of the allowlist one.

* test(core): pin that ask-only rules never activate the allowlist (#9827)

The suite pins ask rules counting toward allowlist membership, but nothing pins the complementary activation boundary: no test constructed a PermissionManager with only permissionsAsk (no permissionsAllow) and asserted the allowlist stays inactive. Current behavior is correct; this guards against a future edit folding ask rules into activation, which would turn an ask-only posture (permissions.ask: ["Shell"], no allow rules — a natural 'always confirm shell' config) into an active allowlist that deregisters every unlisted built-in. The nearest existing test ('no allow rules → allowlist inactive') uses no rules at all and would still pass.

* fix(core): exempt tool_search from the permissions.allow registry gate (#9827)

Under a narrow active allowlist, tool_search itself was gated out of the
registry. Without ToolSearch, client.ts resolveDeferredToolsForReminder
eagerly force-reveals every registered deferred tool (all mcp__* and the
deferred computer_use__* family) into the eager model request, and
preloadDeferredToolsWithinBudget early-returns — inverting the
schema-shrink goal into maximal schema bloat for exactly the deferred
families the other exemptions preserve for ToolSearch discoverability.
Pre-#9827 tool_search always bypassed the legacy coreTools gate as a
non-core tool.

* test(core): pin the deny-rule arm's precedence in the scheduler permission message (#9827)

The three-way message branch in CoreToolScheduler covers the allowlist-miss
arm and the generic fallback arm, but every findMatchingDenyRule mock
returned undefined, so the deny-rule arm — whose position FIRST in the
if/else-if chain is what makes a real denial cite the matching rule instead
of the allowlist attribution — had no scheduler-level coverage. Add two
tests where findMatchingDenyRule returns a matching rule: one with the
allowlist arm armed (active allowlist + uncovered tool) pinning the
if/else-if ordering, one without an active allowlist pinning the deny arm
over the generic declined fallback. Mutation-checked: disabling the deny
arm fails both tests.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): pin deny/ask sibling semantics at the discovery gate (#9827)

The discovery-gate test built its PermissionManager with EMPTY ask/deny
lists, so the gate's two documented sibling semantics were unpinned:
settings.md says a whole-tool deny rule removes a discovered tool from
the registry even under an active allowlist, and an ask rule keeps a
discovered tool registered ("always require confirmation" must never
silently become "tool unavailable"). Add two discovery-gate tests with
deny-covered and ask-covered PermissionManager configurations: the denied
tool is also allow-covered so only the deny branch of isToolEnabled can
reject it, and the ask test carries an uncovered control tool proving the
gate is active in the same run. Mutation-checked: ignoring deny decisions
fails the deny test only; dropping ask coverage from
isCoveredByAllowOrAskRule fails the ask test only.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: match the allowlist activation wording to the real predicate (#9827)

Four surfaces said the permissions.allow registry allowlist activates
"when at least one allow rule is configured", but
PermissionManager.initialize computes activation as at least one VALID
rule from settings.permissions.allow only (getRegistryAllowList): a
malformed entry never activates it, and auto-approval-only sources such
as the --allowed-tools CLI flag / the SDK allowedTools parameter never
do either. Reword settings.md, the SDK docs, the sdk-typescript README
and the coreTools JSDoc to the exact predicate, and complete their
exemption lists with task_stop and tool_search, which isToolEnabled
exempts but the docs did not name. Docs-only.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-25 11:57:24 +00:00
Yu Zhang
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>
2026-08-24 12:40:05 +00:00
ytahdn
80863c0858
feat(web-shell): show subagent sessions in detail panel (#7380)
* feat(web-shell): show subagent sessions in detail panel

* fix(web-shell): harden subagent session details

* chore(sdk): account for subagent client APIs

* fix(core): keep subagent resume history valid

* fix(subagents): stabilize detail session streaming

* test(cli): update telemetry route count

* fix(subagents): address review feedback

* fix(subagents): harden transcript refresh state

* fix(subagents): address maintainer review

* fix(subagents): preserve compact status details

* fix(subagents): address review feedback round 3 (#7380)

* fix(subagents): address remaining detail review feedback

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-22 02:31:08 +00:00
Zqc
cf6c51396c
chore(docs,test): batch three small docs and test fixes (#7373)
* docs(sdk): document auto permission mode

* docs(sdk): clarify auto permission mode

* docs(action): fix GitHub Action input names

* test(weixin): cover exported AES key parser

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
2026-07-21 12:50:03 +00:00
Changxiao Ruan
9b6da8e604
feat(webshell): replay ChatRecord history in readonly WebShell (#6999)
* feat(web-shell): add readonly transcript renderer

* feat(transcript): project chat records to daemon transcript

* chore: remove unrelated merge changes

* fix(cli): align transcript replay test mock

* fix(transcript): preserve replay metadata and todo context

Keep vision disclosures and assistant usage visible across tool replay boundaries.

Propagate plan tool-call identity through daemon projection and provide Todo contexts in WebShell so snapshots remain independent. Accept session_source records and cover the cross-layer behavior with regression tests.

* docs(transcript): translate designs and remove benchmark table

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-19 00:09:44 +00:00
morluto
41157205c1
fix(config): reject fractional session and tool-call limits (#6920)
* fix(config): reject fractional session and tool-call limits

* fix(config): validate persisted session turn limits
2026-07-15 04:56:52 +00:00
Tianyuan
075c3f03e5
feat(cli): forward ask_user_question answers from SDK can_use_tool (#6655)
* feat(cli): forward ask_user_question answers from SDK can_use_tool

SDK-hosted agents could receive ask_user_question calls through the
can_use_tool callback and approve them, but the user's answers never
reached the tool: the CLI called onConfirm(ProceedOnce) with no payload,
so the tool read an empty answers map and the model never got the
decisions.

Route updatedInput.answers from the SDK's allow response into the tool
confirmation payload so the collected answers reach the tool. Reuses the
existing updatedInput channel — no new SDK API or types. Document the
pattern in the TypeScript and Python SDK READMEs.

* fix(cli): forward ask_user_question answers on teammate approval path

Address review feedback on #6655:

- handleTeammateApproval now mirrors the leader path and promotes the
  user's answers from updatedInput into the confirmation payload, so
  ask_user_question calls approved through a teammate no longer drop the
  user's choices (wenshao).
- Extract a shared buildAllowConfirmationPayload helper used by both the
  leader and teammate paths, and only promote `answers` for
  ask_user_question so a same-named field on any other tool's input can't
  leak into the payload.
- Add tests for the teammate path and the defensive guards (array
  updatedInput, array/null/empty answers, foreign answers field).

* test(web-shell): stub Range client-rect methods to fix flaky CI

CodeMirror's async measure pass (scheduled via requestAnimationFrame)
calls getClientRects()/getBoundingClientRect() on a text Range. jsdom
implements these on Element but not on Range, so the call throws
"textRange(...).getClientRects is not a function" from a rAF callback
after the test completed. Vitest surfaces it as an unhandled error and
fails the whole run with exit code 1 even though every assertion passed
(seen intermittently in useComposerCore.dom.test.tsx).

Polyfill both methods on Range.prototype in the shared test setup,
mirroring the existing ResizeObserver/scrollIntoView stubs.

* refactor(cli): use ToolNames constant and broaden permission tests

Address review suggestions on #6655:

- buildAllowConfirmationPayload now gates answers-promotion on the
  ToolNames.ASK_USER_QUESTION constant instead of a bare string literal,
  so a future rename of the tool name is a compile-time break rather than
  a silent regression.
- Add an it.each case for a non-object primitive updatedInput (string) to
  cover the `typeof updatedInput !== 'object'` guard branch.
- Assert the leader path overrides toolCall.request.args with the host's
  sanitized updatedInput before confirming.
- Add a teammate-path test for an allow response with no updatedInput,
  asserting respond is called with (ProceedOnce, undefined).

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-10 23:53:46 +00:00
jinye
a234860a4a
fix(core): Align MCP OAuth guidance and docs (#5589)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* docs: Align docs with current CLI behavior

Update stale documentation and user-facing MCP OAuth guidance to match the current dialog-based flows, SDK permission semantics, current links, and Qwen OAuth status.

Also replace Ink internal imports with public Ink APIs for the shared text input so the workspace builds against Ink 7.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: fix BaseTextInput Ink import (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5589)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): surface MCP OAuth credential read failures

Fix SSE OAuth credential pre-check failures by reporting token storage read errors before connecting.

Update SDK coreTools docs and extension release link text from the follow-up review.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): harden MCP OAuth error handling

Handle stderr warning failures as best-effort and keep SSE 401 OAuth guidance when credential re-read fails.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): keep SSE OAuth pre-read best effort

Avoid blocking SSE MCP connections when the diagnostic credential pre-read fails, and cover BaseTextInput absolute-position edge cases.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): handle SSE OAuth validation errors

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): surface MCP OAuth recovery guidance

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): cover MCP OAuth retry paths

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): address OAuth guidance review

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-24 07:09:53 +08:00
Zqc
643a7d4198
fix(sdk): correct npm package name in SDK install instructions (#4860)
The unscoped 'qwen-code' package on npm is a copycat/squatted package
published by an unofficial maintainer (liangshuai, v0.0.5). Users
following the SDK README would install the wrong package.

Change 'npm install -g qwen-code@^0.4.0' to the correct scoped
'npm install -g @qwen-code/qwen-code@latest'.

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
2026-06-09 10:38:39 +08:00
zhangxy-zju
ed14a33064
feat(core): add NotebookEdit tool for Jupyter notebooks
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / CodeQL (push) Blocked by required conditions
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
Adds NotebookEdit as the structured write counterpart to existing notebook read support.

Summary:
- Add `notebook_edit` for safe cell-level `.ipynb` replace/insert/delete operations.
- Integrate notebook editing with tool registration, permissions, Claude conversion, prior-read enforcement, IDE/inline modify flow, commit attribution, docs, and SDK permission docs.
- Harden notebook read/edit behavior for truncated notebook renders, ambiguous fallback cell IDs, internal modify metadata, compact JSON, UTF-8 BOM notebooks, and cache behavior after structural edits.
- Add unit and integration coverage for notebook read/edit behavior.

Follow-up work remains for tab-indented notebook formatting preservation, a few low-risk unit-test additions, and non-blocking hardening suggestions from review.
2026-05-21 00:06:15 +08:00
ChiGao
80f1e266ba
feat(protocol): add typed daemon event schema v1 (#4217)
Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
2026-05-17 12:31:16 +08:00
ChiGao
57de269f45
feat(sdk): add DaemonSessionClient skeleton (#4201)
* feat(sdk): add daemon session client

* fix(sdk): harden daemon session event replay

* fix(sdk): replay attach-time daemon events

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
2026-05-17 01:01:12 +08:00
ChiGao
cadda23782
chore(deps): upgrade ink 6.2.3 → 7.0.2 + bump Node engine to 22 (#3860)
* chore(deps): upgrade ink 6.2.3 -> 7.0.2 + bump Node engine to 22

ink 7 requires Node >=22 and react-reconciler 0.33 with React >=19.2,
so this PR also bumps:

- Node engines (root + cli + core) 20 -> 22
- React/react-dom 19.1 -> 19.2.4 (pinned exact via overrides to keep
  the transitive React graph deduped to a single instance)
- @types/node pinned to 20.19.1 via overrides to avoid an unrelated
  Dirent NonSharedBuffer regression in sessionService tests
- @vitest/eslint-plugin pinned to 1.3.4 to avoid an unrelated lint
  regression introduced by the 1.6.x rule additions
- react-devtools-core 4.28 -> 6.1 (ink 7 peerOptional requires >=6.1.2)
- ink hoisted to root devDeps so workspace-private peer-dep contention
  doesn't push ink-link/spinner/gradient into nested workspace
  installs (which would skip transitive resolution for terminal-link)

Workflow + image + installer alignment:

- .nvmrc 20 -> 22
- Dockerfile node:20-slim -> node:22-slim
- CI test matrix drops 20.x (keeps 22.x + 24.x)
- terminal-bench workflow Node 20 -> 22
- Linux/Windows install scripts upgrade their Node version targets

Documentation alignment:

- README.md badge + prerequisites
- AGENTS.md, CONTRIBUTING.md, docs/users/quickstart.md,
  docs/users/configuration/settings.md, docs/developers/contributing.md,
  docs/developers/sdk-typescript.md, docs/users/extension/extension-releasing.md,
  packages/sdk-typescript/README.md, packages/zed-extension/README.md,
  scripts/installation/INSTALLATION_GUIDE.md

Test gating:

- Two AuthDialog/AskUserQuestionDialog tests that drive <SelectInput>
  through ink-testing-library now race ink 7's frame-throttled input
  delivery and land on the wrong option. The maintainers had already
  marked one of them unreliable (skip on Win32 + CI+Node20). Extend
  that gate to cover all environments until upstream
  ink-testing-library ships an ink-7-compatible release that flushes
  input deterministically. The other test now uses it.skip with the
  same comment. No business code changes.

Verified locally:

- npm run typecheck across all workspaces: clean
- npm run lint (root): clean
- npm run test --workspaces:
    cli  312/312 files, 4918 passed, 9 skipped
    core 266/266 files, 6836 passed, 3 skipped
    webui 6/6, 201 passed
    sdk  40/40, 283 passed, 1 skipped
- npm ls ink: single ink@7.0.2 instance across all peer deps
- single react@19.2.4 instance

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* chore: align Node 22 floor across all shipping artifacts

Reviewer (tanzhenxin) flagged five surfaces where the >=22 engine bump
leaked: SDK package metadata, web-templates engines, /doctor runtime
check, main bundler target, and SDK bundler target. Each was a separate
escape hatch letting Node 18/20 consumers install or run the artifact
on an unsupported runtime.

- packages/sdk-typescript/package.json: engines.node >=18.0.0 -> >=22.0.0
- packages/web-templates/package.json: engines.node >=20 -> >=22
- packages/cli/src/utils/doctorChecks.ts: MIN_NODE_MAJOR 20 -> 22
- esbuild.config.js: target node20 -> node22 (main CLI bundle)
- packages/sdk-typescript/scripts/build.js: target node18 -> node22 (esm + cjs)
- packages/cli/src/utils/doctorChecks.test.ts: rename test label to v22+

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* ci(e2e): bump E2E workflow Node matrix to 22.x

Reviewer (tanzhenxin) flagged that e2e.yml still pinned node-version
20.x while root engines is now >=22, so every E2E run on push would
either fail at npm ci with engine error or silently exercise the bundle
on a runtime that's no longer in ci.yml's test matrix.

The macOS job in the same workflow already reads .nvmrc (which is 22)
so this only updates the Linux matrix.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(deps): drop root wrap-ansi override so ink 7 gets its declared dep

Reviewer (tanzhenxin) flagged that the root overrides.wrap-ansi: 9.0.2
predates this upgrade and forces every consumer (including ink) to v9,
while ink 7 declares wrap-ansi: ^10.0.0. The lockfile had no nested
install under node_modules/ink/, so ink 7 was running with a transitive
dep one major below its declared minimum.

Dropping the global override lets ink resolve its own wrap-ansi 10
nested install (now visible in the lockfile under
node_modules/ink/node_modules/wrap-ansi), while the cli package's own
direct `wrap-ansi: 9.0.2` dependency keeps the cli code path
(TableRenderer.tsx) on the version it has been tested against. The
nested cliui override is preserved for yargs which still needs v7.

Verified via `npm ls wrap-ansi`:
- ink@7.0.2 -> wrap-ansi@10.0.0 (newly nested)
- @qwen-code/qwen-code -> wrap-ansi@9.0.2 (unchanged)
- yargs/cliui -> wrap-ansi@7.0.0 (unchanged)

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(InputPrompt): un-skip placeholder ID reuse after deletion

Reviewer (tanzhenxin) flagged that the new it.skip on the
'should reuse placeholder ID after deletion' test was undisclosed in
the PR description and removed coverage of real product behavior
(freePlaceholderId / bracketed-paste backspace path) without a
TODO(#NNNN) link.

Their argument was sound: the skip rationale pointed at ink 7's input
throttle, but this same file just bumped the wait helper from 50ms to
150ms specifically to give ink 7 frame time. Re-running the test under
the bumped wait shows it passes reliably (5/5 runs in the full-file
context, 9/10 alone), so the skip was masking the throttle-flake that
the wait bump already addresses, not a real product bug.

Drop the it.skip and the now-stale comment so coverage of the
freePlaceholderId reuse logic is restored.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(InputPrompt): bump first prompt-suggestion test wait to 350ms

The "accepts and submits the prompt suggestion on Enter when the buffer
is empty" test is the first in its describe block, so it pays the
renderer cold-start cost. On macOS-22.x CI runners that pushes the
Enter → onSubmit microtask past the default 150ms post-Enter wait. Match
the 350ms initial render wait used immediately above to absorb the cold
start.

* Revert "test(InputPrompt): bump first prompt-suggestion test wait to 350ms"

This reverts commit 6add83b62ea80c551c81f54af1fda3e6e7478f55.

* test(InputPrompt): wait for followup suggestion debounce before pressing Enter

Root cause of the failing prompt-suggestion tests on macOS and Windows
CI is not flaky timing of the test post-Enter wait — it's the 300ms
debounce inside createFollowupController.setSuggestion (shared core).
The Enter handler reads followup.state.isVisible synchronously, so if
the debounce timer has not fired before stdin.write('\\r'), the
suggestion path is skipped and onSubmit never runs. No amount of
post-Enter wait can recover from that — the keypress was already
processed against stale state.

The original wait(350) only left ~50ms margin over the 300ms debounce,
which ink 7 / React 19.2 mount overhead consumed on slow Windows
runners. Bump the initial wait to 700ms (named SUGGESTION_VISIBLE_WAIT_MS)
to give the debounce timer + cold-start render a generous buffer.

Apply to the two sibling tests too — without the wait their "does not
accept" assertions pass trivially when suggestion is never visible,
which is a false green that hides regressions in the actual reject path.

* fix(deps): align cli wrap-ansi with ink 7 (9.0.2 -> ^10.0.0)

Ink 7 ships its own wrap-ansi@10. CLI's direct dep was pinned to 9.0.2,
causing two copies of wrap-ansi in node_modules and a potential drift in
CJK width / ANSI handling between ink's internal text wrapping and our
TableRenderer.

Upgrading the CLI's direct dep to ^10.0.0 lets npm dedupe to a single
wrap-ansi@10 used by both ink and TableRenderer. API surface is
identical; the only documented behaviour change is that tabs are
expanded to 8-column tab stops before wrapping, which TableRenderer
doesn't feed in.

TableRenderer test suite (43 tests) passes against wrap-ansi@10.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* chore(deps): document @types/node 20.x pin in overrides

The override pinning @types/node to 20.19.1 (while engines require
Node >=22) is intentional: bumping to @types/node@22.x re-introduces
a Dirent<NonSharedBuffer> type regression that breaks
@qwen-code/qwen-code-core/sessionService tests.

Add a sibling "//@types/node" note inside `overrides` so future
maintainers see the rationale and know when to revisit the pin
without having to dig through PR #3860 history.

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(AskUserQuestionDialog): link skipped Submit-tab test to tracking issue

The 'shows unanswered questions as (not answered) in Submit tab' test
was switched to `it.skip` in the ink 7 upgrade because
`ink-testing-library@4.0.0` doesn't flush input deterministically
through ink 7's 30fps throttle.

Add a `// TODO(#4036):` marker so the skip is greppable and can be
re-enabled once upstream ships an ink-7-compatible release.

Refs #4036

Generated with AI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(deps): move @types/node pin comment out of overrides block

npm's `overrides` field requires every key to be a real package name —
the `"//@types/node"` comment-key added in 205855875 trips Arborist with
"Override without name" and breaks `npm ci` across all CI jobs.

Move the explanation to a sibling top-level `"//overrides"` key, which
npm ignores at the document root. Same documentation value, no
override-parser collateral damage.

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-05-11 17:29:50 +08:00
mingholy.lmh
43bb14ddc9 docs(sdk): enhance coreTools/excludeTools/allowedTools documentation with permissions reference
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-24 22:26:54 +08:00
DragonnZhang
ee33a3c35e feat: add system prompt customization options in SDK and CLI 2026-03-16 02:57:31 +08:00
mingholy.lmh
5d939fdb83 feat: add --session-id support for CLI and SDK
- Add --session-id flag to CLI for specifying custom session ID
- Add sessionId option to SDK QueryOptions
- Implement UUID validation for session IDs
- Pass session ID from SDK to CLI via --session-id argument
- Add integration tests for session-id functionality
- Update unit tests for ProcessTransport

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-13 21:40:57 +08:00
mingholy.lmh
3354b56a05 docs(sdk): update sdk docs 2025-12-19 15:47:11 +08:00
mingholy.lmh
bf6abf7752 fix: update timeout settings and default logging level in SDK 2025-12-06 12:27:16 +08:00
mingholy.lmh
96d458fa8c chore: rename @qwen-code/sdk-typescript to @qwen-code/sdk 2025-12-05 21:47:26 +08:00
mingholy.lmh
c218048551 fix: prevent sending control request when query is closed 2025-12-05 18:46:51 +08:00
mingholy.lmh
64de3520b3 docs: update README to include SDK-embedded MCP server details and usage examples 2025-12-05 13:14:55 +08:00
mingholy.lmh
322ce80e2c feat: implement SDK MCP server support and enhance control request handling
- Added new `SdkMcpController` to manage communication between CLI MCP clients and SDK MCP servers.
- Introduced `createSdkMcpServer` function for creating SDK-embedded MCP servers.
- Updated configuration options to support both external and SDK MCP servers.
- Enhanced timeout settings for various SDK operations, including MCP requests.
- Refactored existing control request handling to accommodate new SDK MCP server functionality.
- Updated tests to cover new SDK MCP server features and ensure proper integration.
2025-12-05 13:14:54 +08:00
mingholy.lmh
81c8b3eaec feat: add GitHub Actions workflow for SDK release automation 2025-12-04 17:10:23 +08:00