wezard 2.2.2 → 2.3.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.
- package/CLAUDE.md +7 -4
- package/dist/cli/init.js +2 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/daemon/approval.js +6 -4
- package/dist/daemon/approval.js.map +1 -1
- package/dist/daemon/chat-log.js +6 -1
- package/dist/daemon/chat-log.js.map +1 -1
- package/dist/daemon/danger.js +17 -0
- package/dist/daemon/danger.js.map +1 -1
- package/dist/daemon/detail.js +41 -0
- package/dist/daemon/detail.js.map +1 -1
- package/dist/daemon/handoff.js +8 -2
- package/dist/daemon/handoff.js.map +1 -1
- package/dist/daemon/inbound.js +9 -4
- package/dist/daemon/inbound.js.map +1 -1
- package/dist/daemon/index.js +744 -152
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/jobs.js +62 -23
- package/dist/daemon/jobs.js.map +1 -1
- package/dist/daemon/last-response.js +15 -0
- package/dist/daemon/last-response.js.map +1 -1
- package/dist/daemon/memory-steward.js +124 -68
- package/dist/daemon/memory-steward.js.map +1 -1
- package/dist/daemon/mirror-bridge.js +461 -91
- package/dist/daemon/mirror-bridge.js.map +1 -1
- package/dist/daemon/mirror-store.js.map +1 -1
- package/dist/daemon/model-select.js +99 -0
- package/dist/daemon/model-select.js.map +1 -1
- package/dist/daemon/notices.js +7 -5
- package/dist/daemon/notices.js.map +1 -1
- package/dist/daemon/peers.js +183 -60
- package/dist/daemon/peers.js.map +1 -1
- package/dist/daemon/receipts.js +391 -77
- package/dist/daemon/receipts.js.map +1 -1
- package/dist/daemon/route.js +63 -10
- package/dist/daemon/route.js.map +1 -1
- package/dist/daemon/spawn-tmux.js +24 -13
- package/dist/daemon/spawn-tmux.js.map +1 -1
- package/dist/daemon/task-registry.js +1 -1
- package/dist/daemon/task-registry.js.map +1 -1
- package/dist/daemon/tasks.js +12 -4
- package/dist/daemon/tasks.js.map +1 -1
- package/dist/daemon/usage.js +4 -11
- package/dist/daemon/usage.js.map +1 -1
- package/dist/daemon/wizard-memory.js +36 -4
- package/dist/daemon/wizard-memory.js.map +1 -1
- package/dist/daemon/wizard.js +102 -58
- package/dist/daemon/wizard.js.map +1 -1
- package/dist/mcp/server.js +138 -111
- package/dist/mcp/server.js.map +1 -1
- package/dist/shared/charter-view.js +35 -0
- package/dist/shared/charter-view.js.map +1 -0
- package/dist/shared/chat-http.js +133 -31
- package/dist/shared/chat-http.js.map +1 -1
- package/dist/shared/chat-render.js +6 -3
- package/dist/shared/chat-render.js.map +1 -1
- package/dist/shared/chat-view.js +17 -6
- package/dist/shared/chat-view.js.map +1 -1
- package/dist/shared/cli-backends.js +2 -0
- package/dist/shared/cli-backends.js.map +1 -1
- package/dist/shared/config.js +9 -5
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/daemon-token.js +18 -0
- package/dist/shared/daemon-token.js.map +1 -0
- package/dist/shared/detail-render.js +140 -16
- package/dist/shared/detail-render.js.map +1 -1
- package/dist/shared/detail-store.js +33 -12
- package/dist/shared/detail-store.js.map +1 -1
- package/dist/shared/effort.js +11 -0
- package/dist/shared/effort.js.map +1 -0
- package/dist/shared/model-prices.js +258 -0
- package/dist/shared/model-prices.js.map +1 -0
- package/dist/shared/pricing.js +55 -0
- package/dist/shared/pricing.js.map +1 -0
- package/dist/shared/reminder.js +18 -4
- package/dist/shared/reminder.js.map +1 -1
- package/dist/shared/role-render.js +172 -8
- package/dist/shared/role-render.js.map +1 -1
- package/dist/shared/role-view.js +89 -25
- package/dist/shared/role-view.js.map +1 -1
- package/dist/shared/session-label.js +4 -0
- package/dist/shared/session-label.js.map +1 -1
- package/dist/shared/task-file.js +16 -1
- package/dist/shared/task-file.js.map +1 -1
- package/dist/shared/turn-state.js +24 -0
- package/dist/shared/turn-state.js.map +1 -0
- package/dist/shared/world.js +37 -3
- package/dist/shared/world.js.map +1 -1
- package/dist/svr/index.js +2 -1
- package/dist/svr/index.js.map +1 -1
- package/hooks/pre-tool-use.sh +16 -3
- package/package.json +1 -1
- package/web/chat.css +172 -40
- package/web/chat.js +725 -247
package/dist/mcp/server.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { DAEMON_TOKEN_HEADER, readDaemonToken } from "../shared/daemon-token.js";
|
|
8
|
+
import { EFFORTS } from "../shared/effort.js";
|
|
7
9
|
const DAEMON_BASE = process.env.WEZARD_DAEMON_BASE ?? "http://127.0.0.1:17890";
|
|
8
10
|
const ok = (data) => ({
|
|
9
11
|
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
@@ -40,6 +42,10 @@ server.registerTool = ((toolName, config, cb) => {
|
|
|
40
42
|
const fold = foldLegacyAddress(!!shape.name && !shape.name.isOptional());
|
|
41
43
|
return registerTool(toolName, { ...config, inputSchema: withLegacyAddress(shape) }, ((args, extra) => cb(fold(args), extra)));
|
|
42
44
|
});
|
|
45
|
+
// 人侧 / 罕用的工具 (30 天零调用: graph 三件套、本机会话清单与改接、聊天目录) 不进默认
|
|
46
|
+
// 工具面 —— 每个注册的工具都占模型的上下文, 而这些要么人在 IM 里有对应命令 (`/sessions`
|
|
47
|
+
// `/chats`), 要么由 tell_peer + 回执覆盖。路由都还在; `WEZARD_MCP_EXTRA=1` 把它们挂回来。
|
|
48
|
+
const registerRare = process.env.WEZARD_MCP_EXTRA === "1" ? server.registerTool.bind(server) : (() => undefined);
|
|
43
49
|
// Accept user-friendly prefixes from the LLM: vid:<id> → user:<id>, chatid:<id> → chat:<id>.
|
|
44
50
|
// Pass anything else (already user:/chat:/group:, or empty) through unchanged.
|
|
45
51
|
const normalizeTarget = (raw) => {
|
|
@@ -61,7 +67,7 @@ const normalizeTarget = (raw) => {
|
|
|
61
67
|
// the switch.
|
|
62
68
|
server.registerTool("set_workspace", {
|
|
63
69
|
title: "Switch workspace directory",
|
|
64
|
-
description: "一步换掉这个 wizard 的**工作区**: 杀掉当前 pane, 在给定目录下重开一个全新的会话 —— 等价于往那个目录 `/new`。群里收到新会话的 📂 项目信息气泡当回执; 对话上下文**不会**带过去 (和 /new 一样是全新会话, 但身份的系统提示还在)。调用方就是被替换的那一个时, 它在调用当口就被终结 —— 这是预期行为, 群里那条气泡就是回执。用绝对路径 (或 `~` 开头)。想换目录又想保住手上的上下文: 先
|
|
70
|
+
description: "一步换掉这个 wizard 的**工作区**: 杀掉当前 pane, 在给定目录下重开一个全新的会话 —— 等价于往那个目录 `/new`。群里收到新会话的 📂 项目信息气泡当回执; 对话上下文**不会**带过去 (和 /new 一样是全新会话, 但身份的系统提示还在)。调用方就是被替换的那一个时, 它在调用当口就被终结 —— 这是预期行为, 群里那条气泡就是回执。用绝对路径 (或 `~` 开头)。想换目录又想保住手上的上下文: 先 handoff 把自己的工作压成简报, 或者 spawn_wizard({cwd}) 生一个子 wizard 去那边干。\n"
|
|
65
71
|
+ "**`keep:true` 是另一支**: 人回答「不用换, 就用现在这个目录」时调它 —— 不重开会话, 只把「这个工作区是人认过的」记下来, 此后新会话的开局不再问这件事。一个新聊天的第一个 wizard 落在默认兜底目录里, 开局会被要求先问一句要去哪个项目; 人给路径就走 `cwd`, 人说不用换就走 `keep`。",
|
|
66
72
|
inputSchema: {
|
|
67
73
|
cwd: z.string().optional().describe("Absolute project path, e.g. /Users/foo/projects/bar. ~ is expanded. 与 `keep` 二选一。"),
|
|
@@ -138,16 +144,16 @@ server.registerTool("wecom_doc_call", {
|
|
|
138
144
|
// These let the WeCom-side Claude answer "列出所有 claude session" / "切到 xxx
|
|
139
145
|
// 那个" / "在 /path 下新建一个 session" in natural language. The daemon does the
|
|
140
146
|
// host-wide /proc + tmux scan (an MCP tool can only see its OWN session).
|
|
141
|
-
|
|
147
|
+
registerRare("list_claude_sessions", {
|
|
142
148
|
title: "List running agent sessions",
|
|
143
|
-
description: "
|
|
149
|
+
description: "(人侧/罕用 · 同 IM `/sessions`) 本机 tmux 里所有 agent 会话, 含与企微无关的终端会话; 只在用户要「切换 session」时用。找 wizard 用 wizard_roster。",
|
|
144
150
|
inputSchema: {},
|
|
145
151
|
}, async () => {
|
|
146
152
|
return unwrap("list_claude_sessions", await daemonGet("/sessions/list"));
|
|
147
153
|
});
|
|
148
|
-
|
|
154
|
+
registerRare("switch_claude_session", {
|
|
149
155
|
title: "Switch WeCom mirror to another agent session",
|
|
150
|
-
description: "
|
|
156
|
+
description: "(人侧/罕用 · 同 IM `/sessions <emoji|id>`) 让本聊天改接到一个已在跑的会话 (sessionId 从 list_claude_sessions 拿)。只在用户点名要切时用。",
|
|
151
157
|
inputSchema: {
|
|
152
158
|
sessionId: z.string().describe("The target session's sessionId (a UUID), as returned by list_claude_sessions."),
|
|
153
159
|
},
|
|
@@ -173,7 +179,8 @@ const selfRef = () => {
|
|
|
173
179
|
const daemonPost = async (path, body) => {
|
|
174
180
|
const resp = await fetch(`${DAEMON_BASE}${path}`, {
|
|
175
181
|
method: "POST",
|
|
176
|
-
|
|
182
|
+
// 口令每次现读: daemon 可能在本进程起来之后才生成它。
|
|
183
|
+
headers: { "content-type": "application/json", [DAEMON_TOKEN_HEADER]: readDaemonToken() },
|
|
177
184
|
body: JSON.stringify({ ...selfRef(), ...body }),
|
|
178
185
|
});
|
|
179
186
|
return { j: (await resp.json().catch(() => ({}))), status: resp.status };
|
|
@@ -191,33 +198,21 @@ const unwrapText = (name, r) => r.j.ok && typeof r.j.text === "string" ? { conte
|
|
|
191
198
|
// 地址语法只有一套, 每个吃地址的工具都把它原样重述一遍: 模型单看一个 schema
|
|
192
199
|
// 时没有别的地方能学到它, 而猜出来的地址会安静地指向另一个 wizard 的终端。
|
|
193
200
|
const ADDRESS_DOC = "wizard 的名字 —— 全机唯一, 它就是地址: `'fix'` 或 `'.fix'` 都行, 不分它住在哪个群。`''` = 你这个聊天的默认 wizard。永远别自己拼 key: wizard_roster / spawn_wizard / clone_wizard 返回的名字 (或 409 里的 `address`) 原样传回来。老的 `聊天名#tag` 写法仍然认, 但别再写。";
|
|
201
|
+
// 生 wizard 的三个工具共用: 模型名是口语, 落地的是 `/model` 列表里最接近的那一项。
|
|
202
|
+
const MODEL_DOC = "跑在哪个模型上, 口语写 ('opus' / 'haiku' / 'sonnet 5'); 落地的是它 `/model` 列表里最接近的一项, 见返回的 `model`, 对不上时带 `modelWarning` (仍在跑, 停在默认模型)。写确切的 id ('claude-opus-5-5') 或列表标签 ('Opus 5.5') 且本机跑过它时, 直接带 `--model` 启动, 省掉选择器那一来回。省略 = 该 CLI 默认。之后换用 set_model。";
|
|
203
|
+
const EFFORT_DOC = "推理档位, 启动时就带上 (`--effort`), 之后它每次重启都沿用。判断密集的活给 high 以上, 跑腿的给 low。省略 = 该 CLI 默认 (克隆则跟被克隆者同档)。不支持的 CLI (codebuddy) 忽略它。之后换用 set_model 的 `effort`。";
|
|
194
204
|
// 造一个 wizard: 它的 home 默认是调用方自己的聊天 (所以走 `selfRef`); 给了 `chat`
|
|
195
205
|
// 就落在另一个**起过名字**的聊天里。名字全局唯一, 与 home 无关。
|
|
196
206
|
server.registerTool("new_claude_session", {
|
|
197
207
|
title: "Spawn a blank new wizard",
|
|
198
|
-
description: "
|
|
208
|
+
description: "同 `spawn_wizard({detached:true, cwd})` —— 在某个目录生一个独立长住的白板 wizard (旧名, 只为不打断正在跑的旧会话而保留)。新调用一律用 spawn_wizard。",
|
|
199
209
|
inputSchema: {
|
|
200
|
-
cwd: z.string().describe("
|
|
201
|
-
name: z
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
.string()
|
|
207
|
-
.optional()
|
|
208
|
-
.describe("把它生在哪个聊天里 (list_chats 里显示的名字)。省略 = 你自己的聊天, 用户绝大多数时候指的就是这个。只有**起过名字**的聊天能被指名 —— 没名字就没有地址, 得先有人在那边发一次 `/name <名字>`。"),
|
|
209
|
-
cli: z
|
|
210
|
-
.enum(["claude", "claude-internal", "codebuddy"])
|
|
211
|
-
.optional()
|
|
212
|
-
.describe("用哪个 CLI 启动。用户没点名就省略, 它会继承那个聊天当前的后端。多个后端可以并存。"),
|
|
213
|
-
model: z
|
|
214
|
-
.string()
|
|
215
|
-
.optional()
|
|
216
|
-
.describe("这个 wizard 跑在哪个模型上, 口语化随便写 ('opus' / 'sonnet 5' / '最新的 opus' / 'claude-haiku-4-5' 都行) —— 不是塞给启动参数, 而是等 pane 起来后打开它自己的 `/model` 列表, 在里面挑最接近的一项选中, 所以返回里的 `model` 是列表里真正落地的那一项 (如 'Opus 5.5'), 可能跟你传的字符串不一样; 列表里对不上会带 `modelWarning`, 那时 wizard 还在跑但停在了原来的模型上, 不是失败。省略用该 CLI 的默认。同一个聊天里的 wizard 可以各跑各的模型 —— 又长又要判断的活给 opus, 跑腿的 lint/grep 给 haiku。之后要换用 set_model。"),
|
|
217
|
-
keepalive: z
|
|
218
|
-
.boolean()
|
|
219
|
-
.optional()
|
|
220
|
-
.describe("这个 wizard 要不要被 keepalive 心跳保温 (空闲时定期 ping 一下防 prompt cache 过期)。false = 永远不保温, 省下那份 ping 的钱 —— 适合跑腿一次就收工的临时会话; true = 明确要保温。省略则按 daemon 配置的默认值。"),
|
|
210
|
+
cwd: z.string().describe("工作区绝对路径, 不存在会创建。"),
|
|
211
|
+
name: z.string().optional().describe("它的名字, 全机唯一。省略按目录名生成。"),
|
|
212
|
+
chat: z.string().optional().describe("home 聊天名。省略 = 你的聊天。"),
|
|
213
|
+
cli: z.enum(["claude", "claude-internal", "codebuddy"]).optional().describe("用哪个 CLI。省略则继承。"),
|
|
214
|
+
model: z.string().optional().describe(MODEL_DOC),
|
|
215
|
+
keepalive: z.boolean().optional().describe("要不要保温。省略按配置。"),
|
|
221
216
|
},
|
|
222
217
|
}, async ({ cwd, name, chat, cli, model, keepalive }) => unwrap("new_claude_session", await daemonPost("/sessions/new", {
|
|
223
218
|
cwd,
|
|
@@ -233,7 +228,7 @@ server.registerTool("new_claude_session", {
|
|
|
233
228
|
// point at, and what a chat's default wizard is born named after.
|
|
234
229
|
server.registerTool("name_chat", {
|
|
235
230
|
title: "Name this WeCom chat",
|
|
236
|
-
description: "给**这个聊天**起个短名字: notify 的收件人、
|
|
231
|
+
description: "给**这个聊天**起个短名字: notify 的收件人、spawn_wizard / clone_wizard 的 `chat` 从此能指到这里。没起过名字的聊天不会一直没名字 —— 第一次有人用到时守护进程按它的工作区自动补一个 (`~/develop/foo` → `foo`, 撞名加序号), 所以这个工具的用途是**起一个更好的名字**。这里的默认 wizard 出生时取聊天名; 它若还叫旧聊天名, 会跟着改 (wizard 的名字全局唯一, 撞名加后缀, 返回里的 `wizardRenamed` 就是它的新名字)。wizard 自己的名字用 wizard_identity 改, 与这里无关。用户说「给这个群起名叫 daily」「这个群叫什么」(不传 `name` 就是读) 「取消命名」(传 '-') 时调它。名字全机唯一、大小写不敏感。",
|
|
237
232
|
inputSchema: {
|
|
238
233
|
name: z
|
|
239
234
|
.string()
|
|
@@ -243,14 +238,14 @@ server.registerTool("name_chat", {
|
|
|
243
238
|
}, async ({ name }) => name === undefined
|
|
244
239
|
? unwrap("name_chat", await daemonPost("/chats/list", {})) // read-only path: roster carries this chat's name
|
|
245
240
|
: unwrap("name_chat", await daemonPost("/chats/name", { name })));
|
|
246
|
-
|
|
241
|
+
registerRare("list_chats", {
|
|
247
242
|
title: "List every chat and its sessions",
|
|
248
|
-
description: "
|
|
243
|
+
description: "(罕用 · 同 IM `/chats`) 每个聊天的名字与住在那里的 wizard。只在要给 notify / spawn 的 `chat` 找聊天名时用; 找 wizard 用 wizard_roster。",
|
|
249
244
|
inputSchema: {},
|
|
250
245
|
}, async () => unwrap("list_chats", await daemonPost("/chats/list", {})));
|
|
251
246
|
server.registerTool("peek_peer", {
|
|
252
247
|
title: "Read what another wizard has been saying",
|
|
253
|
-
description: "**不打扰**地读另一个 wizard 的会话: 首行是它此刻的状态 (正在生成 / 空闲 / 停在哪个工具调用上等人点), 下面是它最近 N 个来回的正文 —— `▸` 是别人对它说的, `◂` 是它对这句最新说的一句 (跑完的是终句, 还在跑的是途中最近那句), 最后一条给得最全; 截掉的地方都标了 `…(+N)` / `…(略 N 字)…`。全部从它的 transcript 读: 只有说出来的话, 没有工具调用的过程, 保温的 ping/pong 已经剔掉。回答「.fix 进展如何」「它卡在哪」, 或者判断要不要推它一把,
|
|
248
|
+
description: "**不打扰**地读另一个 wizard 的会话: 首行是它此刻的状态 (正在生成 / 空闲 / 停在哪个工具调用上等人点), 下面是它最近 N 个来回的正文 —— `▸` 是别人对它说的, `◂` 是它对这句最新说的一句 (跑完的是终句, 还在跑的是途中最近那句), 最后一条给得最全; 截掉的地方都标了 `…(+N)` / `…(略 N 字)…`。全部从它的 transcript 读: 只有说出来的话, 没有工具调用的过程, 保温的 ping/pong 已经剔掉。回答「.fix 进展如何」「它卡在哪」, 或者判断要不要推它一把, 都读这里。这是**一个会话**的视角 (它从各处听到的都在里面); 要看**一个群**里谁对谁说了什么、或你和它的私聊往来, 用 read_chat。只读, 随便轮询。",
|
|
254
249
|
inputSchema: {
|
|
255
250
|
name: z.string().describe(ADDRESS_DOC),
|
|
256
251
|
turns: z.number().optional().describe("回最近多少个来回 (1-40, 默认 6); 一个来回 = 一句问话 + 它的回答。"),
|
|
@@ -258,12 +253,12 @@ server.registerTool("peek_peer", {
|
|
|
258
253
|
}, async ({ name, turns }) => unwrapText("peek_peer", await daemonPost("/peers/peek", { name, ...(turns ? { turns } : {}) })));
|
|
259
254
|
server.registerTool("read_chat", {
|
|
260
255
|
title: "Read chat history like a person would",
|
|
261
|
-
description: "像人翻聊天记录那样读往来, 一行一句: `[时刻] 谁 → 谁: 说了什么`。三级收窄, 每一级都可以不给: `role` (谁的视角 —— 只留它说的或听的) → `chat` (哪个群 —— 只留那个群里公开说的) → `target` (和谁 —— 只留与它的往来)。什么都不给 = 你这一轮所在的群: 人说的、各个 wizard 答的、wizard 之间公开说的都在里面。只给 `target` = 你和它的全部往来 (含
|
|
256
|
+
description: "像人翻聊天记录那样读往来, 一行一句: `[时刻] 谁 → 谁: 说了什么`。三级收窄, 每一级都可以不给: `role` (谁的视角 —— 只留它说的或听的) → `chat` (哪个群 —— 只留那个群里公开说的) → `target` (和谁 —— 只留与它的往来)。什么都不给 = 你这一轮所在的群: 人说的、各个 wizard 答的、wizard 之间公开说的都在里面。只给 `target` = 你和它的全部往来 (含 tell_peer 默认走的私聊, 群里看不见的那部分); `role` + `target` = 那两方之间; 只给 `role` = 它在所有群与私聊里的往来; 再加 `chat` 就限定在那个群里。没给 `chat` 时每行会标出这句是在哪个聊天 / 私聊里说的。一条消息一行起头, 正文的续行缩进; 还没等到回答的问话行尾标 `(还没有回复)`。\n" +
|
|
262
257
|
"按时间和条数读: 默认回最新的 `limit` 条; `until` = 只看那之前的 (往回翻页), `since` = 从那时起 (只给 `since` 就从它往后数 `limit` 条)。回执末尾给出翻页用的时刻, 原样传回即可; 往回已经没有了会明说到头。\n" +
|
|
263
258
|
"只有正文: 每个来回取问话和终句, 工具调用的过程不在里面, 保温的 ping/pong 已经剔掉。记录是从各个 wizard 当前会话的 transcript 现拼的: 一个 wizard `/clear` 或交接之前说的不在里面, 除点名的 role / target 外只并最近一周动过的会话。用户说「群里刚才聊了什么」「.fix 之前怎么答的」「我上次让 .docs 干了什么」, 或者你被叫进一个已经聊了一阵的群、需要来龙去脉时调它。要读**某一个 wizard** 会话里的原始对话用 peek_peer。",
|
|
264
259
|
inputSchema: {
|
|
265
260
|
role: z.string().optional().describe("谁的视角: wizard 的名字 ('fix' / '.fix') 或人的 userid。省略 = 不限 (给了 `target` 时默认是你自己)。"),
|
|
266
|
-
chat: z.string().optional().describe("哪个群: 聊天名 (
|
|
261
|
+
chat: z.string().optional().describe("哪个群: 聊天名 (wizard_roster 里的 home 聊天名) 或裸 principal。省略 = 不限群 (公开与私聊都算); 三个都省略 = 你这一轮所在的群。"),
|
|
267
262
|
target: z.string().optional().describe("和谁的往来: wizard 的名字或人的 userid。"),
|
|
268
263
|
since: z.string().optional().describe("从什么时候起: `2h` / `30m` / `3d` (多久以前)、`14:30` (今天)、`09-30 14:30`、或 ISO 时间。"),
|
|
269
264
|
until: z.string().optional().describe("到什么时候为止 (不含), 写法同 `since`。往回翻页就把回执里的「更早的」时刻传进来。"),
|
|
@@ -288,46 +283,59 @@ server.registerTool("read_chat", {
|
|
|
288
283
|
const TELL_SCHEMA = {
|
|
289
284
|
name: z.string().describe(ADDRESS_DOC),
|
|
290
285
|
text: z.string().describe("Message to inject. Plain prompt text; slash commands like '/clear' also work."),
|
|
291
|
-
|
|
292
|
-
.enum(["
|
|
286
|
+
priority: z
|
|
287
|
+
.enum(["normal", "urgent", "now"])
|
|
293
288
|
.optional()
|
|
294
|
-
.describe("
|
|
295
|
-
waitSec: z.number().optional().describe("`
|
|
289
|
+
.describe("对方**正忙**时怎么投 (它闲着三者一样, 立刻投)。`normal` (默认) 等它这一轮结束再投 —— 派新活用它, 否则你和别人的两段话会挤进同一个输入框被当成一轮读掉; 等的是你这次调用 (最多 `waitSec`)。`urgent` 先打断它这一轮 (同 stop_wizard 的 interrupt) 再投 —— 只给真紧急的改道, 它手上那一轮作废; 那一轮若是别的 wizard 派的活, 不打断, 降成 normal (回包 `urgentDowngraded`)。`now` 不等不打断, 落进它的输入框并进当前这一轮 —— 插话、补一句、答它的问。带 `re` 续问时默认 `now`。返回里 `wasBusy` / `interrupted` / `waitedMs` 说明实际怎么投的。"),
|
|
290
|
+
waitSec: z.number().optional().describe("`normal` 等它闲下来最多等多少秒 (10-3600, 默认 600)。等不到就返回失败, 不会强行投。"),
|
|
296
291
|
public: z
|
|
297
292
|
.boolean()
|
|
298
293
|
.optional()
|
|
299
|
-
.describe("true = 在公开频道 (你这一轮所在的群) 里说: 群里出 `.你 → .它` 气泡, 它的回复也进群 (同时照样回执给你)。默认 false = 私聊, 只记在 rolepage。需要人知道 / 该当着人讨论才用 true
|
|
300
|
-
job: z.string().optional().describe("这次派活归到某个工单名下 (open_job 给的 id)。**fan-out 一定要带上它**: 回执里会带 `还差几份 / 全部到齐`, 「齐了吗」由守护进程数给你,
|
|
294
|
+
.describe("true = 在公开频道 (你这一轮所在的群) 里说: 群里出 `.你 → .它` 气泡, 它的回复也进群 (同时照样回执给你)。默认 false = 私聊, 只记在 rolepage。需要人知道 / 该当着人讨论才用 true。带 `job` 时不起作用 —— 工单里的往来一律私聊。"),
|
|
295
|
+
job: z.string().optional().describe("这次派活归到某个工单名下 (open_job 给的 id)。**fan-out 一定要带上它**: 回执里会带 `还差几份 / 全部到齐`, 「齐了吗」由守护进程数给你, 不用你自己在上下文里记。带了它就一律私聊, 往来与回执都不进群。"),
|
|
301
296
|
receipt: z
|
|
302
297
|
.boolean()
|
|
303
298
|
.optional()
|
|
304
299
|
.describe("false = 放出去就不管了, 不要回执 (纯通知、或者你根本不关心它说什么)。默认 true。"),
|
|
300
|
+
kind: z
|
|
301
|
+
.enum(["task", "ask", "fyi"])
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("对方该怎么接这句话 (与 `priority` 的何时投、`receipt` 的要不要回执正交): `task` (默认) 一件活, 干完收口 `RESULT:`; `ask` 只答一个问题, 别为它开工; `fyi` 知会, 不用回 —— 自动不要回执、不进工单、不进群。"),
|
|
304
|
+
re: z.string().optional().describe("续问: 回执或回包里的件号 (`t…`)。接着那件活说, 沿用它的工单与频道; 对不上就按新活发出 (`reUnknown`)。"),
|
|
305
|
+
deadline: z.number().optional().describe("最多等它多少秒 (60-43200, 默认 3600); 到点没答完你收到一份 timeout 回执。"),
|
|
306
|
+
chain: z
|
|
307
|
+
.boolean()
|
|
308
|
+
.optional()
|
|
309
|
+
.describe("你此刻在答别的 wizard 派的活时, 这次派出的活默认算那件活的子活: 它回来之前你这一轮的终句不会先交给上游, 它回来那一轮的终句再续回上游。false = 这件活与上游无关 (顺手的测试、旁支), 回执照常回你, 但不挂住你给上游的交代。"),
|
|
305
310
|
};
|
|
306
|
-
const TELL_DESC = "跟另一个 wizard 说话 —— 文本原样落进它的输入框,
|
|
307
|
-
"
|
|
308
|
-
"
|
|
309
|
-
"**默认是私聊**: 不出任何群气泡, 只记在你们双方的 rolepage 里。**`public:true` 则在公开频道说** —— 你这一轮所在的群里出一条 `.你 → .它` 的气泡, 它那一轮的回复也发进这个群。公开与否由你判断: 需要人知道的、或本该当着人讨论的 (关键决策、给人的结论、要人拍板的分歧) 用 public; 过程性的派活、催进度、对齐细节用私聊。\n" +
|
|
310
|
-
"无论哪种都直说: 要什么、给什么、结论是什么, 不用寒暄、不用引用原文。`text` 只写活本身 —— 守护进程会在后面挂一段信封, 告诉对方这是谁发的、私聊还是公开、它的最后一条消息就是回执 (收口成 `RESULT: …`), 这些别自己再写。拒绝对自己发送。";
|
|
311
|
+
const TELL_DESC = "跟另一个 wizard 说话 —— 文本原样落进它的输入框, 它当成新的一轮接手 (派活、答它的问题、叫它继续都走这里)。目标不存在就先 spawn_wizard / clone_wizard。\n" +
|
|
312
|
+
"**说完就返回**: 对方干完那一轮, 它的最后一条消息作为**新的一轮**自动送到你这里 (回执, 带信封说明是谁、哪场对话、工单还差几份); 你正忙时回执排队等你说完。\n" +
|
|
313
|
+
"默认私聊, 只记在双方 rolepage; 守护进程给 `text` 挂信封 (发话人、私聊/公开、`RESULT: …` 收口)。拒绝对自己发送。";
|
|
311
314
|
const tellBody = (a) => ({
|
|
312
315
|
name: a.name,
|
|
313
316
|
text: a.text,
|
|
314
|
-
...(a.
|
|
317
|
+
...(a.kind ? { kind: a.kind } : {}),
|
|
318
|
+
...(a.priority ? { priority: a.priority } : {}),
|
|
315
319
|
...(a.waitSec ? { waitSec: a.waitSec } : {}),
|
|
316
320
|
...(a.job ? { job: a.job } : {}),
|
|
317
321
|
...(a.public ? { public: true } : {}),
|
|
318
322
|
...(a.receipt === false ? { receipt: false } : {}),
|
|
323
|
+
...(a.re ? { re: a.re } : {}),
|
|
324
|
+
...(a.deadline ? { deadline: a.deadline } : {}),
|
|
325
|
+
...(a.chain === false ? { chain: false } : {}),
|
|
319
326
|
});
|
|
320
327
|
server.registerTool("tell_peer", { title: "Say something to another wizard (reply comes back by itself)", description: TELL_DESC, inputSchema: TELL_SCHEMA }, async (a) => unwrap("tell_peer", await daemonPost("/peers/tell", tellBody(a))));
|
|
321
328
|
server.registerTool("send_peer", {
|
|
322
329
|
title: "(deprecated) alias of tell_peer",
|
|
323
|
-
description: "
|
|
324
|
-
|
|
330
|
+
description: "同 tell_peer (旧名, 只为不打断正在跑的旧会话而保留)。新调用一律用 tell_peer。",
|
|
331
|
+
// 参数与 tell_peer 一样收, 但不再背一份参数说明: 别名只为老调用不断, 不该再占一份上下文。
|
|
332
|
+
inputSchema: Object.fromEntries(Object.entries(TELL_SCHEMA).map(([k, v]) => [k, v.describe("")])),
|
|
325
333
|
}, async (a) => unwrap("send_peer", await daemonPost("/peers/tell", tellBody(a))));
|
|
326
334
|
server.registerTool("notify", {
|
|
327
335
|
title: "Post a message into a chat for people to read",
|
|
328
336
|
description: "把一段 markdown 贴进一个企微聊天**给人看**。和 tell_peer 分工明确: tell_peer 是把话塞进另一个 agent 的输入框 (驱动它干活), notify 是说给人听 —— 不会触发任何一轮对话。\n" +
|
|
329
|
-
"`to` 省略 = 你这一轮所在的群 (人从哪个群叫的你就是哪个; 私聊轮则是你的 home 群); 要发到别的群就写聊天名 (
|
|
330
|
-
"什么时候用: 长活跑完了要通知另一个群的人; 一批分身收工后把汇总播给发起那个群; 定时任务 (schedule_task)
|
|
337
|
+
"`to` 省略 = 你这一轮所在的群 (人从哪个群叫的你就是哪个; 私聊轮则是你的 home 群); 要发到别的群就写聊天名 (wizard_roster 里的 home 聊天名), 一次可以写多个。气泡头自动写成 `emoji .你的名字` 并挂上你的 rolepage 链接, 那边的人一眼知道是谁。\n" +
|
|
338
|
+
"什么时候用: 长活跑完了要通知另一个群的人; 一批分身收工后把汇总播给发起那个群; 定时任务 (schedule_task) 到点跑完把结论送到该看的人那里。",
|
|
331
339
|
inputSchema: {
|
|
332
340
|
to: z
|
|
333
341
|
.array(z.string())
|
|
@@ -340,7 +348,7 @@ server.registerTool("wait_peer", {
|
|
|
340
348
|
title: "(deprecated) block until another wizard stops working",
|
|
341
349
|
description: "**一般不需要它了**: `tell_peer` 之后对方的结论会自动作为新的一轮送到你这里 (回执), 你不必守着。保留它是因为有一件事只有它做得到 —— **在同一轮里拿到答案**: 下一步硬依赖对方的结果、把工作拆成两轮会丢上下文时, 用它阻塞等; 以及 `need:1` 那种「谁先完事就先处理谁」。代价是你这个 pane 在等的过程中什么也干不了。\n" +
|
|
342
350
|
"被它取走的那一份**不会再作为回执注入**一遍 (返回里 `delivered: true` = 回执已经抢先进过你的会话, 别重复处理)。\n" +
|
|
343
|
-
"挂起, 直到点名的 wizard 停下来 (它的终端不再显示中断提示), 然后返回它最新的回复。超时先到则返回 `idle: false` 与原因: 它只是还在干, 你可以 peek 一眼再等。很便宜: 守护进程轮询的是 pane, 不烧 token。回的只算**你上次
|
|
351
|
+
"挂起, 直到点名的 wizard 停下来 (它的终端不再显示中断提示), 然后返回它最新的回复。超时先到则返回 `idle: false` 与原因: 它只是还在干, 你可以 peek 一眼再等。很便宜: 守护进程轮询的是 pane, 不烧 token。回的只算**你上次 tell_peer 之后**它说的: 它收口了 `RESULT: …` 就只回 `result` (`omitted` = 正文还有多少字没给, 要读用 peek_peer), 没收口才回 `lastText` (超长掐中间); `stale: true` = 它停下了却没有新回复 (卡在弹窗上, 或那句话没被接住) —— peek_peer 看一眼, 别把它当成答完了。\n" +
|
|
344
352
|
"**真要等一批就用 `names` 一次等一组**, 别一个一个等: 它们本来在同时干活, 串行等的墙钟是所有人之和, 并行等只等最慢的那一个 (不过 fan-out 的常规做法是给 `tell_peer` 带 `job`, 让回执自己回来并替你数「齐了吗」)。等一组时 `results` 按你给的顺序逐个回, 只等一个则直接摊平在顶层。`need` 决定满几个就返回 (默认全部; `need:1` = 谁先完事就先处理谁, 剩下的还在跑, 再调一次接着等)。",
|
|
345
353
|
inputSchema: {
|
|
346
354
|
name: z.string().optional().describe(`${ADDRESS_DOC} 等一组时改用 \`names\`。`),
|
|
@@ -359,9 +367,9 @@ server.registerTool("wait_peer", {
|
|
|
359
367
|
...(need ? { need } : {}),
|
|
360
368
|
...(timeoutSec ? { timeoutSec } : {}),
|
|
361
369
|
})));
|
|
362
|
-
|
|
370
|
+
registerRare("run_agent_graph", {
|
|
363
371
|
title: "Run a loop graph over several tagged agents",
|
|
364
|
-
description: "
|
|
372
|
+
description: "(罕用) 守护进程驱动的循环流水线: `steps` 依次给 `nodes` 里的 wizard 发提示、等它答完、把回复喂给下一步 (`{{last}}` / `{{<tag>}}` / `{{round}}`), 整表走 `rounds` 遍或回复含 `until` 即停。只活在内存, reload 即丢。仅当用户明说「互相迭代到收敛」时用; 平时 tell_peer + 回执就够。",
|
|
365
373
|
inputSchema: {
|
|
366
374
|
nodes: z
|
|
367
375
|
.array(z.object({
|
|
@@ -388,9 +396,9 @@ server.registerTool("run_agent_graph", {
|
|
|
388
396
|
...(until ? { until } : {}),
|
|
389
397
|
...(idleTimeoutSec ? { idleTimeoutSec } : {}),
|
|
390
398
|
})));
|
|
391
|
-
|
|
399
|
+
registerRare("graph_status", {
|
|
392
400
|
title: "Inspect running / finished agent graphs",
|
|
393
|
-
description: "
|
|
401
|
+
description: "(罕用) run_agent_graph 的进度: 每步的轮次、目标、状态与回复。省略 runId = 本聊天全部。",
|
|
394
402
|
inputSchema: {
|
|
395
403
|
runId: z.string().optional().describe("Run id from run_agent_graph. Omit to list all runs for this chat."),
|
|
396
404
|
},
|
|
@@ -398,50 +406,60 @@ server.registerTool("graph_status", {
|
|
|
398
406
|
const qs = runId ? `?runId=${encodeURIComponent(runId)}` : "";
|
|
399
407
|
return unwrap("graph_status", await daemonGet(`/graph/status${qs}`));
|
|
400
408
|
});
|
|
401
|
-
|
|
409
|
+
registerRare("stop_graph", {
|
|
402
410
|
title: "Cancel a running agent graph",
|
|
403
|
-
description: "
|
|
411
|
+
description: "(罕用) 当前步之后停掉 run_agent_graph, 不打断正在生成的 wizard (要打断用 stop_wizard)。",
|
|
404
412
|
inputSchema: { runId: z.string().describe("Run id from run_agent_graph.") },
|
|
405
413
|
}, async ({ runId }) => unwrap("stop_graph", await daemonPost("/graph/stop", { runId })));
|
|
414
|
+
// 交接只有一个动词: 不点名 = 交接你自己 (你写简报), 点名 = 替别人交接 (守护进程让它写)。
|
|
415
|
+
// 两条路由不变 —— 已在跑的 wizard 的 MCP 进程还在调老名字 wizard_handoff_self。
|
|
416
|
+
const handoffSelf = (tool, brief) => daemonPost("/wizard/handoff-self", { brief }).then((r) => unwrap(tool, r));
|
|
406
417
|
server.registerTool("handoff", {
|
|
407
|
-
title: "Hand
|
|
408
|
-
description: "
|
|
418
|
+
title: "Hand work over to a fresh context (yourself or another wizard)",
|
|
419
|
+
description: "把一段工作压成简报、原地换一个全新的会话接着干 (新进程、上下文清零; 名字 / cwd / 模型 / 身份的系统提示照旧), 简报作为新会话的第一条消息贴回去。\n" +
|
|
420
|
+
"**不传 `name` = 交接你自己**: 简报由你写在 `brief` 里, 守护进程等你这一轮说完再重开 (新会话里 MCP 工具也换成最新的)。简报要写到零上下文的自己仅凭它就能接着干: 总目标 / 已完成与关键决策 / 当前状态 / 有序的下一步 / 关键文件与非显然的坑。\n" +
|
|
421
|
+
"**传 `name` = 替那个 wizard 交接**: 守护进程让它自己写简报 (`focus` 指定要特别交代的点), 抓到后给它重开并贴回; 返回被带过去的那份简报。",
|
|
409
422
|
inputSchema: {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
focus: z.string().optional().describe("
|
|
413
|
-
timeoutSec: z.number().optional().describe("
|
|
423
|
+
name: z.string().optional().describe("替谁交接 —— wizard 的名字 ('fix' / '.fix')。省略 = 你自己。"),
|
|
424
|
+
brief: z.string().optional().describe("交接你自己时必填: 简报全文, 自洽、具体、可执行。"),
|
|
425
|
+
focus: z.string().optional().describe("替别人交接时可选: 简报里要特别交代的点。"),
|
|
426
|
+
timeoutSec: z.number().optional().describe("替别人交接时等它写简报的上限秒数 (30-7200, 默认 600)。"),
|
|
427
|
+
pane: z.string().optional().describe("(旧参数) 按 tmux pane id 指定对方, 同 `name`。"),
|
|
414
428
|
},
|
|
415
|
-
}, async ({
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
})
|
|
429
|
+
}, async ({ name, brief, focus, timeoutSec, pane }) => !name && !pane
|
|
430
|
+
? brief?.trim()
|
|
431
|
+
? handoffSelf("handoff", brief)
|
|
432
|
+
: fail("handoff: 交接你自己要写 `brief` (零上下文也能接手的简报); 替别人交接就传 `name`")
|
|
433
|
+
: unwrap("handoff", await daemonPost("/handoff", {
|
|
434
|
+
...(pane ? { pane } : {}),
|
|
435
|
+
...(name ? { name } : {}),
|
|
436
|
+
...(brief ? { brief } : {}),
|
|
437
|
+
...(focus ? { focus } : {}),
|
|
438
|
+
...(timeoutSec ? { timeoutSec } : {}),
|
|
439
|
+
})));
|
|
421
440
|
// 定时任务 —— 到点把一句话说给一个 wizard 听。和人在群里 at 它说同一句话完全等价:
|
|
422
441
|
// pane 死了会被拉起来, 它干完的活照常出现在群里和详情页。这是 claude/codebuddy 自带
|
|
423
442
|
// 定时器给不了的那一半: 它们的循环活在会话里, 会话一死就没了; 这个活在 daemon 里。
|
|
424
443
|
server.registerTool("schedule_task", {
|
|
425
444
|
title: "Schedule a prompt to run in a wizard session, on a recurring or one-off schedule",
|
|
426
|
-
description: "排一个**到点自动执行**的活,
|
|
427
|
-
"
|
|
428
|
-
"
|
|
429
|
-
"
|
|
430
|
-
"
|
|
431
|
-
"**默认新建, 不在任何已有会话里续**: 定时的活是一件独立的事, 塞进一个常驻 wizard 会把两件不相关的事挤进同一个 transcript, 那个 wizard 正忙时还会连触发时刻一起被拖走。只有用户明确说了「在 .foo 里继续 / 让 .foo 每天…」才传 `name` 点名它 —— 这时到点直接投进它那一轮 (它正忙则仍然另起白板执行)。`prompt` 本身就在说「建两个 wizard 去干 xxx」时, 就算给了 `name` 也照新建办 —— 守护进程自己认这句话, 那个名字只决定在谁的聊天/目录下办。要强行在它那一轮里续, 显式传 `fresh:false`。\n" +
|
|
432
|
-
"`when` 用人话原样写, 别自己翻译成 cron: 「每个工作日晚上9:30」「每天 8:00」「每周三下午3点」「每隔两个小时」「每小时」「每 30 分钟」「20 分钟后」「明早 9 点」都认, 还能叠时间窗口 —— 「白天每隔一个小时」「工作时间每半小时」「8点到20点每小时」。解析不出会报错并列出能认的说法 —— 这时把原话回给用户让他重说, 别自己猜一个时间存进去。\n" +
|
|
433
|
-
"存成功后**必须把回显的 `when` 和 `next` 念给用户**确认 (例: 「每个工作日 21:30, 下次 2026-09-21 21:30」)。`prompt` 要写成一句完整的、零上下文也能执行的指令 —— 到点接活的多半是个刚出生的白板 wizard, 它只看得见这句话。",
|
|
445
|
+
description: "排一个**到点自动执行**的活, 归在你名下 (rolepage「日程」可见)。用户说「每个工作日晚上 9:30 跑一下 xxx」「每天早上看看 yyy」「每 2 小时同步 zzz」「明早 9 点整理 www」时调它。守护进程级, 跨 CLI 重启仍在。\n" +
|
|
446
|
+
"`when` 用人话原样写, 别翻译成 cron (「每个工作日晚上9:30」「每隔两个小时」「白天每小时」「20 分钟后」「明早 9 点」都认); 解析不出会报错 —— 把原话回给用户让他重说, 别自己猜。\n" +
|
|
447
|
+
"**默认到点新起一个白板 wizard** (`<你>-task-xxxx`) 执行, 干完自动收掉; 只有用户明说「在 .foo 里继续」才传 `name` 投进那个已有会话 (它到点正忙就等它这一轮结束再投, 等满 30 分钟仍忙才另起白板执行; prompt 本身要求「建 wizard 去干」时也照新建办, 除非显式传 `fresh:false`)。所以 `prompt` 要写成零上下文也能执行的完整指令。\n" +
|
|
448
|
+
"「有新东西才处理」写成任务文件里的 gate (没活这一轮就不放枪), 别让到点起的 wizard 自己看一眼没有就退出 —— 那是每轮空转一个 pane。\n" +
|
|
449
|
+
"存成功后**必须把回显的 `when` 和 `next` 念给用户确认**。任务落成可直接改的 `~/.wezard/tasks/<id>.task.mjs` (返回的 `file`): 组合触发条件、「有活才放枪」的 gate、暂停, 都 Read 那个文件 —— 写法在它的头注释里, 存盘即生效。",
|
|
434
450
|
inputSchema: {
|
|
435
451
|
when: z.string().describe("什么时候跑, 人话原样传: 「每个工作日晚上9:30」「每天早上9点」「白天每隔一个小时」「工作时间每半小时」「每30分钟」「20分钟后」「明早9点」。时间窗口 (白天 / 工作时间 / 8点到20点) 会被解析成筛子, 窗外的那些枪直接吞掉。"),
|
|
436
452
|
prompt: z.string().describe("到点要说给那个 wizard 听的话。写成自洽的完整指令 (要做什么、在哪个目录/文件上、做完怎么汇报), 别依赖当前对话的上下文。里面可以留 `{{名字}}` 占位, 由任务文件里的 gate 填。"),
|
|
437
453
|
name: z.string().optional().describe(`点名在**哪个已有 wizard**里跑 —— 只有用户要求「在它那儿继续」时才传。省略 = 到点新建一个白板 wizard 干完就收 (默认, 也是「定时新建 wizard 干 xxx」要的那个)。传了它还想要新建, 再加 \`fresh:true\`: 那时它只当模板, 新 wizard 继承它的聊天/目录/模型。${ADDRESS_DOC}`),
|
|
454
|
+
quiet: z.boolean().optional().describe("true = 有事才说: 到点不在群里预告, 那一轮私下跑, 它看完没事就只回一行 `QUIET`, 什么也不发; 有事守护进程才把它的终句转进群。巡检 / 盯梢类 (「每小时看一眼 CI, 挂了才告诉我」) 用它。没活连枪都不该放的, 写 gate。"),
|
|
438
455
|
fresh: z.boolean().optional().describe("覆盖默认: true = 每次到点新建白板 wizard 执行 (给了 name 时用来表达「在它的目录下新开一个干」), false = 注入 name 指向的已有会话。默认由 name 推断 (给了 name = false, 没给 = true)。"),
|
|
439
456
|
id: z.string().optional().describe("任务 id, 同时也是文件名 (`~/.wezard/tasks/<id>.task.mjs`)。省略则从 note/prompt 生成。取个好认的短名 —— 之后你要改这条任务, 改的就是那个文件。"),
|
|
440
457
|
note: z.string().optional().describe("给人看的一句话备注, 在 list_tasks 里回显。"),
|
|
441
458
|
},
|
|
442
|
-
}, async ({ when, prompt, name, note, fresh, id }) => unwrap("schedule_task", await daemonPost("/tasks/schedule", {
|
|
459
|
+
}, async ({ when, prompt, name, note, fresh, quiet, id }) => unwrap("schedule_task", await daemonPost("/tasks/schedule", {
|
|
443
460
|
when, prompt, name: name ?? "", note: note ?? "", id: id ?? "",
|
|
444
461
|
...(fresh === undefined ? {} : { fresh }),
|
|
462
|
+
...(quiet ? { quiet: true } : {}),
|
|
445
463
|
})));
|
|
446
464
|
server.registerTool("list_tasks", {
|
|
447
465
|
title: "List scheduled tasks on this host",
|
|
@@ -488,12 +506,12 @@ server.registerTool("config_set", {
|
|
|
488
506
|
// 分身还剩几个、别人是谁。
|
|
489
507
|
server.registerTool("wizard_whoami", {
|
|
490
508
|
title: "Who am I",
|
|
491
|
-
description: "你自己是谁: 全局唯一的名字 (即地址, 别人用它找你)、home 聊天、工作区、职责、记忆、家谱 (谁生的你、你生了谁), 以及此刻的 contextTokens 与 handoffSuggested。用户问「你是谁」「你叫什么」「你在哪个目录」「你有几个分身」时先调它; 要做任何编排之前也先调它 —— 你得知道自己的工作区在哪、手里已经有哪些分身。handoffSuggested=true 表示上下文该交接了 (见
|
|
509
|
+
description: "你自己是谁: 全局唯一的名字 (即地址, 别人用它找你)、home 聊天、工作区、职责、记忆、家谱 (谁生的你、你生了谁), 以及此刻的 contextTokens 与 handoffSuggested。用户问「你是谁」「你叫什么」「你在哪个目录」「你有几个分身」时先调它; 要做任何编排之前也先调它 —— 你得知道自己的工作区在哪、手里已经有哪些分身。handoffSuggested=true 表示上下文该交接了 (见 handoff)。",
|
|
492
510
|
inputSchema: {},
|
|
493
511
|
}, async () => unwrap("wizard_whoami", await daemonPost("/wizard/whoami", {})));
|
|
494
512
|
server.registerTool("wizard_identity", {
|
|
495
513
|
title: "Name yourself / declare your job",
|
|
496
|
-
description: "改名字、写职责。名字就是别人喊你的那个词 (`.name`), **全机唯一**, 与聊天名无关 (给聊天起名用 name_chat): 撞名时自动加 `-N` 后缀, 返回里的 `name` 是落定的那个, `renamed` 会告诉你发生了改写。职责是一句话的「我是干什么的」—— 别的 wizard 在名册里读到它, 据此决定该不该找你。用户说「你以后叫 X」「你负责 X
|
|
514
|
+
description: "改名字、写职责。名字就是别人喊你的那个词 (`.name`), **全机唯一**, 与聊天名无关 (给聊天起名用 name_chat): 撞名时自动加 `-N` 后缀, 返回里的 `name` 是落定的那个, `renamed` 会告诉你发生了改写。职责是一句话的「我是干什么的」—— 别的 wizard 在名册里读到它, 据此决定该不该找你。用户说「你以后叫 X」「你负责 X」时调它;只传要改的那个字段。",
|
|
497
515
|
inputSchema: {
|
|
498
516
|
name: z.string().optional().describe("新名字, 1-32 位字母/数字/`_`/`-`, 全机唯一。不改就别传。"),
|
|
499
517
|
description: z.string().optional().describe("一句话职责, 例如 '盯 wezard 主仓的重构与发版'。不改就别传。"),
|
|
@@ -504,7 +522,7 @@ server.registerTool("wizard_identity", {
|
|
|
504
522
|
})));
|
|
505
523
|
server.registerTool("wizard_roster", {
|
|
506
524
|
title: "Every wizard and clone",
|
|
507
|
-
description: "这个世界上所有的 wizard 与 clone, 一个一行: 名字 (全局唯一, 即地址)、忙闲 (忙 = 正在生成 / 闲 = 活着没在跑 / 冷 = 没有 pane, 发消息会唤醒)、**home 聊天** (群 / 单聊)、**工作区**、模型 (还没应答过的没有这一项)、多久没动、家谱 (父 / 分身); 写了职责的多一行职责, 活着的多一行「最近」—— 它最近一个来回 (`▸` 问 `◂` 答, 各截到 80 字, 截了带 `…`)。跨聊天的也在里面。这是你感知同伴的唯一入口 —— 用户说「还有谁在跑」「这个群里有谁」「谁在弄那个项目」「让懂 X 的那个来看看」时先调它, 拿到目标的名字再
|
|
525
|
+
description: "这个世界上所有的 wizard 与 clone, 一个一行: 名字 (全局唯一, 即地址)、忙闲 (忙 = 正在生成 / 闲 = 活着没在跑 / 冷 = 没有 pane, 发消息会唤醒)、**home 聊天** (群 / 单聊)、**工作区**、模型 (还没应答过的没有这一项)、多久没动、家谱 (父 / 分身); 写了职责的多一行职责, 活着的多一行「最近」—— 它最近一个来回 (`▸` 问 `◂` 答, 各截到 80 字, 截了带 `…`)。跨聊天的也在里面。这是你感知同伴的唯一入口 —— 用户说「还有谁在跑」「这个群里有谁」「谁在弄那个项目」「让懂 X 的那个来看看」时先调它, 拿到目标的名字再 tell_peer / peek_peer; 要往某个群里对人说话则把那个群名交给 notify。\n" +
|
|
508
526
|
"**这是一张索引, 不是一份名单**: 整台机器上可能有几百个会话, 所以默认只回最相关的一页 (自己 → 活着的 → 最近动过的), 并告诉你 `total` / `matched` 有多少。找人就带上条件: `query` 匹配名字/职责/地址, `cwd` 匹配工作区路径 (「谁在这个目录里干活」), `chat` 限定某个聊天, `alive:true` 只看还活着的。「同群有谁」就是 `chat` 写你自己的群名。别不带条件硬拉全表。",
|
|
509
527
|
inputSchema: {
|
|
510
528
|
query: z.string().optional().describe("在名字 / 职责 / target 里做子串匹配 (不分大小写)。「让懂 X 的那个来看看」就把 X 写在这里。"),
|
|
@@ -522,7 +540,7 @@ server.registerTool("wizard_roster", {
|
|
|
522
540
|
})));
|
|
523
541
|
server.registerTool("route_candidates", {
|
|
524
542
|
title: "Who could take this task",
|
|
525
|
-
description: "一件活要不要转给**已有**的 wizard, 而上下文里还看不出该给谁时调它 (刚派过同一摊活、职责正对口的, 直接判断就行)。守护进程替你把能算的都算好: 每个已有 wizard 的职责 / 最近的话与这件活重叠了哪些词, 它当前上下文里 (上次压缩之后) 读过哪些文件、其中哪些被这件活点到, 它在不在同一个工作区、忙闲、上下文多大 (`ctx`)。没有任何交集的不列, 列出来的按证据强弱排 (文件命中 > 职责 > 最近的话), 末尾永远附「新 spawn
|
|
543
|
+
description: "一件活要不要转给**已有**的 wizard, 而上下文里还看不出该给谁时调它 (刚派过同一摊活、职责正对口的, 直接判断就行)。守护进程替你把能算的都算好: 每个已有 wizard 的职责 / 最近的话与这件活重叠了哪些词, 它当前上下文里 (上次压缩之后) 读过哪些文件、其中哪些被这件活点到, 它在不在同一个工作区、忙闲、上下文多大 (`ctx`), 缓存冷热与唤醒要重写多少缓存 (对比白板 spawn 的倍数)。没有任何交集的不列, 列出来的按证据强弱排 (文件命中 > 职责 > 最近的话), 末尾永远附「新 spawn」; 证据弱又贵的标 ⚠「不划算」。不出分数、不替你拍板。",
|
|
526
544
|
inputSchema: {
|
|
527
545
|
task: z.string().describe("这件活, 原话或一句概括都行 —— 带上提到的文件名 / 模块名 / 函数名, 文件命中是最强的证据。"),
|
|
528
546
|
cwd: z.string().optional().describe("这件活落在哪个工作区。省略 = 你自己的工作区。"),
|
|
@@ -532,17 +550,18 @@ server.registerTool("route_candidates", {
|
|
|
532
550
|
// 换模型和 spawn 时挑模型是同一条路: 守护进程在目标 pane 里打开 `/model` 列表,
|
|
533
551
|
// 读出这台 CLI 此刻真有的每一项, 挑最接近的, 方向键移过去选中。
|
|
534
552
|
server.registerTool("set_model", {
|
|
535
|
-
title: "Switch a wizard's model",
|
|
536
|
-
description: "给一个**已经在跑**的 wizard 换模型 —— 默认换你自己, `name` 点名就换那个 wizard。口语化写要什么 ('opus' / 'haiku' / 'sonnet 5' / '最新的 opus' / '默认'), 守护进程在它的 pane 里打开 `/model` 列表, 读出这台 CLI 此刻真有的每一项, 挑最接近的一项选中; 返回里的 `model` 是真正落地的那一项, `catalog` 是列表里的全部 —— 对不上时 (`ok:false`) 照着 `catalog` 重说一遍。默认只对这一个会话生效 (`scope:'session'`), 不改新开会话的默认模型; `scope:'default'` 则同时把它设成这台 CLI 此后**每个新会话**的默认模型 —— 那是全机的设置, 只在人明说「以后默认都用 X」时才用。返回里的 `scope` 是实际落在哪一档。它之后被重启也会回到这个模型上。正在干活的也能换 (从下一次请求起生效), 但换模型会让它的对话缓存整份重读一遍 —— 别来回切。用户说「换成 opus」「这个用 haiku 跑就行」「把 .fix 切到 sonnet」时调它; 你自己判断手上的活配不上/撑不起当前模型时也可以主动换。",
|
|
553
|
+
title: "Switch a wizard's model / effort",
|
|
554
|
+
description: "给一个**已经在跑**的 wizard 换模型 (和/或推理档位 `effort`) —— 默认换你自己, `name` 点名就换那个 wizard。口语化写要什么 ('opus' / 'haiku' / 'sonnet 5' / '最新的 opus' / '默认'), 守护进程在它的 pane 里打开 `/model` 列表, 读出这台 CLI 此刻真有的每一项, 挑最接近的一项选中; 返回里的 `model` 是真正落地的那一项, `catalog` 是列表里的全部 —— 对不上时 (`ok:false`) 照着 `catalog` 重说一遍。默认只对这一个会话生效 (`scope:'session'`), 不改新开会话的默认模型; `scope:'default'` 则同时把它设成这台 CLI 此后**每个新会话**的默认模型 —— 那是全机的设置, 只在人明说「以后默认都用 X」时才用。返回里的 `scope` 是实际落在哪一档。它之后被重启也会回到这个模型上。正在干活的也能换 (从下一次请求起生效), 但换模型会让它的对话缓存整份重读一遍 —— 别来回切。用户说「换成 opus」「这个用 haiku 跑就行」「把 .fix 切到 sonnet」时调它; 你自己判断手上的活配不上/撑不起当前模型时也可以主动换。",
|
|
537
555
|
inputSchema: {
|
|
538
|
-
model: z.string().describe("要换到哪个模型, 口语化写: 'opus' / 'haiku' / 'sonnet 5' / 'opus 4.7' / '最新的 fable' / '默认'。只写家族名 =
|
|
556
|
+
model: z.string().optional().describe("要换到哪个模型, 口语化写: 'opus' / 'haiku' / 'sonnet 5' / 'opus 4.7' / '最新的 fable' / '默认'。只写家族名 = 那个家族最新的一个。只换 effort 时省略。"),
|
|
557
|
+
effort: z.enum(EFFORTS).optional().describe("顺带 (或只) 换推理档位 —— 只对这个会话生效, 不改全机默认; 之后它重启也沿用。返回里的 `effort` 是落地的那一档。"),
|
|
539
558
|
name: z.string().optional().describe("换谁的 —— wizard 的名字 ('fix' / '.fix')。省略 = 换你自己。"),
|
|
540
559
|
scope: z
|
|
541
560
|
.enum(["session", "default"])
|
|
542
561
|
.optional()
|
|
543
562
|
.describe("'session' (默认) = 只换这一个会话; 'default' = 换这个会话, 并设为此后所有新会话的默认模型 (改的是 CLI 的全局设置, 不指定模型的新 wizard 都会跟着变)。"),
|
|
544
563
|
},
|
|
545
|
-
}, async ({ model, name, scope }) => unwrap("set_model", await daemonPost("/wizard/model", { model, ...(name ? { name } : {}), ...(scope ? { scope } : {}) })));
|
|
564
|
+
}, async ({ model, effort, name, scope }) => unwrap("set_model", await daemonPost("/wizard/model", { ...(model ? { model } : {}), ...(effort ? { effort } : {}), ...(name ? { name } : {}), ...(scope ? { scope } : {}) })));
|
|
546
565
|
// 生孩子与分身是两个操作, 不是一个开关的两档: 一个白纸起步、可以去别的目录;
|
|
547
566
|
// 一个 fork 调用方此刻的上下文、必须留在原地。拆成两个工具, 模型选的是动词,
|
|
548
567
|
// 而不是在一个布尔上猜。两者落到同一条路由 (`inherit` 由工具定死)。
|
|
@@ -550,13 +569,15 @@ const offspringShape = {
|
|
|
550
569
|
description: z.string().describe("它负责什么, 一句话。它会写进它的系统提示, 也会出现在名册里让别人看到。"),
|
|
551
570
|
name: z.string().optional().describe("它的名字 (如 'docs'、'fix', 带不带 '.' 都行)。全机唯一。省略则按 description 首词生成。"),
|
|
552
571
|
task: z.string().optional().describe("就位后立刻派下去的第一件活 (私聊)。省略则它就位待命。"),
|
|
553
|
-
chat: z.string().optional().describe("把它生在另一个聊天里 (
|
|
572
|
+
chat: z.string().optional().describe("把它生在另一个聊天里 (wizard_roster 里的 home 聊天名)。省略 = 你自己的聊天, 这是绝大多数情况。"),
|
|
554
573
|
cli: z.enum(["claude", "claude-internal", "codebuddy"]).optional().describe("用哪个 CLI。省略则继承。"),
|
|
555
|
-
model: z.string().optional().describe(
|
|
574
|
+
model: z.string().optional().describe(MODEL_DOC),
|
|
575
|
+
effort: z.enum(EFFORTS).optional().describe(EFFORT_DOC),
|
|
556
576
|
job: z
|
|
557
577
|
.string()
|
|
558
578
|
.optional()
|
|
559
|
-
.describe("归到某个工单名下 (open_job 给的 id)
|
|
579
|
+
.describe("归到某个工单名下 (open_job 给的 id)。过程在各自的 rolepage 与工单页, 不进群; close_job 会把它们整批回收掉。"),
|
|
580
|
+
chain: z.boolean().optional().describe("同 tell_peer 的 `chain`: 带了 task 而这件活与你此刻在答的上游那件无关 (旁支、测试) 时给 false, 它的回执就不挂住你给上游的交代。"),
|
|
560
581
|
keepalive: z
|
|
561
582
|
.boolean()
|
|
562
583
|
.optional()
|
|
@@ -568,28 +589,32 @@ const bear = (tool, inherit) => async (a) => unwrap(tool, await daemonPost("/wiz
|
|
|
568
589
|
...(a.name ? { name: a.name } : {}),
|
|
569
590
|
...(a.task ? { task: a.task } : {}),
|
|
570
591
|
...(a.from ? { from: a.from } : {}),
|
|
592
|
+
...(a.detached ? { detached: true } : {}),
|
|
571
593
|
...(a.job ? { job: a.job } : {}),
|
|
572
594
|
...(a.cwd ? { cwd: a.cwd } : {}),
|
|
573
595
|
...(a.chat ? { chat: a.chat } : {}),
|
|
574
596
|
...(a.cli ? { cli: a.cli } : {}),
|
|
575
597
|
...(a.model ? { model: a.model } : {}),
|
|
598
|
+
...(a.effort ? { effort: a.effort } : {}),
|
|
576
599
|
...(a.keepalive !== undefined ? { keepalive: a.keepalive } : {}),
|
|
600
|
+
...(a.chain === false ? { chain: false } : {}),
|
|
577
601
|
}));
|
|
578
|
-
const OFFSPRING_TAIL = "名字全局唯一: 撞上的 wizard 静默超过一天就直接顶掉它、名字归新的; 撞上一个还在的 wizard 返回 409
|
|
602
|
+
const OFFSPRING_TAIL = "名字全局唯一: 撞上的 wizard 静默超过一天就直接顶掉它、名字归新的; 撞上一个还在的 wizard 返回 409, 附 `alive` / `busy` / `idleForMs` —— 据此分辨它是真在干活 (换个名字) 还是冷绑定 (先 stop_wizard 再同名重生); 返回里的 `name` 是落定的名字。 它自己也能再生分身, 层级不限; 名下同时活着的有上限。";
|
|
579
603
|
server.registerTool("spawn_wizard", {
|
|
580
604
|
title: "Spawn a blank child wizard",
|
|
581
605
|
description: "**从白板生**一个子 wizard —— 新的 tmux pane、自己的全局名字 (`.name`)、自己的职责, 归你管 (家谱里挂在你名下, 工单收工时可整批回收)。它**不继承你的任何上下文**, 只拿到身份。适合干一件与你手头无关的事, 或者要去别的目录 (`cwd`) / 别的聊天里干活。要一个开局就带着你读过的材料的, 用 clone_wizard。\n" +
|
|
582
|
-
"带上 `task` 可以在它就位的同时把第一件活私聊派下去, 省掉一次
|
|
606
|
+
"带上 `task` 可以在它就位的同时把第一件活私聊派下去, 省掉一次 tell_peer; 要它的回复进群, 就别带 task, 就位后用 `tell_peer({public:true})` 转给它。" +
|
|
583
607
|
OFFSPRING_TAIL,
|
|
584
608
|
inputSchema: {
|
|
585
609
|
...offspringShape,
|
|
586
610
|
cwd: z.string().optional().describe("它的工作区绝对路径。省略 = 跟你同一个目录。"),
|
|
611
|
+
detached: z.boolean().optional().describe("true = 独立长住: 不挂在你名下 (不占分身名额、不随工单回收、不带 job), 等价于人在群里 `/new .name` —— 要一个往后一直在的新 wizard 时用; 干完一件活就收的别用。"),
|
|
587
612
|
},
|
|
588
613
|
}, bear("spawn_wizard", false));
|
|
589
614
|
server.registerTool("clone_wizard", {
|
|
590
615
|
title: "Clone a wizard, context and all",
|
|
591
616
|
description: "**克隆**一个 wizard 出分身 —— fork 它此刻的上下文: 分身开局就拥有它已经读过的一切 (规范、目录结构、刚啃完的那份文档), 不必重读; 被克隆的那个毫发无损。默认克隆你自己; `from` 点名就克隆别的 wizard (比如一个已经把某个模块啃透的同伴) —— 分身仍归你管 (占你的名额、随你的工单回收), 只是上下文来自它。分身留在被克隆者的工作区。\n" +
|
|
592
|
-
"
|
|
617
|
+
"要白纸一张、或要去别的目录, 用 spawn_wizard。被克隆者正在干活时, 分身拿到的是它此刻为止的上下文 —— 想要它读完再分: 是你派的活就等它的回执, 否则先 `wait_peer` 等它停下 (或看 wizard_roster 的忙闲)。\n" +
|
|
593
618
|
"带上 `task` 可以在它就位的同时把第一件活私聊派下去 (分叉本来就由第一句话触发, 省一次往返)。" +
|
|
594
619
|
OFFSPRING_TAIL,
|
|
595
620
|
inputSchema: {
|
|
@@ -599,26 +624,28 @@ server.registerTool("clone_wizard", {
|
|
|
599
624
|
}, bear("clone_wizard", true));
|
|
600
625
|
// ── Job: 一次 fan-out 的工单 ────────────────────────────────────────────────
|
|
601
626
|
// 工单不是第二个编排器: 控制流始终在你自己的上下文里 (你自己 spawn、自己 wait、
|
|
602
|
-
// 自己汇总)。守护进程只替你记一本账 ——
|
|
603
|
-
//
|
|
627
|
+
// 自己汇总)。守护进程只替你记一本账 —— 谁属于这个活、谁是临时生的、各自落成了什么、
|
|
628
|
+
// 收工时该回收谁。工单整个不进群。
|
|
604
629
|
server.registerTool("open_job", {
|
|
605
630
|
title: "Open a job for a fan-out",
|
|
606
|
-
description: "开一个**工单**: 你接下来要同时派出两个以上的分身干同一件事时, 先开它。返回一个 id, 把这个 id 传给 spawn_wizard / clone_wizard /
|
|
607
|
-
"开了工单之后有三件事不一样: ①
|
|
608
|
-
"派活的文本只写活本身: 每个分身收到的那一轮都带着信封, 已经要求它把结论收口成 `RESULT: …` (交付物写进文件就回传路径); wait_peer 摘到就只回这一行, 你汇总时不必从八百字里找结论。\n" +
|
|
631
|
+
description: "开一个**工单**: 你接下来要同时派出两个以上的分身干同一件事时, 先开它。返回一个 id, 把这个 id 传给 spawn_wizard / clone_wizard / tell_peer 的 `job` 参数, 它们就归到这个工单名下。\n" +
|
|
632
|
+
"开了工单之后有三件事不一样: ① 带这个 id 的派活一律私聊, 开工、派活、回执、收工都**不进群** —— 结构在 rolepage 的工单页里, 人在群里只看到你自己那一轮的最终回复。② close_job 会把为这个工单生出来的分身**整批回收**, 不必一个个 stop_wizard —— 忘记回收是常态, 每个分身都占着一个 pane 和一份上下文。③ list_jobs 能看到还开着哪些活。④ 默认验收: 成员交上来的答复没有非空的 `RESULT:` (按 `accept`), 守护进程以你的名义同件号打回一次, 那一份不投给你、不计数, 也不占 `maxTurns`; 再交上来的照收。\n" +
|
|
609
633
|
"只派一个分身、或者只是推某个同伴一把, 不用开工单。",
|
|
610
634
|
inputSchema: {
|
|
611
|
-
title: z.string().describe("一句话说清这个工单要干成什么 ——
|
|
612
|
-
plan: z.string().optional().describe("
|
|
635
|
+
title: z.string().describe("一句话说清这个工单要干成什么 —— 工单页与收工留档的标题。"),
|
|
636
|
+
plan: z.string().optional().describe("计划 (打算分几路、各干什么), 记进工单账本给工单页读。"),
|
|
637
|
+
expect: z.number().optional().describe("这批一共要几份回执。分身是陆续派的, 给了它「全部到齐」就不会在派齐之前提前报。"),
|
|
638
|
+
maxTurns: z.number().optional().describe("派活次数的预算: 带这张工单的每次 tell_peer (含 re 续问、答 NEED) 与带 task 的 spawn/clone 各记一次, 用完再派会被拒 —— 防反复追问兜圈。守护进程按 `accept` 代你打回的那一次不算。省略 = 不限。"),
|
|
639
|
+
accept: z.enum(["result", "artifact", "none"]).optional().describe("成员交差的验收: `result` (默认) 要有非空 RESULT; `artifact` 还要列出 ARTIFACT 交付物; `none` 不验。不合格的自动打回一次。"),
|
|
613
640
|
},
|
|
614
|
-
}, async ({ title, plan }) => unwrap("open_job", await daemonPost("/jobs/open", { title, ...(plan ? { plan } : {}) })));
|
|
641
|
+
}, async ({ title, plan, expect, maxTurns, accept }) => unwrap("open_job", await daemonPost("/jobs/open", { title, ...(plan ? { plan } : {}), ...(expect ? { expect } : {}), ...(maxTurns ? { maxTurns } : {}), ...(accept ? { accept } : {}) })));
|
|
615
642
|
server.registerTool("close_job", {
|
|
616
643
|
title: "Close a job and recycle its clones",
|
|
617
|
-
description: "收工:
|
|
618
|
-
"
|
|
644
|
+
description: "收工: 把汇总结论连同成员与各自那段活**留档** (情景记忆, 不发群), 并**把为这个工单生出来的分身整批回收**。被拉来帮忙的长期 wizard 不在回收之列, 你自己也不会被收。\n" +
|
|
645
|
+
"汇总完就调它。给人的结论写在你自己这一轮的最终回复里 —— 人在群里问的, 那条回复就进群。`stop:false` 只结账不回收 (那些分身后面还有用)。",
|
|
619
646
|
inputSchema: {
|
|
620
647
|
job: z.string().describe("open_job 返回的工单 id。"),
|
|
621
|
-
summary: z.string().optional().describe("汇总结论,
|
|
648
|
+
summary: z.string().optional().describe("汇总结论, 留档 (不发群): 做成了什么、有什么没做成。"),
|
|
622
649
|
stop: z.boolean().optional().describe("是否回收为这个工单生出来的分身。默认 true。"),
|
|
623
650
|
},
|
|
624
651
|
}, async ({ job, summary, stop }) => unwrap("close_job", await daemonPost("/jobs/close", { job, ...(summary ? { summary } : {}), ...(stop === false ? { stop: false } : {}) })));
|
|
@@ -629,13 +656,15 @@ server.registerTool("list_jobs", {
|
|
|
629
656
|
}, async () => unwrap("list_jobs", await daemonPost("/jobs/list", {})));
|
|
630
657
|
server.registerTool("stop_wizard", {
|
|
631
658
|
title: "Interrupt or end another wizard",
|
|
632
|
-
description: "收掉一个 wizard/分身。mode='interrupt' 只打断它当前这一轮 (等价于群里的 /stop, 它还活着, 可以继续派活); mode='end' 结束它并回收 tmux pane (等价于 /kill, 之后再找它会重新长出一个空白会话)
|
|
659
|
+
description: "收掉一个 wizard/分身。mode='interrupt' 只打断它当前这一轮 (等价于群里的 /stop, 它还活着, 可以继续派活); mode='end' 结束它并回收 tmux pane (等价于 /kill, 之后再找它会重新长出一个空白会话)。加 forget=true 连它的身份记录一起抹掉 (名字、职责、记忆), 只在它彻底不会再回来时用。用户说「让 .x 停下」「把那些分身收了」时调它。终结自己也是合法的 (分身干完活自我了结), 只是这次调用不会返回 (这类生命周期事件不进群, 记录在 rolepage)。",
|
|
633
660
|
inputSchema: {
|
|
634
661
|
name: z.string().describe(ADDRESS_DOC),
|
|
635
662
|
mode: z.enum(["end", "interrupt"]).optional().describe("'end' 结束并回收 pane (默认); 'interrupt' 只打断当前这一轮。"),
|
|
636
663
|
forget: z.boolean().optional().describe("仅对 end 有效: 连身份记录 (名字/职责/记忆) 一起删除。默认 false —— 身份留着, 下次它回来还是它。"),
|
|
664
|
+
turn: z.string().optional().describe("仅对 interrupt: 只撤回**你自己派的**这一件 (回执 / tell_peer 回包里的件号)。它正在做的就是这件才按 Esc; 还排着或它在做别人的活, 就只把这件记成取消, 不碰它手上那一轮。"),
|
|
665
|
+
force: z.boolean().optional().describe("仅对 interrupt: 它此刻在做别的 wizard 派的活时, 不带 turn 的打断默认被拒 (会误伤那件); 确要打断加 true。"),
|
|
637
666
|
},
|
|
638
|
-
}, async ({ name, mode, forget }) => unwrap("stop_wizard", await daemonPost("/wizard/stop", { name, ...(mode ? { mode } : {}), ...(forget ? { forget } : {}) })));
|
|
667
|
+
}, async ({ name, mode, forget, turn, force }) => unwrap("stop_wizard", await daemonPost("/wizard/stop", { name, ...(mode ? { mode } : {}), ...(forget ? { forget } : {}), ...(turn ? { turn } : {}), ...(force ? { force } : {}) })));
|
|
639
668
|
server.registerTool("wizard_remember", {
|
|
640
669
|
title: "Write something into your long-term memory",
|
|
641
670
|
description: "写一条长期记忆。它不在对话里 —— 每次 wizard (重)开会话时重新压进系统提示, 所以能跨 /clear、跨交接、跨重启活下来。三种作用域: `self` (默认) 只属于你; `chat` 是**本群共享**的记忆 (这个群里人的习惯、约定), 在这个群出生的每个 wizard 都会读到; `workspace` 是**本工作区共享**的记忆 (这个仓库的硬约束、踩过的坑、'发版前必须更新 CHANGELOG' 这种规矩), 在这个目录下干活的每个 wizard 都会读到。后两者存成 markdown (`~/.wezard/memory/…`), 人也会直接改; 你写的只是一条**提议**, 由定时的记忆整理者去重、改写、合并 (半小时内), 所以不必先读全文查重, 也别为改一个字反复提交。一条一句话, 越具体越有用; 选对作用域 —— 属于群/仓库的别只记在自己身上。传 forget (子串匹配) 删掉 / 提议删掉过时的那条。别拿它存这次任务的临时状态 —— 那种东西属于交接简报。",
|
|
@@ -650,12 +679,10 @@ server.registerTool("wizard_remember", {
|
|
|
650
679
|
...(forget ? { forget } : {}),
|
|
651
680
|
})));
|
|
652
681
|
server.registerTool("wizard_handoff_self", {
|
|
653
|
-
title: "
|
|
654
|
-
description: "
|
|
655
|
-
inputSchema: {
|
|
656
|
-
|
|
657
|
-
},
|
|
658
|
-
}, async ({ brief }) => unwrap("wizard_handoff_self", await daemonPost("/wizard/handoff-self", { brief })));
|
|
682
|
+
title: "(deprecated) alias of handoff for yourself",
|
|
683
|
+
description: "同 `handoff({brief})` 不传 name —— 交接你自己 (旧名, 只为不打断正在跑的旧会话而保留)。新调用一律用 handoff。",
|
|
684
|
+
inputSchema: { brief: z.string().describe("交接简报全文。") },
|
|
685
|
+
}, async ({ brief }) => handoffSelf("wizard_handoff_self", brief));
|
|
659
686
|
const transport = new StdioServerTransport();
|
|
660
687
|
await server.connect(transport);
|
|
661
688
|
//# sourceMappingURL=server.js.map
|