@qwen-code/qwen-code 0.16.0-preview.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/qc-helper/docs/configuration/settings.md +39 -37
- package/bundled/qc-helper/docs/features/_meta.ts +2 -0
- package/bundled/qc-helper/docs/features/approval-mode.md +119 -2
- package/bundled/qc-helper/docs/features/auto-mode.md +263 -0
- package/bundled/qc-helper/docs/features/commands.md +11 -10
- package/bundled/qc-helper/docs/features/skills.md +3 -0
- package/bundled/qc-helper/docs/features/structured-output.md +309 -0
- package/bundled/qc-helper/docs/features/sub-agents.md +47 -5
- package/bundled/qc-helper/docs/qwen-serve.md +119 -14
- package/bundled/review/SKILL.md +12 -3
- package/chunks/{agent-ZNQPH67I.js → agent-2JCG7FDJ.js} +9 -11
- package/chunks/{anthropicContentGenerator-ICBDZ6R2.js → anthropicContentGenerator-RQJNXJIY.js} +6 -3
- package/chunks/{askUserQuestion-WQILGUSQ.js → askUserQuestion-PQPMPNM3.js} +1 -1
- package/chunks/{ca-S3XJMT6P.js → ca-UZ7BANMN.js} +3 -3
- package/chunks/{chunk-XVHR7ATJ.js → chunk-4AOCVI6J.js} +1 -0
- package/chunks/chunk-7LMPOVYW.js +956 -0
- package/chunks/{chunk-MNPZ2WO6.js → chunk-BAZDG3QU.js} +9104 -3955
- package/chunks/{chunk-AEJ2DKLP.js → chunk-C6WMLUNB.js} +1 -1
- package/chunks/{chunk-PPHYLJSS.js → chunk-CAVZVZX6.js} +2 -2
- package/chunks/{chunk-VTPOO6GV.js → chunk-CSWBPY3P.js} +1 -1
- package/chunks/chunk-D6LBYOCX.js +19126 -0
- package/chunks/{chunk-NAID3ZWF.js → chunk-DMIMF3CG.js} +1 -1
- package/chunks/{chunk-2B7UBDY5.js → chunk-GGNTZ2NH.js} +90 -19
- package/chunks/chunk-HW5S7L73.js +379 -0
- package/chunks/{chunk-C3LHPHN2.js → chunk-JCR2WRXZ.js} +1227 -657
- package/chunks/{chunk-EDYSNFEM.js → chunk-L5E26RN6.js} +2 -2
- package/chunks/{chunk-CW44BRRA.js → chunk-MAY32HXD.js} +375 -0
- package/chunks/{chunk-7QXHXMC6.js → chunk-N6GSJHZ4.js} +96 -20
- package/chunks/{chunk-FZIUV27X.js → chunk-PVVL5Q3W.js} +31 -0
- package/chunks/{chunk-YHEAJFCI.js → chunk-USE2VQ5P.js} +3 -0
- package/chunks/{chunk-JYQUJ5DS.js → chunk-YJLGXDQJ.js} +1 -1
- package/chunks/{contextCommand-IGBCEXI4.js → contextCommand-XVRGKS3Q.js} +11 -13
- package/chunks/{cron-create-AVI3Q267.js → cron-create-IGYXQVG4.js} +27 -1
- package/chunks/{cron-delete-ZCEGDXXV.js → cron-delete-ETKIZCWT.js} +1 -1
- package/chunks/{cron-list-VN653OK5.js → cron-list-BVCUSWRU.js} +1 -1
- package/chunks/{de-MNR4SMAI.js → de-V4IE2OOZ.js} +3 -3
- package/chunks/{dist-RRYNPBOE.js → dist-4L54HRX2.js} +2 -2
- package/chunks/{dist-WP4AH3VK.js → dist-BXDUQ2QY.js} +1 -1
- package/chunks/{dist-M6GFCZ7S.js → dist-MN2PDDPR.js} +1 -1
- package/chunks/{edit-74Q4AFHQ.js → edit-3MLXHQPW.js} +22 -12
- package/chunks/{en-FIUWJSZR.js → en-HGJ2SPLM.js} +4 -3
- package/chunks/{enter-worktree-H72HXC7D.js → enter-worktree-OCA4SG6D.js} +35 -11
- package/chunks/{exit-worktree-FGIQO3S3.js → exit-worktree-6EDLXVEV.js} +35 -11
- package/chunks/{exitPlanMode-NBR2PK2D.js → exitPlanMode-H75KHRX4.js} +9 -11
- package/chunks/{fr-OFJFHLCR.js → fr-CJULI7ZX.js} +3 -3
- package/chunks/{geminiContentGenerator-33RP4WKD.js → geminiContentGenerator-E7Y6TCPU.js} +1 -1
- package/chunks/{glob-WEE3CJL6.js → glob-JFFSKARO.js} +9 -11
- package/chunks/{grep-DZKSBFZK.js → grep-7TAFR7MX.js} +9 -11
- package/chunks/{ja-V6OQ6VL7.js → ja-L7CHRQEW.js} +3 -3
- package/chunks/{ls-6F3VSP6S.js → ls-7HD6XG3V.js} +1 -1
- package/chunks/{lsp-67Y7DJN5.js → lsp-ZZSFCIWD.js} +1 -1
- package/chunks/{monitor-EDZWEZVS.js → monitor-YX2ABLXH.js} +21 -11
- package/chunks/notebook-edit-EEJEGFZR.js +756 -0
- package/chunks/{openaiContentGenerator-5NQG3W64.js → openaiContentGenerator-BSAWHGQJ.js} +9 -8
- package/chunks/{pt-ZLE6SA4A.js → pt-M6JULLEQ.js} +3 -3
- package/chunks/{qwenContentGenerator-4DPUUS6R.js → qwenContentGenerator-47XRHQXM.js} +11 -13
- package/chunks/{qwenOAuth2-JE7H47TE.js → qwenOAuth2-EEJGROP7.js} +7 -1
- package/chunks/{read-file-CQOF7BQ2.js → read-file-O53WD46Y.js} +4 -5
- package/chunks/{ripGrep-KR5LKGTI.js → ripGrep-OXNZ5Z3T.js} +9 -11
- package/chunks/{ru-A4OHIUNN.js → ru-QILM4HBC.js} +3 -3
- package/chunks/{send-message-GB4AQZNC.js → send-message-ULK4MQXJ.js} +22 -1
- package/chunks/{serve-GAD2PEST.js → serve-H2REZAYD.js} +13728 -3026
- package/chunks/{shell-E2HMCBGR.js → shell-DET66JID.js} +9 -11
- package/chunks/{skill-KDZH6UZ6.js → skill-ZIXPX3L3.js} +19 -6
- package/chunks/{src-LY4RU5AI.js → src-PN3XGQYP.js} +205 -13
- package/chunks/{syntheticOutput-HFL3DE7R.js → syntheticOutput-IS2X5OZ2.js} +2 -2
- package/chunks/{task-stop-ZQF26RXS.js → task-stop-7QSJGSSP.js} +1 -1
- package/chunks/{todoWrite-U4SC643O.js → todoWrite-7CVACFUX.js} +2 -2
- package/chunks/{tool-search-U4XQVLFU.js → tool-search-GTYLSGZ3.js} +4 -5
- package/chunks/{web-fetch-BRWZ4WSE.js → web-fetch-ENQ2I5JA.js} +5 -2
- package/chunks/{write-file-NBLRMNGB.js → write-file-NILNEZCR.js} +19 -12
- package/chunks/{zh-V32QONGV.js → zh-PWL2NKY3.js} +4 -3
- package/chunks/{zh-TW-552S24LR.js → zh-TW-S3YGWICZ.js} +4 -3
- package/cli.js +14832 -49049
- package/locales/ca.js +4 -5
- package/locales/de.js +4 -5
- package/locales/en.js +6 -5
- package/locales/fr.js +4 -5
- package/locales/ja.js +4 -5
- package/locales/pt.js +4 -5
- package/locales/ru.js +4 -5
- package/locales/zh-TW.js +5 -4
- package/locales/zh.js +5 -4
- package/package.json +2 -2
- package/chunks/chunk-3MBY4GKN.js +0 -350
- package/chunks/chunk-5P5XGNYH.js +0 -93
- package/chunks/chunk-JHMX4QTD.js +0 -2306
- package/chunks/chunk-SYCJMSIJ.js +0 -82
- package/chunks/chunk-Y6Z2O3WR.js +0 -33
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Auto Mode
|
|
2
|
+
|
|
3
|
+
Auto Mode uses an LLM classifier to evaluate each tool call and decide
|
|
4
|
+
whether to auto-approve it. It sits between Auto-Edit (which only
|
|
5
|
+
auto-approves file edits) and YOLO (which auto-approves everything).
|
|
6
|
+
|
|
7
|
+
This page is the reference for configuring and troubleshooting Auto Mode.
|
|
8
|
+
For an introduction, see the
|
|
9
|
+
[Approval Mode overview](./approval-mode.md#4-auto-mode---classifier-driven-approval).
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
When you're in Auto Mode and the agent tries to run a tool, Qwen Code
|
|
14
|
+
walks three layers in order:
|
|
15
|
+
|
|
16
|
+
1. **acceptEdits fast-path** — Edit / Write whose target path is inside
|
|
17
|
+
the workspace is auto-approved without invoking the classifier.
|
|
18
|
+
2. **Safe-tool allowlist** — Read-only and metadata-only built-in tools
|
|
19
|
+
(Read, Grep, Glob, LS, LSP, TodoWrite, AskUserQuestion, etc.) are
|
|
20
|
+
auto-approved without invoking the classifier.
|
|
21
|
+
3. **LLM classifier** — Everything else (shell commands, web fetches,
|
|
22
|
+
sub-agent spawns, edits outside the workspace, MCP tools) is sent to
|
|
23
|
+
a two-stage classifier:
|
|
24
|
+
- **Stage 1 (fast)** — outputs `{ shouldBlock }` only. Around ~300ms.
|
|
25
|
+
If `shouldBlock` is `false`, the action is allowed and the call
|
|
26
|
+
proceeds.
|
|
27
|
+
- **Stage 2 (thinking)** — only runs when Stage 1 said block. Uses
|
|
28
|
+
chain-of-thought review to reduce Stage 1 false positives. Can
|
|
29
|
+
downgrade Stage 1's block to allow. Outputs the user-visible
|
|
30
|
+
`reason` on block.
|
|
31
|
+
|
|
32
|
+
The classifier uses your configured fast model
|
|
33
|
+
(`/model --fast`). If no fast model is configured, the main session
|
|
34
|
+
model is used instead.
|
|
35
|
+
|
|
36
|
+
## Hard rules still win
|
|
37
|
+
|
|
38
|
+
Auto Mode does **not** replace hard permission rules. Before the classifier
|
|
39
|
+
runs:
|
|
40
|
+
|
|
41
|
+
- `permissions.deny` rules block the action with the rule's reason. The
|
|
42
|
+
classifier never sees it.
|
|
43
|
+
- `permissions.allow` rules with specific specifiers (e.g.
|
|
44
|
+
`Bash(git status)`, `Read(./docs/**)`) still auto-allow without the
|
|
45
|
+
classifier.
|
|
46
|
+
- `permissions.ask` rules force manual confirmation even in Auto Mode.
|
|
47
|
+
|
|
48
|
+
## Over-broad allow rules are stripped while in Auto Mode
|
|
49
|
+
|
|
50
|
+
Rules like the following would let the agent execute arbitrary code
|
|
51
|
+
without classifier review:
|
|
52
|
+
|
|
53
|
+
- `Bash` / `Bash(*)` / `Bash()` — auto-allow every shell command
|
|
54
|
+
- `Bash(python:*)`, `Bash(node*)`, `Bash(bash*)` — interpreter wildcards
|
|
55
|
+
- `Agent` / `Agent(coder)` — any allow on the Agent tool
|
|
56
|
+
- `Skill` / `Skill(pdf)` — any allow on the Skill tool
|
|
57
|
+
|
|
58
|
+
When you enter Auto Mode, Qwen Code temporarily removes these rules from
|
|
59
|
+
the active permission set and prints a notice listing them. The rules
|
|
60
|
+
come back the moment you leave Auto Mode. `settings.json` is never
|
|
61
|
+
modified.
|
|
62
|
+
|
|
63
|
+
If you genuinely need these broad rules, use YOLO mode instead.
|
|
64
|
+
|
|
65
|
+
## Configuring hints
|
|
66
|
+
|
|
67
|
+
Auto Mode reads `permissions.autoMode` from your `settings.json`. The
|
|
68
|
+
entries are natural-language descriptions, not rule patterns — they are
|
|
69
|
+
injected additively into the classifier's system prompt alongside the
|
|
70
|
+
built-in defaults.
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"permissions": {
|
|
75
|
+
"autoMode": {
|
|
76
|
+
"hints": {
|
|
77
|
+
"allow": [
|
|
78
|
+
"Running poetry install and poetry update in this Python project",
|
|
79
|
+
"Cleaning build artifacts under ./dist or ./build",
|
|
80
|
+
"Reading any file under /Users/me/code/"
|
|
81
|
+
],
|
|
82
|
+
"deny": [
|
|
83
|
+
"Any network call to intranet.example.com endpoints",
|
|
84
|
+
"Modifying anything under ~/.ssh or ~/.aws",
|
|
85
|
+
"Running migration scripts that touch the production DB"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"environment": [
|
|
89
|
+
"This is a private monorepo with strict commit signing",
|
|
90
|
+
"Production credentials live in 1Password, never in plain files"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Length and count limits
|
|
98
|
+
|
|
99
|
+
To keep the classifier system prompt small:
|
|
100
|
+
|
|
101
|
+
- Each entry is capped at 200 characters (longer entries are truncated
|
|
102
|
+
with a warning).
|
|
103
|
+
- `hints.allow` and `hints.deny` accept up to 50 entries each.
|
|
104
|
+
- `environment` accepts up to 20 entries.
|
|
105
|
+
|
|
106
|
+
### Layering across settings files
|
|
107
|
+
|
|
108
|
+
`autoMode` is merged across system / user / workspace settings the same
|
|
109
|
+
way other permission settings are: arrays are concatenated and
|
|
110
|
+
de-duplicated.
|
|
111
|
+
|
|
112
|
+
## Reading the decision
|
|
113
|
+
|
|
114
|
+
When the classifier blocks an action, the tool call fails with one of
|
|
115
|
+
the following error texts:
|
|
116
|
+
|
|
117
|
+
- **`Blocked by auto mode policy: <reason>`** — the classifier judged
|
|
118
|
+
the action unsafe. The reason comes from Stage 2 of the classifier.
|
|
119
|
+
- **`Auto mode classifier unavailable; action blocked for safety`** —
|
|
120
|
+
the classifier API was unreachable, timed out, or returned an
|
|
121
|
+
un-parseable response. This is fail-closed behavior: when in doubt,
|
|
122
|
+
block.
|
|
123
|
+
|
|
124
|
+
The main LLM sees the same message in the tool result and adjusts its
|
|
125
|
+
approach (asks you, switches tactic, gives up).
|
|
126
|
+
|
|
127
|
+
### Classifier reason language
|
|
128
|
+
|
|
129
|
+
Classifier reasons are produced by the LLM and are not translated. If you
|
|
130
|
+
want non-English reasons, add a hint like
|
|
131
|
+
`Respond reasons in Chinese` to `permissions.autoMode.environment`.
|
|
132
|
+
|
|
133
|
+
## Fallback to manual approval
|
|
134
|
+
|
|
135
|
+
Auto Mode protects you from getting stuck:
|
|
136
|
+
|
|
137
|
+
- After **3 consecutive policy blocks** the next tool call falls back to
|
|
138
|
+
the standard manual-approval prompt. This catches the case where the
|
|
139
|
+
agent keeps trying minor variants of a forbidden command.
|
|
140
|
+
- After **2 consecutive unavailable** results (classifier API failures)
|
|
141
|
+
the next tool call also falls back. This avoids waiting on a broken
|
|
142
|
+
classifier.
|
|
143
|
+
|
|
144
|
+
The session itself stays in Auto Mode — only the single fallback call
|
|
145
|
+
goes through manual approval. The counters reset when you approve the
|
|
146
|
+
fallback call or switch modes.
|
|
147
|
+
|
|
148
|
+
If you find yourself constantly hitting fallback, the most likely causes
|
|
149
|
+
are an outage on the classifier API or hints that need tuning. Switch to
|
|
150
|
+
Default Mode while you investigate.
|
|
151
|
+
|
|
152
|
+
## Troubleshooting
|
|
153
|
+
|
|
154
|
+
**"Auto mode keeps blocking my commands"**
|
|
155
|
+
|
|
156
|
+
Look at the reason in the error message. If the classifier is being too
|
|
157
|
+
conservative for your context, add an entry to
|
|
158
|
+
`permissions.autoMode.hints.allow` describing the pattern in
|
|
159
|
+
natural-language. Examples:
|
|
160
|
+
|
|
161
|
+
- `"Building Docker images for this project (docker build ...)"`
|
|
162
|
+
- `"Running database migrations against the local test DB"`
|
|
163
|
+
|
|
164
|
+
**"Auto mode classifier unavailable"**
|
|
165
|
+
|
|
166
|
+
The classifier API didn't respond. Possible causes:
|
|
167
|
+
|
|
168
|
+
- Network issue between you and the model endpoint.
|
|
169
|
+
- The configured fast model is no longer available — check `/model --fast`.
|
|
170
|
+
- The transcript is too long and exceeds the fast-model context window.
|
|
171
|
+
|
|
172
|
+
While diagnosing, switch back to Default Mode: `/approval-mode default`.
|
|
173
|
+
|
|
174
|
+
**"Falling back to manual approval"**
|
|
175
|
+
|
|
176
|
+
You've hit either the 3-consecutive-block or 2-consecutive-unavailable
|
|
177
|
+
guard. Approve or reject the prompt as you normally would. After one
|
|
178
|
+
approved fallback the consecutive counter resets.
|
|
179
|
+
|
|
180
|
+
**The classifier sees sensitive data in my prompts**
|
|
181
|
+
|
|
182
|
+
Tool inputs are projected through each tool's `toAutoClassifierInput`
|
|
183
|
+
method before they reach the classifier. Long edit content, web fetch
|
|
184
|
+
prompts, and sub-agent prompts are truncated. Tool results (file
|
|
185
|
+
contents, web pages) are never sent to the classifier — only the user's
|
|
186
|
+
text and assistant tool-use calls go through.
|
|
187
|
+
|
|
188
|
+
If a specific tool is exposing fields you'd rather redact, file an issue
|
|
189
|
+
with the tool name; the projection is per-tool and is meant to be
|
|
190
|
+
tightened over time.
|
|
191
|
+
|
|
192
|
+
## Limitations
|
|
193
|
+
|
|
194
|
+
- **Not offline-capable.** The classifier requires an LLM call.
|
|
195
|
+
- **Adds latency on the slow path.** Allowlist + acceptEdits cover most
|
|
196
|
+
calls without latency, but a `run_shell_command` typically adds
|
|
197
|
+
~300ms (fast classifier path) or ~3-5s (slow path with thinking
|
|
198
|
+
review).
|
|
199
|
+
- **Not a substitute for `deny` rules.** The classifier is best-effort.
|
|
200
|
+
For commands you're sure should never run, put them in
|
|
201
|
+
`permissions.deny`.
|
|
202
|
+
- **MCP tools default to conservative blocking.** Third-party MCP tools
|
|
203
|
+
(`mcp__*`) opt-in to argument forwarding via the
|
|
204
|
+
`toAutoClassifierInput` override. Tools that have not opted in expose
|
|
205
|
+
only their name to the classifier — most such calls are
|
|
206
|
+
conservatively blocked unless you've written an explicit `allow`
|
|
207
|
+
rule. This is fail-closed by design (credentials and voluminous
|
|
208
|
+
content do not leak into the classifier LLM). If you trust a
|
|
209
|
+
specific MCP tool, add `permissions.allow: ["mcp__server__tool"]` so
|
|
210
|
+
it bypasses the classifier entirely.
|
|
211
|
+
|
|
212
|
+
## FAQ
|
|
213
|
+
|
|
214
|
+
**Does Auto Mode send my code to a third party?**
|
|
215
|
+
|
|
216
|
+
Auto Mode reuses your existing model configuration — same endpoint as
|
|
217
|
+
the main agent. If you've configured Qwen Code to use a self-hosted
|
|
218
|
+
model, the classifier runs against that endpoint too.
|
|
219
|
+
|
|
220
|
+
**Will my secrets / `.env` contents reach the classifier?**
|
|
221
|
+
|
|
222
|
+
The classifier sees only what each tool's `toAutoClassifierInput`
|
|
223
|
+
projection exposes:
|
|
224
|
+
|
|
225
|
+
- `read_file` and other read-only tools: not invoked (they're on the
|
|
226
|
+
fast-path allowlist).
|
|
227
|
+
- `edit` / `write_file`: file_path plus the first 80 characters of
|
|
228
|
+
old/new content. Full content is not forwarded.
|
|
229
|
+
- `run_shell_command`: the full command (it has to — that's what the
|
|
230
|
+
classifier judges).
|
|
231
|
+
- `web_fetch`: the URL only. The prompt field is not forwarded.
|
|
232
|
+
- `agent`: subagent type plus the full prompt. The prompt is the
|
|
233
|
+
instruction the sub-agent will follow, so the classifier needs it
|
|
234
|
+
in full to detect attacks that would steer the sub-agent toward
|
|
235
|
+
destructive actions — same reason `run_shell_command` forwards the
|
|
236
|
+
full command.
|
|
237
|
+
|
|
238
|
+
Tool results (the actual content returned by tools) are stripped from
|
|
239
|
+
the classifier transcript entirely.
|
|
240
|
+
|
|
241
|
+
MCP tools (`mcp__*`) follow a stricter default: their parameters are
|
|
242
|
+
not forwarded unless the MCP tool author explicitly opted in via the
|
|
243
|
+
`toAutoClassifierInput` override. The classifier sees the tool name
|
|
244
|
+
but no arguments, so most MCP calls will be conservatively blocked
|
|
245
|
+
unless the user has written an explicit allow rule. This is fail-
|
|
246
|
+
closed by design — third-party tools should not leak credentials or
|
|
247
|
+
voluminous file content into the classifier LLM without intent.
|
|
248
|
+
|
|
249
|
+
**Can I disable the first-time information message?**
|
|
250
|
+
|
|
251
|
+
It only shows once per user-settings file. After dismissal,
|
|
252
|
+
`ui.autoModeAcknowledged: true` is set in your user settings.
|
|
253
|
+
|
|
254
|
+
**How is this different from Auto-Edit?**
|
|
255
|
+
|
|
256
|
+
Auto-Edit auto-approves file edits and nothing else — shell commands
|
|
257
|
+
still ask. Auto Mode uses a classifier to also auto-approve safe shell
|
|
258
|
+
commands and other tool calls while still blocking risky ones.
|
|
259
|
+
|
|
260
|
+
**How is this different from YOLO?**
|
|
261
|
+
|
|
262
|
+
YOLO auto-approves everything without any review. Auto Mode has the
|
|
263
|
+
classifier in the loop and blocks risky actions.
|
|
@@ -212,16 +212,17 @@ this setting.
|
|
|
212
212
|
|
|
213
213
|
Commands for obtaining information and performing system settings.
|
|
214
214
|
|
|
215
|
-
| Command
|
|
216
|
-
|
|
|
217
|
-
| `/help`
|
|
218
|
-
| `/
|
|
219
|
-
| `/
|
|
220
|
-
| `/
|
|
221
|
-
| `/
|
|
222
|
-
| `/
|
|
223
|
-
| `/
|
|
224
|
-
| `/
|
|
215
|
+
| Command | Description | Usage Examples |
|
|
216
|
+
| --------------- | ----------------------------------------------- | -------------------------------- |
|
|
217
|
+
| `/help` | Display help information for available commands | `/help` or `/?` |
|
|
218
|
+
| `/status` | Display version information | `/status` or `/about` |
|
|
219
|
+
| `/status paths` | Display current session file and log paths | `/status paths` |
|
|
220
|
+
| `/stats` | Display detailed statistics for current session | `/stats` |
|
|
221
|
+
| `/settings` | Open settings editor | `/settings` |
|
|
222
|
+
| `/auth` | Change authentication method | `/auth` |
|
|
223
|
+
| `/bug` | Submit issue about Qwen Code | `/bug Button click unresponsive` |
|
|
224
|
+
| `/copy` | Copy last output content to clipboard | `/copy` |
|
|
225
|
+
| `/quit` | Exit Qwen Code immediately | `/quit` or `/exit` |
|
|
225
226
|
|
|
226
227
|
### 1.9 Common Shortcuts
|
|
227
228
|
|
|
@@ -74,6 +74,7 @@ Create a `SKILL.md` file with YAML frontmatter and Markdown content:
|
|
|
74
74
|
---
|
|
75
75
|
name: your-skill-name
|
|
76
76
|
description: Brief description of what this Skill does and when to use it
|
|
77
|
+
priority: 10
|
|
77
78
|
---
|
|
78
79
|
|
|
79
80
|
# Your Skill Name
|
|
@@ -91,11 +92,13 @@ Qwen Code currently validates that:
|
|
|
91
92
|
|
|
92
93
|
- `name` is a non-empty string matching `/^[\p{L}\p{N}_:.-]+$/u` — Unicode letters and digits (CJK / Cyrillic / accented Latin all OK), plus `_`, `:`, `.`, `-`. Whitespace, slashes, brackets and other structurally unsafe characters are rejected at parse time.
|
|
93
94
|
- `description` is a non-empty string
|
|
95
|
+
- `priority` is optional. When present, it must be a finite number. Higher values sort earlier in the `/skills` listing only — slash-command completion (typing `/`) and the `/help` custom commands view stay alphabetical, so a high-priority Skill never reorders built-in commands. Omitted or invalid values are treated as unset, which behaves like `0`.
|
|
94
96
|
|
|
95
97
|
Recommended conventions:
|
|
96
98
|
|
|
97
99
|
- Prefer lowercase ASCII with hyphens for shareable names (e.g. `tsx-helper`)
|
|
98
100
|
- Make `description` specific: include both **what** the Skill does and **when** to use it (key words users will naturally mention)
|
|
101
|
+
- Use `priority` sparingly for Skills that should reliably appear before the default alphabetical order in `/skills`. Negative priorities are allowed and sort below unset Skills.
|
|
99
102
|
|
|
100
103
|
### Optional: gate a Skill on file paths (`paths:`)
|
|
101
104
|
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Structured Output (`--json-schema`)
|
|
2
|
+
|
|
3
|
+
Constrain the model's final answer to a JSON Schema you supply. Qwen
|
|
4
|
+
Code registers a synthetic terminal tool the model is required to call,
|
|
5
|
+
parses the call's arguments against your schema, and exposes the
|
|
6
|
+
validated payload on stdout (or in the JSON / stream-json result
|
|
7
|
+
envelope). The first valid call ends the run.
|
|
8
|
+
|
|
9
|
+
Headless only — works with `qwen -p`, a positional prompt, or a prompt
|
|
10
|
+
piped via stdin.
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
qwen --prompt "Summarize the changes in HEAD with risk_level" \
|
|
16
|
+
--json-schema '{
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"summary": { "type": "string" },
|
|
20
|
+
"risk_level": { "type": "string", "enum": ["low", "medium", "high"] }
|
|
21
|
+
},
|
|
22
|
+
"required": ["summary", "risk_level"],
|
|
23
|
+
"additionalProperties": false
|
|
24
|
+
}'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Output on stdout (default `--output-format text`):
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{ "summary": "…", "risk_level": "low" }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The line is exactly the JSON-stringified payload + newline — no
|
|
34
|
+
envelope, no event log. Pipe it straight into `jq` or another consumer.
|
|
35
|
+
|
|
36
|
+
In **text** mode, stdout is reserved for the JSON payload on success
|
|
37
|
+
and is empty on failure; error messages and log lines go to stderr.
|
|
38
|
+
That makes `$(qwen --json-schema …) || exit 1` capture patterns safe
|
|
39
|
+
under text mode — failures land in stderr, not mixed into the captured
|
|
40
|
+
variable. The model's incidental prose during planning is **not**
|
|
41
|
+
mirrored to stderr either — text mode discards it; reach for
|
|
42
|
+
`--output-format json` or `stream-json` if you need to see it.
|
|
43
|
+
|
|
44
|
+
In `--output-format json` and `stream-json`, the failure result
|
|
45
|
+
message is emitted on **stdout** alongside the success path (as the
|
|
46
|
+
final element of the JSON array, or the terminating `result` line on
|
|
47
|
+
the JSONL stream). Not all failure modes emit a result to stdout —
|
|
48
|
+
max-session-turns (exit 53) and signal interrupts (exit 130) exit with
|
|
49
|
+
stderr output only. Check the exit code first; `is_error` on the
|
|
50
|
+
result object disambiguates within the subset of failures that do
|
|
51
|
+
produce a result event.
|
|
52
|
+
|
|
53
|
+
> **Empty schema:** Passing `{}` produces `{}` (an empty JSON object)
|
|
54
|
+
> on stdout. The model calls `structured_output` with no arguments;
|
|
55
|
+
> the upstream argument-normalisation path turns the empty function
|
|
56
|
+
> call into an empty-object payload, which passes validation against
|
|
57
|
+
> the empty schema and is emitted verbatim.
|
|
58
|
+
|
|
59
|
+
## Supplying the schema
|
|
60
|
+
|
|
61
|
+
Two equivalent forms:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Inline JSON literal
|
|
65
|
+
qwen -p "…" --json-schema '{"type":"object", "properties":{…}}'
|
|
66
|
+
|
|
67
|
+
# Read from a file
|
|
68
|
+
qwen -p "…" --json-schema @./schemas/summary.json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `@path` form expands `~`, normalizes the path, and reads the file
|
|
72
|
+
with `utf8` encoding.
|
|
73
|
+
|
|
74
|
+
> **Latency note:** Successful runs incur a shutdown holdback **capped
|
|
75
|
+
> at ~500 ms** while in-flight background agents flush their final
|
|
76
|
+
> notifications before the result is emitted. The holdback exits early
|
|
77
|
+
> if no background tasks are pending, so simple runs barely notice it;
|
|
78
|
+
> batch pipelines that fan out hundreds of `--json-schema` invocations
|
|
79
|
+
> against busy agents should account for this upper bound.
|
|
80
|
+
|
|
81
|
+
> **Security note:** Schemas may contain user-supplied regular
|
|
82
|
+
> expressions in `pattern` keywords. Ajv compiles these with the
|
|
83
|
+
> ECMAScript regex engine, which is vulnerable to catastrophic
|
|
84
|
+
> backtracking. Because tool arguments are always objects, the
|
|
85
|
+
> `pattern` keyword only fires inside string properties — a malicious
|
|
86
|
+
> schema like
|
|
87
|
+
> `{"type":"object","properties":{"value":{"type":"string","pattern":"(a+)+b"}}}`
|
|
88
|
+
> can hang the CLI when the model supplies a moderately long
|
|
89
|
+
> matching value. Only run `--json-schema` with schemas from sources
|
|
90
|
+
> you trust.
|
|
91
|
+
|
|
92
|
+
Validation at parse time:
|
|
93
|
+
|
|
94
|
+
- The file must be a regular file (no FIFOs, character devices, or
|
|
95
|
+
directories).
|
|
96
|
+
- File size is capped at 4 MiB. Real-world JSON schemas are well under
|
|
97
|
+
this; multi-MiB files almost always indicate a wrong-path mistake.
|
|
98
|
+
- The schema must be valid JSON. For `@path` input, the parse error is
|
|
99
|
+
generic ("content of `<path>` is not valid JSON") rather than echoing
|
|
100
|
+
the SyntaxError detail, so a wrapping process that surfaces stderr
|
|
101
|
+
can't read a prefix of the file's contents back from the error.
|
|
102
|
+
- The schema must compile under the strict Ajv configuration —
|
|
103
|
+
typos like `propertees` are surfaced, but spec-valid patterns
|
|
104
|
+
(e.g. `required` without listing every key in `properties`) are
|
|
105
|
+
accepted.
|
|
106
|
+
- The schema root must accept object-typed values. Function-calling
|
|
107
|
+
APIs (Gemini, OpenAI, Anthropic) all require tool arguments to be
|
|
108
|
+
JSON objects, so a non-object root would register an unusable tool.
|
|
109
|
+
|
|
110
|
+
The root-acceptance check walks `type`, `const`, `enum`, `anyOf`,
|
|
111
|
+
`oneOf`, `allOf`, `not`, and `if`/`then`/`else` (best-effort for the
|
|
112
|
+
decidable cases). When in doubt it defers to Ajv at runtime.
|
|
113
|
+
|
|
114
|
+
> **Root `$ref` is rejected** by the parse-time check. If your schema
|
|
115
|
+
> reuses a definition via `$ref`, wrap it in `allOf`:
|
|
116
|
+
>
|
|
117
|
+
> ```jsonc
|
|
118
|
+
> // Rejected:
|
|
119
|
+
> { "$ref": "#/$defs/MyObj", "$defs": { "MyObj": { "type": "object", "properties": { "name": { "type": "string" } } } } }
|
|
120
|
+
>
|
|
121
|
+
> // Accepted (root accepts objects via the allOf branch):
|
|
122
|
+
> { "allOf": [{ "$ref": "#/$defs/MyObj" }], "$defs": { "MyObj": { "type": "object", "properties": { "name": { "type": "string" } } } } }
|
|
123
|
+
> ```
|
|
124
|
+
>
|
|
125
|
+
> `$ref` inside `anyOf` / `oneOf` / `allOf` is deferred to Ajv at
|
|
126
|
+
> runtime, so the wrapped form passes the root-acceptance check.
|
|
127
|
+
|
|
128
|
+
## Output shape per format
|
|
129
|
+
|
|
130
|
+
| `--output-format` | What goes to stdout |
|
|
131
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
132
|
+
| `text` (default) | `JSON.stringify(payload) + "\n"` — one line, the validated object. |
|
|
133
|
+
| `json` | A single JSON **array** of message objects (the full event log). The final element is the `type: "result"` message, which carries both `result` (`JSON.stringify(payload)`) and `structured_result` (the raw object). |
|
|
134
|
+
| `stream-json` | Each event on its own line as JSONL. The terminating `result` line carries `result` (stringified) and `structured_result` (raw object). |
|
|
135
|
+
|
|
136
|
+
In both JSON formats, prefer reading `structured_result` over `result`
|
|
137
|
+
when you want the object; `result` is the stringified form provided for
|
|
138
|
+
consumers that always expect a string in that field. For `--output-format
|
|
139
|
+
json`, read the last element of the array and pull `structured_result`
|
|
140
|
+
from there (e.g. `jq '.[-1].structured_result'`); for `stream-json`,
|
|
141
|
+
read the final `type: "result"` line on the stream.
|
|
142
|
+
|
|
143
|
+
## Restrictions
|
|
144
|
+
|
|
145
|
+
| Combination | Behavior |
|
|
146
|
+
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| `--json-schema` + `-i` / `--prompt-interactive` | Rejected at parse time. The synthetic tool's "session ends now" message has no terminator in the TUI loop. |
|
|
148
|
+
| `--json-schema` + `--input-format stream-json` | Rejected at parse time. The single-shot terminal contract is incompatible with the long-lived stream-json input protocol. |
|
|
149
|
+
| `--json-schema` + `--acp` / `--experimental-acp` | Rejected at parse time. ACP runs its own turn loop that doesn't honor the synthetic-tool terminal contract. |
|
|
150
|
+
| `--json-schema` with no prompt and no piped stdin | Rejected at parse time. Headless mode needs a prompt — pass `-p`, a positional argument, or pipe one in. |
|
|
151
|
+
| `--bare` + `--json-schema` | Supported. The synthetic tool is registered alongside the bare three (`read_file`, `edit`, `run_shell_command`). |
|
|
152
|
+
| `--json-schema` inside a subagent | Tool is NOT registered. Only the main / drain turns of the top-level run honor the terminal contract; a subagent calling the tool would receive "session ends now" and then keep running because its loop has no terminator. |
|
|
153
|
+
|
|
154
|
+
## Retry and failure modes
|
|
155
|
+
|
|
156
|
+
> **Cost note.** Two things multiply token spend in a `--json-schema`
|
|
157
|
+
> run, both worth designing for:
|
|
158
|
+
>
|
|
159
|
+
> - **Schema embedded in every turn.** The schema ships as the
|
|
160
|
+
> `structured_output` function declaration's `parameters` block on
|
|
161
|
+
> every model request, not just the first. Large schemas (up to the
|
|
162
|
+
> 4 MiB parse cap) proportionally increase per-turn input tokens
|
|
163
|
+
> for the entire run.
|
|
164
|
+
> - **Each validation retry is a full model turn.** A schema the
|
|
165
|
+
> model misses repeatedly is multiplied per failure (request +
|
|
166
|
+
> inference + response). Keep schemas constrained enough to guide
|
|
167
|
+
> the model and simple enough to nail on the first try; raise
|
|
168
|
+
> `--max-session-turns` when retries are expected.
|
|
169
|
+
|
|
170
|
+
The session ends on the first valid call. Until then:
|
|
171
|
+
|
|
172
|
+
- **Args fail validation.** `structured_output` returns a tool-result
|
|
173
|
+
error with Ajv's message, the model sees it on the next turn, and
|
|
174
|
+
may correct the arguments and call again.
|
|
175
|
+
- **Model calls a side-effecting tool in the same turn as
|
|
176
|
+
`structured_output`.** The pre-scan suppresses the sibling — it
|
|
177
|
+
never runs, regardless of whether the structured call ultimately
|
|
178
|
+
validates. The two paths split on what the model sees next:
|
|
179
|
+
- **Validation succeeds:** the run ends immediately, and the model
|
|
180
|
+
never gets another turn — the suppressed sibling is silently
|
|
181
|
+
discarded.
|
|
182
|
+
- **Validation fails:** the model gets another turn and sees a
|
|
183
|
+
synthesised "Skipped:" `tool_result` for the suppressed call,
|
|
184
|
+
so it can re-issue that call in a **separate turn** (one that
|
|
185
|
+
does not include `structured_output`).
|
|
186
|
+
- **Model emits plain text instead of calling
|
|
187
|
+
`structured_output`.** Exit code `1`. The error message includes
|
|
188
|
+
the turn count and a truncated preview of the model's output so
|
|
189
|
+
you can see what it actually said.
|
|
190
|
+
- **Run reaches `maxSessionTurns`.** Exit code `53`. Standard
|
|
191
|
+
"Reached max session turns" exit, plus a `--json-schema`-specific
|
|
192
|
+
hint that points at the three common stuck-run causes: model never
|
|
193
|
+
called the tool, `structured_output` is denied by permission rules,
|
|
194
|
+
or the schema is unsatisfiable.
|
|
195
|
+
- **Run is interrupted (SIGINT / Ctrl-C).** Exit code `130`. The
|
|
196
|
+
structured result is normally not emitted, but the shutdown
|
|
197
|
+
holdback loop does not poll the abort signal, so a SIGINT that
|
|
198
|
+
arrives after a successful call has been captured but before the
|
|
199
|
+
result reaches stdout may still land on stdout. Treat the exit
|
|
200
|
+
code as the source of truth.
|
|
201
|
+
|
|
202
|
+
## Privacy
|
|
203
|
+
|
|
204
|
+
The args you submit through `structured_output` ARE the structured
|
|
205
|
+
payload — already emitted on stdout. To avoid persisting the same
|
|
206
|
+
payload a second time into on-device surfaces that may be exported off
|
|
207
|
+
the machine, args are redacted with the placeholder
|
|
208
|
+
`{ __redacted: 'structured_output payload (see stdout result)' }` on:
|
|
209
|
+
|
|
210
|
+
- The `ToolCallEvent` telemetry path (OTLP exports, QwenLogger,
|
|
211
|
+
ui-telemetry stream, chat-recording UI event mirror).
|
|
212
|
+
- The on-disk chat-recording JSONL at
|
|
213
|
+
`~/.qwen/projects/<sanitized-cwd>/chats/<sessionId>.jsonl` (re-fed
|
|
214
|
+
into model context on `--continue` / `--resume`), including every
|
|
215
|
+
validation-failure retry.
|
|
216
|
+
|
|
217
|
+
Tool-call metrics (duration, success, decision) and surrounding event
|
|
218
|
+
metadata are preserved.
|
|
219
|
+
|
|
220
|
+
> **Schema is sent to the model provider.** Redaction covers the
|
|
221
|
+
> _call arguments_ on local surfaces only. The schema itself rides
|
|
222
|
+
> on every model request as the `structured_output` function
|
|
223
|
+
> declaration's `parameters` block — so any literal values you put
|
|
224
|
+
> inside it (`enum`, `const`, `default`, `examples`, `description`,
|
|
225
|
+
> `$comment`, etc.) reach the provider in cleartext just like prompt
|
|
226
|
+
> text. Schemas should describe shape and constraints; treat them as
|
|
227
|
+
> public toward the provider and keep secrets, customer records, and
|
|
228
|
+
> other sensitive payloads out of the schema body.
|
|
229
|
+
|
|
230
|
+
> **Hooks see raw args.** The redaction described above only applies
|
|
231
|
+
> to telemetry and chat-recording. `PreToolUse`, `PostToolUse`, and
|
|
232
|
+
> `PostToolUseFailure` hooks (including HTTP hooks that can forward
|
|
233
|
+
> payloads off-device) receive the unredacted `tool_input` for
|
|
234
|
+
> `structured_output`, since the hook contract is "see what the tool
|
|
235
|
+
> sees." If you operate audit-style catch-all hooks, either disable
|
|
236
|
+
> them for `structured_output` (filter on `tool_name`) or add
|
|
237
|
+
> hook-side redaction before running `--json-schema` against
|
|
238
|
+
> sensitive data.
|
|
239
|
+
|
|
240
|
+
## Session resumption (`--continue` / `--resume`)
|
|
241
|
+
|
|
242
|
+
`--json-schema` is a per-run flag, not a per-session property. The
|
|
243
|
+
synthetic tool is registered when the CLI parses its arguments, so:
|
|
244
|
+
|
|
245
|
+
- Re-pass `--json-schema` on every `--continue` / `--resume` you want
|
|
246
|
+
the terminal contract to apply to. The same schema as the original
|
|
247
|
+
run is the safe default — a mid-session schema swap is allowed but
|
|
248
|
+
changes the contract the model is being held to.
|
|
249
|
+
- If you `--continue` without `--json-schema`, the resumed run is an
|
|
250
|
+
ordinary headless session: `structured_output` simply doesn't
|
|
251
|
+
exist as a tool, and the model will respond in free-form text.
|
|
252
|
+
- The `__redacted` placeholder in the resumed chat-recording does
|
|
253
|
+
not affect resumability in practice. A successful `structured_output`
|
|
254
|
+
call terminates the session immediately, so the only redacted args
|
|
255
|
+
a resumed run could see are from failed attempts. The model still
|
|
256
|
+
has each attempt's Ajv validation error in the recorded `tool_result`
|
|
257
|
+
and the live parameter schema (re-registered from `--json-schema`),
|
|
258
|
+
which is enough to retry.
|
|
259
|
+
|
|
260
|
+
## Permission gating
|
|
261
|
+
|
|
262
|
+
`structured_output` deliberately bypasses the `--core-tools` allowlist:
|
|
263
|
+
the tool only exists when `--json-schema` is set, so excluding it
|
|
264
|
+
would leave the run with no terminal contract.
|
|
265
|
+
|
|
266
|
+
Explicit `permissions.deny` rules and `--exclude-tools` settings DO
|
|
267
|
+
take effect — both use the same deny mechanism and both prevent
|
|
268
|
+
`structured_output` from being registered, so the model never sees
|
|
269
|
+
the tool declaration. The typical result is that the model answers in
|
|
270
|
+
plain text (exit 1). If the model loops through other tools without
|
|
271
|
+
ever producing text, it will eventually hit `maxSessionTurns`
|
|
272
|
+
(exit 53) and the `--json-schema` hint in the error message tells you
|
|
273
|
+
where to look.
|
|
274
|
+
|
|
275
|
+
> **`--bare` caveat.** Bare mode ignores most settings-derived inputs,
|
|
276
|
+
> including settings-level `permissions.deny` and `tools.exclude`. The
|
|
277
|
+
> synthetic tool stays registered, so a settings-only deny of
|
|
278
|
+
> `structured_output` will silently no-op under `--bare`. Argv-level
|
|
279
|
+
> `--exclude-tools structured_output` still applies in bare mode — use
|
|
280
|
+
> the flag rather than settings if you need to lock down a bare run.
|
|
281
|
+
|
|
282
|
+
## Conflict with MCP tools
|
|
283
|
+
|
|
284
|
+
If an MCP server registers a tool literally named `structured_output`,
|
|
285
|
+
the tool-registry collision check renames the MCP tool to
|
|
286
|
+
`mcp__<server-name>__structured_output` so the synthetic tool keeps
|
|
287
|
+
the bare name. The user-supplied schema is always the one the model
|
|
288
|
+
sees.
|
|
289
|
+
|
|
290
|
+
## Example: gating a multi-step run on the structured output
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
RESULT=$(qwen --prompt "Audit this diff and rate its risk." \
|
|
294
|
+
--json-schema @./schemas/audit.json) || exit 1
|
|
295
|
+
|
|
296
|
+
risk=$(jq -r '.risk_level' <<<"$RESULT")
|
|
297
|
+
if [ "$risk" = "high" ]; then
|
|
298
|
+
echo "High-risk diff; pausing pipeline." >&2
|
|
299
|
+
exit 2
|
|
300
|
+
fi
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## See also
|
|
304
|
+
|
|
305
|
+
- [Headless Mode](headless.md) — the `-p`-based flow `--json-schema`
|
|
306
|
+
builds on.
|
|
307
|
+
- [Dual Output](dual-output.md) — records a JSON-event sidecar
|
|
308
|
+
alongside the TUI (a different approach to machine-readable output;
|
|
309
|
+
does not require `--json-schema`).
|