feat(core): add CodeModeOnly programmatic tool calling MVP - #10414
feat(core): add CodeModeOnly programmatic tool calling MVP#10414DragonnZhang wants to merge 10 commits into
Conversation
Local black-box E2E reportTested the committed standalone bundle on macOS 27.0 arm64 with Node.js v24.18.0 against local OpenAI-compatible and Anthropic protocol servers. The tests exercised Direct regression
CodeModeOnly surface and execution
Isolation, timeout, and cancellation
Automated regression evidence
Bundle hashes used for the final post-fix probes:
Not covered locally: Windows/Linux runtime probes, a live Gemini request, and interactive TUI approval because the local probe hit an unrelated Ink rendering failure. Gemini declarations and approval routing are covered by unit tests. |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterℹ️ No screenshot changed against the PR base — but this PR edits 1 render-shaping file:
Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to Full-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
# Conflicts: # packages/cli/src/config/settingsSchema.ts # packages/core/src/agents/runtime/agent-core.ts # packages/core/src/config/config.ts # packages/core/src/core/client.ts # packages/core/src/core/coreToolScheduler.test.ts # packages/core/src/permissions/permission-manager.test.ts # packages/core/src/permissions/permission-manager.ts # packages/core/src/tools/tool-registry.ts # packages/sdk-typescript/README.md # packages/sdk-typescript/src/types/types.ts # packages/vscode-ide-companion/schemas/settings.schema.json
|
@/tmp/10414_note_fixed.md |
# Conflicts: # packages/core/src/config/config.test.ts # packages/core/src/tools/tool-search.ts
…ge contract (QwenLM#10414) 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.
|
CI note update (follow-up to the 2026-08-28 note above): the Evidence that it is not environmental: in the same CI window where this PR failed the suite, other branches' runs passed it — Root cause. The E2E asserted the pre-bridge contract: after Fix. Test-only change (no production code): flip the three fresh-session assertions from "tool is advertised after 中文说明CI 说明更新(承接上方 2026-08-28 的说明): 非环境问题的证据: 在本 PR 该套件失败的同一 CI 时间窗内,其他分支的运行通过了它—— 根因。 该 E2E 断言的是桥接之前的契约: 修复。 仅测试变更(不动产品代码):把三处新会话断言从"select: 之后工具被声明"翻转为"工具保持隐藏",并加注释说明桥接契约。第四个测试( |
QwenLM#10414) verifyBundleArtifacts now requires dist/codeModeWorker.js (added with the CodeModeOnly worker), but createBundleArtifacts never created it, so every preparePackage-driven fixture test exited early with 'Required package artifact not found' (process.exit 1) before reaching the audio-capture paths under test — e.g. 'fails packaging when required audio-capture package JSON is invalid' got the exit instead of the expected throw. Fixture now ships the empty worker alongside cli.js; package-assets suite 33/33 green locally (1 pre-existing root-user skip).
What this PR does
This PR adds a default-off
tools.codeModeOnlyMVP that narrows the model-visible tool surface to a structuredexec({source})control tool plus an audited set of direct-only controls. Ordinary registered tools remain available inside an isolated JavaScript runtime through deterministictools.*bindings and a read-onlyALL_TOOLScatalog, whiletool_search,tool_call, and recursiveexeccalls are excluded from nested execution. Direct mode remains the default and preserves the existing deferred-tool bridge.Each execution uses a fresh QuickJS isolate in a worker with hard source, output, nested-call, memory, cancellation, and ten-second CPU limits. Nested calls re-enter the existing Core or ACP scheduling paths so registry lookup, parameter validation, permissions, approvals, hooks, telemetry, UI lifecycle events, and cancellation continue to apply to the real nested tool. OpenAI-compatible, Anthropic, and Gemini/Qwen declaration paths preserve the same structured source-only schema.
This is intentionally the CodeModeOnly MVP slice. Hybrid CodeMode,
waitand persistent cells, store/yield helpers, multimodal projection, and native freeform provider input remain outside this PR.Why it's needed
Large top-level tool surfaces consume prompt context and force additional model-to-tool round trips for deterministic branching, fan-out, filtering, and aggregation. Programmatic tool calling keeps that orchestration local while retaining Qwen Code's existing permission and lifecycle controls, and it provides a rollback-safe experimental path because the feature is disabled by default.
Reviewer Test Plan
How to verify
tools.codeModeOnlyand confirm the provider receives the existing direct declarations, does not receiveexec, and can still discover then invoke a deferred tool throughtool_searchandtool_call.tools.codeModeOnly, restart, and confirm the provider receives onlyexecplus the audited direct-only controls. Confirm ordinary tools and the deferred bridge controls are absent from the top-level declarations, while theexecschema requires exactly one stringsourcefield.tools.read_file, including sequential and concurrent calls, and confirm the outer response belongs toexecwhile nested permission, hook, telemetry, and lifecycle events use the real tool name.sourcevalues are rejected, recursive controls are unavailable, and Node, process, import, network, console, shared-memory, and WebAssembly capabilities are absent.Evidence (Before & After)
Before: Direct mode exposed the existing 11-tool headless surface and the deferred bridge could discover and invoke
cron_list; no code execution control existed.After: repeated OpenAI-compatible and Anthropic captures exposed a stable 10-tool CodeModeOnly surface with the required source-only schema.
execsuccessfully calledread_file;source: 42andsource: truereturnedis_error: true; syntax errors remained failures; nested bridge/control calls were unavailable; an infinite loop timed out at 10,006 ms and the provider completed the following round; SIGINT cancelled an awaited nested request and closed its socket. Focused Core tests passed 406/406 and the full ACP session suite passed 719/719. Full build, typecheck, lint, bundle, and package preparation completed successfully.Tested on
Environment (optional)
macOS 27.0 arm64, Node.js v24.18.0, standalone bundled CLI, and local fake OpenAI-compatible and Anthropic providers.
Risk & Scope
wait/cell lifecycle, persistent store/yield helpers, multimodal output projection, and native freeform provider input.tools.codeModeOnlyrequires a restart.Linked Issues
Refs #10377
中文说明
本 PR 做了什么
本 PR 新增默认关闭的
tools.codeModeOnlyMVP,将模型顶层可见工具收敛为结构化的exec({source})控制工具和经过审计的少量 direct-only 控制面。普通已注册工具仍可在隔离 JavaScript runtime 中通过确定性的tools.*绑定和只读ALL_TOOLS目录使用;tool_search、tool_call和递归exec不允许作为嵌套调用。Direct 模式继续作为默认值,并保持现有 deferred-tool bridge 行为。每次执行都会在 worker 中创建全新的 QuickJS isolate,并施加源码、输出、嵌套调用、内存、取消和十秒 CPU 硬限制。嵌套调用重新进入现有 Core 或 ACP 调度路径,因此 registry lookup、参数校验、权限、审批、hooks、遥测、UI 生命周期事件和取消仍作用于真实嵌套工具。OpenAI-compatible、Anthropic 和 Gemini/Qwen 的声明路径保持相同的结构化 source-only schema。
本 PR 有意只交付 CodeModeOnly MVP。Hybrid CodeMode、
wait和持久 cell、store/yield helpers、多模态结果投影及 provider 原生 freeform 输入不在本 PR 范围内。为什么需要
大量顶层工具 schema 会占用 prompt context,并让确定性的分支、fan-out、过滤和聚合产生更多模型—工具往返。程序化工具调用把这些编排留在本地,同时保留 Qwen Code 现有的权限与生命周期控制;由于功能默认关闭,也提供了可安全回滚的实验路径。
Reviewer 测试计划
如何验证
tools.codeModeOnly启动,确认 provider 收到现有 Direct 声明、不包含exec,并且仍能通过tool_search和tool_call发现及执行 deferred tool。tools.codeModeOnly并重启,确认 provider 只收到exec和经过审计的 direct-only 控制工具。普通工具及 deferred bridge 控制工具不应出现在顶层声明中,execschema 应严格要求唯一的字符串source字段。tools.read_file的 JavaScript,包括顺序与并发调用;确认外层响应属于exec,而嵌套权限、hook、遥测和生命周期事件使用真实工具名。source会被拒绝,递归控制工具不可用,并且不存在 Node、process、import、网络、console、共享内存和 WebAssembly 能力。证据(Before & After)
Before:Direct 模式暴露原有 11 项 headless 工具面,deferred bridge 能发现并调用
cron_list,但不存在代码执行控制工具。After:多轮 OpenAI-compatible 和 Anthropic 抓包均得到稳定的 10 项 CodeModeOnly 工具面及严格 source-only schema。
exec成功调用read_file;source: 42和source: true返回is_error: true;语法错误继续正确失败;嵌套 bridge/control 调用不可用;无限循环在 10,006 ms 超时,provider 随后正常完成下一轮;SIGINT 能取消正在等待的嵌套请求并关闭 socket。Core 聚焦测试 406/406、ACP session 全套 719/719;完整 build、typecheck、lint、bundle 和 package preparation 均成功。测试平台
环境(可选)
macOS 27.0 arm64、Node.js v24.18.0、standalone bundled CLI,以及本地 fake OpenAI-compatible 和 Anthropic providers。
风险与范围
wait/cell 生命周期、持久 store/yield helpers、多模态输出投影和 provider 原生 freeform 输入。tools.codeModeOnly后需要重启。关联 Issue
Refs #10377