wezard 2.0.3 → 2.0.4
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/CLAUDE.md +1 -1
- package/README.md +119 -291
- package/dist/daemon/index.js +73 -19
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/mirror-bridge.js +57 -8
- package/dist/daemon/mirror-bridge.js.map +1 -1
- package/dist/daemon/model-select.js +173 -61
- package/dist/daemon/model-select.js.map +1 -1
- package/dist/daemon/peers.js +54 -8
- package/dist/daemon/peers.js.map +1 -1
- package/dist/daemon/spawn-tmux.js +16 -4
- package/dist/daemon/spawn-tmux.js.map +1 -1
- package/dist/daemon/wizard.js +16 -4
- package/dist/daemon/wizard.js.map +1 -1
- package/dist/mcp/server.js +21 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/shared/detail-render.js +6 -230
- package/dist/shared/detail-render.js.map +1 -1
- package/package.json +1 -1
- package/web/chat.css +13 -13
- package/web/chat.js +13 -4
package/CLAUDE.md
CHANGED
|
@@ -85,7 +85,7 @@ Three processes, all coordinating via 127.0.0.1:17890 and `~/.wezard/`:
|
|
|
85
85
|
- `hooks/hooks.json` is registered automatically by Claude Code when the plugin is installed (uses `${CLAUDE_PLUGIN_ROOT:-${CODEBUDDY_PLUGIN_ROOT}}` so the same file works under CodeBuddy's plugin loader if ever published there).
|
|
86
86
|
- For non-plugin installs (`claude-internal` wrapper), `cli/sync.ts` writes the MCP server entry + `WEZARD_DAEMON_BASE` env into target `settings.json`s listed in `config.sync.targets`. **It deliberately strips legacy hook entries** for `kind:"claude"` targets — registering both the plugin's `hooks.json` AND a settings.json hook fires the hook twice per tool call (= duplicate cards). For `kind:"codebuddy"` targets, since the wezard plugin is not published to CodeBuddy's marketplace, sync **writes the hook directly into `~/.codebuddy/settings.json`** with an absolute path to `hooks/pre-tool-use.sh` — this is the only registration path for CodeBuddy. `~/.wezard/sync.lock.json` tracks what was written so `--remove` is reversible.
|
|
87
87
|
|
|
88
|
-
**MCP server** (`mcp/server.ts`): stdio transport, completely stateless w.r.t. WeCom. Every tool just `POST`s to the daemon. Add a tool here AND a route handler in `daemon/`. The collaboration tools (`wizard_whoami` / `wizard_identity` / `wizard_roster` / `spawn_wizard` / `clone_wizard` / `stop_wizard` / `wizard_remember` / `wizard_handoff_self`, plus the older `list_peers` / `peek_peer` / `send_peer` / `wait_peer` / `run_agent_graph` / `graph_status` / `stop_graph`) all send `selfRef()` = `{sessionId, tmuxPane}` so the daemon can resolve *which* session is asking — env sessionId goes stale after a `/clear`, pane id doesn't. Their descriptions are the whole discovery mechanism for wizard↔wizard collaboration: a wizard only realizes it can clone itself, or drive `.fix`, because the tool descriptions say so. Their address parameter is `name` (`fix` / `.fix`); the daemon still accepts the legacy `tag`/`tags` fields, because an already-running wizard's MCP process is old code. Tool NAMES stayed on the older `peer` vocabulary on purpose — a name is an address, and renaming would break every hint, doc and habit pointing at it; the descriptions carry the wizard/clone vocabulary.
|
|
88
|
+
**MCP server** (`mcp/server.ts`): stdio transport, completely stateless w.r.t. WeCom. Every tool just `POST`s to the daemon. Add a tool here AND a route handler in `daemon/`. The collaboration tools (`wizard_whoami` / `wizard_identity` / `wizard_roster` / `spawn_wizard` / `clone_wizard` / `stop_wizard` / `wizard_remember` / `wizard_handoff_self` / `set_model`, plus the older `list_peers` / `peek_peer` / `send_peer` / `wait_peer` / `run_agent_graph` / `graph_status` / `stop_graph`) all send `selfRef()` = `{sessionId, tmuxPane}` so the daemon can resolve *which* session is asking — env sessionId goes stale after a `/clear`, pane id doesn't. Their descriptions are the whole discovery mechanism for wizard↔wizard collaboration: a wizard only realizes it can clone itself, or drive `.fix`, because the tool descriptions say so. Their address parameter is `name` (`fix` / `.fix`); the daemon still accepts the legacy `tag`/`tags` fields, because an already-running wizard's MCP process is old code. Tool NAMES stayed on the older `peer` vocabulary on purpose — a name is an address, and renaming would break every hint, doc and habit pointing at it; the descriptions carry the wizard/clone vocabulary.
|
|
89
89
|
|
|
90
90
|
## Important patterns / pitfalls
|
|
91
91
|
|
package/README.md
CHANGED
|
@@ -19,46 +19,32 @@
|
|
|
19
19
|
|
|
20
20
|

|
|
21
21
|
|
|
22
|
-
三个进程,一条 WebSocket。daemon 常驻在你的电脑上,是唯一和企业微信说话的那个;hook 与 MCP 都只是它的本地 HTTP 客户端:
|
|
23
|
-
|
|
24
|
-
```mermaid
|
|
25
|
-
flowchart LR
|
|
26
|
-
you["你 · 企业微信"] <-- WebSocket --> daemon["wezard daemon<br/>常驻本机 · 127.0.0.1:17890"]
|
|
27
|
-
daemon -- "tmux 粘贴消息" --> cli["tmux 里的 Agent CLI<br/>claude · claude-internal · codebuddy"]
|
|
28
|
-
cli -- "流式回复 / 工具调用" --> daemon
|
|
29
|
-
cli -- "每次工具调用先问一句" --> hook["PreToolUse hook"]
|
|
30
|
-
hook -- "阻塞等你点卡片" --> daemon
|
|
31
|
-
cli -- "主动汇报 / 读写文档" --> mcp["MCP server"]
|
|
32
|
-
mcp --> daemon
|
|
33
|
-
```
|
|
34
|
-
|
|
35
22
|
| 功能<img width="160"> | 说明 |
|
|
36
23
|
| --- | --- |
|
|
37
|
-
| 🛎 **远程审批** | Agent 要跑 `Bash` / `Edit
|
|
38
|
-
| 📋 **计划审批** | Agent
|
|
39
|
-
| 🪞 **会话镜像** |
|
|
40
|
-
| 🖼 **图片直贴** |
|
|
41
|
-
| 🔍
|
|
42
|
-
| 📡
|
|
43
|
-
| 📄 **文档读写** | Agent
|
|
44
|
-
| 🗂
|
|
45
|
-
| 🧙 **多 wizard 协同** |
|
|
46
|
-
|
|
|
24
|
+
| 🛎 **远程审批** | Agent 要跑 `Bash` / `Edit` 时,审批卡片直推 IM:`✅` 放行一次、`⏱` 一段时间内自动放行、`✅总是` 记成规则、`❌` 拒绝。危险操作(`rm` / 强推 / `DROP` / 敏感路径)每次都要单独确认。 |
|
|
25
|
+
| 📋 **计划审批** | Agent 做完计划时,摘要和审批卡推到 IM:`✅同意` 开始执行,`✏️继续改` 接着完善。Agent 提的选择题也会变成投票卡。 |
|
|
26
|
+
| 🪞 **会话镜像** | 电脑上 Agent 的回复、工具调用、思考过程实时同步到企业微信;IM 里发的消息原样落进 CLI 输入框。回到电脑前 `tmux attach -t wezard` 接着干,对话一字不少。 |
|
|
27
|
+
| 🖼 **图片直贴** | 在企业微信里发图,Agent 直接当贴图处理。 |
|
|
28
|
+
| 🔍 **详情页** | 每次工具调用 / 审批请求都有详情页,IM 里点链接看完整 input / result / git diff。 |
|
|
29
|
+
| 📡 **主动汇报** | Agent 可以主动给你发消息、发卡片、向你提问。 |
|
|
30
|
+
| 📄 **文档读写** | Agent 直接新建企业微信在线文档、写 Markdown、读文档链接、操作智能表格。 |
|
|
31
|
+
| 🗂 **多会话** | 列出本机所有在跑的会话、切换 IM 镜像到哪一个、在指定目录新开会话。每个会话有一枚固定的 emoji,审批卡和回复一眼区分。 |
|
|
32
|
+
| 🧙 **多 wizard 协同** | 每个会话是一个有名字的 wizard,写作 `.name`,在任何群里都叫得到;有职责、有记忆,能分身、能互相派活,把一件大活拆成几路并行做完。 |
|
|
33
|
+
| ⏰ **定时任务** | 「每个工作日晚上 9:30 跑一遍回归」——到点自动起一个 wizard 去干,结果发回群里。 |
|
|
34
|
+
| 🔄 **重启即续** | 电脑重启 / tmux 关了 / daemon 崩了都不掉档:下一条 IM 消息自动把会话拉起来,历史完整继承。 |
|
|
47
35
|
|
|
48
36
|
<details>
|
|
49
37
|
<summary><b>目录</b></summary>
|
|
50
38
|
|
|
51
39
|
- [快速开始](#快速开始)
|
|
52
|
-
- [
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
55
|
-
- [
|
|
56
|
-
- [
|
|
57
|
-
- [
|
|
58
|
-
- [
|
|
59
|
-
- [
|
|
60
|
-
- [多 CLI 后端](#多-cli-后端claude--claude-internal--codebuddy)
|
|
61
|
-
- [Prompt-cache 保活(省钱心跳)](#prompt-cache-保活省钱心跳)
|
|
40
|
+
- [多 wizard 协同](#多-wizard-协同)
|
|
41
|
+
- [任务编排](#任务编排)
|
|
42
|
+
- [rolepage](#rolepage)
|
|
43
|
+
- [聊天命名](#聊天命名)
|
|
44
|
+
- [定时任务与跨群通知](#定时任务与跨群通知)
|
|
45
|
+
- [企业微信文档](#企业微信文档)
|
|
46
|
+
- [多 CLI 后端](#多-cli-后端)
|
|
47
|
+
- [缓存保活](#缓存保活)
|
|
62
48
|
- [常用命令](#常用命令)
|
|
63
49
|
- [常见问题](#常见问题)
|
|
64
50
|
- [深入了解](#深入了解)
|
|
@@ -101,336 +87,178 @@ wezard init
|
|
|
101
87
|
|
|
102
88
|
---
|
|
103
89
|
|
|
104
|
-
##
|
|
105
|
-
|
|
106
|
-
IM 来消息 → tmux 粘进活的 TUI,CLI 里像你自己敲进去的一样;Agent 的回应、调用了哪些工具、思考过程都逐字流式推回 IM。一对一绑定 IM 聊天 ↔ tmux 窗口,原地累计上下文——真·远程结对编程。
|
|
107
|
-
|
|
108
|
-
IM 里发 `/new` 开新 tmux 窗口 + 新 Agent 会话,`/clear` 清当前上下文,带图消息自动注入剪贴板。所有 IM 聊天共享一个 tmux session(默认名 `wezard`),每个聊天一个独立 window。
|
|
109
|
-
|
|
110
|
-
**关 tmux / daemon 崩了 / 整机重启都能自愈**:
|
|
111
|
-
|
|
112
|
-
- IM ↔ 会话绑定 write-through 落到 `~/.wezard/mirror-attachments.json`,daemon 起来就 eager restore;
|
|
113
|
-
- 重启后 pane 全死,下一条 IM 消息触发 `claude --resume <sid>` 拉起新 pane;
|
|
114
|
-
- `--resume` fork 出的新 jsonl 由 watcher 从 EOF 无缝接管,不会把整段历史再推一遍到 IM;
|
|
115
|
-
- 中途在别处 `/clear` 把 jsonl rotate 掉也不丢绑定,会自愈到同项目目录下最新的 jsonl。
|
|
116
|
-
|
|
117
|
-
> 💡 **不必先在 CLI 里开 tmux**:首次发任意消息就会自动 spawn + 绑定。回家 `tmux attach -t wezard` 接管即可。
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## 体验是什么样
|
|
90
|
+
## 多 wizard 协同
|
|
122
91
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
> 🛎 授权请求: Bash
|
|
126
|
-
> `rm -rf node_modules`
|
|
127
|
-
> ⚠️ 命中危险名单:删除目录 rm
|
|
128
|
-
> [❌] [✅ 确认执行]
|
|
129
|
-
|
|
130
|
-
你点 ✅,卡片立刻刷新成 `✅ 已通过`,电脑上的 Agent 解除阻塞继续跑。
|
|
131
|
-
|
|
132
|
-
**镜像场景**:你 tmux 里开着 Agent 在写代码。出门后给机器人发:
|
|
133
|
-
|
|
134
|
-
> 把刚才那个函数改成异步的
|
|
135
|
-
|
|
136
|
-
这条消息自动粘进 CLI 输入框 + 回车提交。Agent 的回应、调了哪些工具、改了哪些文件,逐字流式推回 IM。回家打开终端,对话一字不少都在那里。
|
|
137
|
-
|
|
138
|
-
**文档场景**:你说「周报给我整理成一篇企业微信文档」。Agent 自己调 `wecom_doc_list_tools` 看可用方法,再调 `wecom_doc_call` 新建文档、写入 Markdown,最后把链接贴回会话——全程不离开会话,文档归属到你的 userid,每日 20 篇限额按 userid 计。
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## 文档 / 智能表格 / 智能文档
|
|
143
|
-
|
|
144
|
-
`wezard` 把企业微信智能机器人的远端 MCP(doc / smartsheet / contact)桥接到本地 Agent,**全程内网、不走 corp access_token**。Agent 先调 `wecom_doc_list_tools` 看某 category 有哪些方法,再调 `wecom_doc_call` 执行——新建在线文档、写 Markdown、读链接、操作智能表格。首次用需在「工作台 - 智能机器人 - 可使用权限」里勾选「文档」「智能表格」。
|
|
145
|
-
|
|
146
|
-
桥接机制、curl 验证、`requesterUserId` 解析规则见 [技术说明](技术说明.md#文档-mcp-如何桥接)。
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## 定时任务 / 跨群通知
|
|
151
|
-
|
|
152
|
-
**定时任务**:到点把一句话说给某个 wizard 听——等价于那一刻有人在群里对它说了这句话,所以它**真的会去干活**,产出照常落在群里。它活在守护进程里,跨 CLI 重启、`/clear`、会话结束都还在。每条任务是一份 `~/.wezard/tasks/<id>.task.mjs`,热加载。
|
|
153
|
-
|
|
154
|
-
**日程跟着 wizard 走**:每条任务记着排它的那个 wizard(`owner`),它的 rolepage 顶栏「日程」里就列着它名下的任务;默认到点新起一个白板 wizard 去干,名字是 `<owner>-task-xxxx`、家谱挂在 owner 名下,干完收掉。
|
|
155
|
-
|
|
156
|
-
`when` 用人话说就行,不用翻译成 cron:
|
|
157
|
-
|
|
158
|
-
| 说 | 工具 | 干什么 |
|
|
159
|
-
| --- | --- | --- |
|
|
160
|
-
| 「每个工作日晚上 9:30 跑一遍回归」 | `schedule_task(when, prompt, name?)` | 归在自己名下;省略 `name` = 到点新起白板 wizard,点了 `name` = 投进那个已有 wizard |
|
|
161
|
-
| 「我设了什么定时」 | `list_tasks(mine?)` | 列出 id / 人话回显的 when / 下次触发时刻 / owner / 目标;`mine:true` 只看自己名下的 |
|
|
162
|
-
| 「取消那个定时」 | `cancel_task(id)` | 按 id 删 |
|
|
163
|
-
|
|
164
|
-
认得的说法:`每天 8:00`、`每个工作日晚上9:30`、`每周三下午3点`、`每隔两小时`、`每 30 分钟`、`20 分钟后`、`明早 9 点`。
|
|
165
|
-
|
|
166
|
-
**跨群通知**:`notify(to?, markdown)` 把一段 markdown 贴进指定聊天**给人看**——和 `send_peer` 正好相反,它不驱动任何 agent、不触发一轮对话。`to` 写聊天名(见[给聊天命名](#跨聊天给聊天命名)),省略就是自己这一轮所在的群(人从哪个群叫的它,就回哪个群);气泡头是它的 `emoji .name`,挂着它的 rolepage。
|
|
167
|
-
|
|
168
|
-
典型用法:一个长活在 `build` 群跑完,`notify(["ops"], "🔴 回归挂了 3 例…")` 把结论送到该看的人那里。
|
|
169
|
-
|
|
170
|
-
## wizard:全局唯一的 `.name`
|
|
171
|
-
|
|
172
|
-
一个绑定了聊天的会话,在 wezard 里叫一个 **wizard**:有自己的终端、工作区、名字和职责,知道还有谁在,也叫得动它们。**名字全局唯一**,写作 `.name`——它就是地址,不管那个 wizard 住在哪个群,在任何群里都叫得到它。
|
|
92
|
+
一个绑定了聊天的会话叫一个 **wizard**:有自己的终端、工作区、名字和职责,知道还有谁在,也叫得动它们。名字**全局唯一**,写作 `.name`——不管它住在哪个群,在任何群里都叫得到。
|
|
173
93
|
|
|
174
94
|

|
|
175
95
|
|
|
176
|
-
**名字从哪来**
|
|
177
|
-
|
|
178
|
-
| wizard | 名字 |
|
|
179
|
-
| --- | --- |
|
|
180
|
-
| 聊天的默认 wizard | 聊天的名字(`/name` 起的,或按工作区自动补的,见[给聊天命名](#跨聊天给聊天命名)) |
|
|
181
|
-
| `.docs /new` 叫出来的 / 分身 | 你给的那个名字 |
|
|
182
|
-
| 撞名 | 自动加后缀:`fix` → `fix-2` → `fix-3`(大小写不敏感) |
|
|
183
|
-
|
|
184
|
-
wizard 自己也能改名(对它说「以后你就叫 sanitizer」→ `wizard_identity`),撞名同样加后缀并告诉它。名字只是名册 `~/.wezard/wizards.json` 里的一个字段:内部的会话 key(`chat:xxx#slot`)不随改名变,改名不会动到它的 pane、jsonl 和历史。
|
|
185
|
-
|
|
186
96
|
**创建**
|
|
187
97
|
|
|
188
98
|
```
|
|
189
|
-
.docs /new 让一个叫 docs 的 wizard
|
|
99
|
+
.docs /new 让一个叫 docs 的 wizard 在这个群里就位
|
|
190
100
|
.api 看下这个报错 开头写一个还不存在的 .api = 就地叫出一个新的 api,并把这句话发给它
|
|
191
|
-
/new
|
|
101
|
+
/new 重开本聊天默认的那个
|
|
192
102
|
```
|
|
193
103
|
|
|
194
|
-
|
|
104
|
+
**叫它**
|
|
195
105
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
```mermaid
|
|
199
|
-
flowchart LR
|
|
200
|
-
msg["IM 里的一条消息<br/>(群 B)"] --> scan["扫 .name<br/>空白或首尾分隔"]
|
|
201
|
-
scan -- "无 / 不认识" --> dflt["群 B 的默认 wizard"]
|
|
202
|
-
scan -- ".docs (住在群 B)" --> docs[".docs"]
|
|
203
|
-
scan -- ".fix (住在群 A)" --> fix[".fix"]
|
|
204
|
-
scan -- "开头的 .x (没人叫这个)" --> newx["在群 B 叫出新的 .x"]
|
|
205
|
-
docs --> back["回复带 🦊 .docs<br/>回到群 B"]
|
|
206
|
-
fix --> back2["回复带 🐬 .fix<br/>回到群 B (不是它的 home 群 A)"]
|
|
207
|
-
scan -. "其余已知的 .name<br/>原样透传给模型" .-> hint["尾部挂一条不可见提示:<br/>.b 是活着的同类, 去叫它"]
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
**回复回到发话的那个群**:人在群 B 里叫住在群 A 的 `.fix`,它这一轮的回复(连同审批卡、提问卡)推回群 B,而不是它的 home 群 A——在哪儿问,就在哪儿答。
|
|
106
|
+
消息里任意位置带一个已有的 `.name`,就发给那个 wizard;不带 `.name` 的消息发给本聊天默认的那个。在哪个群叫它,回复就回到哪个群。
|
|
211
107
|
|
|
212
108
|
```
|
|
213
109
|
.docs 帮我把 README 的目录补一下
|
|
214
110
|
帮我看下这个报错 .api
|
|
215
|
-
/pwd .docs
|
|
216
|
-
/stop .api
|
|
217
|
-
/clear .docs
|
|
111
|
+
/pwd .docs 只看 docs 的工作区
|
|
112
|
+
/stop .api 只打断 api
|
|
113
|
+
/clear .docs 只清 docs 的上下文
|
|
218
114
|
```
|
|
219
115
|
|
|
220
|
-
|
|
116
|
+
每条回复都带 `emoji .name` 头(emoji 固定),在群里引用它的气泡就等于跟它说话。句中的 `a.ts`、`.gitignore` 这类不会被当成名字。
|
|
221
117
|
|
|
222
|
-
|
|
118
|
+
**名字**
|
|
223
119
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
改名之前发出去的老 `#tag` 气泡照样能引用——名字就是从 tag 迁移过来的,指得还是那个 wizard。
|
|
231
|
-
|
|
232
|
-
**名字语法**:`[\p{L}\p{N}_-]{1,32}`,支持中英文数字与 `_`、`-`;代码块与行内代码里的 `.x` 不算提及。
|
|
233
|
-
|
|
234
|
-
**身份、记忆与分身**
|
|
235
|
-
|
|
236
|
-
每个 wizard 的身份(名字、工作区、职责、记忆、家谱)在启动时以**系统提示**压进进程——不占一轮对话、群里看不见、`/clear` 也抹不掉。它一睁眼就知道自己是谁、还有谁在、自己能做什么:
|
|
237
|
-
|
|
238
|
-
```
|
|
239
|
-
你叫什么 / 你负责什么 → 它调 wizard_whoami
|
|
240
|
-
以后你就叫 sanitizer → wizard_identity(全局唯一,撞名加后缀)
|
|
241
|
-
记住:发版前必须更新 CHANGELOG → wizard_remember,跨 /clear 活着,每次重开重新入场
|
|
242
|
-
记到这个群 / 这个仓库的记忆里 → wizard_remember({scope:"chat" | "workspace"}),见下
|
|
243
|
-
还有谁在跑 / 谁在弄那个项目 → wizard_roster(名字、职责、忙闲、谁是谁的分身)
|
|
244
|
-
分个身去把这三个目录都扫一遍 → clone_wizard ×3,干完 stop_wizard 收掉
|
|
245
|
-
上下文快满了 → 它自己 wizard_handoff_self:写简报、原地重开、把简报贴回去
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
**三份记忆**:`wizard_remember` 的 `scope` 决定记在哪——
|
|
249
|
-
|
|
250
|
-
| scope | 落在 | 谁读得到 |
|
|
251
|
-
| --- | --- | --- |
|
|
252
|
-
| `self`(默认) | 它自己的名册记录 | 只有它自己 |
|
|
253
|
-
| `chat` | `~/.wezard/memory/chats/<聊天>.md` | 以这个群为 home 的每个 wizard |
|
|
254
|
-
| `workspace` | `~/.wezard/memory/workspaces/<工作区>.md` | 在这个目录下干活的每个 wizard |
|
|
255
|
-
|
|
256
|
-
每个 wizard 出生时,群记忆与工作区记忆都注进它的系统提示——「这个群习惯先出方案再动手」「这个仓库没有测试、reload 要先 build」不必再教一遍新来的。两份都是 markdown,你也可以直接打开改。
|
|
257
|
-
|
|
258
|
-
**分身(clone)继承上下文**:`clone_wizard` 用 `--resume <父> --fork-session` 起 pane,CLI 把父亲的 transcript 复制一份再往下写——分身开局就带着父亲读过的材料,父亲毫发无损。于是「先把公共文档读进一个基座,再分出 N 个干活的」成立:材料只读一遍,却进了 N 份上下文。也可以 `clone_wizard({from})` 从别的 wizard 分——分身仍归你管,上下文来自它。要白纸一张用 `spawn_wizard`(那时才能顺便换工作区)。分身自己也能再分身,层级不限;名下同时活着的分身有上限(`wrc.mirror.cloneMax`,默认 8)——一次跑飞的递归编排足以把 fd 吃光。
|
|
259
|
-
|
|
260
|
-
**模型是 wizard 的属性**,不是 spawn 那一瞬的开关:`/new [cli] [model]`、`new_claude_session({model})`、`spawn_wizard` / `clone_wizard({model})`、`run_agent_graph` 的节点都能挑,挑了就写进绑定记录。pane 死了自愈重生仍在那个模型上,名册每一行也看得见谁跑在什么模型上——要判断的给 opus、跑腿的给 haiku,这句话得看得见才执行得了。
|
|
261
|
-
|
|
262
|
-
**群是公开频道,wizard 之间默认私聊**:`send_peer` 派的活、分身之间的来回,默认只落进双方的 rolepage,群里不出气泡。wizard 自己判断「这件事该当着人说」时传 `send_peer({public:true})`:群里出一条 `🦊 .a → 🐬 .b` 的气泡,对方这一轮的回复也发进这个群。分身出生、派活、回程结论都不再各刷一条;群里只留**人发起的那一轮的回复**、`notify`、工单的**开工 / 收工**两条,以及它们主动公开的讨论。
|
|
263
|
-
|
|
264
|
-
**名册会自己更新,不必去问**:charter 里那份「出生时还有谁」只是快照,只会越来越假。谁出生、谁收工、谁改了职责,会以一行 `<system-reminder>` 挂在**下一次**进到它的任何文本尾巴上——不占一轮、不进气泡、不进 transcript。感知 = spawn 时的快照 + turn 时的增量。要当下完整的名册仍然是 `wizard_roster`,它按 `query` / `cwd` / `chat` / `alive` 过滤(「谁在这个目录里干活」问得出来)。
|
|
265
|
-
|
|
266
|
-
**cwd 是聊天级的,不是 session 级**:同一聊天里默认 wizard 与在这里叫出来的 wizard **共用**一个 cwd,`.foo /new` 在当前聊天绑定的 cwd 下起 pane。换项目直接对 AI 说「切到 /path/to/proj」,它调 `set_workspace` 一步到位:杀掉当前 pane、在新 cwd 重开新会话,以新会话的 📂 项目回执为准,上下文不延续。多个 wizard 因此天然对齐到同一个项目根,不用重新指路径。
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
|
-
## rolepage:一个 wizard 视角下的 IM
|
|
120
|
+
| wizard | 名字 |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| 聊天的默认 wizard | 聊天的名字(见[聊天命名](#聊天命名)) |
|
|
123
|
+
| `.docs /new` 叫出来的 / 分身 | 你给的那个名字 |
|
|
124
|
+
| 撞名 | 自动加后缀:`fix` → `fix-2`;对方若已静默超过一天,则直接接手这个名字 |
|
|
271
125
|
|
|
272
|
-
|
|
126
|
+
名字支持中英文、数字、`_`、`-`,最长 32 个字符,大小写不敏感。
|
|
273
127
|
|
|
274
|
-
|
|
275
|
-
flowchart LR
|
|
276
|
-
subgraph side["侧栏"]
|
|
277
|
-
g["群聊<br/>(公开频道)"] --> subs["子项: 群里其他 role<br/>点开 = 只看我与它在这个群里的往来"]
|
|
278
|
-
dm["单聊"] --> h["与人<br/>(企业微信单聊)"]
|
|
279
|
-
dm --> w["与 wizard<br/>(私聊)"]
|
|
280
|
-
end
|
|
281
|
-
subgraph main["消息窗"]
|
|
282
|
-
mright["我发的 → 靠右"]
|
|
283
|
-
mleft["我收到的 → 靠左"]
|
|
284
|
-
end
|
|
285
|
-
subgraph topbar["顶栏"]
|
|
286
|
-
ident[".name · 职责 · 家谱<br/>cwd · 创建时间 · 模型"]
|
|
287
|
-
ses["session 切换<br/>(/clear /new 轮换)"]
|
|
288
|
-
rel["关系图"]
|
|
289
|
-
sch["日程"]
|
|
290
|
-
end
|
|
291
|
-
side --> main
|
|
292
|
-
mleft -. "点它右边的空白 / 头像" .-> flip["切到对端的视角"]
|
|
293
|
-
mright -. "点它左边的空白" .-> flip
|
|
294
|
-
```
|
|
128
|
+
**直接对它说人话**
|
|
295
129
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
130
|
+
| 对它说 | 效果 |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| 你叫什么 / 你负责什么 | 报出自己的名字、职责和分身 |
|
|
133
|
+
| 以后你就叫 sanitizer | 改名,历史和上下文不受影响 |
|
|
134
|
+
| 记住:发版前必须更新 CHANGELOG | 写进它的长期记忆,`/clear` 之后仍在 |
|
|
135
|
+
| 记到这个群 / 这个仓库的记忆里 | 提交到群或工作区的共享记忆,之后来的 wizard 开局就知道 |
|
|
136
|
+
| 还有谁在跑 / 谁在弄那个项目 | 列出全部 wizard:名字、职责、忙闲、谁是谁的分身 |
|
|
137
|
+
| 让 .fix 看一眼这个报错 | 它去找 `.fix` 派活,等结果回来再答你 |
|
|
138
|
+
| 分个身去把这三个目录都扫一遍 | 分出三个带着当前上下文的分身并行干,干完收掉 |
|
|
139
|
+
| 切到 /path/to/proj | 换工作区,重开会话 |
|
|
140
|
+
|
|
141
|
+
- **记忆分三层**:自己的、本群共享的、本工作区共享的。「这个群习惯先出方案再动手」「这个仓库 reload 要先 build」不必每来一个新 wizard 再教一遍。
|
|
142
|
+
- **分身继承上下文**:分身开局就带着原 wizard 读过的全部材料,原 wizard 不受影响;也可以生一个白板的子 wizard,顺便换个工作区。分身还能再分身。
|
|
143
|
+
- **模型可以挑**:`/new [cli] [model]`,或让它生分身时指定——要判断的给 opus,跑腿的给 haiku。
|
|
144
|
+
- **群里只留结论**:wizard 之间派活、来回默认是私聊,不刷群;群里只出现回给你的那条回复、通知、工单的开工 / 收工,以及它们主动公开的讨论。过程想看去 [rolepage](#rolepage)。
|
|
145
|
+
- **上下文满了自己交接**:它会写一份简报、原地重开,接着干。
|
|
300
146
|
|
|
301
147
|
---
|
|
302
148
|
|
|
303
|
-
##
|
|
149
|
+
## 任务编排
|
|
304
150
|
|
|
305
|
-
|
|
151
|
+
遇到「读完材料才知道要分几路」的活,wizard 自己拆、自己派、自己等、自己汇总:
|
|
306
152
|
|
|
307
153
|
```mermaid
|
|
308
154
|
sequenceDiagram
|
|
309
155
|
autonumber
|
|
310
156
|
participant U as 群里的人
|
|
311
157
|
participant W as 发起的 wizard
|
|
312
|
-
participant K as 分身
|
|
313
|
-
|
|
314
|
-
Note over W
|
|
315
|
-
W->>U: 📋
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
Note over W,K:
|
|
321
|
-
W->>K: wait_peer({names:[…], need})
|
|
322
|
-
K-->>W: 各自的 RESULT: 行
|
|
323
|
-
W->>U: 📋 收工 close_job(汇总)
|
|
324
|
-
Note over W,K: 为这个工单生出来的分身整批回收,被拉来帮忙的长期 wizard 不在其列
|
|
158
|
+
participant K as 分身 ×N
|
|
159
|
+
|
|
160
|
+
Note over W: 先读完公共材料
|
|
161
|
+
W->>U: 📋 开工(标题 + 计划)
|
|
162
|
+
W->>K: 分出 N 个分身,各领一份活
|
|
163
|
+
Note over K: 并行干活,过程不刷群
|
|
164
|
+
K-->>W: 各自交回结论
|
|
165
|
+
W->>U: 📋 收工(汇总)
|
|
166
|
+
Note over W,K: 为这单活生出来的分身整批回收
|
|
325
167
|
```
|
|
326
168
|
|
|
327
|
-
|
|
169
|
+
- **群里只有两条气泡**:开工和收工。中间的过程在各自的 rolepage 里。
|
|
170
|
+
- **材料只读一遍**:分身是从发起方的上下文里分出来的,不用各自重读。
|
|
171
|
+
- **并行**:几路同时干,总耗时只取决于最慢的那一路。
|
|
172
|
+
- **自动回收**:收工时为这单活生出来的分身一并收掉;被拉来帮忙的长期 wizard 不受影响。
|
|
328
173
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
| **投递时机** `send_peer({when:"idle"})` | 两个 wizard 同时找第三个时,两段文本会挤进同一个输入框被当成一轮读掉——这在协同网络里是常态而非边角。`idle` 先等对方闲下来再投;「回答它的提问」「打断它」仍用默认的 `now`。返回一律带 `wasBusy`。 |
|
|
174
|
+
流程固定的循环(几个 wizard 互相评审、迭代到收敛)可以让它搭成一条流水线,按轮次跑,达到约定的结束条件就提前收工。
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## rolepage
|
|
335
179
|
|
|
336
|
-
|
|
180
|
+
点气泡头的 `emoji .name`,打开那个 wizard 的 **rolepage**——它视角下的 IM:
|
|
181
|
+
|
|
182
|
+
- **侧栏**:它参与的每个群聊,以及与人、与其他 wizard 的单聊。
|
|
183
|
+
- **消息窗**:它发的靠右,收到的靠左;工具调用和用量细节折叠在回复里。
|
|
184
|
+
- **切视角**:点一条消息对侧的空白,切到对方的 rolepage。
|
|
185
|
+
- **顶栏**:名字、职责、工作区、模型;历史会话切换;关系图(谁是谁的分身、谁给谁派过活);日程(它名下的定时任务和工单)。
|
|
337
186
|
|
|
338
187
|
---
|
|
339
188
|
|
|
340
|
-
##
|
|
189
|
+
## 聊天命名
|
|
341
190
|
|
|
342
|
-
|
|
191
|
+
给聊天起个名字,它就有了能写进消息的地址——通知发到哪个群、新 wizard 生在哪个群,都写这个名字。它同时也是这个群默认 wizard 的名字。
|
|
343
192
|
|
|
344
193
|
```
|
|
345
194
|
/name daily 给本聊天起名为 daily
|
|
346
195
|
/name 查看当前名字
|
|
347
196
|
/name - 取消命名
|
|
348
|
-
/chats
|
|
197
|
+
/chats 列出所有聊天,以及各自跑着哪些 wizard
|
|
349
198
|
```
|
|
350
199
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
**寻址**只剩一种写法——wizard 的名字:
|
|
354
|
-
|
|
355
|
-
| 写法 | 指向 |
|
|
356
|
-
|---|---|
|
|
357
|
-
| `fix` / `.fix` | 名册里叫 fix 的那个 wizard,不管它住在哪个群 |
|
|
358
|
-
| `daily` | 名字叫 daily 的 wizard——也就是 daily 这个聊天的默认 wizard |
|
|
359
|
-
| `''` | 调用方自己这个聊天的默认 wizard |
|
|
360
|
-
| `chat:wr…#slot` | 全量 key(调试用);改名前的老写法 `daily#fix` 也还认得,但别再写 |
|
|
200
|
+
名字 1–32 个字符,字母 / 数字 / `_` / `-`,全机唯一、大小写不敏感。没起名的聊天会按工作区目录名自动补一个(`~/develop/weclaude` → `weclaude`),你起过的名字不会被覆盖。
|
|
361
201
|
|
|
362
|
-
|
|
202
|
+
起了名,在任何群里都可以直接说:
|
|
363
203
|
|
|
364
204
|
```
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
别的群还有谁在跑 → AI 调 list_chats
|
|
205
|
+
在 daily 里开个 .ingest 跑 ~/repo 在 daily 群里新开一个 wizard
|
|
206
|
+
别的群还有谁在跑 列出所有聊天和 wizard
|
|
368
207
|
```
|
|
369
208
|
|
|
370
|
-
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 定时任务与跨群通知
|
|
371
212
|
|
|
372
|
-
|
|
213
|
+
**定时任务**:到点把一句话说给 wizard 听,它就真的去干活,结果照常发回群里。任务常驻在后台,CLI 重启、`/clear`、会话结束都不受影响。
|
|
373
214
|
|
|
374
|
-
|
|
215
|
+
时间用人话说就行:
|
|
216
|
+
|
|
217
|
+
| 对它说 | 效果 |
|
|
218
|
+
| --- | --- |
|
|
219
|
+
| 每个工作日晚上 9:30 跑一遍回归 | 排一个定时任务,到点新起一个 wizard 去干,干完收掉 |
|
|
220
|
+
| 每天早上 8 点在 .daily 里继续整理日报 | 到点把这句话发给已有的 `.daily` |
|
|
221
|
+
| 我设了什么定时 | 列出任务、时间和下次触发时刻 |
|
|
222
|
+
| 取消那个定时 | 删掉任务 |
|
|
223
|
+
|
|
224
|
+
认得的说法:`每天 8:00`、`每个工作日晚上9:30`、`每周三下午3点`、`每隔两小时`、`每 30 分钟`、`20 分钟后`、`明早 9 点`。每个 wizard 排的任务列在它 rolepage 的「日程」里。
|
|
225
|
+
|
|
226
|
+
**跨群通知**:让 wizard 把一段话贴进另一个群给人看,比如一个长活在 `build` 群跑完,把「🔴 回归挂了 3 例」送到 `ops` 群。
|
|
375
227
|
|
|
376
228
|
---
|
|
377
229
|
|
|
378
|
-
##
|
|
230
|
+
## 企业微信文档
|
|
379
231
|
|
|
380
|
-
|
|
232
|
+
对 Agent 说「把周报整理成一篇企业微信文档」,它会新建在线文档、写入内容,把链接贴回会话。同样可以读文档链接、操作智能表格。文档归属到你本人。
|
|
381
233
|
|
|
382
|
-
|
|
383
|
-
/new 沿用「当前会话」的 CLI 新开
|
|
384
|
-
/new codebuddy 换到 codebuddy 新开
|
|
385
|
-
/new claude-internal 换到 claude-internal 新开
|
|
386
|
-
```
|
|
234
|
+
首次使用需在企业微信「工作台 - 智能机器人 - 可使用权限」里勾选「文档」「智能表格」。实现细节见 [技术说明](技术说明.md#文档-mcp-如何桥接)。
|
|
387
235
|
|
|
388
|
-
|
|
236
|
+
---
|
|
389
237
|
|
|
390
|
-
|
|
238
|
+
## 多 CLI 后端
|
|
239
|
+
|
|
240
|
+
`claude` / `claude-internal` / `codebuddy` 可以同时用:一个会话跑 `claude`,另一个跑 `codebuddy`,各自绑不同的聊天或 wizard。
|
|
391
241
|
|
|
392
242
|
```
|
|
243
|
+
/new 沿用当前会话的 CLI 新开
|
|
244
|
+
/new codebuddy 换到 codebuddy 新开
|
|
393
245
|
.docs /new codebuddy 用 codebuddy 叫出一个叫 docs 的 wizard
|
|
394
|
-
.docs 帮我改 README → 路由到那个 codebuddy 会话
|
|
395
|
-
/clear .docs → 只清它,且仍留在 codebuddy 上
|
|
396
246
|
```
|
|
397
247
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
- `/clear .name` rotate 出的新 jsonl 仍落在该 CLI 的 projects 目录,watcher 按该后端的 dialect 迁移绑定;
|
|
401
|
-
- pane 挂了自愈 `--resume` 用的是**该会话所属**的二进制,不会串到 `defaultCli`;
|
|
402
|
-
- 新叫出的 `.name` 还没有自己的历史时,**继承本聊天基础会话的 CLI**(同 cwd 的聊天级继承规则),不会悄悄退回默认后端;
|
|
403
|
-
- `/sessions` 列表在混用多个 CLI 时,每行自动标注 `(codebuddy)` 之类的来源。
|
|
248
|
+
会话一直留在它所属的 CLI 上,`/clear`、重启自愈都不会换。默认后端由配置项 `wrc.defaultCli` 决定(缺省 `claude`)。
|
|
404
249
|
|
|
405
250
|
---
|
|
406
251
|
|
|
407
|
-
##
|
|
408
|
-
|
|
409
|
-
Anthropic 的 prompt cache 只活 ~5 分钟,且**写缓存 1.25x、读缓存 0.1x**。pane 一旦空闲(wizard 在等同伴回话、或后台任务在跑),整份上下文掉出缓存,下一轮真实对话得按 1.25x 重写一遍。保活在缓存**即将过期前**注入一次极小的 ping,逼出一次廉价请求(命中前缀走 0.1x 读)把 TTL 往前滑,真实那轮就只写增量。
|
|
252
|
+
## 缓存保活
|
|
410
253
|
|
|
411
|
-
|
|
412
|
-
- **给的是预算,不是时限**:一次真实对话之后最多补 `rounds` 次(默认 6,节奏 255 秒,合计约 26 分钟),然后放手让缓存冷掉;真实对话一来预算清零。老会话不会因为「ping 刷新了时间戳」被误判成活跃而无限保活。
|
|
413
|
-
- **两道成本保险**:缓存已冷(距上次触碰 ≥ TTL)绝不 ping——那是为 no-op 付整份冷写;预算用尽直接放弃。
|
|
414
|
-
- **零污染**:ping 逼出一个约 1 token 的回复且禁止任何工具动作,该轮**完全不进聊天**,但**记入 chat detail 时间线**(ping 原文 + 真实回复 + cache-read usage),留痕可审计。
|
|
415
|
-
- **顺手救活断掉的一轮**:上一轮死在 API 报错 / 限额横幅上时,这一 ping 改发 `resumePing`(默认 `continue`)把活接上。纯按规则判定,不问模型自己的意见。
|
|
416
|
-
- **`/stop` 手动暂停**:IM 里发 `/stop` 同时暂停该会话的保活,下次有真实对话自动恢复。
|
|
417
|
-
- **只针对活 pane**:spawn 模式无 TTY、pane 已死、正在流式输出或会话轮换中的,一律跳过。
|
|
254
|
+
Agent 空闲超过约 5 分钟,模型侧的 prompt 缓存就会过期,下一轮对话要为整份上下文重新付费。wezard 在缓存快过期时自动发一次极小的心跳把它续上:
|
|
418
255
|
|
|
419
|
-
|
|
256
|
+
- 每次真实对话后最多续 6 次(约 26 分钟),之后不再续;
|
|
257
|
+
- 心跳不出现在聊天里,只记在详情页时间线;
|
|
258
|
+
- 上一轮因报错 / 限额中断时,心跳会顺手把活接上;
|
|
259
|
+
- `/stop` 同时暂停保活,下次真实对话自动恢复。
|
|
420
260
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
```jsonc
|
|
424
|
-
"keepalive": {
|
|
425
|
-
"enabled": true, // 总开关
|
|
426
|
-
"ttlSec": 300, // 缓存 TTL,Anthropic 默认 5min
|
|
427
|
-
"marginSec": 45, // 提前多少秒 ping(留出注入落地的余量)
|
|
428
|
-
"rounds": 6, // 一次真实对话后最多补几次 ping,之后让缓存冷掉
|
|
429
|
-
"ping": "keepalive — reply with just \"pong\", take no other action",
|
|
430
|
-
"resumeOnStall": true, // 上一轮死在报错/限额上时,改发 resumePing 续上
|
|
431
|
-
"resumePing": "continue"
|
|
432
|
-
}
|
|
433
|
-
```
|
|
261
|
+
配置项在 `wrc.mirror.keepalive`,完整规则见 [技术说明](技术说明.md#prompt-cache-保活省钱心跳)。
|
|
434
262
|
|
|
435
263
|
---
|
|
436
264
|
|