wezard 2.4.0 → 2.5.0

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.
Files changed (101) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +113 -57
  3. package/cli/wezard.sh +2 -0
  4. package/config.example.jsonc +7 -4
  5. package/dist/cli/wx.js +78 -0
  6. package/dist/cli/wx.js.map +1 -0
  7. package/dist/daemon/aibot-media.js +62 -0
  8. package/dist/daemon/aibot-media.js.map +1 -0
  9. package/dist/daemon/approval.js +30 -20
  10. package/dist/daemon/approval.js.map +1 -1
  11. package/dist/daemon/ask.js +3 -2
  12. package/dist/daemon/ask.js.map +1 -1
  13. package/dist/daemon/cc-session.js +27 -4
  14. package/dist/daemon/cc-session.js.map +1 -1
  15. package/dist/daemon/chat-log.js +2 -2
  16. package/dist/daemon/chat-log.js.map +1 -1
  17. package/dist/daemon/chat-name.js +6 -2
  18. package/dist/daemon/chat-name.js.map +1 -1
  19. package/dist/daemon/config-api.js +25 -10
  20. package/dist/daemon/config-api.js.map +1 -1
  21. package/dist/daemon/direction.js +53 -0
  22. package/dist/daemon/direction.js.map +1 -0
  23. package/dist/daemon/ilink.js +162 -0
  24. package/dist/daemon/ilink.js.map +1 -0
  25. package/dist/daemon/inbound.js +64 -18
  26. package/dist/daemon/inbound.js.map +1 -1
  27. package/dist/daemon/index.js +415 -109
  28. package/dist/daemon/index.js.map +1 -1
  29. package/dist/daemon/jobs.js +4 -3
  30. package/dist/daemon/jobs.js.map +1 -1
  31. package/dist/daemon/last-response.js +6 -3
  32. package/dist/daemon/last-response.js.map +1 -1
  33. package/dist/daemon/media.js +108 -0
  34. package/dist/daemon/media.js.map +1 -0
  35. package/dist/daemon/mirror-bridge.js +111 -93
  36. package/dist/daemon/mirror-bridge.js.map +1 -1
  37. package/dist/daemon/model-select.js +159 -54
  38. package/dist/daemon/model-select.js.map +1 -1
  39. package/dist/daemon/notices.js +59 -12
  40. package/dist/daemon/notices.js.map +1 -1
  41. package/dist/daemon/outbound.js +2 -1
  42. package/dist/daemon/outbound.js.map +1 -1
  43. package/dist/daemon/peers.js +46 -27
  44. package/dist/daemon/peers.js.map +1 -1
  45. package/dist/daemon/pending-items.js +1 -1
  46. package/dist/daemon/pending-items.js.map +1 -1
  47. package/dist/daemon/reach.js +6 -0
  48. package/dist/daemon/reach.js.map +1 -0
  49. package/dist/daemon/receipts.js +149 -7
  50. package/dist/daemon/receipts.js.map +1 -1
  51. package/dist/daemon/route.js +11 -4
  52. package/dist/daemon/route.js.map +1 -1
  53. package/dist/daemon/spawn-tmux.js +157 -60
  54. package/dist/daemon/spawn-tmux.js.map +1 -1
  55. package/dist/daemon/steward-guard.js +48 -7
  56. package/dist/daemon/steward-guard.js.map +1 -1
  57. package/dist/daemon/top-only-nudge.js +29 -0
  58. package/dist/daemon/top-only-nudge.js.map +1 -0
  59. package/dist/daemon/weixin-cmd.js +268 -0
  60. package/dist/daemon/weixin-cmd.js.map +1 -0
  61. package/dist/daemon/weixin-port.js +226 -0
  62. package/dist/daemon/weixin-port.js.map +1 -0
  63. package/dist/daemon/weixin.js +772 -0
  64. package/dist/daemon/weixin.js.map +1 -0
  65. package/dist/daemon/wizard.js +56 -18
  66. package/dist/daemon/wizard.js.map +1 -1
  67. package/dist/mcp/server.js +43 -23
  68. package/dist/mcp/server.js.map +1 -1
  69. package/dist/shared/chat-http.js +61 -20
  70. package/dist/shared/chat-http.js.map +1 -1
  71. package/dist/shared/chat-render.js +9 -1
  72. package/dist/shared/chat-render.js.map +1 -1
  73. package/dist/shared/chat-view.js +16 -2
  74. package/dist/shared/chat-view.js.map +1 -1
  75. package/dist/shared/cli-backends.js +3 -0
  76. package/dist/shared/cli-backends.js.map +1 -1
  77. package/dist/shared/config.js +45 -10
  78. package/dist/shared/config.js.map +1 -1
  79. package/dist/shared/json-map-store.js +1 -0
  80. package/dist/shared/json-map-store.js.map +1 -1
  81. package/dist/shared/reminder.js +1 -0
  82. package/dist/shared/reminder.js.map +1 -1
  83. package/dist/shared/role-render.js +7 -4
  84. package/dist/shared/role-render.js.map +1 -1
  85. package/dist/shared/role-view.js +116 -37
  86. package/dist/shared/role-view.js.map +1 -1
  87. package/dist/shared/seen-log.js +26 -0
  88. package/dist/shared/seen-log.js.map +1 -0
  89. package/dist/shared/session-label.js +84 -31
  90. package/dist/shared/session-label.js.map +1 -1
  91. package/dist/shared/turn-state.js +1 -1
  92. package/dist/shared/turn-state.js.map +1 -1
  93. package/dist/shared/world.js.map +1 -1
  94. package/dist/shared/wx-text.js +101 -0
  95. package/dist/shared/wx-text.js.map +1 -0
  96. package/dist/svr/index.js +5 -2
  97. package/dist/svr/index.js.map +1 -1
  98. package/hooks/pre-tool-use.sh +9 -6
  99. package/package.json +6 -2
  100. package/web/chat.css +32 -5
  101. package/web/chat.js +160 -92
package/CLAUDE.md CHANGED
@@ -56,7 +56,7 @@ Three processes, all coordinating via 127.0.0.1:17890 and `~/.wezard/`:
56
56
  - `mirror-bridge.ts` tails `<projectsDir>/<encoded(cwd)>/<sid>.jsonl` and pushes new `assistant` content to a chat — but only for turns the chat itself started: a turn opened by a human's CLI keystroke is recorded to the turn store (rolepage) and kept out of the chat (`wrc.mirror.chatOriginOnly`, brief mode only); approval/askq cards and `[mirror]` notices bypass that gate. Inbound injection is either `claude --resume <sid> -p` (writes a turn into the same jsonl) or `tmux paste-buffer` against a live TTY (only path that makes WeCom messages visible in the user's CLI window). `recentInjects` ring buffer suppresses the WeCom→CLI echo when tailing (which is why an injected line's text is stamped as the turn's `userQuery` at inject time — the tail never sees it). **Every turn has a channel** (`TurnDetailRecord.channel`): the base of the public chat it was spoken in and where its reply goes — a human can `.name` a wizard homed in another chat and the reply (text and cards) returns to the chat that asked, not the wizard's home; `""` = a private wizard↔wizard turn whose reply is pushed nowhere and lives only on the rolepage.
57
57
  - `claim.ts` is a single-shot bootstrap: an exact-match magic phrase from any sender bypasses `allowFrom`, writes that principal as `defaultChat`, and appends to `allowFrom`. This is the **only** legitimate ingress for first-time setup.
