qwen-code/packages/core/src/config
Dragon 78eadd4bf1
feat(core): declare create_sub_session only under qwen serve (#9425)
* feat(core): declare create_sub_session only under qwen serve

create_sub_session needs the daemon bridge, which only exists under `qwen serve`, yet it was declared in every session. Interactive TUI and headless runs therefore carried a tool that can never succeed, polluting the model's action space and ToolSearch results. The tool is now registered by the ACP session at the same point it wires the sub-session spawner, so it exists exactly where it can work and nowhere else.

* fix(core): keep create_sub_session on registries built with a wired spawner

Dropping the unconditional registration also dropped the tool from every
registry rebuilt after the daemon session starts: sub-agent and override
registries are built through createToolRegistry with forSubAgent, and
copyDiscoveredToolsFrom carries discovered tools only, never built-ins.
Daemon sub-agents therefore lost the capability silently.

Restore the lazy registration but gate it on a sub-session spawner being
wired onto the Config, so interactive, headless and SDK runs still do not
advertise a tool that cannot work there, while daemon sub-agent and
override configs pick it up through prototype delegation. Going back
through the lazy path also restores the PermissionManager.isToolEnabled
gate for these registries.

Harden the negative test to assert on both registration entry points; a
regression that re-adds the tool eagerly never touches registerFactory,
so the previous assertion would have stayed green. Add a positive test
covering a subagent registry rebuilt after the spawner is wired.

* docs(core): align setSubSessionSpawner doc with the new gate

The setter's JSDoc still described the pre-PR behaviour — that leaving the
spawner unset makes the tool report itself as daemon-only. With the
spawner-gated registration the tool is never registered in interactive TUI
or headless, so nothing reports anything there, and the comment contradicted
the three sibling doc sites this PR already updated.

* fix(cli): permission-gate the daemon create_sub_session registration

The eager registration in the Session constructor called
ToolRegistry.registerTool() directly, which honors only `tools.disabled` —
so a daemon whose operator restricts `tools.core` or denies the whole tool
still advertised create_sub_session and failed every call with
EXECUTION_DENIED, exactly the "declared but unusable" pollution this change
set out to remove.

Registration now lives in an awaited helper that applies the same
PermissionManager.isToolEnabled() check the core-side gate in
createToolRegistry applies, and the daemon calls it once per session it
creates, after the Session has wired the spawner and before the session is
published. Also drops the unused CreateSubSessionParams public export.

* fix(cli): declare create_sub_session only on daemon-backed sessions, revealed to the model

Address the R4 review findings:

- Wire the sub-session spawner only when the daemon's QWEN_CODE_SERVE=1
  stamp is present. A standalone --acp session's peer is the editor,
  which answers the bridge's qwen/control/* ext methods with JSON-RPC
  -32601, so the tool was declared there but could never run. Gate
  registerCreateSubSessionTool on the spawner being wired so the tool
  exists exactly where it can execute.
- Reveal the deferred tool and refresh the declaration snapshot after
  registering: the registration lands after startChat() froze the chat's
  declarations, so without the reveal the model was never offered the
  tool for the session's first lifetime.
- Pin that newSession awaits the registration before the session is
  served, so the first prompt's declarations always include the tool.

* fix(core): pin the create_sub_session reveal across /clear resets

The reveal applied at daemon-session creation was permanently lost by the
first /clear whenever the deferred-tool startup preload did not fit its
all-or-nothing schema budget (or was disabled by a <= 0 / non-finite
operator threshold): resetChat() clears the revealed set, the preload
restores nothing, and registerCreateSubSessionTool never re-runs — the
tool silently dropped out of the declaration list for the rest of the
session.

Add ToolRegistry.pinDeferredToolReveal(): pinned reveals are session-setup
state (not ToolSearch discovery) and are re-applied by
clearRevealedDeferredTools() while the tool stays registered and deferred,
so the fresh session's startChat -> setTools() re-declares it. Pin
create_sub_session at registration.

* test(cli): pin create_sub_session registration on the permission-manager-enabled path

* docs(core): correct DAEMON_ONLY_MESSAGE reachability in create-sub-session header

Per wenshao's runtime verification (N2): the guard is reachable only for
a daemon session whose spawner was cleared mid-flight; in non-daemon
sessions the tool is absent from the registry, so a stale direct call
hits the registry-miss error before execute() is reached.
2026-08-24 02:07:23 +00:00
..
approval-mode.ts feat(core): Add leader approval for plan-required teammates (#6138) 2026-07-01 23:34:18 +00:00
clearContextDefaults.ts
config-session-env.test.ts fix(review): gate the recovered incremental anchor on the model that certified it (#9184) 2026-08-18 07:43:40 +00:00
config.safe-mode.test.ts feat(cli): show loaded context files alongside the first prompt (#8855) 2026-08-19 15:53:48 +00:00
config.test.ts feat(core): declare create_sub_session only under qwen serve (#9425) 2026-08-24 02:07:23 +00:00
config.ts feat(core): declare create_sub_session only under qwen serve (#9425) 2026-08-24 02:07:23 +00:00
config.workflow-registration.test.ts feat: visualize ordinary-session plan execution (#7580) 2026-08-01 10:48:30 +00:00
config.workflows.test.ts
constants.ts
models.ts chore: update default model to qwen3.7-max (#6978) 2026-07-15 23:51:13 +00:00
storage.test.ts fix(serve): Isolate daemon session maintenance writers (#7975) 2026-07-30 14:01:50 +00:00
storage.ts fix(serve): Isolate daemon session maintenance writers (#7975) 2026-07-30 14:01:50 +00:00