Repository navigation
feat(workflows): let the model invoke extension workflows that declare whenToUse - #11957
Conversation
…e whenToUse
An extension workflow whose script declares `meta.whenToUse` is now listed
for the model, with its description and that condition, so the model can
start it when a request matches. Every run still goes through the workflow
approval; a workflow without `whenToUse` stays out of the model's listing.
- Carry `whenToUse` on `ExtensionWorkflowDefinition` and
`SavedWorkflowEntry`, trimmed and shortened like the description.
- `SavedWorkflowLoader` marks such a command model-invocable and puts the
condition in its model description.
- Outside the interactive UI (headless, ACP, and the model invoking the
command through the Skill tool) a tool dispatch cannot run, so the command
expands to a prompt asking the model to call `Workflow({ name })` with the
qualified name. The interactive `/name` path is unchanged, and the command
no longer restricts itself to interactive mode.
- The Workflow tool's opt-in rule counts a skill or command the model itself
ran through the Skill tool.
yiliang114
left a comment
There was a problem hiding this comment.
LGTM at 2e37ae3 — no blocking issues; first review pass. The exposure is scoped exactly right: only extension workflows with an explicit meta.whenToUse become model-invocable (user-saved ones never do), every run still goes through the approval dialog with the content-pinned grant from #11943, and whenToUse lives inside the digested script so editing it re-triggers install consent. The prompt expansion for headless/ACP names the qualified workflow name, and a collision-renamed command still runs the right one. CI green.
chiga0
left a comment
There was a problem hiding this comment.
Reviewed for blocking issues only. None found.
- Skill listing correctly gated on
extensionsource +whenToUsepresence; project/user workflows excluded. - Prompt expansion uses the qualified workflow name (not the collision-renamed command name) and JSON.stringifies it in the tool call.
- Mode restriction removal is safe: action branches on
executionMode, all non-interactive paths still end at the Workflow tool's approval gate. whenToUseis trimmed, blank = absent, clamped to 500 code points.- No approval bypass path introduced.
|
@qwen-code /review |
|
Qwen Code review request accepted. Review is queued for an available runner; follow the workflow run for progress. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes. |
Local verification on macOS — real build, real extension, real TUIThis is the hands-on run the PR's own Risk & Scope and the triage gate both listed as missing: an interactive session where the model reaches the extension workflow on its own, gets the expansion, calls Tree tested: PR head Rig: macOS 15 (darwin 25.6.0), Node 24.18.1, isolated 1 · The model-invoked path works end to endAsked a question matching the condition, the model calls the Skill tool with the workflow's qualified name: It receives the expansion and calls Approved, the run dispatches its subagent and the turn finishes on the workflow's result: 2 · The typed command is unchanged
3 · A/B against the base buildSame rig, same scripted model, base build: the Skill call is refused and the headless command is rejected outright. 4 · What the model actually receivedEvery string here comes from the recorded HTTP bodies, not from the terminal: 5 · Full matrixAlso re-run on macOS, which CI skips for this PR: core 330 passed / cli 137 passed (the author's numbers reproduce exactly), Follow-ups for the maintainer
Not verified: a real LLM deciding by itself to pick the workflow — the listing, the expansion, the dispatch, the approval and the run are all real, but the choice to call 中文说明macOS 本地实测:真实构建 + 真实扩展 + 真实 TUI这正是 PR 自己的 Risk & Scope 和 triage 门禁都标为"未验证"的那条链路:在交互会话里,模型自己触达扩展 workflow、拿到展开文本、调用 被测树: PR head 装置: macOS 15(darwin 25.6.0)、Node 24.18.1、隔离 1 · 模型发起的链路端到端成立。 问一个符合条件的问题后,模型以限定名调用 Skill 工具(图 1),拿到展开文本后调用 2 · 交互式输入命令行为不变。 在 TUI 敲 3 · 与基线构建的 A/B。 同一装置、同一脚本模型,在基线上 Skill 调用被判"not found",headless 命令直接被拒(图 5)。 4 · 模型实际收到的字节(全部取自记录的 HTTP 报文,非终端回显)见图 6;完整矩阵见图 7。 另外在 CI 对本 PR 跳过的 macOS 上复跑:core 330 通过 / cli 137 通过(与作者数字一致);在完整构建产物上跑全 workspace 给维护者的后续项
未验证: 真实 LLM 自行决定选用该 workflow——列表、展开、调度、审批与运行都是真实的,但"调用 Skill"这一步是脚本化的;Windows; |







What this PR does
An extension workflow whose script declares
meta.whenToUseis now listed for the model. Its command appears in the model's skill listing with the description and that condition, so the model can start the workflow when a request matches, through the Skill tool and thenWorkflow({ name }). Every run still goes through the workflow approval. A workflow withoutwhenToUsestays out of the model's listing, and project and user workflows are unchanged.whenToUseis carried onExtensionWorkflowDefinitionandSavedWorkflowEntry, trimmed and shortened to 500 characters like the description; a blank value counts as absent.Outside the interactive UI a
{type:'tool'}command return cannot run. That covers the model invoking the command through the Skill tool, headlessqwen -p, and ACP. There, a saved-workflow command now expands to a prompt:Run the "<name>" workflow., the description, the condition, andInvoke: Workflow({ name: "<name>", args: … }). The prompt uses the qualified workflow name, so a command renamed on a collision with its extension's skill still runs the right workflow. Typing/<name>in the interactive UI still dispatches the tool with the script path, unchanged. Because every mode can now run the command, it no longer restricts itself to interactive mode.The Workflow tool's opt-in rule now reads: "A skill or slash command that ran — invoked by the user, or by you through the Skill tool — instructs you to use this tool."
Why it's needed
An extension that ships a workflow had no way to let the model choose it. The command was never model-invocable, and the three model-invocable command executors accept only
submit_prompt, so even a listed command would have come back as "not found". The opt-in rule also counted only commands the user invoked. The only way to get the model to run an extension workflow was an instruction in QWEN.md telling it to, which the opt-in rule does not accept and which fires on every matching request regardless of what the extension author intended.Claude Code wraps every workflow as a prompt command with its
whenToUse, expanding to "Run the … workflow … Invoke: Workflow({name})". This follows the same shape, but lists only extension workflows whose author wrote the condition: the author says when it applies, the user consented to the extension, workflows are enabled, and the run is approved.Reviewer Test Plan
How to verify
Enable workflows, install or link an extension with
workflows/analysis.jsdeclaringwhenToUse, and a second script without it. Start a session and ask a question that matches the condition. The model should call the Skill tool with the workflow's name, receive the "Run the … workflow" text, and callWorkflow({ name }), which opens the usual approval withSaved workflow: <name>. The workflow withoutwhenToUsedoes not appear in the model's skill listing.Type
/dataworks:analysisin the interactive UI: it starts the workflow directly, as before. Runqwen -p '/dataworks:analysis': the command is no longer rejected as unsupported, and the model runs the workflow by name.Unit tests:
330 core and 137 cli tests pass locally, as do the core type check and ESLint on the changed files. The cli type check is left to CI: locally it reads the other workspace packages' built output, which predates this change.
Evidence (Before & After)
Output from the real modules on this branch: an extension discovered from disk with
loadExtensionWorkflows, adapted bySavedWorkflowLoader, registered inCommandService, and rendered with the skill-listing renderer. Before this change the listing had no entry for either workflow, the command action returned the tool dispatch in every context (which the executors turn into "not found" and headless turns into "Tool execution from slash commands is not supported in non-interactive mode."), and both commands were listed for the interactive mode only.Tested on
Environment (optional)
Unit tests, the core type check, and a throwaway vitest file driving the real modules. No interactive TUI session was run.
Risk & Scope
whenToUse, the user consented to the extension,tools.workflowsEnabledis on (off by default), and the user approves the run.whenToUselives in the script, so changing it changes the content digest and the next update asks for consent again. Headless and ACP/<name>now go through one model turn instead of being refused, which matches how skills and file commands behave there.whenToUseto go on); adisableModelInvocationmeta field; switching the interactive/<name>path fromscriptPathtoname, which would stop existingWorkflow(scriptPath:…)rules from matching it.Linked Issues / Bugs
Part of #11013
中文说明
这个 PR 做了什么
脚本
meta声明了whenToUse的扩展 workflow 现在会列给模型。它的命令带着描述和适用条件出现在模型的技能列表里,请求匹配时模型可以经 Skill 工具、再调用Workflow({ name })发起运行。每次运行仍走 workflow 审批。没写whenToUse的 workflow 不进模型的列表,project 和 user workflow 不变。ExtensionWorkflowDefinition与SavedWorkflowEntry带上whenToUse,去掉首尾空白,并与描述一样截到 500 字符;空白值视为未声明。在交互界面之外,命令返回的
{type:'tool'}无法执行,包括模型经 Skill 工具调起命令、headlessqwen -p和 ACP。这些场景下,已保存 workflow 的命令现在展开为一段提示:Run the "<name>" workflow.、描述、适用条件,以及Invoke: Workflow({ name: "<name>", args: … })。提示里用的是限定名,所以命令和同扩展的同名 skill 撞名被改名后,仍然运行正确的 workflow。在交互界面里敲/<name>仍按脚本路径直接调度工具,行为不变。既然所有模式都能运行,命令也不再只声明交互模式。Workflow 工具显式请求规则的第 3 条改为:"A skill or slash command that ran — invoked by the user, or by you through the Skill tool — instructs you to use this tool."
为什么需要
扩展随附的 workflow 没有办法让模型自行选用。命令从来不是模型可调用的;三处模型可调用命令执行器只接受
submit_prompt,即便列出来也会返回"找不到";规则也只承认用户调起的命令。想让模型跑扩展 workflow,只能在 QWEN.md 里要求它这么做,而这既不被显式请求规则承认,也不管扩展作者的意图,匹配就触发。Claude Code 把每个 workflow 包装成带
whenToUse的 prompt 命令,展开为 "Run the … workflow … Invoke: Workflow({name})"。本 PR 采用相同形态,但只列出作者写了适用条件的扩展 workflow:作者说明何时适用、用户同意安装扩展、workflows 已开启、运行经过审批。验证方式
开启 workflows,安装或链接一个扩展:
workflows/analysis.js声明whenToUse,另一个脚本不声明。开一个会话,问一个符合条件的问题。模型应以 workflow 名调用 Skill 工具,拿到 "Run the … workflow" 文本,再调用Workflow({ name }),弹出常规审批,标题为Saved workflow: <name>。没写whenToUse的 workflow 不出现在模型的技能列表里。在交互界面敲
/dataworks:analysis:和之前一样直接启动。运行qwen -p '/dataworks:analysis':命令不再被判为不支持,而是由模型按名运行 workflow。单元测试命令见英文部分。本地 core 330 个、cli 137 个测试通过,core 类型检查与改动文件的 ESLint 通过。cli 类型检查交给 CI:本地它读取其他 workspace 包的旧构建产物。
证据
见英文部分代码块:用本分支真实模块从磁盘发现扩展、经
SavedWorkflowLoader适配、注册到CommandService,并用技能列表渲染器输出。改动之前,列表里两个 workflow 都没有条目;命令在任何上下文都返回工具调度,执行器会把它变成"找不到",headless 会报 "Tool execution from slash commands is not supported in non-interactive mode.";两个命令都只在交互模式下列出。测试平台
Linux ✅;macOS⚠️ ;Windows ⚠️ 。
风险与范围
whenToUse、用户同意安装扩展、tools.workflowsEnabled已开启(默认关闭)、用户批准这次运行。whenToUse写在脚本里,改动它会改变内容摘要,下次更新会重新征求同意。headless 与 ACP 的/<name>现在经模型一轮运行,而不是被拒绝,与 skill 和文件命令在这些场景的行为一致。whenToUse可依据);disableModelInvocationmeta 字段;把交互式/<name>从scriptPath改为name,那会让已有的Workflow(scriptPath:…)规则不再匹配。关联 Issue
Part of #11013