58
58
  - `peers.ts` is the read side of multi-agent awareness — "who else is working in this chat, and are they busy". A wizard's HOME chat is its target key's base principal (`baseOfKey`): the untagged default plus every slot `chat:xxx#slot`. Pure parsing only — bounded transcript-tail reads (`tailTurns` / `lastAssistantText` / `summarizeTail`, shared with `session-scan.ts`) and pane classification (`paneIsBusy`). **What a wizard reads about another comes from the transcript, never from a pane capture**: text turns only, keepalive ping/pong stripped (`talkTurns` / `withoutKeepalive` — pass the ping signature), and "where is it stuck" is `openToolUses` (a tool call left hanging on an idle session = parked on an approval card). The pane is consulted for exactly one thing, `busy`. Read-side tools (`wizard_roster` / `peek_peer` / `read_chat`) answer in plain text rendered daemon-side, not json; `read_chat` (`chat-log.ts`) is computed from session transcripts alone, not the turn store: a log = every recently-active session's ask + final answer per round, merged by timestamp, then narrowed on the rolepage's axis `role` → `chat` → `target` and cut by `since` / `until` / `limit`. Which channel a line belongs to and who said it is read back from the envelope riding on that user line (`parseEnvelope`, `shared/reminder.ts`): every wezard `<system-reminder>` carries its facts as opening-tag attributes (`wezard=envelope|mention|roster`, `kind` / `from` / `chat` / `scope`) and its prose only for the model — rewording the prose never breaks parsing; old attribute-less wordings keep parsing via its `LEGACY` table. The rolepage collapses each reminder into a `<reminder>` tag that expands to raw on click; no envelope = a human speaking in the session's home chat. A clone's transcript starts with its parent's forked history, so its rounds older than `bornAt` are dropped. Identity (name / job / memory / lineage) lives in `wizard.ts`, not here. **Busy detection matches the TUI spinner footer** (`… (12m 31s · …)` on current versions, `esc to interrupt` on older ones) in the last 8 non-blank rows — transcript mtime is useless here, it goes quiet during long tool calls.
