@qwen-code/qwen-code 0.15.12-preview.0 → 0.15.12-preview.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/features/commands.md +5 -6
- package/bundled/qc-helper/docs/features/hooks.md +299 -1
- package/bundled/qc-helper/docs/features/language.md +23 -16
- package/bundled/qc-helper/docs/qwen-serve.md +27 -19
- package/cli.js +6117 -5984
- package/locales/ca.js +0 -20
- package/locales/de.js +14 -19
- package/locales/en.js +13 -19
- package/locales/fr.js +13 -19
- package/locales/ja.js +12 -19
- package/locales/pt.js +13 -19
- package/locales/ru.js +13 -19
- package/locales/zh-TW.js +159 -164
- package/locales/zh.js +17 -24
- package/package.json +2 -2
|
@@ -45,12 +45,11 @@ Commands for adjusting interface appearance and work environment.
|
|
|
45
45
|
|
|
46
46
|
Commands specifically for controlling interface and output language.
|
|
47
47
|
|
|
48
|
-
| Command | Description
|
|
49
|
-
| --------------------- |
|
|
50
|
-
| `/language` | View or change language settings
|
|
51
|
-
| → `ui [language]` | Set UI interface language
|
|
52
|
-
| → `output [language]` | Set LLM output language
|
|
53
|
-
| → `translate on/off` | Toggle AI translation for dynamic slash command descriptions (default: off) | `/language translate on` |
|
|
48
|
+
| Command | Description | Usage Examples |
|
|
49
|
+
| --------------------- | -------------------------------- | -------------------------- |
|
|
50
|
+
| `/language` | View or change language settings | `/language` |
|
|
51
|
+
| → `ui [language]` | Set UI interface language | `/language ui zh-CN` |
|
|
52
|
+
| → `output [language]` | Set LLM output language | `/language output Chinese` |
|
|
54
53
|
|
|
55
54
|
- Available built-in UI languages: `zh-CN` (Simplified Chinese), `en-US` (English), `ru-RU` (Russian), `de-DE` (German), `ja-JP` (Japanese), `pt-BR` (Portuguese - Brazil), `fr-FR` (French), `ca-ES` (Catalan)
|
|
56
55
|
- Output language examples: `Chinese`, `English`, `Japanese`, etc.
|
|
@@ -30,13 +30,14 @@ Hooks are user-defined scripts or programs that are automatically executed by Qw
|
|
|
30
30
|
|
|
31
31
|
## Hook Types
|
|
32
32
|
|
|
33
|
-
Qwen Code supports
|
|
33
|
+
Qwen Code supports four hook executor types:
|
|
34
34
|
|
|
35
35
|
| Type | Description |
|
|
36
36
|
| :--------- | :--------------------------------------------------------------------------------------------- |
|
|
37
37
|
| `command` | Execute a shell command. Receives JSON via `stdin`, returns results via `stdout`. |
|
|
38
38
|
| `http` | Send JSON as a `POST` request body to a specified URL. Returns results via HTTP response body. |
|
|
39
39
|
| `function` | Directly call a registered JavaScript function (session-level hooks only). |
|
|
40
|
+
| `prompt` | Use an LLM to evaluate hook input and return a decision. |
|
|
40
41
|
|
|
41
42
|
### Command Hooks
|
|
42
43
|
|
|
@@ -134,6 +135,102 @@ Function hooks directly call registered JavaScript/TypeScript functions. They ar
|
|
|
134
135
|
|
|
135
136
|
**Note**: For most use cases, use **command hooks** or **HTTP hooks** instead, which can be configured in settings files.
|
|
136
137
|
|
|
138
|
+
### Prompt Hooks
|
|
139
|
+
|
|
140
|
+
Prompt hooks use an LLM to evaluate hook input and return a decision. This is useful for making intelligent decisions based on context, such as determining whether to allow or block an operation.
|
|
141
|
+
|
|
142
|
+
**How it works:**
|
|
143
|
+
|
|
144
|
+
1. The hook input JSON is injected into your prompt using the `$ARGUMENTS` placeholder
|
|
145
|
+
2. The prompt is sent to an LLM (default: your current model)
|
|
146
|
+
3. The LLM returns a JSON response with the decision
|
|
147
|
+
4. Qwen Code processes the decision and continues or blocks execution accordingly
|
|
148
|
+
|
|
149
|
+
**Configuration:**
|
|
150
|
+
|
|
151
|
+
| Field | Type | Required | Description |
|
|
152
|
+
| :-------------- | :--------- | :------- | :-------------------------------------------------- |
|
|
153
|
+
| `type` | `"prompt"` | Yes | Hook type |
|
|
154
|
+
| `prompt` | `string` | Yes | Prompt sent to LLM. Use `$ARGUMENTS` for hook input |
|
|
155
|
+
| `model` | `string` | No | Model to use (defaults to your current model) |
|
|
156
|
+
| `timeout` | `number` | No | Timeout in seconds, default 30 |
|
|
157
|
+
| `name` | `string` | No | Hook name (for logging) |
|
|
158
|
+
| `description` | `string` | No | Hook description |
|
|
159
|
+
| `statusMessage` | `string` | No | Status message displayed during execution |
|
|
160
|
+
|
|
161
|
+
**Response Format:**
|
|
162
|
+
|
|
163
|
+
The LLM must return JSON with the following structure:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"ok": true,
|
|
168
|
+
"reason": "Explanation of the decision",
|
|
169
|
+
"additionalContext": "Optional context to inject into the conversation"
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
| Field | Description |
|
|
174
|
+
| :------------------ | :------------------------------------------------------------------------- |
|
|
175
|
+
| `ok` | `true` to allow/continue, `false` to block/stop |
|
|
176
|
+
| `reason` | Required when `ok` is `false`. Shown to the model to explain the block |
|
|
177
|
+
| `additionalContext` | Optional. Additional context to inject into the conversation when allowing |
|
|
178
|
+
|
|
179
|
+
**Supported Events:**
|
|
180
|
+
|
|
181
|
+
Prompt hooks can be used with most hook events, including:
|
|
182
|
+
|
|
183
|
+
- `PreToolUse` - Evaluate whether to allow a tool call
|
|
184
|
+
- `PostToolUse` - Evaluate tool results and potentially inject context
|
|
185
|
+
- `Stop` - Determine whether to continue or stop
|
|
186
|
+
- `SubagentStop` - Evaluate subagent results
|
|
187
|
+
- `UserPromptSubmit` - Evaluate or enrich user prompts
|
|
188
|
+
|
|
189
|
+
**Example: Stop Hook**
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"hooks": {
|
|
194
|
+
"Stop": [
|
|
195
|
+
{
|
|
196
|
+
"hooks": [
|
|
197
|
+
{
|
|
198
|
+
"type": "prompt",
|
|
199
|
+
"prompt": "You are evaluating whether Qwen Code should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
|
|
200
|
+
"timeout": 30
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
When `ok` is `false`, Qwen Code will continue working and use the `reason` as context for the next response.
|
|
210
|
+
|
|
211
|
+
**Example: PreToolUse Hook**
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"hooks": {
|
|
216
|
+
"PreToolUse": [
|
|
217
|
+
{
|
|
218
|
+
"matcher": "Bash",
|
|
219
|
+
"hooks": [
|
|
220
|
+
{
|
|
221
|
+
"type": "prompt",
|
|
222
|
+
"prompt": "Evaluate this tool call for security concerns. Tool input: $ARGUMENTS\n\nCheck for:\n- Dangerous commands (rm -rf, curl | sh, etc.)\n- Unauthorized access attempts\n- Data exfiltration patterns\n\nRespond with {\"ok\": true} if safe, or {\"ok\": false, \"reason\": \"concern\"} if blocked.",
|
|
223
|
+
"model": "sonnet",
|
|
224
|
+
"timeout": 30,
|
|
225
|
+
"name": "security-evaluator"
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
137
234
|
## Hook Events
|
|
138
235
|
|
|
139
236
|
Hooks fire at specific points during a Qwen Code session. Different events support different matchers to filter trigger conditions.
|
|
@@ -152,6 +249,8 @@ Hooks fire at specific points during a Qwen Code session. Different events suppo
|
|
|
152
249
|
| `PreCompact` | Before conversation compaction | Trigger (`manual`, `auto`) |
|
|
153
250
|
| `Notification` | When notifications are sent | Type (`permission_prompt`, `idle_prompt`, `auth_success`) |
|
|
154
251
|
| `PermissionRequest` | When permission dialog is shown | Tool name |
|
|
252
|
+
| `TodoCreated` | When a new todo item is created | None (always fires) |
|
|
253
|
+
| `TodoCompleted` | When a todo item is marked as completed | None (always fires) |
|
|
155
254
|
|
|
156
255
|
### Matcher Patterns
|
|
157
256
|
|
|
@@ -165,6 +264,7 @@ Hooks fire at specific points during a Qwen Code session. Different events suppo
|
|
|
165
264
|
| Session Events | `SessionEnd` | ✅ Regex | Reason: `clear`, `logout`, `prompt_input_exit`, etc. |
|
|
166
265
|
| Notification Events | `Notification` | ✅ Exact match | Type: `permission_prompt`, `idle_prompt`, `auth_success` |
|
|
167
266
|
| Compact Events | `PreCompact` | ✅ Exact match | Trigger: `manual`, `auto` |
|
|
267
|
+
| Todo Events | `TodoCreated`, `TodoCompleted` | ❌ No | N/A |
|
|
168
268
|
| Prompt Events | `UserPromptSubmit` | ❌ No | N/A |
|
|
169
269
|
| Stop Events | `Stop` | ❌ No | N/A |
|
|
170
270
|
|
|
@@ -754,6 +854,204 @@ Hook output supports three categories of fields:
|
|
|
754
854
|
}
|
|
755
855
|
```
|
|
756
856
|
|
|
857
|
+
#### TodoCreated
|
|
858
|
+
|
|
859
|
+
**Purpose**: Executed when a new todo item is created via the `todo_write` tool. Allows validation, logging, or blocking of todo creation.
|
|
860
|
+
|
|
861
|
+
Todo hooks run in two phases:
|
|
862
|
+
|
|
863
|
+
- `validation`: runs before persistence. Use this phase for validation only; returning `block` or `deny` prevents the write.
|
|
864
|
+
- `postWrite`: runs after persistence. Use this phase for side effects such as logging or syncing; `block` or `deny` is ignored in this phase.
|
|
865
|
+
|
|
866
|
+
**Event-specific fields**:
|
|
867
|
+
|
|
868
|
+
```json
|
|
869
|
+
{
|
|
870
|
+
"todo_id": "unique identifier for the todo item",
|
|
871
|
+
"todo_content": "content/description of the todo item",
|
|
872
|
+
"todo_status": "pending | in_progress | completed",
|
|
873
|
+
"all_todos": "array of all todo items in the current list",
|
|
874
|
+
"phase": "validation | postWrite"
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
**Output Options**:
|
|
879
|
+
|
|
880
|
+
- `decision`: "allow", "block", or "deny"
|
|
881
|
+
- `reason`: human-readable explanation for the decision (required when blocking)
|
|
882
|
+
|
|
883
|
+
**Blocking Behavior**:
|
|
884
|
+
|
|
885
|
+
During the `validation` phase, when `decision` is `block` or `deny` (exit code 2), todo creation is prevented. The todo list remains unchanged, and the reason is provided as feedback to the model.
|
|
886
|
+
|
|
887
|
+
During the `postWrite` phase, the todo has already been persisted. Hooks may still return output, but `block` / `deny` does not undo the write and should not be used for validation.
|
|
888
|
+
|
|
889
|
+
**Example Output (Allow)**:
|
|
890
|
+
|
|
891
|
+
```json
|
|
892
|
+
{
|
|
893
|
+
"decision": "allow",
|
|
894
|
+
"reason": "Todo content validated successfully"
|
|
895
|
+
}
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
**Example Output (Block)**:
|
|
899
|
+
|
|
900
|
+
```json
|
|
901
|
+
{
|
|
902
|
+
"decision": "block",
|
|
903
|
+
"reason": "Todo content too short. Minimum 5 characters required."
|
|
904
|
+
}
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**Example Hook Script**:
|
|
908
|
+
|
|
909
|
+
```bash
|
|
910
|
+
#!/bin/bash
|
|
911
|
+
# ~/.qwen/hooks/todo-validator.sh
|
|
912
|
+
# Validates todo content before creation
|
|
913
|
+
|
|
914
|
+
INPUT=$(cat)
|
|
915
|
+
CONTENT=$(echo "$INPUT" | jq -r '.todo_content')
|
|
916
|
+
|
|
917
|
+
# Check minimum length
|
|
918
|
+
if [ ${#CONTENT} -lt 5 ]; then
|
|
919
|
+
echo '{"decision": "block", "reason": "Todo content must be at least 5 characters"}'
|
|
920
|
+
exit 2
|
|
921
|
+
fi
|
|
922
|
+
|
|
923
|
+
# Block test-related todos
|
|
924
|
+
if [[ "$CONTENT" =~ "test" ]]; then
|
|
925
|
+
echo '{"decision": "block", "reason": "Test todos are not allowed in production"}'
|
|
926
|
+
exit 2
|
|
927
|
+
fi
|
|
928
|
+
|
|
929
|
+
echo '{"decision": "allow"}'
|
|
930
|
+
exit 0
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
**Example Configuration**:
|
|
934
|
+
|
|
935
|
+
```json
|
|
936
|
+
{
|
|
937
|
+
"hooks": {
|
|
938
|
+
"TodoCreated": [
|
|
939
|
+
{
|
|
940
|
+
"hooks": [
|
|
941
|
+
{
|
|
942
|
+
"type": "command",
|
|
943
|
+
"command": "$HOME/.qwen/hooks/todo-validator.sh",
|
|
944
|
+
"name": "todo-validator",
|
|
945
|
+
"timeout": 5000
|
|
946
|
+
}
|
|
947
|
+
]
|
|
948
|
+
}
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
#### TodoCompleted
|
|
955
|
+
|
|
956
|
+
**Purpose**: Executed when a todo item is marked as completed. Allows validation, logging, or blocking of todo completion.
|
|
957
|
+
|
|
958
|
+
Todo hooks run in two phases:
|
|
959
|
+
|
|
960
|
+
- `validation`: runs before persistence. Use this phase for validation only; returning `block` or `deny` prevents the write.
|
|
961
|
+
- `postWrite`: runs after persistence. Use this phase for side effects such as logging or syncing; `block` or `deny` is ignored in this phase.
|
|
962
|
+
|
|
963
|
+
**Event-specific fields**:
|
|
964
|
+
|
|
965
|
+
```json
|
|
966
|
+
{
|
|
967
|
+
"todo_id": "unique identifier for the todo item",
|
|
968
|
+
"todo_content": "content/description of the todo item",
|
|
969
|
+
"previous_status": "pending | in_progress (status before completion)",
|
|
970
|
+
"all_todos": "array of all todo items in the current list",
|
|
971
|
+
"phase": "validation | postWrite"
|
|
972
|
+
}
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
**Output Options**:
|
|
976
|
+
|
|
977
|
+
- `decision`: "allow", "block", or "deny"
|
|
978
|
+
- `reason`: human-readable explanation for the decision (required when blocking)
|
|
979
|
+
|
|
980
|
+
**Blocking Behavior**:
|
|
981
|
+
|
|
982
|
+
During the `validation` phase, when `decision` is `block` or `deny` (exit code 2), todo completion is prevented. The todo item remains in its previous status, and the reason is provided as feedback to the model.
|
|
983
|
+
|
|
984
|
+
During the `postWrite` phase, the todo has already been persisted. Hooks may still return output, but `block` / `deny` does not undo the write and should not be used for validation.
|
|
985
|
+
|
|
986
|
+
**Example Output (Allow)**:
|
|
987
|
+
|
|
988
|
+
```json
|
|
989
|
+
{
|
|
990
|
+
"decision": "allow",
|
|
991
|
+
"reason": "Todo completion approved"
|
|
992
|
+
}
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
**Example Output (Block)**:
|
|
996
|
+
|
|
997
|
+
```json
|
|
998
|
+
{
|
|
999
|
+
"decision": "block",
|
|
1000
|
+
"reason": "Cannot complete this todo until dependent tasks are finished."
|
|
1001
|
+
}
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
**Example Hook Script**:
|
|
1005
|
+
|
|
1006
|
+
```bash
|
|
1007
|
+
#!/bin/bash
|
|
1008
|
+
# ~/.qwen/hooks/todo-completion-validator.sh
|
|
1009
|
+
# Validates todo completion conditions
|
|
1010
|
+
|
|
1011
|
+
INPUT=$(cat)
|
|
1012
|
+
TODO_ID=$(echo "$INPUT" | jq -r '.todo_id')
|
|
1013
|
+
ALL_TODOS=$(echo "$INPUT" | jq -r '.all_todos')
|
|
1014
|
+
|
|
1015
|
+
# Check if there are incomplete dependent todos (example logic)
|
|
1016
|
+
INCOMPLETE_COUNT=$(echo "$ALL_TODOS" | jq '[.[] | select(.status != "completed")] | length')
|
|
1017
|
+
|
|
1018
|
+
if [ "$INCOMPLETE_COUNT" -gt 5 ]; then
|
|
1019
|
+
echo '{"decision": "block", "reason": "Too many incomplete todos. Complete other tasks first."}'
|
|
1020
|
+
exit 2
|
|
1021
|
+
fi
|
|
1022
|
+
|
|
1023
|
+
echo '{"decision": "allow"}'
|
|
1024
|
+
exit 0
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
**Example Configuration**:
|
|
1028
|
+
|
|
1029
|
+
```json
|
|
1030
|
+
{
|
|
1031
|
+
"hooks": {
|
|
1032
|
+
"TodoCompleted": [
|
|
1033
|
+
{
|
|
1034
|
+
"hooks": [
|
|
1035
|
+
{
|
|
1036
|
+
"type": "command",
|
|
1037
|
+
"command": "$HOME/.qwen/hooks/todo-completion-validator.sh",
|
|
1038
|
+
"name": "completion-validator",
|
|
1039
|
+
"timeout": 5000
|
|
1040
|
+
}
|
|
1041
|
+
]
|
|
1042
|
+
}
|
|
1043
|
+
]
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
**Use Cases**:
|
|
1049
|
+
|
|
1050
|
+
- **Logging**: Track todo creation and completion for audit or analytics
|
|
1051
|
+
- **Validation**: Enforce content quality standards (minimum length, required keywords)
|
|
1052
|
+
- **Workflow Control**: Block completion until prerequisites are met
|
|
1053
|
+
- **Integration**: Sync todos with external task management systems (Jira, Trello, etc.)
|
|
1054
|
+
|
|
757
1055
|
## Hook Configuration
|
|
758
1056
|
|
|
759
1057
|
Hooks are configured in Qwen Code settings, typically in `.qwen/settings.json` or user configuration files:
|
|
@@ -55,22 +55,6 @@ Detection priority:
|
|
|
55
55
|
3. System locale via JavaScript Intl API
|
|
56
56
|
4. Default: English
|
|
57
57
|
|
|
58
|
-
### Dynamic Command Translation
|
|
59
|
-
|
|
60
|
-
Dynamic slash command descriptions from skills, extensions, file commands, and
|
|
61
|
-
MCP prompts can be translated with AI. This is **off by default** to avoid
|
|
62
|
-
unexpected model calls, latency, and token usage.
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
/language translate status # Show current status
|
|
66
|
-
/language translate on # Enable AI translation for dynamic descriptions
|
|
67
|
-
/language translate off # Disable AI translation
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Use `/language translate cache refresh` to re-translate cached dynamic
|
|
71
|
-
descriptions after enabling translation, or `/language translate cache clear` to
|
|
72
|
-
remove cached translations.
|
|
73
|
-
|
|
74
58
|
## LLM Output Language
|
|
75
59
|
|
|
76
60
|
The LLM output language controls what language the AI assistant responds in, regardless of what language you type your questions in.
|
|
@@ -145,6 +129,29 @@ User directory takes precedence over built-in translations.
|
|
|
145
129
|
> Contributions are welcome! If you’d like to improve built-in translations or add new languages.
|
|
146
130
|
> For a concrete example, see [PR #1238: feat(i18n): add Russian language support](https://github.com/QwenLM/qwen-code/pull/1238).
|
|
147
131
|
|
|
132
|
+
### Maintaining `zh-TW` (Traditional Chinese for Taiwan)
|
|
133
|
+
|
|
134
|
+
`zh-TW` is **not** an automatic OpenCC s2t conversion of `zh.js` — it is a hand-maintained Taiwan-vocabulary translation. When adding or updating keys, please follow the conventions below.
|
|
135
|
+
|
|
136
|
+
The "CI enforced?" column indicates whether `npm run check-i18n` will fail the build on a violation. Rows marked **No** are style guidance enforced by review only — typically because the offending form has a legitimate non-UI meaning (`文件` can mean "document", `打開` is colloquially fine in Taiwan).
|
|
137
|
+
|
|
138
|
+
| Avoid | Use instead | CI enforced? | Reason |
|
|
139
|
+
| --------------------- | --------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| 文件 (file) | 檔案 | No | Taiwan term for filesystem files (but `文件` can legitimately mean "document") |
|
|
141
|
+
| 服務器 / 服务器 | 伺服器 | Yes | Taiwan term for "server" |
|
|
142
|
+
| 菜單 / 菜单 | 選單 | Yes | Taiwan term for "menu" |
|
|
143
|
+
| 鏈接 / 链接 | 連結 | Yes | Taiwan term for "link" (bare `鏈` is fine — e.g. 區塊鏈) |
|
|
144
|
+
| 打開 | 開啟 | No | Taiwan-preferred verb for "open" (UI); `打開` is colloquially common |
|
|
145
|
+
| 爲 / 啓 / 曆史 / 鏈接 | 為 / 啟 / 歷史 / 連結 | Yes | Variant Traditional forms from raw OpenCC s2t. Note: `曆` is context-dependent and correct in calendar terms (日曆, 農曆, 西曆); CI only flags the bigram `曆史`, not bare `曆`. |
|
|
146
|
+
|
|
147
|
+
If you are not a Traditional Chinese speaker and need to bootstrap a value, **do not paste raw OpenCC `s2t` output**: the default s2t profile emits variant Traditional characters (e.g. 爲, 啓) that Taiwan does not use, and never rewrites Mainland-Chinese vocabulary (服務器, 菜單). Prefer `s2twp.json` (Simplified → Taiwan with phrase mapping) as a starting point and then ask a Taiwan-Chinese speaker to review.
|
|
148
|
+
|
|
149
|
+
The `check-i18n` script (run in CI via `npm run check-i18n`) will fail the build if any of the CI-enforced substrings above end up in a `zh-TW` value. See `scripts/check-i18n.ts → ZH_TW_FORBIDDEN_PATTERNS` for the full list. If a translation legitimately needs to contain a CI-forbidden substring, add its key to `ZH_TW_ALLOWED_EXCEPTIONS` in the same file with a brief justification.
|
|
150
|
+
|
|
151
|
+
> [!note]
|
|
152
|
+
>
|
|
153
|
+
> The check uses plain substring matching, which does not understand Chinese word boundaries. A bigram pattern can therefore false-positive across compound-word boundaries — for example, `區塊鏈接口` (= `區塊鏈` + `接口`) contains the substring `鏈接` even though neither word is incorrect. If you hit a surprising CI failure of this kind, add the translation key to `ZH_TW_ALLOWED_EXCEPTIONS` rather than removing the pattern.
|
|
154
|
+
|
|
148
155
|
### Language Pack Format
|
|
149
156
|
|
|
150
157
|
```javascript
|
|
@@ -8,9 +8,10 @@ Run Qwen Code as a local HTTP daemon so multiple clients (IDE plugins, web UIs,
|
|
|
8
8
|
|
|
9
9
|
## What it gives you
|
|
10
10
|
|
|
11
|
-
- **One agent process, many clients** — under the default `sessionScope: 'single'`, every client connecting to the
|
|
11
|
+
- **One agent process, many clients** — under the default `sessionScope: 'single'`, every client connecting to the daemon shares one ACP session. Live cross-client collaboration on the same conversation, the same file diffs, the same permission prompts.
|
|
12
12
|
- **Reconnect-safe streaming** — SSE with `Last-Event-ID` reconnect lets a client drop and pick up exactly where it left off (within the ring's replay window).
|
|
13
13
|
- **First-responder permissions** — when the agent asks for permission to run a tool, every connected client sees the request; whichever client answers first wins.
|
|
14
|
+
- **One daemon, one workspace** — each `qwen serve` process binds to exactly one workspace at boot (per [#3803](https://github.com/QwenLM/qwen-code/issues/3803) §02). Multi-workspace deployments run one daemon per workspace on separate ports (or behind an orchestrator).
|
|
14
15
|
|
|
15
16
|
## Quickstart
|
|
16
17
|
|
|
@@ -19,11 +20,11 @@ Run Qwen Code as a local HTTP daemon so multiple clients (IDE plugins, web UIs,
|
|
|
19
20
|
```bash
|
|
20
21
|
cd your-project/
|
|
21
22
|
qwen serve
|
|
22
|
-
# → qwen serve listening on http://127.0.0.1:4170 (mode=http-bridge)
|
|
23
|
+
# → qwen serve listening on http://127.0.0.1:4170 (mode=http-bridge, workspace=/path/to/your-project)
|
|
23
24
|
# → qwen serve: bearer auth disabled (loopback default). Set QWEN_SERVER_TOKEN to enable.
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
The default bind is `127.0.0.1:4170`. Bearer auth is **off** on loopback so local development "just works".
|
|
27
|
+
The default bind is `127.0.0.1:4170`. Bearer auth is **off** on loopback so local development "just works". The daemon binds to the current working directory; use `--workspace /path/to/dir` to override.
|
|
27
28
|
|
|
28
29
|
### 2. Sanity-check it
|
|
29
30
|
|
|
@@ -32,19 +33,23 @@ curl http://127.0.0.1:4170/health
|
|
|
32
33
|
# → {"status":"ok"}
|
|
33
34
|
|
|
34
35
|
curl http://127.0.0.1:4170/capabilities
|
|
35
|
-
# → {"v":1,"mode":"http-bridge","features":["health","capabilities","session_create",...]}
|
|
36
|
+
# → {"v":1,"mode":"http-bridge","features":["health","capabilities","session_create",...],"workspaceCwd":"/path/to/your-project"}
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
The `workspaceCwd` field surfaces the bound workspace so clients can pre-flight check + omit `cwd` on `POST /session`.
|
|
40
|
+
|
|
38
41
|
### 3. Open a session
|
|
39
42
|
|
|
40
43
|
```bash
|
|
41
44
|
curl -X POST http://127.0.0.1:4170/session \
|
|
42
45
|
-H 'Content-Type: application/json' \
|
|
43
|
-
-d '{
|
|
46
|
+
-d '{}'
|
|
44
47
|
# → {"sessionId":"<uuid>","workspaceCwd":"…","attached":false}
|
|
45
48
|
```
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
`cwd` may be omitted — the route falls back to the daemon's bound workspace. Posting a `cwd` that doesn't match the bound workspace returns `400 workspace_mismatch` (the daemon is bound to exactly one workspace; start a separate daemon for a different one).
|
|
51
|
+
|
|
52
|
+
A second client posting to `/session` (any matching `cwd` or none) gets `"attached": true` — they're now sharing the agent.
|
|
48
53
|
|
|
49
54
|
### 4. Subscribe to the event stream (in another terminal first)
|
|
50
55
|
|
|
@@ -94,7 +99,7 @@ Clients then send `Authorization: Bearer $QWEN_SERVER_TOKEN` on every request. `
|
|
|
94
99
|
|
|
95
100
|
```bash
|
|
96
101
|
curl -H "Authorization: Bearer $QWEN_SERVER_TOKEN" http://your-host:4170/capabilities
|
|
97
|
-
# → {"v":1,"mode":"http-bridge","features":[...],"modelServices":[]}
|
|
102
|
+
# → {"v":1,"mode":"http-bridge","features":[...],"modelServices":[],"workspaceCwd":"/path/to/your-project"}
|
|
98
103
|
# Wrong token → 401
|
|
99
104
|
```
|
|
100
105
|
|
|
@@ -102,14 +107,15 @@ The token comparison is constant-time (SHA-256 + `crypto.timingSafeEqual`); 401
|
|
|
102
107
|
|
|
103
108
|
## CLI flags
|
|
104
109
|
|
|
105
|
-
| Flag | Default
|
|
106
|
-
| ----------------------- |
|
|
107
|
-
| `--port <n>` | `4170`
|
|
108
|
-
| `--hostname <addr>` | `127.0.0.1`
|
|
109
|
-
| `--token <str>` | —
|
|
110
|
-
| `--max-sessions <n>` | `20`
|
|
111
|
-
| `--
|
|
112
|
-
| `--
|
|
110
|
+
| Flag | Default | Purpose |
|
|
111
|
+
| ----------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| `--port <n>` | `4170` | TCP port. `0` = OS-assigned ephemeral port. |
|
|
113
|
+
| `--hostname <addr>` | `127.0.0.1` | Bind interface. Anything beyond loopback requires a token. |
|
|
114
|
+
| `--token <str>` | — | Bearer token. Falls back to `QWEN_SERVER_TOKEN` env var (with leading/trailing whitespace stripped — handy for `$(cat token.txt)`). |
|
|
115
|
+
| `--max-sessions <n>` | `20` | Cap on concurrent live sessions. New `POST /session` requests that would spawn a fresh child return `503` (with `Retry-After: 5`) when the cap is hit; attaches to existing sessions are NOT counted. Set to `0` to disable. Sized for single-user / small-team usage; raise it if your deployment has the RAM/FD headroom (~30–50 MB per session). |
|
|
116
|
+
| `--workspace <path>` | `process.cwd()` | Absolute workspace path this daemon binds to (per [#3803](https://github.com/QwenLM/qwen-code/issues/3803) §02 — 1 daemon = 1 workspace). `POST /session` requests with a mismatched `cwd` return `400 workspace_mismatch`. For multi-workspace deployments, run one `qwen serve` per workspace on separate ports. |
|
|
117
|
+
| `--max-connections <n>` | `256` | Listener-level TCP connection cap (`server.maxConnections`). Bounds raw socket count irrespective of session count — slow / phantom SSE clients get rejected at accept time once full. Raise alongside `--max-sessions` if your deployment expects many SSE subscribers per session. |
|
|
118
|
+
| `--http-bridge` | `true` | Stage 1 mode: one `qwen --acp` child per daemon (bound to one workspace at boot, per [#3803](https://github.com/QwenLM/qwen-code/issues/3803) §02); N sessions multiplex onto that child via ACP `newSession()`. Stage 2 native in-process becomes available later. |
|
|
113
119
|
|
|
114
120
|
> **Sizing the load knobs.** `--max-sessions` is the **new-child** cap.
|
|
115
121
|
> Three other layers also limit load — when sizing for a high-concurrency
|
|
@@ -157,13 +163,15 @@ The token comparison is constant-time (SHA-256 + `crypto.timingSafeEqual`); 401
|
|
|
157
163
|
> swallow RSTs may want to lower `server.keepAliveTimeout` via a
|
|
158
164
|
> reverse proxy or accept periodic daemon restarts.
|
|
159
165
|
|
|
160
|
-
## Multi-session &
|
|
166
|
+
## Multi-session & multi-workspace deployment
|
|
167
|
+
|
|
168
|
+
Per [#3803](https://github.com/QwenLM/qwen-code/issues/3803) §02, each `qwen serve` process binds to **one workspace** at boot. Within that workspace it multiplexes N sessions onto a single `qwen --acp` child via the agent's native session map — sessions share the child's process / OAuth state / file-read cache / hierarchy-memory parse.
|
|
161
169
|
|
|
162
|
-
|
|
170
|
+
To host **multiple workspaces** (one user, several repos; or several users on the same host), run **multiple daemon processes** — one per workspace, each on its own port, supervised by systemd / docker-compose / k8s / a `qwen-coordinator` reference orchestrator. The trade-off is intentional: one workspace per child means `loadSettings(cwd)` / OAuth / MCP server scope stay aligned with the bound directory and don't drift across requests.
|
|
163
171
|
|
|
164
172
|
> **Subscribe BEFORE posting `modelServiceId` on attach.** When a client `POST /session` with a `modelServiceId` and the workspace already has a session running a different model, the daemon issues an internal `setSessionModel` call — failures are NOT propagated as an HTTP error (the session stays operational on its current model). The visible failure signal is a `model_switch_failed` event on the session's SSE stream. If you call `POST /session` and only THEN open `GET /session/:id/events`, you'll miss the failure event and silently keep talking to the wrong model. Open the SSE stream first, or pass `Last-Event-ID: 0` on subscribe to replay the ring's oldest available event.
|
|
165
173
|
|
|
166
|
-
To handle multiple **users** (each with their own quota, audit log, sandbox) or to scale beyond one process's reach (cold-start budget, FD count, RSS),
|
|
174
|
+
To handle multiple **users** (each with their own quota, audit log, sandbox) or to scale beyond one process's reach (cold-start budget, FD count, RSS), spawn one daemon per workspace per user behind an external orchestrator. That orchestrator (multi-tenancy / OIDC / Quota / Audit / k8s) is **out of scope** for the qwen-code project — see issue [#3803](https://github.com/QwenLM/qwen-code/issues/3803) "External Reference Architecture" for the design pointers.
|
|
167
175
|
|
|
168
176
|
## Durability model
|
|
169
177
|
|
|
@@ -255,7 +263,7 @@ Concrete cost at N=5 sessions on the same workspace:
|
|
|
255
263
|
| Auto-memory learned facts | shared | one knowledge base per child |
|
|
256
264
|
| Cold start | first only | <200 ms after first session |
|
|
257
265
|
|
|
258
|
-
The bridge keeps **one channel per
|
|
266
|
+
The bridge keeps **one channel per daemon** (one daemon per workspace, per §02). The channel stays alive while at least one session is live; the last `killSession` (or a channel-level crash) kills the child.
|
|
259
267
|
|
|
260
268
|
**MCP server children** are still per-session today — each session's config can specify different servers, so they're independently spawned. Stage 1.5 follow-up: refcount MCP server children by `(workspace, config-hash)` so identical configs share. Not in scope for this PR.
|
|
261
269
|
|