@mmmbuto/qwen-code-termux 0.16.1-termux → 0.18.0-termux
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/README.md +79 -109
- package/bundled/new-app/SKILL.md +22 -0
- package/bundled/qc-helper/SKILL.md +29 -24
- package/bundled/qc-helper/docs/_meta.ts +1 -0
- package/bundled/qc-helper/docs/configuration/_meta.ts +0 -3
- package/bundled/qc-helper/docs/configuration/settings.md +37 -31
- package/bundled/qc-helper/docs/configuration/themes.md +39 -0
- package/bundled/qc-helper/docs/features/_meta.ts +1 -3
- package/bundled/qc-helper/docs/features/approval-mode.md +35 -35
- package/bundled/qc-helper/docs/features/auto-mode.md +54 -9
- package/bundled/qc-helper/docs/features/channels/_meta.ts +1 -0
- package/bundled/qc-helper/docs/features/channels/feishu.md +170 -0
- package/bundled/qc-helper/docs/features/commands.md +115 -35
- package/bundled/qc-helper/docs/features/followup-suggestions.md +2 -2
- package/bundled/qc-helper/docs/features/headless.md +32 -0
- package/bundled/qc-helper/docs/features/markdown-rendering.md +21 -1
- package/bundled/qc-helper/docs/features/memory.md +22 -5
- package/bundled/qc-helper/docs/features/scheduled-tasks.md +1 -1
- package/bundled/qc-helper/docs/features/status-line.md +168 -32
- package/bundled/qc-helper/docs/features/sub-agents.md +60 -0
- package/bundled/qc-helper/docs/features/worktree.md +345 -0
- package/bundled/qc-helper/docs/overview.md +4 -4
- package/bundled/qc-helper/docs/quickstart.md +4 -4
- package/bundled/qc-helper/docs/qwen-serve-deploy-local.md +221 -0
- package/bundled/qc-helper/docs/qwen-serve.md +234 -24
- package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +16 -0
- package/bundled/qc-helper/docs/support/Uninstall.md +19 -1
- package/bundled/qc-helper/docs/support/troubleshooting.md +2 -1
- package/bundled/simplify/SKILL.md +123 -0
- package/chunks/agent-IDS4HMOX.js +56 -0
- package/chunks/agent-headless-5Q2EUSPS.js +50 -0
- package/chunks/{anthropicContentGenerator-SSGKR6DO.js → anthropicContentGenerator-2HBRNQ3B.js} +52 -9
- package/chunks/{askUserQuestion-PJWUUXKN.js → askUserQuestion-75TDJVK2.js} +45 -3
- package/chunks/{ca-UZ7BANMN.js → ca-BARBRL6N.js} +89 -5
- package/chunks/{chunk-GGNTZ2NH.js → chunk-2Y5SYSD3.js} +368 -597
- package/chunks/{chunk-2LA2TREA.js → chunk-3AA2DK35.js} +1448 -207
- package/chunks/{chunk-I2V5WXHJ.js → chunk-3AUHFMSK.js} +80 -38
- package/chunks/chunk-3DHXZ6EV.js +241 -0
- package/chunks/{chunk-PR4T27R7.js → chunk-3HTIVKZE.js} +42 -8
- package/chunks/chunk-3HX5LZ6R.js +1798 -0
- package/chunks/chunk-3PJXIDKI.js +2517 -0
- package/chunks/{chunk-MYAKAFEC.js → chunk-55ZMG67I.js} +7451 -3517
- package/chunks/{chunk-66CXYE4B.js → chunk-5IFG2VC4.js} +293 -242
- package/chunks/chunk-64WXLC72.js +98 -0
- package/chunks/{chunk-C6WMLUNB.js → chunk-72LDN5PP.js} +1 -1
- package/chunks/{chunk-F23NCRJ2.js → chunk-A7B4ISQP.js} +1 -1
- package/chunks/chunk-B7HXHOHU.js +393 -0
- package/chunks/{chunk-XEGHDASV.js → chunk-D3RHSPAS.js} +435 -540
- package/chunks/{chunk-XKS5KBFJ.js → chunk-EYENRK4D.js} +694 -384
- package/chunks/chunk-H6BD2ELD.js +36 -0
- package/chunks/{chunk-XP27SJMH.js → chunk-HR7SV7AY.js} +79 -48
- package/chunks/{chunk-D5NTAHYL.js → chunk-IDX6COTE.js} +7 -2
- package/chunks/{chunk-SHT4VJWU.js → chunk-IWKSG2AR.js} +2 -2
- package/chunks/chunk-J37FGIOA.js +1623 -0
- package/chunks/chunk-J5MDQKJL.js +2230 -0
- package/chunks/{chunk-USE2VQ5P.js → chunk-JTQAQBTV.js} +21 -0
- package/chunks/{chunk-NCTLV2NB.js → chunk-KQJMQJPI.js} +1 -1
- package/chunks/{chunk-5FBA5XC2.js → chunk-KRIHGKNA.js} +1 -1
- package/chunks/chunk-LD2XBG6Z.js +102 -0
- package/chunks/{chunk-MAY32HXD.js → chunk-M6VTDSVR.js} +3 -1
- package/chunks/chunk-MRO43B25.js +30 -0
- package/chunks/{chunk-N4WOREMD.js → chunk-NVFMZBX2.js} +43 -3
- package/chunks/chunk-OHEGWO4L.js +264 -0
- package/chunks/{chunk-K6O2NBMF.js → chunk-OQ7NJIY7.js} +4604 -6397
- package/chunks/chunk-QQDPRDVW.js +25 -0
- package/chunks/{chunk-KXZ4TJB4.js → chunk-SEGYWKIH.js} +1 -1
- package/chunks/chunk-SKBPNJEW.js +45 -0
- package/chunks/{chunk-4AOCVI6J.js → chunk-SNGELLWX.js} +52 -6
- package/chunks/{chunk-3OCRHZA3.js → chunk-TD4OPI4T.js} +56742 -44104
- package/chunks/{chunk-DQ4QTG7E.js → chunk-VV4F63BD.js} +11 -11
- package/chunks/chunk-XBY7E2FX.js +605 -0
- package/chunks/{chunk-JKMBWLFB.js → chunk-YILFYI5W.js} +48 -26
- package/chunks/chunk-YOGAOMYB.js +159 -0
- package/chunks/{chunk-QWSRH265.js → chunk-Z2Z3GUXZ.js} +777 -776
- package/chunks/{chunk-SDHRQFOS.js → chunk-ZTZ4DDQE.js} +2 -2
- package/chunks/computer-use-W2TYQNEE.js +825 -0
- package/chunks/contextCommand-6FGX3A7J.js +52 -0
- package/chunks/{cron-create-3ZBBN7WB.js → cron-create-APL5LU6I.js} +3 -3
- package/chunks/{cron-delete-NAGKKIIG.js → cron-delete-4SBJSCN4.js} +3 -3
- package/chunks/{cron-list-PAGRXNAI.js → cron-list-2AMGOMVO.js} +3 -3
- package/chunks/{de-V4IE2OOZ.js → de-YGKK2BC4.js} +89 -5
- package/chunks/{devtools-TWVXEJQB.js → devtools-FM6GJPYG.js} +2 -1
- package/chunks/{dist-4L54HRX2.js → dist-4LXD6L6X.js} +24 -5
- package/chunks/dist-H6ONXVLG.js +94146 -0
- package/chunks/{dist-XKWIWPWQ.js → dist-KAZ3SEBX.js} +1083 -3856
- package/chunks/{dist-BXDUQ2QY.js → dist-PK7DFCAW.js} +1 -1
- package/chunks/{edit-NVO3FOAK.js → edit-ZCEZC264.js} +30 -22
- package/chunks/{en-HGJ2SPLM.js → en-DHGYHIHX.js} +127 -6
- package/chunks/{enter-worktree-UEBG4WFC.js → enter-worktree-BBHCFCHG.js} +30 -20
- package/chunks/enterPlanMode-3M6KTD3B.js +158 -0
- package/chunks/{exit-worktree-UZ3MAQZN.js → exit-worktree-73YPIEQO.js} +27 -19
- package/chunks/exitPlanMode-TYZM6BAE.js +703 -0
- package/chunks/{fr-CJULI7ZX.js → fr-JXBKPJKQ.js} +89 -5
- package/chunks/{geminiContentGenerator-3UZFXGNT.js → geminiContentGenerator-7N2V3VW2.js} +8 -6
- package/chunks/{getMachineId-bsd-JXOSIJV2.js → getMachineId-bsd-4CASPIU4.js} +4 -4
- package/chunks/{getMachineId-darwin-TE4QRR42.js → getMachineId-darwin-HPQPEMZR.js} +4 -4
- package/chunks/{getMachineId-linux-S3OL52XK.js → getMachineId-linux-AUARKYHL.js} +3 -3
- package/chunks/{getMachineId-unsupported-DWUSBAPX.js → getMachineId-unsupported-S32ZDA2T.js} +3 -3
- package/chunks/{getMachineId-win-AAC5P3AP.js → getMachineId-win-4EFLHYIJ.js} +4 -4
- package/chunks/{glob-KNHSFFFG.js → glob-5XBCPQ2A.js} +27 -19
- package/chunks/{grep-LACWDZW4.js → grep-VIUU3A7X.js} +30 -19
- package/chunks/{ja-L7CHRQEW.js → ja-TGPZSP2B.js} +89 -5
- package/chunks/{keychain-token-storage-335UOLJ6.js → keychain-token-storage-6IU6ORQN.js} +3 -3
- package/chunks/{ls-AGXQOKSG.js → ls-JRGYIGLY.js} +4 -4
- package/chunks/{lsp-UDMUHNPA.js → lsp-SHMKFOAC.js} +3 -3
- package/chunks/{monitor-ETKWPJEH.js → monitor-6R4LIJL5.js} +40 -25
- package/chunks/{multipart-parser-3QWGTLK3.js → multipart-parser-AJ4WASWR.js} +2 -2
- package/chunks/{notebook-edit-QJJLPNYT.js → notebook-edit-5E7ULDVQ.js} +28 -20
- package/chunks/{openaiContentGenerator-CNNN424U.js → openaiContentGenerator-ZVHFKM3O.js} +17 -14
- package/chunks/{pt-M6JULLEQ.js → pt-TIBG6BIO.js} +89 -5
- package/chunks/{qwenContentGenerator-BOLCGK3R.js → qwenContentGenerator-B2VTVSPJ.js} +31 -23
- package/chunks/{qwenOAuth2-EEJGROP7.js → qwenOAuth2-2KCKWDCF.js} +6 -4
- package/chunks/read-file-GIT7BCDR.js +27 -0
- package/chunks/ripGrep-MWKFVYMS.js +48 -0
- package/chunks/{ru-QILM4HBC.js → ru-JBCHCK4L.js} +89 -5
- package/chunks/scheduler-5VOOYGBH.js +308 -0
- package/chunks/send-message-4QNWQJF4.js +244 -0
- package/chunks/{serve-OLSI7WSR.js → serve-MN6HZBWN.js} +14262 -7414
- package/chunks/shell-NQZQGFM2.js +56 -0
- package/chunks/{skill-D6YRHTTI.js → skill-WCFW4644.js} +145 -119
- package/chunks/{src-TMOD5X6F.js → src-7XL4G4DC.js} +88 -46
- package/chunks/{src-4QH4FZ6I.js → src-IHA6DTUV.js} +452 -62
- package/chunks/{syntheticOutput-5PVFDDJ4.js → syntheticOutput-YTYS2ZMQ.js} +4 -4
- package/chunks/task-create-MPORPYN6.js +19 -0
- package/chunks/task-list-R2YDYPZT.js +151 -0
- package/chunks/{task-stop-AJKPSR6R.js → task-stop-SYWJYBCM.js} +3 -3
- package/chunks/task-update-E4NSLKMQ.js +408 -0
- package/chunks/team-create-7R7KA5IP.js +314 -0
- package/chunks/team-delete-25OIWUPN.js +116 -0
- package/chunks/{todoWrite-VLAUG4CA.js → todoWrite-4YHMIF4X.js} +16 -5
- package/chunks/{tool-search-MZGHUUKD.js → tool-search-YBRVZCLI.js} +29 -11
- package/chunks/{tts-notification-K3X7X7MN.js → tts-notification-7SOEMQK4.js} +5 -4
- package/chunks/{web-fetch-OILB464A.js → web-fetch-MFIRHIHI.js} +5 -5
- package/chunks/workflow-5RIKVCIE.js +960 -0
- package/chunks/{write-file-BIQAA57V.js → write-file-DMQTJZOM.js} +32 -24
- package/chunks/{zh-PWL2NKY3.js → zh-7H5OQC4I.js} +135 -11
- package/chunks/{zh-TW-S3YGWICZ.js → zh-TW-P4IDHD3M.js} +128 -11
- package/cli.js +45402 -20570
- package/examples/agent/agents/diary.md +86 -0
- package/examples/agent/qwen-extension.json +5 -0
- package/examples/commands/commands/fs/grep-code.md +3 -0
- package/examples/commands/qwen-extension.json +5 -0
- package/examples/context/QWEN.md +8 -0
- package/examples/context/qwen-extension.json +5 -0
- package/examples/mcp-server/example.ts +60 -0
- package/examples/mcp-server/package.json +18 -0
- package/examples/mcp-server/qwen-extension.json +12 -0
- package/examples/mcp-server/tsconfig.json +13 -0
- package/examples/skills/qwen-extension.json +5 -0
- package/examples/skills/skills/synonyms/SKILL.md +48 -0
- package/examples/starter/QWEN.md +30 -0
- package/examples/starter/README.md +59 -0
- package/examples/starter/agents/diary.md +86 -0
- package/examples/starter/commands/writing/polish.md +13 -0
- package/examples/starter/example.ts +64 -0
- package/examples/starter/package.json +18 -0
- package/examples/starter/qwen-extension.json +12 -0
- package/examples/starter/skills/synonyms/SKILL.md +48 -0
- package/examples/starter/tsconfig.json +13 -0
- package/fzfWorker.js +1083 -0
- package/locales/ca.js +118 -5
- package/locales/de.js +117 -5
- package/locales/en.js +169 -7
- package/locales/fr.js +119 -5
- package/locales/ja.js +114 -5
- package/locales/pt.js +117 -5
- package/locales/ru.js +116 -5
- package/locales/zh-TW.js +161 -12
- package/locales/zh.js +169 -12
- package/package.json +4 -2
- package/scripts/postinstall.cjs +2 -1
- package/bundled/qc-helper/docs/features/checkpointing.md +0 -77
- package/chunks/agent-7ZN3CRHR.js +0 -48
- package/chunks/chunk-6PCB2DEF.js +0 -434
- package/chunks/chunk-EM6ETG2K.js +0 -60
- package/chunks/chunk-G7YTSRES.js +0 -150
- package/chunks/contextCommand-7IBASARL.js +0 -44
- package/chunks/exitPlanMode-PZAMWIRW.js +0 -227
- package/chunks/multipart-parser-IXGBIOIN.js +0 -384
- package/chunks/read-file-CCUEUFG2.js +0 -24
- package/chunks/ripGrep-TADOH2HK.js +0 -40
- package/chunks/send-message-YL44UZFC.js +0 -151
- package/chunks/shell-7KKKC5G7.js +0 -48
- package/chunks/src-IPWIHNMI.js +0 -1406
- package/chunks/undici-F6ZOXSS5.js +0 -8
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Worktrees
|
|
2
|
+
|
|
3
|
+
> Isolate experimental work in a temporary [git worktree](https://git-scm.com/docs/git-worktree) without leaving your current session. Useful when the model is about to make wide-ranging edits you want to keep separate from your main checkout, or when you want a subagent to work in a sandbox of its own.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Start the session inside a worktree (`--worktree` flag)
|
|
8
|
+
|
|
9
|
+
If you know up front that the entire session should run inside a worktree, pass `--worktree` at launch:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Auto-generated slug (e.g. tender-jemison-037f0a)
|
|
13
|
+
qwen --worktree
|
|
14
|
+
|
|
15
|
+
# Explicit name
|
|
16
|
+
qwen --worktree my-feature
|
|
17
|
+
|
|
18
|
+
# `=` form (recommended when also passing a positional prompt — see tip below)
|
|
19
|
+
qwen --worktree=my-feature
|
|
20
|
+
|
|
21
|
+
# PR reference — fetches refs/pull/<N>/head from `origin`
|
|
22
|
+
qwen --worktree=#4174
|
|
23
|
+
qwen --worktree https://github.com/QwenLM/qwen-code/pull/4174
|
|
24
|
+
|
|
25
|
+
# Continue a previous --worktree session — re-attaches to the existing dir
|
|
26
|
+
qwen --resume <session-id> --worktree=my-feature
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> **Tip — bare `--worktree` followed by a positional prompt is ambiguous.** Because `--worktree` takes an optional value, `qwen --worktree "say hi"` makes yargs consume `"say hi"` as the slug (and reject it because of the space). Use one of:
|
|
30
|
+
>
|
|
31
|
+
> - `qwen --worktree=my-feature "say hi"` (always works — explicit slug via `=`)
|
|
32
|
+
> - `qwen "say hi" --worktree` (positional first, flag at the end → auto slug)
|
|
33
|
+
> - `qwen --worktree --approval-mode yolo "say hi"` (any flag between them anchors the bare form)
|
|
34
|
+
|
|
35
|
+
> **Tip — `qwen --resume --worktree foo` (no session ID) shows an empty picker on first use.** The picker scopes to the chosen worktree's session storage; sessions started outside that worktree are not listed. To resume a session that was started inside `foo`, use `qwen --resume <id> --worktree foo` directly — the CLI re-attaches to the existing `foo/` directory rather than re-creating it.
|
|
36
|
+
|
|
37
|
+
`process.cwd()` and the model's workspace are switched to the worktree before the first turn runs. Exit with `Ctrl+C` twice and the [Exit Dialog](#exit-dialog-ctrlc--ctrld) prompts to keep or remove the worktree.
|
|
38
|
+
|
|
39
|
+
The `--worktree` flag cannot be combined with `--acp`/`--experimental-acp` — for ACP hosts (like Zed), pass the worktree path as the `cwd` of the `loadSession`/`newSession` request instead.
|
|
40
|
+
|
|
41
|
+
### Or ask mid-session
|
|
42
|
+
|
|
43
|
+
Alternatively, ask Qwen Code in plain language to create a worktree from inside an existing session:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
> start a worktree called experiment-a
|
|
47
|
+
Worktree experiment-a created on branch worktree-experiment-a
|
|
48
|
+
.qwen/worktrees/experiment-a
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
From this point on, the model routes every file edit and shell command through `.qwen/worktrees/experiment-a/`. Your original working directory is untouched.
|
|
52
|
+
|
|
53
|
+
When you are done:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
> exit the worktree and remove it
|
|
57
|
+
Removed worktree experiment-a (branch worktree-experiment-a)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If you want to come back later, ask to exit with the worktree kept on disk instead:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
> exit the worktree but keep it
|
|
64
|
+
Kept worktree experiment-a at .qwen/worktrees/experiment-a
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## When Worktrees Are Used
|
|
68
|
+
|
|
69
|
+
Worktrees are activated in four independent paths:
|
|
70
|
+
|
|
71
|
+
| Trigger | What happens |
|
|
72
|
+
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
73
|
+
| You launch with `--worktree` | The CLI creates the worktree before any model turn runs and chdirs the session into it. PR forms (`#N`, full URL) fetch first. |
|
|
74
|
+
| You explicitly ask for a worktree mid-session | Model calls `enter_worktree`; subsequent file edits go inside it. |
|
|
75
|
+
| You explicitly ask to leave | Model calls `exit_worktree` with `keep` or `remove`. |
|
|
76
|
+
| Model spawns a sub-agent with isolation enabled | A throwaway worktree (`agent-<hex>`) is created automatically and cleaned up if the agent has no diffs. |
|
|
77
|
+
|
|
78
|
+
The two mid-session tools (`enter_worktree` / `exit_worktree`) are deliberately gated behind explicit phrasing — saying "fix this bug" or "create a branch" will **not** trigger them. You must say something like "use a worktree", "start a worktree", or "in a worktree". The `--worktree` CLI flag has no such guard; it always creates one when present.
|
|
79
|
+
|
|
80
|
+
## What Gets Created
|
|
81
|
+
|
|
82
|
+
Every Qwen-managed worktree is placed under your project's `.qwen` directory:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
<repoRoot>/.qwen/worktrees/<slug>/ # Working directory
|
|
86
|
+
↳ branch worktree-<slug> # Created off your current branch
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- **Slug** — letters, digits, dot, underscore, hyphen; max 64 chars. If you don't specify a name, an `<adjective>-<noun>-<6hex>` slug is auto-generated (e.g. `tender-jemison-037f0a`). PR references produce `pr-<N>`.
|
|
90
|
+
- **Branch** — always `worktree-<slug>`, branched from whichever branch you have checked out when you ask for the worktree (not necessarily the main working tree's `HEAD`). For PR worktrees the branch is `worktree-pr-<N>` and is based on `FETCH_HEAD` (the PR's tip on the GitHub side) rather than your local branch.
|
|
91
|
+
- **Hooks** — the worktree's `core.hooksPath` is automatically pointed at the main repo's `.husky/` (preferred) or `.git/hooks/` so commits inside the worktree still trigger your existing pre-commit / commit-msg hooks.
|
|
92
|
+
- **Optional symlinks** — directories listed in `worktree.symlinkDirectories` (see [Settings](#settings)) are symlinked from the main repo into the new worktree so heavy dirs like `node_modules` can be reused without reinstalling.
|
|
93
|
+
|
|
94
|
+
The general-purpose worktree path is **not configurable** — it must live under `<repoRoot>/.qwen/worktrees/` so the CLI can find it on restart and on stale-cleanup sweeps. (The unrelated `agents.arena.worktreeBaseDir` setting controls only [Agent Arena](./arena.md) worktrees, which use a separate path tree under `~/.qwen/arena/`.)
|
|
95
|
+
|
|
96
|
+
## Footer and Status Line
|
|
97
|
+
|
|
98
|
+
When a worktree is active, the Footer shows a dim indicator on its own row:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
⎇ worktree-experiment-a (experiment-a)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
If you use a [custom status line script](./status-line.md), it also receives a `worktree` object in the JSON payload piped to stdin:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"worktree": {
|
|
109
|
+
"name": "experiment-a",
|
|
110
|
+
"path": "/path/to/repo/.qwen/worktrees/experiment-a",
|
|
111
|
+
"branch": "worktree-experiment-a",
|
|
112
|
+
"original_cwd": "/path/to/repo",
|
|
113
|
+
"original_branch": "main"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The payload field is present **only** when a worktree is active, so a `null`-check (`input.worktree?.name`) is enough.
|
|
119
|
+
|
|
120
|
+
If your custom status line already renders worktree info, you can hide the built-in Footer row to avoid duplication — see [Settings](#settings) below.
|
|
121
|
+
|
|
122
|
+
## Exit Dialog (Ctrl+C / Ctrl+D)
|
|
123
|
+
|
|
124
|
+
Pressing the quit shortcut twice while a worktree is active opens the **Worktree Exit Dialog** instead of closing the CLI:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
⎇ Active worktree: "experiment-a" (worktree-experiment-a)
|
|
128
|
+
|
|
129
|
+
• 2 new commit(s) on worktree-experiment-a
|
|
130
|
+
• 3 uncommitted file(s)
|
|
131
|
+
Removing the worktree will discard everything above.
|
|
132
|
+
|
|
133
|
+
What would you like to do?
|
|
134
|
+
○ Keep worktree (exit without deleting)
|
|
135
|
+
○ Remove worktree and branch (discards 2 commit(s), 3 file(s))
|
|
136
|
+
○ Cancel (stay in session)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The dialog inspects the worktree on open (`git status --porcelain` + `git rev-list <baseHEAD>..HEAD`) and surfaces both counts so you know exactly what you'd be discarding. `ESC` cancels.
|
|
140
|
+
|
|
141
|
+
If `git status` itself fails (e.g. corrupt index, worktree directory was removed under the CLI), the dialog shows a `⚠ Could not measure worktree state` warning and the counts may be unreliable — choose **Keep** or **Cancel** until you've diagnosed the underlying repo problem.
|
|
142
|
+
|
|
143
|
+
## `--resume` Restore
|
|
144
|
+
|
|
145
|
+
The active worktree binding is persisted to a sidecar file alongside your session transcript:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
<chatsDir>/<sessionId>.worktree.json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
When you launch the CLI with `--resume <sessionId>` (or pick the session from `/resume`), three things happen consistently across **interactive TUI**, **headless `-p`**, and **ACP/Zed** modes:
|
|
152
|
+
|
|
153
|
+
1. The sidecar is loaded and the worktree directory is verified to still exist on disk.
|
|
154
|
+
2. If alive, the model receives a one-shot reminder on its very next prompt:
|
|
155
|
+
```
|
|
156
|
+
[Resumed] Active worktree: "<slug>" at <path> (branch: <branch>). Continue using this path for all file operations.
|
|
157
|
+
```
|
|
158
|
+
3. If the worktree directory was deleted between sessions, the stale sidecar is cleaned up automatically — no error, the resume just continues without worktree context.
|
|
159
|
+
|
|
160
|
+
Each mode chooses its own injection mechanism, but the user-visible behavior is identical:
|
|
161
|
+
|
|
162
|
+
| Mode | Mechanism |
|
|
163
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------ |
|
|
164
|
+
| Interactive (TUI) | `INFO` history item + system-reminder prefix on the next user prompt. |
|
|
165
|
+
| Headless (`-p`) | `<system-reminder>` prefix on the prompt + `worktree_restored` JSON system event in the output stream. |
|
|
166
|
+
| ACP (e.g. Zed) | Pending notice attached to the next `prompt()` call. |
|
|
167
|
+
|
|
168
|
+
The model is **not** automatically `chdir`'d into the worktree — the reminder is what keeps it routing edits through the worktree path.
|
|
169
|
+
|
|
170
|
+
## Sub-Agent Isolation
|
|
171
|
+
|
|
172
|
+
The `agent` tool accepts an optional `isolation: "worktree"` parameter. When set, Qwen Code creates an ephemeral worktree at `<repoRoot>/.qwen/worktrees/agent-<7hex>/` before the sub-agent starts, and:
|
|
173
|
+
|
|
174
|
+
- **No changes** → the worktree is automatically removed when the agent finishes.
|
|
175
|
+
- **Has changes** → the worktree is preserved; its path and branch are appended to the agent's result, e.g.
|
|
176
|
+
```
|
|
177
|
+
…agent output…
|
|
178
|
+
[worktree preserved: /path/to/.qwen/worktrees/agent-3f2a1b9 (branch worktree-agent-3f2a1b9)]
|
|
179
|
+
```
|
|
180
|
+
Review the diff and merge or delete it manually.
|
|
181
|
+
|
|
182
|
+
Two constraints:
|
|
183
|
+
|
|
184
|
+
- `isolation: "worktree"` requires a `subagent_type` — forked sub-agents (no `subagent_type`) reuse the parent's full conversation context, so isolating them would split intent from working tree.
|
|
185
|
+
- Background agents (`run_in_background: true`) work fine with isolation; the cleanup runs when the agent reports completion.
|
|
186
|
+
|
|
187
|
+
### Automatic Stale Cleanup
|
|
188
|
+
|
|
189
|
+
Ephemeral agent worktrees that survived a crash or `--no-cleanup` shutdown are reaped on every CLI startup, with conservative fail-closed rules:
|
|
190
|
+
|
|
191
|
+
| Guard | Behavior |
|
|
192
|
+
| -------------------------------------- | ---------------------------------------------- |
|
|
193
|
+
| Slug must match `agent-<7hex>` pattern | Named worktrees you created are never touched. |
|
|
194
|
+
| Directory `mtime` > 30 days | Newer entries are skipped. |
|
|
195
|
+
| Any uncommitted tracked change | Skip the entry (don't delete). |
|
|
196
|
+
| Any commit not reachable from a remote | Skip the entry (don't delete). |
|
|
197
|
+
| Any error reading git state | Skip the entry (don't delete). |
|
|
198
|
+
|
|
199
|
+
Named user worktrees (`enter_worktree` slugs) are **never** auto-cleaned — you keep them around until you ask to remove them.
|
|
200
|
+
|
|
201
|
+
## Safety Guards on `exit_worktree action="remove"`
|
|
202
|
+
|
|
203
|
+
Three independent guards trigger before the directory and branch are deleted:
|
|
204
|
+
|
|
205
|
+
1. **Session ownership** — each worktree carries a sidecar marker with the session ID that created it. A different session trying to remove it is refused with a clear error pointing at `git worktree remove` for the manual escape hatch.
|
|
206
|
+
2. **Dirty working tree** — uncommitted tracked or untracked changes block removal. Pass `discard_changes: true` to override. (Bypass requires explicit user confirmation — `action: "remove"` is never auto-approved in AUTO_EDIT mode.)
|
|
207
|
+
3. **Unmerged commits** — commits on `worktree-<slug>` that no other local branch or remote ref points at block removal unconditionally; there is no "discard commits" flag because losing committed work is rarely what users mean. Merge, push, or rename the branch elsewhere first.
|
|
208
|
+
|
|
209
|
+
The same three guards apply to the `WorktreeExitDialog → Remove` button.
|
|
210
|
+
|
|
211
|
+
## Settings
|
|
212
|
+
|
|
213
|
+
Two settings shape the general-purpose worktree experience:
|
|
214
|
+
|
|
215
|
+
| Key | Type | Default | Effect |
|
|
216
|
+
| --------------------------------- | ---------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
217
|
+
| `ui.hideBuiltinWorktreeIndicator` | boolean | `false` | Hides the built-in `⎇ worktree-… (…)` Footer row. The `worktree` field is still delivered to custom status line scripts. Set to `true` only if your status line already renders the worktree — otherwise you lose all UI affordance. |
|
|
218
|
+
| `worktree.symlinkDirectories` | `string[]` | `undefined` | Directories under the main repo to symlink into every general-purpose worktree on creation. Paths are relative to the repo root; absolute paths and any entry containing `..` are rejected. Missing sources and existing destinations are silently skipped (no overwrite). |
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
|
|
222
|
+
```jsonc
|
|
223
|
+
// ~/.qwen/settings.json or <repo>/.qwen/settings.json
|
|
224
|
+
{
|
|
225
|
+
"worktree": {
|
|
226
|
+
"symlinkDirectories": ["node_modules", ".turbo", "dist"],
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Applies to ALL worktree-creation paths: `--worktree` flag, `enter_worktree` tool, and `agent isolation: "worktree"`.
|
|
232
|
+
|
|
233
|
+
Settings unrelated to general worktrees but worth knowing about:
|
|
234
|
+
|
|
235
|
+
- `agents.arena.worktreeBaseDir` — controls **Agent Arena** worktree placement (default `~/.qwen/arena`). Does not affect general-purpose worktrees, which always live under `<repoRoot>/.qwen/worktrees/`.
|
|
236
|
+
|
|
237
|
+
There is no schema for `worktree.sparsePaths` yet — that's a roadmap item (see [Limitations](#limitations)).
|
|
238
|
+
|
|
239
|
+
## Tool Reference
|
|
240
|
+
|
|
241
|
+
### `enter_worktree`
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{ "name": "experiment-a" }
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
| Field | Type | Required | Notes |
|
|
248
|
+
| ------ | ------ | -------- | ------------------------------------------------------------------------------------------ |
|
|
249
|
+
| `name` | string | no | Slug. Letters, digits, dot, underscore, hyphen; max 64 chars. Auto-generated when omitted. |
|
|
250
|
+
|
|
251
|
+
Refuses to run when:
|
|
252
|
+
|
|
253
|
+
- The CLI is not in a git repository.
|
|
254
|
+
- The current working directory is already inside `.qwen/worktrees/` (no nested worktrees).
|
|
255
|
+
|
|
256
|
+
### `exit_worktree`
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{ "name": "experiment-a", "action": "remove", "discard_changes": false }
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| Field | Type | Required | Notes |
|
|
263
|
+
| ----------------- | ---------------------- | ------------------------------------- | ------------------------------------------------------------------ |
|
|
264
|
+
| `name` | string | yes | Must match the slug used in `enter_worktree`. |
|
|
265
|
+
| `action` | `"keep"` \| `"remove"` | yes | `keep` preserves dir + branch; `remove` deletes both. |
|
|
266
|
+
| `discard_changes` | boolean | only when `action="remove"` and dirty | Overrides the dirty-tree guard. Has no effect for `action="keep"`. |
|
|
267
|
+
|
|
268
|
+
`action: "remove"` always prompts for confirmation, including under `AUTO_EDIT` approval mode — it is treated as a destructive shell operation, not an info-only tool.
|
|
269
|
+
|
|
270
|
+
### `agent` — `isolation` parameter
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"subagent_type": "my-agent",
|
|
275
|
+
"description": "…",
|
|
276
|
+
"prompt": "…",
|
|
277
|
+
"isolation": "worktree"
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
| Field | Type | Required | Notes |
|
|
282
|
+
| ----------- | ------------ | -------- | ------------------------------------------------------------------------------------------------- |
|
|
283
|
+
| `isolation` | `"worktree"` | no | Runs the agent in a fresh `agent-<7hex>` worktree. Requires `subagent_type` to be set (no forks). |
|
|
284
|
+
|
|
285
|
+
See [Sub-Agents](./sub-agents.md) for the rest of the agent tool reference.
|
|
286
|
+
|
|
287
|
+
## CLI Reference
|
|
288
|
+
|
|
289
|
+
### `--worktree [name | #N | url]`
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
qwen --worktree # auto-generate slug
|
|
293
|
+
qwen --worktree my-feature # explicit slug
|
|
294
|
+
qwen --worktree=my-feature # = form
|
|
295
|
+
qwen --worktree=#123 # PR reference
|
|
296
|
+
qwen --worktree https://github.com/owner/repo/pull/123 # PR URL
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
| Input | Result |
|
|
300
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
301
|
+
| Bare flag (no value) | Auto slug `<adjective>-<noun>-<6hex>`, branch `worktree-<slug>`, base = current branch. |
|
|
302
|
+
| Plain slug | Branch `worktree-<slug>`, base = current branch. Slug validation: letters/digits/dot/underscore/hyphen, max 64 chars. |
|
|
303
|
+
| `#N` or `<github-url>/pull/N` | Slug `pr-<N>`, branch `worktree-pr-<N>`, base = `FETCH_HEAD` after `git fetch origin pull/<N>/head` (30s timeout). |
|
|
304
|
+
|
|
305
|
+
`--worktree` cannot be combined with `--acp` / `--experimental-acp`.
|
|
306
|
+
|
|
307
|
+
When `--worktree` is combined with `--resume <session-id>`, the worktree wins: the resumed session's saved worktree (if any) is overridden and a stderr line + first-prompt reminder report the override.
|
|
308
|
+
|
|
309
|
+
For interactive (TUI) and headless (`-p`) modes the worktree is automatically created and the session chdirs into it before the first turn.
|
|
310
|
+
|
|
311
|
+
PR-fetch failure modes (exit code != 0, no worktree created):
|
|
312
|
+
|
|
313
|
+
| Cause | Message excerpt |
|
|
314
|
+
| ----------------------------- | ---------------------------------------------------------- |
|
|
315
|
+
| Missing `origin` remote | `requires an "origin" remote that points at GitHub` |
|
|
316
|
+
| PR doesn't exist on origin | `Failed to fetch PR #<N>: the PR does not exist on origin` |
|
|
317
|
+
| 30s network timeout | `Failed to fetch PR #<N>: timed out after 30s` |
|
|
318
|
+
| PR number out of range / zero | `Invalid PR number` |
|
|
319
|
+
|
|
320
|
+
## Limitations
|
|
321
|
+
|
|
322
|
+
The following items are intentionally not implemented in the current phase:
|
|
323
|
+
|
|
324
|
+
- **No sparse checkout.** Large monorepos check out the full tree. (`worktree.sparsePaths` is a roadmap item.)
|
|
325
|
+
- **No tmux integration.** The CLI does not spawn worktree sessions in new tmux windows.
|
|
326
|
+
- **Worktrees are separate "projects" for session storage.** Sessions started with `--worktree foo` are saved under that worktree's chats dir; to resume them later you must pass `--worktree foo` again. Sessions started without `--worktree` are saved under the main checkout and won't appear in the worktree's resume picker.
|
|
327
|
+
- **No cross-slug session override.** `qwen --resume <sid> --worktree second` where `<sid>` was created with `--worktree first` will fail to find the session — sessions and worktrees are tightly bound by `projectHash(cwd)`. To switch worktrees on an existing session you must exit, then re-launch with the new `--worktree` and a fresh prompt. A future architectural change (anchoring storage at the repo root instead of `cwd`) would lift this constraint.
|
|
328
|
+
- **Mid-session `enter_worktree` does NOT switch `process.cwd()` or `Config.targetDir`.** That tool uses the model-context-only convention (see [Sub-Agents](./sub-agents.md)). Only the startup `--worktree` flag actually switches the process working directory.
|
|
329
|
+
- **Relative paths in other arg fields are resolved BEFORE the worktree chdir.** Path-taking flags (`--mcp-config`, `--openai-logging-dir`, `--json-file`, `--input-file`, `--telemetry-outfile`, `--include-directories`) are normalized to absolute paths against the launch cwd when `--worktree` is set. Other path-shaped argv fields not in this list still resolve against the worktree cwd — use absolute paths to be safe.
|
|
330
|
+
|
|
331
|
+
Track the roadmap in `docs/design/worktree.md`.
|
|
332
|
+
|
|
333
|
+
## Troubleshooting
|
|
334
|
+
|
|
335
|
+
**The Footer shows no worktree indicator even though I just created one.**
|
|
336
|
+
Check that `ui.hideBuiltinWorktreeIndicator` is not set to `true`. Also confirm the slug is non-empty in the tool's success message.
|
|
337
|
+
|
|
338
|
+
**`--resume` does not restore my worktree.**
|
|
339
|
+
Check `<chatsDir>/<sessionId>.worktree.json` exists. The CLI deletes the sidecar automatically when the worktree directory is gone, so a missing sidecar plus a missing directory is the normal "no worktree to restore" state — not a bug. Run with `--debug` and grep for `restoreWorktreeContext` to see the reason.
|
|
340
|
+
|
|
341
|
+
**`exit_worktree` says "created by a different session".**
|
|
342
|
+
This is the session-ownership guard. Resume the original session and exit from there, or run the suggested `git worktree remove …` command manually.
|
|
343
|
+
|
|
344
|
+
**Stale `agent-<hex>` worktrees keep piling up.**
|
|
345
|
+
The 30-day cutoff is conservative; sweep manually with `git worktree list && git worktree remove <path>`, or wait — the next CLI startup after the 30-day mark will reap them as long as they are clean and pushed.
|
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
### Install Qwen Code:
|
|
11
11
|
|
|
12
12
|
The recommended installer uses a standalone archive when one is available for
|
|
13
|
-
your platform. If it falls back to npm, Node.js
|
|
13
|
+
your platform. If it falls back to npm, Node.js 22 or later with npm must be
|
|
14
14
|
available on PATH.
|
|
15
15
|
|
|
16
16
|
**Linux / macOS**
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash
|
|
19
|
+
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh | bash
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
**Windows**
|
|
23
23
|
|
|
24
|
-
```
|
|
25
|
-
|
|
24
|
+
```powershell
|
|
25
|
+
irm https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.ps1 | iex
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
> [!note]
|
|
@@ -21,13 +21,13 @@ To install Qwen Code, use one of the following methods:
|
|
|
21
21
|
**Linux / macOS**
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash
|
|
24
|
+
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh | bash
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
**Windows
|
|
27
|
+
**Windows**
|
|
28
28
|
|
|
29
|
-
```
|
|
30
|
-
|
|
29
|
+
```powershell
|
|
30
|
+
irm https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.ps1 | iex
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
> [!note]
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Local launch templates for `qwen serve` (v0.16-alpha)
|
|
2
|
+
|
|
3
|
+
Reference templates for running `qwen serve` as a long-lived background process on a developer workstation. Pairs with the [v0.16-alpha known limits](./qwen-serve.md#v016-alpha-known-limits) — local-only, single-user, BYO bearer token. Containerized / multi-host / TLS-fronted deployments defer to v0.16.x.
|
|
4
|
+
|
|
5
|
+
> **Audience**: dogfooding developers who want the daemon up across reboots, with logs going somewhere durable, and a clean `restart-on-failure` story. If you only need the daemon for the duration of a single shell session, plain `qwen serve` (foreground, Ctrl-C to stop) is fine.
|
|
6
|
+
|
|
7
|
+
## Generate a bearer token (once)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
openssl rand -hex 32 > ~/.qwen-serve-token # user-managed, NOT a built-in path
|
|
11
|
+
chmod 600 ~/.qwen-serve-token
|
|
12
|
+
export QWEN_SERVER_TOKEN="$(cat ~/.qwen-serve-token)"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The path / filename is yours to choose; v0.16-alpha does not auto-generate or auto-locate a token file (deferred to v0.16.x). See the [Authentication](./qwen-serve.md#authentication) section of the user guide for the canonical BYO setup.
|
|
16
|
+
|
|
17
|
+
> **Scope this `export` to the current shell session only.** Don't add it to `~/.bashrc` / `~/.zshrc` — a profile-level export exposes the bearer token to every process spawned from that shell (IDE subprocesses, browser debuggers, `npm` scripts from unrelated projects). For long-running setups, use the systemd `EnvironmentFile=` / launchd `EnvironmentVariables` mechanisms below — both scope the token to just the daemon process.
|
|
18
|
+
|
|
19
|
+
The daemon reads the bearer token from either `--token <value>` on the CLI or the `QWEN_SERVER_TOKEN` env var (whitespace stripped from both). The TypeScript SDK's `DaemonClient` constructor falls back to `QWEN_SERVER_TOKEN` when no `token` option is passed (PR 27 fallback — clients with the env var set never need to thread the value through their script).
|
|
20
|
+
|
|
21
|
+
One shell-level `export` covers both server boot and SDK client construction (just keep it scoped to the session, per the note above).
|
|
22
|
+
|
|
23
|
+
## Linux: systemd user unit
|
|
24
|
+
|
|
25
|
+
> **Find your `qwen` binary first.** The unit file's `ExecStart=` must hold an **absolute path** — service managers don't read your shell's `PATH`. Run `which qwen` to discover it. Common locations: `/usr/local/bin/qwen` (Linuxbrew, manual installs), `~/.nvm/versions/node/vX.Y.Z/bin/qwen` (nvm), `~/.fnm/aliases/default/bin/qwen` (fnm), `~/.volta/bin/qwen` (Volta). Substitute the actual path everywhere the templates below show `/PATH/TO/qwen`.
|
|
26
|
+
|
|
27
|
+
`~/.config/systemd/user/qwen-serve.service`:
|
|
28
|
+
|
|
29
|
+
```ini
|
|
30
|
+
[Unit]
|
|
31
|
+
Description=Qwen Code daemon (loopback HTTP + SSE)
|
|
32
|
+
After=network.target
|
|
33
|
+
|
|
34
|
+
[Service]
|
|
35
|
+
Type=simple
|
|
36
|
+
# Replace with your project; %h expands to $HOME under user units.
|
|
37
|
+
WorkingDirectory=%h/your-project
|
|
38
|
+
# Run `which qwen` to find the absolute path. systemd does NOT read $PATH.
|
|
39
|
+
ExecStart=/PATH/TO/qwen serve --hostname 127.0.0.1 --port 4170
|
|
40
|
+
# Read the bearer token from a chmod 600 file rather than inlining it
|
|
41
|
+
# in the unit. `Environment=` would expose the token in the unit file
|
|
42
|
+
# (typically 644 = world-readable). EnvironmentFile keeps the token in
|
|
43
|
+
# the user-owned secret file you already created with `chmod 600`.
|
|
44
|
+
EnvironmentFile=%h/.qwen-serve-token-env
|
|
45
|
+
Restart=on-failure
|
|
46
|
+
RestartSec=5
|
|
47
|
+
StandardOutput=journal
|
|
48
|
+
StandardError=journal
|
|
49
|
+
|
|
50
|
+
[Install]
|
|
51
|
+
WantedBy=default.target
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Build the env file once (the token file from the setup step holds the raw value; this wraps it in `KEY=value` form so systemd reads it as an env assignment):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
echo "QWEN_SERVER_TOKEN=$(cat ~/.qwen-serve-token)" > ~/.qwen-serve-token-env
|
|
58
|
+
chmod 600 ~/.qwen-serve-token-env
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Manage:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
systemctl --user daemon-reload
|
|
65
|
+
systemctl --user enable --now qwen-serve.service
|
|
66
|
+
loginctl enable-linger "$(whoami)" # keep the user manager running after logout / across reboot
|
|
67
|
+
journalctl --user -u qwen-serve -f # tail logs
|
|
68
|
+
systemctl --user restart qwen-serve.service # after token rotation
|
|
69
|
+
systemctl --user disable --now qwen-serve.service
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Without `loginctl enable-linger`, the user-level systemd instance shuts down when the user logs out and only restarts on next login — on a headless dev box the daemon would not survive an SSH session ending. `enable-linger` is what makes "across reboots" actually work.
|
|
73
|
+
|
|
74
|
+
**System-wide alternative** (shared dev hosts, less common): drop the unit at `/etc/systemd/system/qwen-serve@.service` with `User=%i`, manage via `sudo systemctl enable --now qwen-serve@<username>.service`. Same `[Service]` body otherwise — but world-readable `Environment=` exposure is even more problematic at this level, so always use `EnvironmentFile=` pointing at the user's `chmod 600` file. Pick user-level + linger for single-user workstations.
|
|
75
|
+
|
|
76
|
+
## macOS: launchd user agent
|
|
77
|
+
|
|
78
|
+
> **Find your `qwen` binary first.** Same constraint as systemd — `ProgramArguments` must hold an **absolute path**. Run `which qwen` to discover it. Common locations on macOS: `/opt/homebrew/bin/qwen` (Homebrew on Apple Silicon), `/usr/local/bin/qwen` (Homebrew on Intel, manual installs), `~/.nvm/versions/node/vX.Y.Z/bin/qwen` (nvm), `~/.volta/bin/qwen` (Volta). Substitute below where the template shows `/PATH/TO/qwen`.
|
|
79
|
+
|
|
80
|
+
`~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist`:
|
|
81
|
+
|
|
82
|
+
```xml
|
|
83
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
84
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
85
|
+
<plist version="1.0">
|
|
86
|
+
<dict>
|
|
87
|
+
<key>Label</key>
|
|
88
|
+
<string>com.qwenlm.qwen-serve</string>
|
|
89
|
+
<key>ProgramArguments</key>
|
|
90
|
+
<array>
|
|
91
|
+
<!-- Run `which qwen` to find the absolute path; launchd does NOT read $PATH. -->
|
|
92
|
+
<string>/PATH/TO/qwen</string>
|
|
93
|
+
<string>serve</string>
|
|
94
|
+
<string>--hostname</string>
|
|
95
|
+
<string>127.0.0.1</string>
|
|
96
|
+
<string>--port</string>
|
|
97
|
+
<string>4170</string>
|
|
98
|
+
</array>
|
|
99
|
+
<!-- launchd does NOT expand `~` or `$HOME` — use absolute paths. -->
|
|
100
|
+
<key>WorkingDirectory</key>
|
|
101
|
+
<string>/Users/YOUR-USERNAME/your-project</string>
|
|
102
|
+
<key>EnvironmentVariables</key>
|
|
103
|
+
<dict>
|
|
104
|
+
<!-- DO NOT COMMIT this file with a real token. Also chmod 600 the
|
|
105
|
+
plist itself so the inlined token is not world-readable. -->
|
|
106
|
+
<key>QWEN_SERVER_TOKEN</key>
|
|
107
|
+
<string>PASTE-YOUR-TOKEN-HERE</string>
|
|
108
|
+
</dict>
|
|
109
|
+
<key>RunAtLoad</key>
|
|
110
|
+
<true/>
|
|
111
|
+
<!-- Restart only on non-zero exits (matches systemd Restart=on-failure).
|
|
112
|
+
A bare `<true/>` would respawn even after a clean SIGTERM, making
|
|
113
|
+
`kill <pid>` impossible to use as a stop signal — operator would
|
|
114
|
+
have to `launchctl unload`. SuccessfulExit=false fixes that. -->
|
|
115
|
+
<key>KeepAlive</key>
|
|
116
|
+
<dict>
|
|
117
|
+
<key>SuccessfulExit</key>
|
|
118
|
+
<false/>
|
|
119
|
+
</dict>
|
|
120
|
+
<!-- Throttle restart storms on persistent failures (mirrors systemd
|
|
121
|
+
RestartSec=5; launchd's default would respawn every <1s). -->
|
|
122
|
+
<key>ThrottleInterval</key>
|
|
123
|
+
<integer>10</integer>
|
|
124
|
+
<!-- Log into the user's Library, not /tmp. /tmp is world-writable
|
|
125
|
+
(symlink-attack risk on shared workstations) and gets cleaned by
|
|
126
|
+
periodic-daily after 3 days; `~/Library/Logs/qwen-serve/` is
|
|
127
|
+
user-scoped and survives. launchd truncates these on every
|
|
128
|
+
`load`, so the unload→load token-rotation cycle wipes prior
|
|
129
|
+
diagnostic logs — back them up if you need post-incident
|
|
130
|
+
inspection. -->
|
|
131
|
+
<key>StandardOutPath</key>
|
|
132
|
+
<string>/Users/YOUR-USERNAME/Library/Logs/qwen-serve/out.log</string>
|
|
133
|
+
<key>StandardErrorPath</key>
|
|
134
|
+
<string>/Users/YOUR-USERNAME/Library/Logs/qwen-serve/err.log</string>
|
|
135
|
+
</dict>
|
|
136
|
+
</plist>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Manage:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
mkdir -p ~/Library/Logs/qwen-serve # first time only
|
|
143
|
+
chmod 600 ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist # plist holds the inline token
|
|
144
|
+
launchctl load ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist
|
|
145
|
+
launchctl unload ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist # to stop
|
|
146
|
+
tail -f ~/Library/Logs/qwen-serve/out.log ~/Library/Logs/qwen-serve/err.log
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
After editing the plist (e.g., rotating the token) you must `unload` then `load` again — `launchctl` does not auto-reload on plist changes the way `systemd daemon-reload` does. Note: each `load` truncates the log files, so save them off if you're investigating an incident before rotating.
|
|
150
|
+
|
|
151
|
+
## tmux session (interactive supervision)
|
|
152
|
+
|
|
153
|
+
Assumes `QWEN_SERVER_TOKEN` is already exported in your shell (see the setup section above):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
tmux new -d -s qwen-serve "cd ~/your-project && qwen serve --hostname 127.0.0.1"
|
|
157
|
+
tmux attach -t qwen-serve # see live logs; Ctrl-b d to detach
|
|
158
|
+
tmux kill-session -t qwen-serve
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`tmux new -d` inherits the parent shell's environment, so `QWEN_SERVER_TOKEN` flows through automatically. Best when you want to occasionally watch the daemon's stdout (auth warnings, MCP discovery progress, slow-client warnings) without committing to a service unit. Survives terminal close but not host reboot.
|
|
162
|
+
|
|
163
|
+
## nohup one-liner (quick + dirty)
|
|
164
|
+
|
|
165
|
+
Assumes `QWEN_SERVER_TOKEN` is already exported in your shell:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
nohup bash -c 'cd ~/your-project && qwen serve --hostname 127.0.0.1' > qwen-serve.log 2>&1 &
|
|
169
|
+
echo $! # daemon PID; capture if you want to `kill` cleanly later
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The wrapping `bash -c '...'` ensures the daemon binds to `~/your-project` rather than wherever you happened to run the command. Without that `cd`, `qwen serve` defaults to `process.cwd()` and a `POST /session` from a client expecting your project workspace returns `400 workspace_mismatch` — silent foot-gun.
|
|
173
|
+
|
|
174
|
+
OK for one-off "let me run this in the background while I poke at the API" workflows. **Not recommended** for anything beyond a single session — no restart-on-crash, log file grows unbounded, no clean way to find the daemon if you forget the PID. Prefer tmux for interactive supervision or systemd / launchd for anything you want to outlast a reboot.
|
|
175
|
+
|
|
176
|
+
## Verifying the daemon is up
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
curl http://127.0.0.1:4170/health # → {"status":"ok"}
|
|
180
|
+
curl -H "Authorization: Bearer $QWEN_SERVER_TOKEN" \
|
|
181
|
+
http://127.0.0.1:4170/capabilities | jq .protocolVersions # daemon's feature set
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
When auth is configured (i.e., the daemon was started with `--token` / `QWEN_SERVER_TOKEN` set, OR `--require-auth=true`), every route except `/health` on loopback binds requires `Authorization: Bearer <token>`. If you started the daemon without a token on the loopback default (the `qwen serve` zero-config path), neither call requires a header. The templates above all configure a token, so the `Authorization` header is needed in practice. If `/capabilities` returns `401`, the unit / plist token doesn't match the env-exported token your `curl` is using.
|
|
185
|
+
|
|
186
|
+
## Token rotation
|
|
187
|
+
|
|
188
|
+
1. Generate a new token + write the env file the unit references:
|
|
189
|
+
```bash
|
|
190
|
+
openssl rand -hex 32 > ~/.qwen-serve-token
|
|
191
|
+
chmod 600 ~/.qwen-serve-token
|
|
192
|
+
echo "QWEN_SERVER_TOKEN=$(cat ~/.qwen-serve-token)" > ~/.qwen-serve-token-env
|
|
193
|
+
chmod 600 ~/.qwen-serve-token-env
|
|
194
|
+
```
|
|
195
|
+
(For the launchd / nohup / tmux templates: edit the plist's `<string>` value or re-`export QWEN_SERVER_TOKEN`. Don't forget `chmod 600` on the plist if you regenerate it.)
|
|
196
|
+
2. Restart the daemon:
|
|
197
|
+
- **systemd**: `systemctl --user restart qwen-serve.service`
|
|
198
|
+
- **launchd**: `launchctl unload ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist && launchctl load ~/Library/LaunchAgents/com.qwenlm.qwen-serve.plist`
|
|
199
|
+
- **tmux / nohup**: `kill <pid>` then re-run with the new token in env
|
|
200
|
+
3. Update any client SDKs / scripts. The TypeScript SDK's `DaemonClient` reads `QWEN_SERVER_TOKEN` automatically (PR 27 fallback) — re-`export` the new value in any client shell and reconstruct the client.
|
|
201
|
+
|
|
202
|
+
## Restart and crash behavior
|
|
203
|
+
|
|
204
|
+
Service-manager restart semantics differ across the templates:
|
|
205
|
+
|
|
206
|
+
- **systemd `Restart=on-failure`** — restart only on non-zero exit / signal. A clean SIGTERM (`systemctl stop`) does **not** trigger a restart loop.
|
|
207
|
+
- **launchd `KeepAlive` with `SuccessfulExit=false`** (the template above) — matches systemd behavior. A bare `<true/>` would have respawned even after a clean exit. `ThrottleInterval=10` rate-limits restart storms on persistent failures, mirroring systemd's `RestartSec=5`.
|
|
208
|
+
- **tmux / nohup** — no automatic restart. A daemon crash leaves you with a dead PID until you re-run.
|
|
209
|
+
|
|
210
|
+
Within a **single daemon process lifetime**, client disconnects recover via SSE `Last-Event-ID` resume per the [Durability model](./qwen-serve.md#durability-model) section of the user guide — the replay ring is in-memory.
|
|
211
|
+
|
|
212
|
+
A daemon **restart** drops all in-memory sessions; clients reconnect and start fresh. Cross-restart durability of session content (prompts, tool calls, conversation history) is **NOT** in v0.16-alpha.
|
|
213
|
+
|
|
214
|
+
## Out of scope (defers to v0.16.x or later)
|
|
215
|
+
|
|
216
|
+
- **Containerized deployment** — Dockerfile, docker-compose, Kubernetes manifests, nginx + TLS reverse proxy, multi-instance token isolation. Defers to v0.16.x once an enterprise pilot is committed; the doc would otherwise rot from no-one-validating.
|
|
217
|
+
- **Cross-host federation / multi-daemon coordination on one host** — `1 daemon = 1 workspace × N sessions` is enforced. Instance-path token keying + stale-token cleanup defer to v0.16.x.
|
|
218
|
+
- **Auto-generated daemon tokens** — alpha is BYO-token. Auto-gen + token-store infrastructure defers to v0.16.x.
|
|
219
|
+
- **Windows native service** (`nssm`, Service Control Manager wrapper) — for now use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/) and follow the systemd section above.
|
|
220
|
+
|
|
221
|
+
See the [v0.16-alpha known limits](./qwen-serve.md#v016-alpha-known-limits) callout in the main user guide for the full deferred-features list, and [#4175](https://github.com/QwenLM/qwen-code/issues/4175) for the v0.16-alpha rollout tracking issue.
|