59
- - `wizard.ts` is the identity layer over those sessions: a target key plus a **globally unique name** — the wizard's only address, written `.name` everywhere a human or model sees it (a chat's default session takes the chat's name, a slot takes its slot id; collisions get `-N` via `pickName`; `settleName` claims lazily, `settleAll` migrates at boot, default sessions first). The slot id inside the key is NOT the name: a rename never rekeys a session. Plus a one-line job description, a long-term `memory` list, and a `parent` link that makes a session a CLONE of another. The registry is a write-through json (`~/.wezard/wizards.json`, `wrc.mirror.wizardsFile`) bound process-wide via `bindWizardStore` so the IM side can read it too; everything else in the module is a pure renderer. **Identity travels as a system prompt, never as a message**: `renderCharter` builds the wizard's charter (who it is, its workspace, its clones, its memory, the chat's and the workspace's shared memory (`wizard-memory.ts`: `~/.wezard/memory/{chats,workspaces}/*.md`, human-editable, appended by `wizard_remember({scope})`), the tool roster, the orchestration playbook, the public-vs-private etiquette) and `spawn-tmux` presses it in with `--append-system-prompt "$(cat <file>)"` — so it survives `/clear`, costs no turn, and never shows in chat. Every spawn path (`/new`, a dead-pane respawn, a clone) pulls it from `MirrorBridge.setCharterProvider`, installed in `index.ts`. Since a running wizard never sees a new charter, `config_set` renders every chartered wizard's charter before and after the change and tells the affected ones (and the caller) to `handoff` — generic over whatever config the charter reads. Backends with no such flag (codebuddy) spawn without a charter rather than with a rejected flag.
59
+ - `wizard.ts` is the identity layer over those sessions: a target key plus a **globally unique name** — the wizard's only address, written `.name` everywhere a human or model sees it (a chat's default session takes the chat's name, a slot takes its slot id; collisions get `-N` via `pickName`; `settleName` claims lazily, `settleAll` migrates at boot, default sessions first). The slot id inside the key is NOT the name: a rename never rekeys a session. Plus a one-line job description, a long-term `memory` list, and a `parent` link that makes a session a CLONE of another. The registry is a write-through json (`~/.wezard/wizards.json`, `wrc.mirror.wizardsFile`) bound process-wide via `bindWizardStore` so the IM side can read it too; everything else in the module is a pure renderer. **Identity travels as a system prompt, never as a message**: `renderCharter` builds the wizard's charter (who it is, its workspace, its clones, its memory, the chat's and the workspace's shared memory (`wizard-memory.ts`: `~/.wezard/memory/{chats,workspaces}/*.md`, human-editable, appended by `wizard_remember({scope})`), the tool roster, the orchestration playbook, the public-vs-private etiquette) and `spawn-tmux` presses it in with `--append-system-prompt "$(cat <file>)"` — so it survives `/clear`, costs no turn, and never shows in chat. Every spawn path (`/new`, a dead-pane respawn, a clone) pulls it from `MirrorBridge.setCharterProvider`, installed in `index.ts`. Since a running wizard never sees a new charter, `config_set` renders every chartered wizard's charter before and after the change and tells the affected ones (and the caller) to `handoff` — generic over whatever config the charter reads. Backends with no such flag spawn without a charter rather than with a rejected flag.
60
60
  - **Cloning is a context fork**: `MirrorBridge.cloneSession` spawns `--resume <parent sid> --fork-session`, so the child starts holding everything the parent had read while the parent is untouched — that is what makes "load the shared material once, then fan out" work. Two invariants: `--fork-session` is mandatory (without it a second pane appends to the parent's jsonl and two chats cross-wire), and the forked transcript only appears when that pane takes its FIRST message — so the clone's opening line is the trigger, and the attach waits for the fork; if it never appears the pane is killed rather than bound to the parent's sid.
61
61
  - **Chats are public channels, wizard↔wizard is private by default.** `send_peer` injects with `channel:""` and posts no bubble; `public:true` (the model decides) posts `emoji .a → .b` into the sender's current channel and the reply follows there. No spawn/relay/job bubbles remain — only `notify`. A `tell_peer` carrying a `job` is always private (`public` is ignored), so a fan-out's whole process stays on the rolepage; the human sees only the initiator's own closing reply, routed back along the chain that asked it. A chat with `chatPolicy.<chat>.topOnly` (top-level-only mode) on goes further (daemon-enforced in `tellPeerAs` / `/notify`): every wizard↔wizard line there is private regardless of `public`, and a wizard whose turn is working for a peer (a private peer task, or a receipt whose `k` is a peer) may not `notify` it (the steward is exempt in its own chat) — the charter tells top-level wizards to explain the result in their own words.
62
62
  - **`tell_peer` delivery policy** (`priority`) only matters when the target is busy: `normal` (default) waits for its turn to end before pasting — two senders pasting into one busy box get read as one turn; `urgent` presses Esc first (the same interrupt as `stop_wizard`, without the teardown that would drop others' queued lines); `now` pastes into the running turn (interject, answer its question; the default for `re`).
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  <p><b>在企业微信里远程操控本机的 AI 编程 Agent,并让多个 Agent 互相感知、协同干活。</b></p>
8
8
 
9
9
  <p>地铁上发一句话,电脑上的 Agent 就开始干:每次工具调用推一张审批卡等你点,回复、工具调用、思考过程实时回到聊天,回到电脑前 <code>tmux attach</code> 接着干。<br/>
10
- 一个聊天里住着多个 <b>wizard</b>——全局唯一的 <code>.name</code>、各自的职责与记忆,彼此看得见也叫得动。没点名的话落到管家,由它判断转给对口的那个还是新起一个;派出去的活不必守着等,对方干完结论自动回到发话方。</p>
10
+ 一个聊天里住着多个 <b>wizard</b>——全局唯一的 <code>.name</code>、各自的职责与记忆,彼此看得见也叫得动。没点名的话落到管家,由它派给对口的那个、新起一个,或交给一个 lead 带队;派出去的活不必守着等,对方干完结论自动回到发话方。</p>
11
11
 
12
12
  <p>
13
13
  <a href="https://www.npmjs.com/package/wezard"><img src="https://img.shields.io/npm/v/wezard?style=flat-square&color=07C160&label=npm" alt="npm version" /></a>
@@ -24,15 +24,19 @@
24
24
 
25
25
  1. 🪞 **远程控制本机 Agent CLI** — 企业微信里发的消息原样落进 CLI 输入框;Agent 的回复、工具调用、思考过程实时同步回聊天。回到电脑前 `tmux attach -t wezard` 接着干,对话一字不少。支持 `claude` / `claude-internal` / `codebuddy`。
26
26
  2. 🛎 **远程审批** — Agent 要跑 `Bash` / `Edit` 时审批卡直推 IM:`✅` 放行一次、`⏱` 一段时间内自动放行、`✅总是` 记成规则、`❌` 拒绝。危险操作(`rm` / 强推 / `DROP` / 敏感路径)每次都要单独确认;做完计划推计划卡(`✅同意` / `✏️继续改`),Agent 提的选择题变成投票卡。
27
- 3. 🧙 **多 wizard** — 每个会话是一个有名字的 wizard,写作 `.name`,全局唯一,在任何群里都叫得到;有职责、有记忆,看得见彼此在干什么。克隆一个分身让它继承当前上下文,或者从白板新起一个。
28
- 4. 🎩 **管家分派** — 没点名的话落到聊天的默认 wizard:一两句能答完的它自己答,其余判断转给对口的那个还是新起一个。转交是公开的,被转到的那个直接在群里回你。
29
- 5. 📮 **派完就放手** — wizard 之间派活不必守着等:对方那一轮一结束,守护进程把它的结论作为新的一轮自动送回发话方。
30
- 6. 🧠 **三层记忆** — 自己的、本群共享的、本工作区共享的。「这个仓库 reload 要先 build」这类约定写一次,之后每个新 wizard 开局就知道;共享的两层由一个定时整理者去重合并。
31
- 7. 🪪 **rolepage** — 每个 wizard 视角下的 IM:`⌘K` 搜遍 role / 会话 / 消息,关系图看清谁和谁对过话,用量条一眼看出模型、轮次、上下文与 token 花在哪。
32
- 8. ⏰ **定时任务** — 「每个工作日晚上 9:30 跑一遍回归」,到点自动起一个 wizard 去干,结果发回群里。任务本身是一个可以直接编辑的 `.mjs` 文件,存盘即生效。
33
- 9. 🔍 **详情页与主动汇报** — 每次工具调用、审批请求都有详情页,IM 里点链接看完整 input / result / git diff;Agent 也能主动把一段话贴进任意一个群。
34
- 10. 📄 **企业微信文档** — 新建在线文档、写 Markdown、读文档链接、操作智能表格。
35
- 11. 🔄 **重启即续** — 电脑重启 / tmux 关了 / daemon 崩了都不掉档:下一条 IM 消息自动把会话拉起来,历史完整继承。
27
+ 3. 🧙 **多 wizard** — 每个会话是一个有名字的 wizard,写作 `.name`,全局唯一,在任何群里都叫得到;有职责、有记忆,看得见彼此在干什么。克隆一个分身让它继承当前上下文,或者从白板新起一个;按活的难度挑档位(`mini` → `ultra`,从 haiku 到 opus·max)。
28
+ 4. 🎩 **管家分派** — 没点名的话落到聊天的默认 wizard:一两句能答完的它自己答,其余一步 `dispatch` 给对口的那个或新起一个;要 coder + reviewer 来回几轮的复杂活交给一个 lead 带队。管家有手闸,想自己埋头改代码会被拦下改去分派。
29
+ 5. 📮 **派完就放手** — wizard 之间派活不必守着等:对方那一轮一结束,守护进程把它的结论作为新的一轮自动送回发话方;对方反问(`NEED:`)就接着答,闲着不交差会被追问一句,没了结的事由守护进程替管家记着。
30
+ 6. 🔝 **顶层模式** — 开了之后群里只有管家和你点名的那个说话,wizard 之间的往来全在私下,结论由顶层用自己的话讲给你。
31
+ 7. 🧠 **三层记忆** — 自己的、本群共享的、本工作区共享的。「这个仓库 reload 要先 build」这类约定写一次,之后每个新 wizard 开局就知道;共享的两层由守护进程定时起一个整理者去重合并。
32
+ 8. 🪪 **rolepage** — 每个 wizard 视角下的 IM:`⌘K` 搜遍 role / 会话 / 消息,关系图看清谁和谁对过话,工单页看一次分派的全过程,用量条一眼看出模型、档位、上下文、token 与估算费用。
33
+ 9. ⏰ **定时任务** — 「每个工作日晚上 9:30 跑一遍回归」,到点自动起一个 wizard 去干,结果发回群里;可以设成「有事才说」。任务本身是一个可以直接编辑的 `.mjs` 文件,存盘即生效。
34
+ 10. 🔍 **详情页与主动汇报** — 每次工具调用、审批请求都有详情页,IM 里点链接看完整 input / result / git diff;Agent 也能主动把一段话贴进任意一个群。
35
+ 11. 🎛 **说人话改配置** — 「把这个群改成顶层模式」「hard 档换成 opus·max」,它查清这一项的说明与默认值再改;放权类的改动(审批、白名单)推一张确认卡等你点。
36
+ 12. 📄 **企业微信文档** — 新建在线文档、写 Markdown、读文档链接、操作智能表格。
37
+ 13. 🔄 **重启即续** — 电脑重启 / tmux 关了 / daemon 崩了都不掉档:下一条 IM 消息自动把会话拉起来,历史完整继承。
38
+ 14. 📎 **图片与文件** — 发给机器人的图片、文件、视频落到本机交给 Agent;Agent 产出的截图、文件用 `send_file` 发回聊天(企业微信与微信两边都行)。
39
+ 15. 💬 **绑定个人微信(默认关)** — 经微信 ClawBot 扫码绑定,每个扫码的微信号成为一个聊天,用法与企业微信群一致;审批卡在微信里变成编号菜单。受微信侧限额约束,适合随身遥控。
36
40
 
37
41
  <details>
38
42
  <summary><b>目录</b></summary>
@@ -44,9 +48,12 @@
44
48
  - [rolepage](#rolepage)
45
49
  - [聊天命名](#聊天命名)
46
50
  - [定时任务与跨群通知](#定时任务与跨群通知)
51
+ - [说人话改配置](#说人话改配置)
52
+ - [图片与文件](#图片与文件)
53
+ - [绑定个人微信](#绑定个人微信默认关)
47
54
  - [企业微信文档](#企业微信文档)
48
55
  - [多 CLI 后端](#多-cli-后端)
49
- - [缓存保活](#缓存保活)
56
+ - [缓存保活](#缓存保活默认关)
50
57
  - [常用命令](#常用命令)
51
58
  - [常见问题](#常见问题)
52
59
  - [深入了解](#深入了解)
@@ -91,11 +98,11 @@ wezard init
91
98
 
92
99
  ## 双向镜像
93
100
 
94
- 左边是 tmux 里的 CLI,右边是企业微信里的同一段对话。
101
+ 左边是企业微信里的一句话(连同两张截图),右边是 tmux 里的 CLI:同一句话原样落进输入框,Agent 接着排查、改代码,进展一路回到聊天。
95
102
 
96
- ![tmux 与企业微信双向镜像](images/mirror.png)
103
+ ![企业微信与 tmux 双向镜像](images/mirror.png)
97
104
 
98
- - **IM → CLI**:企业微信里发的消息由守护进程 `tmux paste-buffer` 进 CLI 的输入框——这是唯一能让远程消息出现在你本机窗口里的通路,你坐回电脑前看到的,就是它替你敲的那一行。
105
+ - **IM → CLI**:企业微信里发的消息由守护进程 `tmux paste-buffer` 进 CLI 的输入框——这是唯一能让远程消息出现在你本机窗口里的通路,你坐回电脑前看到的,就是它替你敲的那一行。图片随消息一起贴进去;尾巴上那段 `<system-reminder>` 是守护进程顺手捎给它的群况(谁在、忙不忙),不占一轮对话。
99
106
  - **CLI → IM**:Agent 的回复、工具调用、思考过程实时推回聊天。默认只推**聊天自己发起的那一轮**:你在电脑前敲出来的轮次只记进 [rolepage](#rolepage),不刷群;审批卡、投票卡和 `[mirror]` 通知不受这条限制。
100
107
  - **随时接管**:`tmux attach -t wezard` 回到那个 pane 接着干,对话一字不少。
101
108
 
@@ -105,8 +112,6 @@ wezard init
105
112
 
106
113
  一个绑定了聊天的会话叫一个 **wizard**:有自己的终端、工作区、名字和职责,知道还有谁在,也叫得动它们。名字**全局唯一**,写作 `.name`——不管它住在哪个群,在任何群里都叫得到。
107
114
 
108
- ![多 wizard 协同](images/wizards.png)
109
-
110
115
  **创建**
111
116
 
112
117
  ```
@@ -127,34 +132,33 @@ wezard init
127
132
  /clear .docs 只清 docs 的上下文
128
133
  ```
129
134
 
130
- 每条回复都带 `emoji .name` 头(emoji 固定),在群里引用它的气泡就等于跟它说话。句中的 `a.ts`、`.gitignore` 这类不会被当成名字。
135
+ 非默认 wizard 的每条回复都带 `.name:` 头(群的默认 wizard 在自己群里不带头——人本来就在跟它说话),在群里引用它的气泡就等于跟它说话。句中的 `a.ts`、`.gitignore` 这类不会被当成名字。
131
136
 
132
137
  **管家**
133
138
 
134
- 每个聊天的默认 wizard 是这个群的**管家**。没点名的话都落到它这儿——它一两句能答完的(问进度、谁在干什么、闲聊)自己答;要读很多代码、改文件、跑很久的活,哪怕它自己做得了也**转出去**:它一忙,这个群里没点名的话就都排在它后面。
139
+ 每个聊天的默认 wizard 是这个群的**管家**,默认跑 `light` 档(`models.steward`,可按群在 `chatPolicy.<chat>.steward` 覆盖)。没点名的话都落到它这儿——一两句能答完的(问进度、谁在干什么、闲聊)自己答;要读很多代码、改文件、跑很久的活,哪怕它自己做得了也**派出去**:它一忙,这个群里没点名的话就都排在它后面。
135
140
 
136
- 转给谁由它判断:上下文里已经看得出该给谁(刚派过同一摊活的、职责对口的)就直接转;拿不准才调一张候选表——谁的职责、最近的话、读过的文件和这件活有交集,在不在同一个工作区,忙不忙,上下文多重。相关且接着用那段上下文划算就转给它;没有对口的,或对口的那个上下文已经太重,就从白板新起一个。
141
+ 派活是一步 `dispatch`:守护进程先列候选——谁的职责、最近的话、读过的文件和这件活有交集,在不在同一个工作区,忙不忙,上下文多重、缓存冷没冷——再给一个默认决定:证据够强、不忙、接着用那段上下文划算的头一个候选就交给它,否则从白板新起一个,档位按活定(一句话小活 `light`、常规 `standard`、带队 `hard`)。管家照办,或显式推翻(点名、硬要新的、换档)。
137
142
 
138
- **转交是公开的**:群里出现一条 `.管家 → .它` 的气泡,它的回复直接发进这个群。你不用等管家转述,管家也只回一句「已转给 `.它`」。
143
+ **复杂的活交给 lead**:要 coder + reviewer、来回几轮的,管家开一张需求单交给一个 `hard` 档的 lead。lead 自己开工单、按需分身写代码,reviewer 必须从白板起步、不带作者的上下文;管家和你都只面对 lead。交付之后你说「好」它归档,说「不对」它让 lead 返工;一直没人验收,管家会隔一阵在群里提一句(4 小时 → 1 天 → 2 天,只在白天)。
139
144
 
140
145
  ```mermaid
141
- flowchart TD
142
- msg["群里的一条消息"] --> named{"点了 .name ?"}
143
- named -- "是" --> direct["直达那个 wizard<br/>不经过管家"]
144
- named -- "否" --> butler["管家 (聊天的默认 wizard)"]
145
- butler --> small{"一两句能答完 ?"}
146
- small -- "是" --> self["自己答"]
147
- small -- "否" --> who{"该给谁 ?"}
148
- who -- "上下文里看得出" --> pick["选中已有的"]
149
- who -- "拿不准" --> table["候选表<br/>职责 · 最近的话 · 读过的文件<br/>工作区 · 忙闲 · ctx"]
150
- table --> judge{"相关, 且接着用<br/>那段上下文划算 ?"}
151
- judge -- "是" --> pick
152
- judge -- "否 / 没有候选" --> fresh["白板新起一个"]
153
- pick --> relay["公开转交<br/>群里一条气泡"]
154
- fresh --> relay
155
- relay --> reply["它的回复直接进群"]
146
+ flowchart LR
147
+ msg["群里一条消息"] -- "点了 .name" --> direct["直达那个 wizard"]
148
+ msg -- "没点名" --> butler(["管家"])
149
+ butler -- "一两句的事" --> self["自己答"]
150
+ butler -- "要组队的复杂活" --> lead["hard 档 lead"]
151
+ butler -- "其余 · dispatch" --> pick{"有对口、不忙、<br/>划算的 ?"}
152
+ pick -- "有" --> exist["交给它"]
153
+ pick -- "没有" --> fresh["白板新起一个"]
156
154
  ```
157
155
 
156
+ **手闸**:管家常常说着分派、实际埋头自己干。守护进程在审批链最前面拦它——改文件、开子代理一律拒;读、搜、跑命令每轮最多 4 次(`chatPolicy.<群>.stewardBudget`),超了就拒并叫它 `dispatch`。放行窗口、`✅总是`、跳过所有审批都压不过这道闸。
157
+
158
+ **转交默认是公开的**:群里出现一条 `.管家 → .它` 的气泡,它的回复直接发进这个群,管家只回一句「已转给 `.它`」。公开往来一多,群就被 wizard 之间的对话刷屏——这时管家会问你要不要开**顶层模式**。
159
+
160
+ **顶层模式**:开了之后,你在这个群里只和**顶层 wizard**(管家,以及你 `.name` 点名的那个)打交道。wizard 之间的派活、反问、回执一律私聊,只在 [rolepage](#rolepage) 看得到;被派活的 wizard 不能往群里发通知,结论回到顶层,由它用自己的话讲给你——讲结论、取舍和要你拍板的,不贴过程。对管家说「把这个群改成顶层模式」即可(`chatPolicy.<群>.topOnly`),即时生效。
161
+
158
162
  **名字**
159
163
 
160
164
  | wizard | 名字 |
@@ -176,14 +180,16 @@ flowchart TD
176
180
  | 还有谁在跑 / 谁在弄那个项目 | 列出全部 wizard:名字、职责、忙闲、谁是谁的分身 |
177
181
  | 让 .fix 看一眼这个报错 | 它把活交给 `.fix` 就放手去干别的,`.fix` 的结论自动送回来,它再答你 |
178
182
  | 分个身去把这三个目录都扫一遍 | 分出三个带着当前上下文的分身并行干,干完收掉 |
183
+ | 还有哪些事没了结 | 管家列出派出去还在等的、等你拍板的、超时没回的 |
184
+ | 把这个群改成顶层模式 | 开顶层模式,wizard 之间的往来不再进群 |
179
185
  | 切到 /path/to/proj | 换工作区,重开会话 |
180
186
 
181
- - **记忆分三层**:自己的、本群共享的、本工作区共享的。「这个群习惯先出方案再动手」「这个仓库 reload 要先 build」不必每来一个新 wizard 再教一遍。共享的那两层是**提议**——一个定时的整理者去重、改写、合并后才写进去,所以不会立刻生效,也不会被几十个 wizard 越写越长。
187
+ - **记忆分三层**:自己的、本群共享的、本工作区共享的。「这个群习惯先出方案再动手」「这个仓库 reload 要先 build」不必每来一个新 wizard 再教一遍。共享的那两层是**提议**——守护进程每 30 分钟看一眼,有提议才起一个整理者去重、改写、合并(已经写在仓库 `CLAUDE.md` 里的不再收),所以不会立刻生效,也不会被几十个 wizard 越写越长;合并后在场读这份记忆的 wizard 下一条消息会收到一行「记忆已更新」。
182
188
  - **分身继承上下文**:分身开局就带着原 wizard 读过的全部材料,原 wizard 不受影响;也可以生一个白板的子 wizard,顺便换个工作区。分身还能再分身。
183
- - **模型可以挑**:`/new [cli] [model]`,或让它生分身时指定——要判断的给 opus,跑腿的给 haiku。
184
- - **派完就放手**:wizard 之间说一句就走,不守着等;对方那一轮一结束,守护进程把它的结论作为新的一轮送回发话方(约 0.2 秒内,由 CLI 自己的忙闲事件触发)。对方正忙时可以约好「等它闲下来再说」,同样是它一停下就投。
185
- - **群里只留结论**:wizard 之间派活、来回默认是私聊,不刷群;生分身、收工、交接这类生命周期事件也不进群。群里只出现回给你的那条回复、通知、工单的开工 / 收工、定时任务的 ⏰,以及它们主动公开的讨论。过程想看去 [rolepage](#rolepage)。
186
- - **上下文满了自己交接**:它把工作压成一份简报、原地换一个全新会话接着干,名字和职责都不变。交接那几秒里进来的话(人说的、同伴派的、回执、定时任务)一律等它重开完再送进去,一句不丢。
189
+ - **模型按档挑**:生分身时给一个档位,由轻到重 `mini` / `light` / `standard` / `hard` / `ultra`(缺省 haiku·low / haiku·low / sonnet·medium / opus·high / opus·max,`models.tiers` 可改);也可以直接点模型与 effort,`/new [cli] [model]` 同理。档位直接带进启动参数,死 pane 重生、交接重开都沿用。
190
+ - **派完就放手**:wizard 之间说一句就走,不守着等;对方那一轮一结束,守护进程把它的结论作为新的一轮送回发话方(约 0.2 秒内,由 CLI 自己的忙闲事件触发)。每件活有件号:对方可以收口成 `NEED:` 反问,发话方接着答同一件;对方闲着却迟迟不交差,满 5 分钟守护进程代问一句「进展如何」;超时(默认 1 小时)、对方没了、被撤回也会回一份说明——「没有答案」也是答案。对方正忙时默认等它这一轮结束再投,急事可以打断它或直接插话。
191
+ - **群里只留结论**:wizard 之间派活、来回默认是私聊,不刷群;生分身、收工、交接、工单这类过程也不进群。群里只出现回给你的那条回复、通知、定时任务的 ⏰,以及它们主动公开的讨论。过程想看去 [rolepage](#rolepage)。
192
+ - **上下文过了 200k 自己交接**:手上这摊告一段落,它就把工作压成一份简报、原地换一个全新会话接着干,名字和职责都不变——交接不是压缩,大上下文每一轮都在付钱。交接那几秒里进来的话(人说的、同伴派的、回执、定时任务)一律等它重开完再送进去,一句不丢。
187
193
 
188
194
  ---
189
195
 
@@ -198,20 +204,23 @@ sequenceDiagram
198
204
  participant W as 发起的 wizard
199
205
  participant K as 分身 ×N
200
206
 
201
- Note over W: 先读完公共材料
202
- W->>U: 📋 开工(标题 + 计划)
207
+ U->>W: 一件要分几路的活
208
+ Note over W: 先读完公共材料, 开一张工单
203
209
  W->>K: 分出 N 个分身,各领一份活
204
- Note over K: 并行干活,过程不刷群
210
+ Note over K: 并行干活,过程只在 rolepage
205
211
  K-->>W: 结论自动回执(第几份 / 还差几份)
206
- W->>U: 📋 收工(汇总)
207
- Note over W,K: 为这单活生出来的分身整批回收
212
+ Note over W,K: 没写 RESULT 的那份自动打回一次
213
+ W->>U: 用自己的话汇总(群里只有这一条)
214
+ Note over W,K: 收工, 为这单活生出来的分身整批回收
208
215
  ```
209
216
 
210
- - **群里只有两条气泡**:开工和收工。中间的过程在各自的 rolepage 里。
217
+ - **群里只有最后那一条**:开工、派活、回执、收工都不进群,只记进工单页;你看到的是发起者自己那一轮的汇总,沿着你问它的那条链回来。
211
218
  - **材料只读一遍**:分身是从发起方的上下文里分出来的,不用各自重读。
212
219
  - **并行**:几路同时干,总耗时只取决于最慢的那一路。
213
- - **齐了才汇总**:回执是陆续回来的,守护进程替它数——每一份都写着第几份、还差几份,最后一份明说「全部到齐」,它才汇总收工。
214
- - **自动回收**:收工时为这单活生出来的分身一并收掉;被拉来帮忙的长期 wizard 不受影响。
220
+ - **齐了才汇总**:回执是陆续回来的,守护进程按成员替它数——每一份都写着第几份、还差几份,最后一份明说「全部到齐」,它才汇总收工。分几批派的可以先定好总份数。
221
+ - **验收与刹车**:成员交上来的没有 `RESULT:` 收口(或要求列交付物却没列),守护进程替发起者打回一次;工单可以设派活预算,反问、续问来回兜圈也会撞上限停下。
222
+ - **自动回收**:收工时为这单活生出来的分身一并收掉;被拉来帮忙的长期 wizard 不受影响。收工结论留档进发起者的情景记忆。
223
+ - **层级**:复杂的活是两层——管家只面对 lead,lead 再开自己的工单带 coder / reviewer。每层直接成员不超过 5 个、深度不超过 3 层;下层的结论一层层冒泡回去,不越级。
215
224
 
216
225
  流程固定的循环(几个 wizard 互相评审、迭代到收敛)可以让它搭成一条流水线,按轮次跑,达到约定的结束条件就提前收工。
217
226
 
@@ -219,15 +228,18 @@ sequenceDiagram
219
228
 
220
229
  ## rolepage
221
230
 
222
- 点气泡头的 `emoji .name`,打开那个 wizard 的 **rolepage**——它视角下的 IM:
231
+ 点气泡头的 `.name`,打开那个 wizard 的 **rolepage**——它视角下的 IM(页首那张截图就是管家 `.wezard` 的 rolepage):
223
232
 
224
- - **侧栏**:它参与的每个群聊,以及与人、与其他 wizard 的单聊;有新消息亮未读红点。
225
- - **消息窗**:它发的靠右,收到的靠左;每一轮的工具调用收进一个可折叠的虚线框,终句是一颗气泡,模型 / ctx / 耗时写在名字那一行。
226
- - **切视角**:点一条消息对侧的空白,切到对方的 rolepage,气泡带动画换边。
233
+ - **侧栏**:它参与的每个群聊,以及与人、与其他 wizard 的单聊;群下展开与它有往来的成对子项(切到「全部」再列出群里其他 role)。也可以切成关系图,两者是同一份数据。
234
+ - **消息窗**:它发的靠右,收到的靠左;每一轮的工具调用收进一个可折叠的虚线框,派活、移交各占一行,终句是一颗气泡;多方会话里模型 / ctx / 耗时写在名字那一行,进行中的那一轮按秒走表。守护进程挂在消息尾巴上的 `<system-reminder>` 收成一个小标签,点开看原文。
235
+ - **两种视角**:点头像或名字,整页切到那个 role(名片、侧栏、关系图中心都换);点卡片、会话项的其余地方或气泡对侧的空白,只换详情区里谁靠右,气泡带动画换边。
236
+ - **未读**:会话项右边的红点只数点开这一项看得到的;名字旁的数字是切到那个 role 之后它能看到的全部未读。已读全局只记一份,在任何一处读过,各处一起消掉。
227
237
  - **`⌘K` 搜索**:按 role 名字、会话名、消息正文搜,键盘选中回车直接跳到那个会话的那一句;空查询时是最近会话的快速切换器。
228
- - **关系图**:侧栏切到关系图,一棵树把家谱(谁是谁的分身)和对话(谁和谁说过话、几次)画在一起,人也是节点。卡片就是会话项——最近一句、时刻、未读都和会话列表同一口径;点卡片打开它与图上相连者之间的往来。
229
- - **用量条**:页脚两条,一条是当前视角,一条是窗口对端那个 wizard——模型、轮次、工具调用、API 请求、上下文峰值、耗时,以及 cache read / input / cache write / output 的分布。
230
- - **名片**:名字、职责、工作区、模型与忙闲;点名字复制 `.name`;历史会话切换(默认看全部);日程列出它名下的定时任务和工单。
238
+ - **关系图**:一棵树把家谱(谁是谁的分身)和对话(谁和谁说过话、几次)画在一起,人也是节点。卡片就是会话项——排序、最近一句、时刻、未读、忙闲都和会话列表同一份实现;点卡片打开它与图上相连者之间的往来。
239
+ - **工单页**:经手过工单的会话与关系挂 📋,点进去按时间排开开工(成员与各自状态)、派活、回执、收工结论;对话里的回执挂一枚「↩ 回执 · 已交 / 反问 / 超时 · 第 i/n 份」,点它跳回派活那句。
240
+ - **忙闲**:空闲 / 执行中 / 等人点(停在审批卡或提问卡上,紫色)/ 已关闭。
241
+ - **用量条**:页脚两条,一条是当前视角,一条是窗口对端那个 wizard——模型与 effort 档位、估算费用(按 LiteLLM 价格表逐轮计)、轮次、工具调用、API 请求、上下文,以及 cache read / input / cache write / output 的分布。
242
+ - **名片**:名字、职责、工作区、模型与忙闲;点名字复制 `.name`;历史会话切换(默认看全部);日程列出它名下的定时任务。
231
243
 
232
244
  ---
233
245
 
@@ -262,7 +274,8 @@ sequenceDiagram
262
274
  | 对它说 | 效果 |
263
275
  | --- | --- |
264
276
  | 每个工作日晚上 9:30 跑一遍回归 | 排一个定时任务,到点新起一个 wizard 去干,干完收掉 |
265
- | 每天早上 8 点在 .daily 里继续整理日报 | 到点把这句话发给已有的 `.daily` |
277
+ | 每天早上 8 点在 .daily 里继续整理日报 | 到点把这句话发给已有的 `.daily`;它正忙就等这一轮结束再说(最多 30 分钟,仍忙才另起一个) |
278
+ | 每小时看一眼 CI,挂了再告诉我 | 「有事才说」:到点不预告、私下跑,没事只回一行 `QUIET`,群里什么也不出现;有事才把结论转进群 |
266
279
  | 我设了什么定时 | 列出任务、时间和下次触发时刻 |
267
280
  | 取消那个定时 | 删掉任务 |
268
281
 
@@ -272,6 +285,47 @@ sequenceDiagram
272
285
 
273
286
  ---
274
287
 
288
+ ## 说人话改配置
289
+
290
+ 不用打开 `config.jsonc`,对 wizard 说就行:
291
+
292
+ | 对它说 | 效果 |
293
+ | --- | --- |
294
+ | 现在模型档位是怎么配的 | 逐层列出这一节的每一项:类型、当前值、默认值、说明,标出哪些要人确认、哪些要 reload;`models` 下附各档本周 / 今日用量 |
295
+ | 把这个群改成顶层模式 | 写 `chatPolicy.<群>.topOnly`,即时生效 |
296
+ | hard 档换成 opus·max | 先预演给你看改前改后的 diff,再落盘,注释原样保留 |
297
+ | 只审危险操作 | 改 `approval.mode`——这类**放权**改动由守护进程推一张确认卡,你点了才写 |
298
+
299
+ - 改动经 zod 整份校验才落盘;热生效的项写完即生效,其余提示 `wezard reload`。
300
+ - **放权项**(`approval.*`、`wrc.allowFrom`、CLI 二进制与启动参数、监听地址等)一律推卡确认,跳过所有审批也压不过;`bot.*` 与 secrets 里的字段读写都不行。
301
+ - 改动若影响到 wizard 的宪章(比如顶层模式开关),受影响的 wizard 会在下一条消息里收到「交接一下才按新规矩办」的提醒。
302
+
303
+ ---
304
+
305
+ ## 图片与文件
306
+
307
+ - **收**:在企业微信里发给机器人的图片随消息贴进 CLI 输入框;文件、视频解密后落进 `wrc.mirror.inboxDir`(默认 `~/.wezard/inbox`,超 7 天自动清理),以一行 `[文件: <路径> (<大小>)]` 交给 Agent;语音只有企业微信给的转写文字。企业微信只在**单聊**里推送图片 / 文件 / 视频 / 语音,群里 @ 机器人带图走图文混排。
308
+ - **发**:对 Agent 说「把截图发给我」,它调 MCP 工具 `send_file({path, kind?, to?, caption?})`,默认发到本轮所在的聊天。企业微信侧上限:图片 png / jpg / gif ≤10MB、视频 mp4 ≤10MB、语音 amr ≤2MB、其余文件 ≤20MB;超限直接报错并给出改法。
309
+
310
+ ---
311
+
312
+ ## 绑定个人微信(默认关)
313
+
314
+ 经腾讯的微信 ClawBot(iLink)把个人微信接进来:**每个扫码绑定的微信用户成为一个聊天**,与企业微信群同构——有自己的默认 wizard,`.name` 点名、叫出新 wizard 都照常。
315
+
316
+ 1. 开启:`weixin.enabled` 是放权项,对 Agent 说「开微信通道」,点确认卡后 `wezard reload`。
317
+ 2. 绑定:审批人在任意聊天发 `/wx bind [聊天名]`(或终端 `wezard wx bind`,会先推一张确认卡),二维码回到发起的聊天,用**要绑定的那个微信**扫码并确认;微信上显示配对码时发 `/wx code <码>`。
318
+ 3. 绑定成功后先在微信里给「微信 ClawBot」说一句话——对方开口之前机器人不能发消息。
319
+
320
+ 须知:
321
+
322
+ - 微信里只有纯文本:markdown 降成纯文本,审批 / 提问卡变成编号菜单(`【1】允许 【2】… · 短码 k7q`),回 `1` 或 `k7q 1` 作答。
323
+ - 送达受微信限制:人每说一句之后只能回有限几条(`weixin.sendCap`,默认 3),连发会合并、超出的压着,人再说话时补发;被频控会暂停出站;人说完话超过 `weixin.ctxTtlSec`(默认 120 秒)后,微信会静默丢弃主动推送——这之后的消息压着、在企业微信里提醒,等对方下次开口时补发;所以发往微信的**定时提醒做不到准点**。
324
+ - 图片 / 文件 / 视频双向可收发(单文件 ≤25MB);不发语音条。
325
+ - `/wx` 看状态,`/wx unbind <序号|聊天名>` 解绑(聊天与记录保留)。一个微信号同时只能连一个 Claw,绑这里可能让别处(OpenClaw / WorkBuddy 等)的绑定失效。扫码提示「运行环境存在风险」多半是本机出网 IP 被风控,关掉 VPN / 全局代理再试。
326
+
327
+ ---
328
+
275
329
  ## 企业微信文档
276
330
 
277
331
  对 Agent 说「把周报整理成一篇企业微信文档」,它会新建在线文档、写入内容,把链接贴回会话。同样可以读文档链接、操作智能表格。文档归属到你本人。
@@ -318,6 +372,7 @@ IM 里发 `/help` 可随时拉出完整命令表;每次 `/new`、`/clear` 之
318
372
  /reveal 把终端的 tmux 窗口切到本会话
319
373
  /peers · /wizards 本聊天的 wizard:名字、职责、忙闲、家谱
320
374
  /name [名字|-] · /chats 给本聊天起名 / 跨聊天目录
375
+ /wx · /wx bind|code|unbind 微信 ClawBot:状态 / 扫码绑定 / 解绑(审批人)
321
376
  /id · /pwd · /usage · /cost · /audit 信息查询(免授权)
322
377
  /cfgsync [apply] 预演 / 执行跨 CLI 项目配置同步
323
378
  /help 全部命令
@@ -330,6 +385,7 @@ wezard status # 看 daemon + WS 健康
330
385
  wezard logs -f # 实时日志
331
386
  wezard send <chat> <text> # 主动推消息
332
387
  wezard sync # 重写 hook/MCP/env 进各 settings.json
388
+ wezard wx [bind|list|unbind] # 微信 ClawBot:终端扫码绑定 / 查看 / 解绑
333
389
  wezard reload # 重启 daemon(改了配置后用)
334
390
  wezard migrate # 一次性:从旧名 weclaude 迁移到 wezard
335
391
  wezard unsync # 卸载 hook/MCP(保留 daemon)
@@ -365,7 +421,7 @@ wezard uninstall # 完整卸载(先于 npm uninstall)
365
421
 
366
422
  ## 深入了解
367
423
 
368
- - [技术说明](技术说明.md) — 架构、消息双向同步、`.name` 路由与频道、wizard 网络(身份 / 分身 / 感知 / 编排)、rolepage、保活判定、文档 MCP 桥接
424
+ - [技术说明](技术说明.md) — 架构、消息双向同步、`.name` 路由与频道、wizard 网络(身份 / 记忆 / 分身 / 感知 / 分派 / 编排 / 回执 / 交接)、顶层模式、rolepage、保活与冷热判定、文档 MCP 桥接
369
425
  - [审批配置](审批配置.md) — 审批粒度、danger 名单、跳过开关的优先关系、完整判定链
370
426
  - [CLAUDE.md](CLAUDE.md) — 模块级职责与代码约定
371
427
  - [CHANGELOG.md](CHANGELOG.md) — 各版本变更
package/cli/wezard.sh CHANGED
@@ -41,6 +41,7 @@ wezard <subcommand>
41
41
  config-path show resolved config path
42
42
  sync write hooks/MCP/env into sync.targets settings.json
43
43
  unsync remove our entries from sync.targets settings.json
44
+ wx [bind|list|unbind] WeChat ClawBot: scan-to-bind a WeChat user as a chat (terminal QR)
44
45
  audit [tag] token/cost breakdown for current Claude session (main + subagents)
45
46
  update npm i -g wezard@latest + repoint/restart daemon + sync (one command, see /wezard:update)
46
47
  uninstall full teardown: stop daemon + unsync + plugin uninstall + remove daemon (run before `npm uninstall -g`)
@@ -165,6 +166,7 @@ svc_unregister() {
165
166
  case "$cmd" in
166
167
  init) exec_node dist/cli/init.js "$@" ;;
167
168
  migrate) exec_node dist/cli/migrate.js "$@" ;;
169
+ wx) exec_node dist/cli/wx.js "$@" ;;
168
170
  status)
169
171
  if out=$(http_get /status 2>/dev/null); then
170
172
  echo "$out" | jq . 2>/dev/null || echo "$out"
@@ -39,7 +39,10 @@
39
39
  // "codebuddy": { "bin": "/usr/local/bin/codebuddy" }
40
40
  // },
41
41
  "cwd": "~", // default workdir for a new chat's first session
42
- "extraArgs": [], // appended to the spawned CLI command line
42
+ // Appended to the spawned CLI command line. Default drops tools wizards never use that cost
43
+ // ~18k tokens per session (Artifact 11.3k, Workflow/SendFeedback/ScheduleWakeup 5.6k, claude.ai Docs/Drive 1.0k);
44
+ // an explicit value replaces it wholesale (no merge).
45
+ "extraArgs": ["--disallowedTools=Artifact,Workflow,SendFeedback,ScheduleWakeup,mcp__claude_ai_Claude_Docs__*,mcp__claude_ai_Google_Drive__*"],
43
46
  "mirror": {
44
47
  // Pin a session id; empty → auto-pick latest .jsonl mtime under
45
48
  // ~/.claude/projects/<encoded(cwd)>/.
@@ -128,14 +131,14 @@
128
131
 
129
132
  // ── 按难度分档的模型 ─────────────────────────────────────────────
130
133
  // spawn_wizard / clone_wizard 的 `tier` 落到这里; 显式给的 model / effort / cli 压过档位。
131
- // router = 各群管家 (群的默认会话) 新起时用的模型。省略整段 = 下面这组默认值。
134
+ // steward = 各群管家 (群的默认会话) 用哪一档, 按群覆盖写 chatPolicy.<chat>.steward。省略整段 = 下面这组默认值。
132
135
  "models": {
133
136
  "tiers": {
134
- "light": { "model": "haiku", "effort": "low" },
137
+ "light": { "model": "sonnet", "effort": "medium" },
135
138
  "standard": { "model": "sonnet", "effort": "medium" },
136
139
  "hard": { "model": "opus", "effort": "high" }
137
140
  },
138
- "router": { "model": "sonnet", "effort": "low" }
141
+ "steward": "light"
139
142
  },
140
143
 
141
144
  // ── claude / codebuddy wrapper sync ───────────────────────────────
package/dist/cli/wx.js ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ // `wezard wx …` —— 在本机终端绑定 / 查看 / 解绑微信 ClawBot。路由都要出示 daemon 口令 (guarded);
3
+ // 绑定另要审批人在企微点一张确认卡 —— 口令每个 wizard 都读得到, 光凭它不够。
4
+ import QRCode from "qrcode";
5
+ import { createInterface } from "node:readline/promises";
6
+ import { DAEMON_TOKEN_HEADER, readDaemonToken } from "../shared/daemon-token.js";
7
+ const DAEMON = process.env.WEZARD_DAEMON_BASE ?? "http://127.0.0.1:17890";
8
+ const call = async (method, p, body) => {
9
+ const r = await fetch(`${DAEMON}${p}`, {
10
+ method,
11
+ headers: { "content-type": "application/json", [DAEMON_TOKEN_HEADER]: readDaemonToken() },
12
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
13
+ });
14
+ return (await r.json().catch(() => ({ ok: false, reason: `HTTP ${r.status}` })));
15
+ };
16
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
17
+ const ago = (t) => (t ? `${Math.round((Date.now() - t) / 60_000)} 分钟前` : "—");
18
+ const list = async () => {
19
+ const r = await call("GET", "/wx/list");
20
+ if (!r.accounts)
21
+ return void console.error("daemon 没回账号表 —— `wezard status` 看看它在不在");
22
+ console.log(`微信 ClawBot ${r.accounts.length}/${r.max}${r.enabled ? "" : " (⚠️ weixin.enabled=false, 通道未开启)"}`);
23
+ r.accounts.forEach((a, i) => console.log(` ${i + 1}. ${a.name || a.chatId} ${a.state === "live" ? "🟢" : "🔴 掉线"} 上次说话 ${ago(a.lastInAt)} 额度 ${a.budget} 压着 ${a.held} 发 ${a.sent}/败 ${a.failed}`));
24
+ };
25
+ const bind = async (name) => {
26
+ console.log("· 已往企微给审批人推了一张确认卡, 点「同意」后这里出二维码 (3 分钟内有效)…");
27
+ const r = await call("POST", "/wx/bind", { name });
28
+ if (!r.ok)
29
+ return void console.error(`❌ ${String(r.reason ?? "bind failed")}`);
30
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
31
+ let shownQr = "";
32
+ let last = "";
33
+ try {
34
+ for (;;) {
35
+ const s = await call("GET", "/wx/bind/status?id=cli");
36
+ const v = s.view;
37
+ if (!v)
38
+ return void console.error("❌ 绑定会话丢了 (daemon 重启?), 重新 `wezard wx bind`");
39
+ if (v.qrUrl && v.qrUrl !== shownQr) {
40
+ shownQr = v.qrUrl;
41
+ console.log(await QRCode.toString(v.qrUrl, { type: "terminal", small: true }));
42
+ console.log(`用要绑定的那个人的微信「扫一扫」扫上面的码 (约 2 分钟有效)。扫不出来就在微信里打开: ${v.qrUrl}\n`);
43
+ }
44
+ if (v.message !== last) {
45
+ last = v.message;
46
+ console.log(`· ${v.message}`);
47
+ }
48
+ if (v.status === "need_code" && !/校验中/.test(v.message)) {
49
+ const code = (await rl.question("配对码: ")).trim();
50
+ if (code) {
51
+ const c = await call("POST", "/wx/bind/code", { code });
52
+ if (!c.ok)
53
+ console.error(`❌ ${String(c.reason)}`);
54
+ }
55
+ continue;
56
+ }
57
+ if (v.status === "done")
58
+ return void console.log(`✅ 已绑定为群聊「${v.name}」—— 去微信里先给「微信 ClawBot」发一句话, 之后就像企微群一样用`);
59
+ if (v.status === "already" || v.status === "failed")
60
+ return void console.log(v.status === "already" ? `ℹ️ ${v.message}` : `❌ ${v.message}`);
61
+ await sleep(1_500);
62
+ }
63
+ }
64
+ finally {
65
+ rl.close();
66
+ }
67
+ };
68
+ const unbind = async (arg) => {
69
+ if (!arg)
70
+ return void console.error("用法: wezard wx unbind <序号|群名>");
71
+ const r = await call("POST", "/wx/unbind", { chat: arg });
72
+ console.log(r.ok ? "🗑️ 已解绑 (群聊、wizard 与记录保留)" : `❌ ${String(r.reason)}`);
73
+ };
74
+ const [verb = "list", ...rest] = process.argv.slice(2);
75
+ const arg = rest.join(" ").trim();
76
+ const run = verb === "bind" ? bind(arg) : verb === "unbind" ? unbind(arg) : verb === "list" ? list() : Promise.resolve(void console.log("用法: wezard wx [list | bind [群名] | unbind <序号|群名>]"));
77
+ run.catch((e) => { console.error(`❌ ${e.message}`); process.exit(1); });
78
+ //# sourceMappingURL=wx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wx.js","sourceRoot":"","sources":["../../cli/wx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ // SendMedia 的企微 aibot 实现: 上传临时素材 → 主动推送 (aibot_send_msg)。
2
+ // 不走被动 replyMedia: 它要绑一条入站帧的 req_id, 而 wizard 发文件的时刻多半不在
3
+ // 人开的那一轮里 (私聊轮、回执轮、定时任务); 主动推送对任何已互动过的会话都成立。
4
+ import { readFileSync } from "node:fs";
5
+ import { baseOfKey } from "../shared/session-label.js";
6
+ import { errText } from "./last-response.js";
7
+ import { planMedia } from "./media.js";
8
+ // SDK 1.0.7 把每个回执的等待写死 5s (aibot-node-sdk #27), 大文件分片的回执常 6–8s 才到,
9
+ // 被误判失败、重传又被服务端拒。上传期间放宽, 最后一个上传结束时还原 —— 期间别的发送
10
+ // 也跟着宽限, 只是失败报得晚些, 无害。
11
+ const UPLOAD_ACK_MS = 30_000;
12
+ const widenAck = (client) => {
13
+ const mgr = client.wsManager;
14
+ if (!mgr || typeof mgr.replyAckTimeout !== "number")
15
+ return () => { };
16
+ const st = ackState.get(mgr) ?? { n: 0, orig: mgr.replyAckTimeout };
17
+ ackState.set(mgr, { ...st, n: st.n + 1 });
18
+ mgr.replyAckTimeout = Math.max(st.orig, UPLOAD_ACK_MS);
19
+ return () => {
20
+ const cur = ackState.get(mgr);
21
+ if (cur.n > 1) {
22
+ ackState.set(mgr, { ...cur, n: cur.n - 1 });
23
+ return;
24
+ }
25
+ ackState.delete(mgr);
26
+ mgr.replyAckTimeout = cur.orig;
27
+ };
28
+ };
29
+ const ackState = new WeakMap();
30
+ /** `chat:wr…#slot` → `wr…`: SDK 只认裸 chatid / userid。 */
31
+ const chatIdOf = (target) => baseOfKey(target).replace(/^(user|chat|group):/, "");
32
+ export const aibotSendMedia = (client, log) => async (target, spec) => {
33
+ const plan = planMedia(spec);
34
+ if (!plan.ok)
35
+ return plan;
36
+ const chatId = chatIdOf(target);
37
+ if (!chatId)
38
+ return { ok: false, reason: `认不出收件聊天: ${target}` };
39
+ if (!client.isConnected)
40
+ return { ok: false, reason: "企微长连接没连上, 稍后再试" };
41
+ const { kind, name, bytes } = plan;
42
+ const upload = async () => {
43
+ const restore = widenAck(client);
44
+ try {
45
+ return (await client.uploadMedia(readFileSync(spec.path), { type: kind, filename: name })).media_id;
46
+ }
47
+ finally {
48
+ restore();
49
+ }
50
+ };
51
+ try {
52
+ const video = kind === "video" ? { title: spec.title, description: spec.description } : undefined;
53
+ await client.sendMediaMessage(chatId, kind, await upload(), video);
54
+ log.info({ chatId, kind, name, bytes }, "tx media");
55
+ return { ok: true, kind, name, bytes };
56
+ }
57
+ catch (e) {
58
+ log.warn({ chatId, kind, name, bytes, err: errText(e) }, "tx media failed");
59
+ return { ok: false, reason: `企微拒收 (${kind} ${name}): ${errText(e)}` };
60
+ }
61
+ };
62
+ //# sourceMappingURL=aibot-media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aibot-media.js","sourceRoot":"","sources":["../../daemon/aibot-media.ts"],"names":[],"mappings":"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"}