pi-proactive 0.2.0-beta.7 → 0.2.0-beta.8

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/CHANGELOG.md CHANGED
@@ -8,7 +8,24 @@
8
8
  ---
9
9
 
10
10
 
11
- ### 0.2.0-beta.7 — 补齐交付三件套(HTML 使用手册 + BREAKING CHANGES)
11
+ ### 0.2.0-beta.8 — App 模式(不想开终端就能用)
12
+
13
+ **用户可见新增**
14
+ - `app/app.mjs`:App 最小可用外壳 —— 一条命令 `start` 起 pi 并注入 pi-proactive,附通知与只读监督面
15
+ · 子命令:`start` / `stop` / `status` / `describe` / `guide` / `ask "<…>"`
16
+ · **状态等待代替固定等待**:等扩展真的注册了才叫就绪;超时明确报【未就绪】+ 诊断(不 sleep 后假装成功)
17
+ · 默认用【专属短路径 agent 根】(端点 <100B);`--shared` 才与宿主共用 `~/.pi/agent`
18
+ · `stop` 后七处零残留(进程 / socket / 端点 / agent 根 / 锁 / 日志 / launchd 单元)
19
+ - `app/evidence.mjs`:一条命令打出 AC1–AC6 + D1–D4 的实际取证
20
+ - 使用手册 `manual/USAGE.html` 新增 App 一节
21
+
22
+ **修复**
23
+ - 凭据谎报:pi 在新建 agent 根会写空 `{}` auth.json —— 此前被当作"有凭据"而谎报"能做事";现按内容判定(只有真有 provider 条目才算)
24
+
25
+ **仍标【未验证】**
26
+ - 模型真回话(隔离根无凭据,零真实模型调用)· 通知中心肉眼可见 · "助理第一次主动联系我" · 信箱路径未取证 · Windows 分支未实现
27
+
28
+ 0.2.0-beta.7 — 补齐交付三件套(HTML 使用手册 + BREAKING CHANGES)
12
29
 
13
30
  **用户可见新增**
14
31
  - `docs/USAGE.html`:随包发布的 HTML 使用手册(安装 / 三条入口 / 常驻助理 / 状态一屏三块 / 按成因分性的故障排查 / 开发者向枚举与对拍)
package/README.md CHANGED
@@ -406,25 +406,25 @@ Usage: `/proactive <子命令>`。下表**逐条对应实现里实际注册的
406
406
  <!-- BEGIN:GEN commands -->
407
407
  | 命令 | 参数形态 | 做什么 | 状态 |
408
408
  | --- | --- | --- | --- |
409
- | `/proactive status` | `无` | 总览:开关、空闲计时、待触发任务、对端在线、上次主动、本小时用量。 | 已实现 · `pi-proactive/src/commands.ts:632` |
410
- | `/proactive on` | `无` | 打开全部「代理自发」行为(空闲触发、模型自调度、跨会话自发请求)。 | 已实现 · `pi-proactive/src/commands.ts:668` |
411
- | `/proactive off` | `无` | 关闭全部「代理自发」行为;已排期任务保留但不再触发。 | 已实现 · `pi-proactive/src/commands.ts:669` |
412
- | `/proactive test` | `无` | 立即模拟一次触发(走完整「护栏 → 投递」链路),用于验证主动能力是否真的生效。 | 已实现 · `pi-proactive/src/commands.ts:758` |
413
- | `/proactive tasks` | `无` | 列出所有待触发任务(编号 / 类型 / 下次到期 / 归属 / 触发次数)。 | 已实现 · `pi-proactive/src/commands.ts:761` |
414
- | `/proactive cancel` | `<id>` | 取消一个任务。 | 已实现 · `pi-proactive/src/commands.ts:787` |
415
- | `/proactive idle` | `on\|off` | 打开 / 关闭空闲触发并设定阈值(on 可带时长参数,省略 = 用上次设定)。 | 已实现 · `pi-proactive/src/commands.ts:827` |
416
- | `/proactive peers` | `[--all\|--idle\|--name <子串>\|--detail <agentId>]` | 列出已注册的对端与在线状态(名字 / 忙闲 / 最后心跳);可加 --all / --idle / --name / --detail。 | 已实现 · `pi-proactive/src/commands.ts:689` |
417
- | `/proactive spawn` | `[名字]` | 创建一个对端会话(子进程 pi --mode rpc);本会话退出时一并终止。 | 已实现 · `pi-proactive/src/commands.ts:735` |
418
- | `/proactive in` | `<时长> <内容>` | 排一个延迟任务(默认只属于当前会话)。 | 已实现 · `pi-proactive/src/commands.ts:805` |
419
- | `/proactive at` | `<HH:MM> <内容>` | 排一个绝对时间任务。 | 已实现 · `pi-proactive/src/commands.ts:806` |
420
- | `/proactive every` | `<时长> <内容>` | 排一个周期任务。 | 已实现 · `pi-proactive/src/commands.ts:807` |
421
- | `/proactive help` | `无` | 打印命令速查表(命令表 / 配置键 / 最常见故障 / 详细文档路径)。 | 已实现 · `pi-proactive/src/commands.ts:842` |
422
- | `/proactive history` | `[peer\|me] [条数]` | 查看最近的主动行为记录(时间 / 类型 / 目标 / 结果 / 被护栏拦下的原因)。 | 已实现 · `pi-proactive/src/commands.ts:765` |
423
- | `/proactive doctor` | `[--json]` | 只读体检:逐项健康判定 + 处置建议(--json 机器可读);不打扰、不耗额度。 | 已实现 · `pi-proactive/src/commands.ts:640` |
424
- | `/proactive progress` | `无` | 项目进度 + ETA:区间 + 置信度 + 归因 + 阻塞项(不给单点数字;口径 = scripts/progress-clock.py)。 | 已实现 · `pi-proactive/src/commands.ts:648` |
425
- | `/proactive tell` | `<peer> <内容> [--kind ask\|task\|notify]` | 人侧给另一个会话下指令(--kind ask\|task\|notify,缺省 ask 要回话);回信落回本会话。 | 已实现 · `pi-proactive/src/commands.ts:755` |
426
- | `/proactive assistant` | `on\|off\|status\|restart` | 常驻助理(7×24)控制:on 打开(后台装,不阻塞会话)/ off 关闭(回执含残留终检)/ status 看宿主·进程·心跳·上次联系 / restart 重装。与终端 CLI 共用同一套安装/卸载实现。 | 已实现 · `pi-proactive/src/commands.ts:724` |
427
- | `/proactive config` | `[undo] [--json]` | 列出全部配置项(当前值 / 缺省值 / 取值范围 / 一句说明),先报警写错的项;config undo 撤销上一次配置改动。 | 已实现 · `pi-proactive/src/commands.ts:655` |
409
+ | `/proactive status` | `无` | 总览:开关、空闲计时、待触发任务、对端在线、上次主动、本小时用量。 | 已实现 · `pi-proactive/src/commands.ts:633` |
410
+ | `/proactive on` | `无` | 打开全部「代理自发」行为(空闲触发、模型自调度、跨会话自发请求)。 | 已实现 · `pi-proactive/src/commands.ts:669` |
411
+ | `/proactive off` | `无` | 关闭全部「代理自发」行为;已排期任务保留但不再触发。 | 已实现 · `pi-proactive/src/commands.ts:670` |
412
+ | `/proactive test` | `无` | 立即模拟一次触发(走完整「护栏 → 投递」链路),用于验证主动能力是否真的生效。 | 已实现 · `pi-proactive/src/commands.ts:806` |
413
+ | `/proactive tasks` | `无` | 列出所有待触发任务(编号 / 类型 / 下次到期 / 归属 / 触发次数)。 | 已实现 · `pi-proactive/src/commands.ts:809` |
414
+ | `/proactive cancel` | `<id>` | 取消一个任务。 | 已实现 · `pi-proactive/src/commands.ts:835` |
415
+ | `/proactive idle` | `on\|off` | 打开 / 关闭空闲触发并设定阈值(on 可带时长参数,省略 = 用上次设定)。 | 已实现 · `pi-proactive/src/commands.ts:875` |
416
+ | `/proactive peers` | `[--all\|--idle\|--name <子串>\|--detail <agentId>]` | 列出已注册的对端与在线状态(名字 / 忙闲 / 最后心跳);可加 --all / --idle / --name / --detail。 | 已实现 · `pi-proactive/src/commands.ts:690` |
417
+ | `/proactive spawn` | `[名字]` | 创建一个对端会话(子进程 pi --mode rpc);本会话退出时一并终止。 | 已实现 · `pi-proactive/src/commands.ts:783` |
418
+ | `/proactive in` | `<时长> <内容>` | 排一个延迟任务(默认只属于当前会话)。 | 已实现 · `pi-proactive/src/commands.ts:853` |
419
+ | `/proactive at` | `<HH:MM> <内容>` | 排一个绝对时间任务。 | 已实现 · `pi-proactive/src/commands.ts:854` |
420
+ | `/proactive every` | `<时长> <内容>` | 排一个周期任务。 | 已实现 · `pi-proactive/src/commands.ts:855` |
421
+ | `/proactive help` | `无` | 打印命令速查表(命令表 / 配置键 / 最常见故障 / 详细文档路径)。 | 已实现 · `pi-proactive/src/commands.ts:890` |
422
+ | `/proactive history` | `[peer\|me] [条数]` | 查看最近的主动行为记录(时间 / 类型 / 目标 / 结果 / 被护栏拦下的原因)。 | 已实现 · `pi-proactive/src/commands.ts:813` |
423
+ | `/proactive doctor` | `[--json]` | 只读体检:逐项健康判定 + 处置建议(--json 机器可读);不打扰、不耗额度。 | 已实现 · `pi-proactive/src/commands.ts:641` |
424
+ | `/proactive progress` | `无` | 项目进度 + ETA:区间 + 置信度 + 归因 + 阻塞项(不给单点数字;口径 = scripts/progress-clock.py)。 | 已实现 · `pi-proactive/src/commands.ts:649` |
425
+ | `/proactive tell` | `<peer> <内容> [--kind ask\|task\|notify]` | 人侧给另一个会话下指令(--kind ask\|task\|notify,缺省 ask 要回话);回信落回本会话。 | 已实现 · `pi-proactive/src/commands.ts:803` |
426
+ | `/proactive assistant` | `on\|off\|status\|restart` | 常驻助理(7×24)控制:on 打开(后台装,不阻塞会话)/ off 关闭(回执含残留终检)/ status 看宿主·进程·心跳·上次联系 / restart 重装。与终端 CLI 共用同一套安装/卸载实现。 | 已实现 · `pi-proactive/src/commands.ts:772` |
427
+ | `/proactive config` | `[undo] [--json]` | 列出全部配置项(当前值 / 缺省值 / 取值范围 / 一句说明),先报警写错的项;config undo 撤销上一次配置改动。 | 已实现 · `pi-proactive/src/commands.ts:656` |
428
428
  <!-- END:GEN commands -->
429
429
 
430
430
  排期命令的排期结果会**当场回执**(任务 id、相对到期、绝对到期),任务**落盘**在任务表里,进程退出后仍在,
package/app/app.mjs CHANGED
@@ -1024,6 +1024,8 @@ export async function stopApp(opts = {}) {
1024
1024
  status: "stopped",
1025
1025
  pid: lock.pid,
1026
1026
  piPid: lock.piPid,
1027
+ piBin: lock.piBin,
1028
+ piBinSource: lock.piBinSource,
1027
1029
  residue: residueReport(target, lock),
1028
1030
  };
1029
1031
  }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `app/evidence.mjs` —— **M1a 验收取证脚本**(AC1–AC6 + D1–D4 的可复跑证据)。
4
+ *
5
+ * 用法:`node pi-proactive/app/evidence.mjs`
6
+ *
7
+ * App 本身的入口(同一目录的 app.mjs):
8
+ * node pi-proactive/app/app.mjs start 起 pi 并注入 pi-proactive(等扩展真的注册才算就绪)
9
+ * node pi-proactive/app/app.mjs stop 停止 + 残留终检
10
+ * node pi-proactive/app/app.mjs status 状态(含与面板同源的只读快照)
11
+ * node pi-proactive/app/app.mjs describe 三问:它是谁 / 在干什么 / 怎么让它做事
12
+ * node pi-proactive/app/app.mjs guide 首启引导(≤3 步)
13
+ * node pi-proactive/app/app.mjs ask "…" 经 JSONL 下发一条 prompt(id 关联响应)
14
+ *
15
+ * 为什么有它:测试给的是「断言通过」,取证给的是**逐条的实际输出**(谁在跑、端点在哪、
16
+ * 通知发了没有、残留是不是零)。本脚本用**真 pi**(不碰模型:隔离 agent 根没有凭据 →
17
+ * prompt 被本地拒绝)跑一遍 AC1–AC6 + D1–D4 的外部可观察面。
18
+ *
19
+ * 纪律:
20
+ * - 隔离:agent 根 = /tmp 下的 pp-evidence-<随机> 目录里的 pp-app-<pid>(**不写** live 目录 ~/.pi/agent/proactive);
21
+ * - 零真实模型调用:隔离根无凭据 → pi 在本地拒绝 prompt(不发网络请求);
22
+ * - 会真弹一条 macOS 通知(AC4 取证需要「系统侧可见」);不可用时明确报降级;
23
+ * - 结束清零:临时根、锁、日志、pi 子进程全部清掉。
24
+ */
25
+ import { spawnSync } from "node:child_process";
26
+ import fs from "node:fs";
27
+ import path from "node:path";
28
+ import { fileURLToPath, pathToFileURL } from "node:url";
29
+
30
+ const here = path.dirname(fileURLToPath(import.meta.url));
31
+ const PKG_ROOT = path.resolve(here, "..");
32
+ const app = await import(pathToFileURL(path.join(PKG_ROOT, "app", "app.mjs")).href);
33
+
34
+ const base = fs.mkdtempSync("/tmp/pp-evidence-");
35
+ const opts = { tmpDir: base, pid: process.pid, timeoutMs: 25_000, env: { ...process.env } };
36
+ const line = (t) => console.log(`\n=== ${t} ===`);
37
+ /** 本机在跑的 pi 进程数(按进程名计数 → 用来证明「没有第二份实例」)。 */
38
+ const countPi = () =>
39
+ spawnSync("sh", ["-c", "ps ax -o comm= | grep -cx 'pi'"], { encoding: "utf8" }).stdout.trim();
40
+
41
+ try {
42
+ line("D2 可发现性:三问(node app/app.mjs describe --json)");
43
+ const desc = app.describeApp();
44
+ for (const k of ["who", "what", "how"]) console.log(`${k}: ${desc[k]}`);
45
+ const src = fs.readFileSync(path.join(PKG_ROOT, "app", "app.mjs"), "utf8");
46
+ console.log("三问答案逐字存在于产物 app/app.mjs:", ["who", "what", "how"].every((k) => src.includes(desc[k])));
47
+
48
+ line("D1 引导:≤3 步、一次只问一件事、每步可「算了」");
49
+ const steps = app.guideSteps();
50
+ for (const s of steps) console.log(`- ${s.question}(${s.options.join(" / ")})`);
51
+ console.log("步数(≤3):", steps.length);
52
+
53
+ line("启动(真 pi · 单次注入)");
54
+ const before = countPi();
55
+ const started = await app.startApp(opts);
56
+ console.log("status :", started.status);
57
+ console.log("pi 进程 :", started.piPid, started.piBin);
58
+ console.log("启动参数 :", (started.launchArgs || []).join(" "));
59
+ console.log("agent 根 :", started.agentRoot);
60
+ console.log("凭据 :", started.credential.detail);
61
+ console.log("通知通道 :", JSON.stringify(started.notify));
62
+ console.log("面板 :", started.panelUrl);
63
+ console.log("本机 pi 进程数 :", before, "→", countPi());
64
+
65
+ line("AC1 注入可证明(进程内扩展清单 = RPC get_commands 的真实返回)");
66
+ console.log("取证来源 :", started.evidence.extensionListSource);
67
+ console.log("扩展入口 :", started.evidence.extensionPath);
68
+ console.log("入口文件真实存在 :", fs.existsSync(String(started.evidence.extensionPath)));
69
+ console.log("命令名 :", started.evidence.extensionCommand);
70
+
71
+ line("AC2 承载(端点真的出现 + 短路径 + <100B)");
72
+ const ep = String(started.evidence.endpointPath);
73
+ console.log("端点路径 :", ep);
74
+ console.log("字节数 :", Buffer.byteLength(ep, "utf8"), "/ 上限 100");
75
+ console.log("落在 agent 根下 :", ep.startsWith(String(started.agentRoot)));
76
+ console.log("真实存在 :", fs.existsSync(ep));
77
+ console.log("是 unix socket :", fs.existsSync(ep) ? fs.statSync(ep).isSocket() : false);
78
+ console.log("信箱目录 :", path.join(String(started.agentRoot), "proactive", "mailbox", String(started.agentId)));
79
+
80
+ line("D3 面板数字与命令面同源逐字一致");
81
+ const panel = await (await fetch(`${started.panelUrl}/api/state`)).json();
82
+ const cliPath = fs.existsSync(path.join(PKG_ROOT, "build", "cli.mjs"))
83
+ ? path.join(PKG_ROOT, "build", "cli.mjs")
84
+ : path.join(PKG_ROOT, "tools", "cli.mjs");
85
+ const cmdState = JSON.parse(
86
+ spawnSync(process.execPath, [cliPath, "web", "--agent-dir", String(started.agentRoot), "--json"], {
87
+ encoding: "utf8",
88
+ }).stdout,
89
+ );
90
+ const strip = (o) => {
91
+ const c = { ...o };
92
+ delete c.generatedAt;
93
+ return c;
94
+ };
95
+ console.log(
96
+ "面板 /api/state ≡ 命令面 `pi-proactive web --json`:",
97
+ JSON.stringify(strip(panel)) === JSON.stringify(strip(cmdState)),
98
+ );
99
+ console.log(
100
+ "面板 /api/state ≡ App status 回执的 supervisor:",
101
+ JSON.stringify(strip(panel)) === JSON.stringify(strip(started.supervisor)),
102
+ );
103
+
104
+ line("AC3 驱动(JSONL 下发 prompt → id 关联响应)");
105
+ const asked = await app.askApp({ ...opts, message: "记住:CI 变绿时告诉我" });
106
+ console.log("请求 :", JSON.stringify(asked.request));
107
+ console.log("响应 :", JSON.stringify(asked.response));
108
+ console.log("id 关联 :", asked.response.id === asked.request.id);
109
+ console.log("说明 : 隔离 agent 根无凭据 → pi 在**本地**拒绝(不发网络请求 → 零真实模型调用)");
110
+
111
+ line("AC5 幂等:二次启动(不产生第二份实例 / 第二份通知注册)");
112
+ const again = await app.startApp(opts);
113
+ console.log("二次启动 status :", again.status, "|pi 进程:", again.piPid, "|App 进程:", again.pid);
114
+ console.log("本机 pi 进程数 :", countPi(), "(与首次启动相同 ⇒ 没有第二份实例)");
115
+
116
+ line("AC4 呈现:真实系统通知(macOS osascript → Notification Center 可见)");
117
+ const notifier = await app.createNotifier({ pkgRoot: PKG_ROOT });
118
+ console.log("通道可用 :", notifier.available, notifier.channel);
119
+ const sent = await notifier.send("M1a 取证:这是一条来自 pi-proactive App 的真实通知", {
120
+ title: "助理找你有事",
121
+ });
122
+ console.log("发送结果 :", JSON.stringify({ ok: sent.ok, detail: sent.detail }));
123
+ console.log("通知正文(四段):\n" + sent.payload.body);
124
+
125
+ line("AC5/D4 停止 → 零残留终检");
126
+ const stopped = await app.stopApp(opts);
127
+ console.log("status :", stopped.status);
128
+ console.log("残留终检 :", JSON.stringify(stopped.residue));
129
+ console.log(
130
+ "七处零残留 :",
131
+ !stopped.residue.appProcessAlive &&
132
+ !stopped.residue.piProcessAlive &&
133
+ !stopped.residue.endpointExists &&
134
+ !stopped.residue.socketExists &&
135
+ !stopped.residue.agentRootExists &&
136
+ !stopped.residue.lockExists &&
137
+ !stopped.residue.logExists,
138
+ );
139
+ console.log("本机 pi 进程数 :", countPi(), "(回到启动前 ⇒ 无孤儿 pi)");
140
+ console.log("基目录残留 :", fs.readdirSync(base).filter((n) => n.startsWith("pp-app") || n.endsWith(".lock")));
141
+ } finally {
142
+ fs.rmSync(base, { recursive: true, force: true });
143
+ console.log("\n取证脚本结束(临时根已清)。");
144
+ }
@@ -8,6 +8,7 @@ import { formatPeerDirectory, formatPeerList } from "./peer.js";
8
8
  import { readProgressEta } from "./progress-eta.js";
9
9
  import { handleAssistantCommand } from "./assistant.js";
10
10
  import { Registry } from "./registry.js";
11
+ import { buildPanel, ensureSupervisorWeb, stopSupervisorWeb, DEFAULT_SUPERVISOR_WEB_PORT } from "./supervisor-panel.js";
11
12
  const DETECT_LABEL = {
12
13
  terminal: "终端输入",
13
14
  "input-only": "输入事件(保守模式)",
@@ -455,6 +456,53 @@ export async function dispatchProactiveCommand(args, ctx, opts = {}) {
455
456
  }
456
457
  return { text, level: "info" };
457
458
  }
459
+ case "panel": {
460
+ // 交互面板(三侧面):**同源状态块** —— 数字与 status / peers / history / Web 看板一致。
461
+ //
462
+ // 为什么在命令面就地取数:① 监督聚合 = `collectSupervisorState`(同一份);
463
+ // ② 对端目录 = **与 `/proactive peers --all` 同一个查询**(`queryPeerDirectory`);
464
+ // ③ 主动联系流水 = `contacts.jsonl`(既有读法)→ 三块不各算一套。
465
+ const agentDir = ctx.agentDir ?? resolveAgentDir();
466
+ const registry = ctx.registry ?? new Registry({ agentDir });
467
+ const { directory } = queryPeerDirectory({
468
+ registry,
469
+ selfAgentId: ctx.agentId,
470
+ inFlight: ctx.inFlightForPeer,
471
+ query: { includeOffline: true },
472
+ });
473
+ const panel = buildPanel({ agentDir, agentId: ctx.agentId, peers: directory });
474
+ return { text: panel.text, level: "info" };
475
+ }
476
+ case "web": {
477
+ // 只读 Web 看板(**不阻塞**):后台起服务后立刻回执可点 URL;重复调用**不重起**。
478
+ const flags = parts.slice(1);
479
+ const portAt = flags.indexOf("--port");
480
+ let port = DEFAULT_SUPERVISOR_WEB_PORT;
481
+ if (portAt >= 0) {
482
+ const raw = flags[portAt + 1] ?? "";
483
+ port = Number(raw);
484
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
485
+ return { text: `proactive: --port 需要 0-65535 的整数(实际:${raw})`, level: "error" };
486
+ }
487
+ }
488
+ if (flags.includes("stop")) {
489
+ const stopped = await stopSupervisorWeb();
490
+ return { text: stopped ? "监督面已停止(端口已释放)" : "监督面本来就没在跑", level: "info" };
491
+ }
492
+ const outcome = await ensureSupervisorWeb({
493
+ port,
494
+ agentDir: ctx.agentDir ?? resolveAgentDir(),
495
+ agentId: ctx.agentId,
496
+ });
497
+ if (!outcome.ok) {
498
+ return { text: `proactive: ${outcome.error ?? "监督面启动失败"}`, level: "error" };
499
+ }
500
+ const head = outcome.alreadyRunning === true ? `已在运行 ${outcome.url}` : `监督面已启动 ${outcome.url}`;
501
+ return {
502
+ text: `${head}\n只读(改配置请回 Pi 会话)· 只监听 127.0.0.1 · 停止:/proactive web stop`,
503
+ level: "info",
504
+ };
505
+ }
458
506
  case "assistant": {
459
507
  // 会话内常驻助理(on / off / status / restart)。
460
508
  // **与模型工具面共用同一段实现**(`handleAssistantCommand`)→ 输出逐字一致。
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 交互入口的**接线层**:TUI 面板编排 + 只读 Web 服务的生命周期。
3
+ *
4
+ * 为什么单独一个模块:交互入口卡要接**两个**入口(TUI 面板 / Web 看板),
5
+ * 而这两件事都必须**同源**(同一份聚合、逐字一致)⇒ 编排与生命周期放在一处,
6
+ * 渲染留在 `src/supervisor-tui.ts`(纯文本,可被 Node 测试直接断言)。
7
+ *
8
+ * 纪律:
9
+ * - **只读**:本模块只读既有事实面(心跳 / 值班状态 / 事件流 / 主动联系清单 / 对端注册表);
10
+ * - **不重算指标**:监督数字来自 `src/supervisor-state.ts` 的**同一份聚合**,
11
+ * 对端名单来自 `/proactive peers` 的**同一次目录查询**(渲染逐字复用);
12
+ * - **幂等**:Web 服务重复调用**不重起**(端口数不增加);失败必须**明确报错 + 可执行建议**。
13
+ */
14
+ import { readContactEntries } from "./daemon-config.js";
15
+ import { resolveAgentDir } from "./config.js";
16
+ import { collectSupervisorState } from "./supervisor-state.js";
17
+ import { formatInteractivePanel } from "./supervisor-tui.js";
18
+ import { startSupervisorWeb } from "./supervisor-web.js";
19
+ /** Web 监督面的缺省端口(与 CLI `pi-proactive web` 同一个)。 */
20
+ export const DEFAULT_SUPERVISOR_WEB_PORT = 7799;
21
+ /**
22
+ * 采集并渲染交互面板(**只读**:不写任何文件)。
23
+ *
24
+ * @param input 见 {@link PanelInput}。
25
+ */
26
+ export function buildPanel(input = {}) {
27
+ const agentDir = input.agentDir ?? resolveAgentDir();
28
+ const now = input.now ?? Date.now();
29
+ const state = collectSupervisorState({
30
+ agentDir,
31
+ now,
32
+ ...(input.agentId !== undefined ? { agentId: input.agentId } : {}),
33
+ });
34
+ const contacts = readContactEntries(agentDir);
35
+ const text = formatInteractivePanel({
36
+ state,
37
+ contacts,
38
+ ...(input.peers !== undefined ? { peers: input.peers } : {}),
39
+ }).join("\n");
40
+ return { state, contacts, text };
41
+ }
42
+ /** 当前进程内已在跑的看板句柄(**幂等的唯一真源**;进程退出即释放端口)。 */
43
+ let running;
44
+ /** 探一下端口上是不是**已经有一份监督面**(端口被别人占 ≠ 监督面在跑)。 */
45
+ async function probeSupervisorWeb(port) {
46
+ try {
47
+ const res = await fetch(`http://127.0.0.1:${port}/api/state`, { signal: AbortSignal.timeout(1000) });
48
+ if (!res.ok)
49
+ return undefined;
50
+ const body = (await res.json());
51
+ if (typeof body?.status !== "object" || body.status === null)
52
+ return undefined;
53
+ return `http://127.0.0.1:${port}`;
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ /**
60
+ * 起只读 Web 监督面(**幂等**)。
61
+ *
62
+ * - 已在跑 → 直接回「已在运行 <url>」,**不重起**(端口数不增加);
63
+ * - 端口被占 → 若占着的正是监督面 → 如实说「已在运行」;否则**明确报错 + 可执行建议**。
64
+ */
65
+ export async function ensureSupervisorWeb(input = {}) {
66
+ if (running !== undefined) {
67
+ return { ok: true, url: running.url, port: running.port, alreadyRunning: true };
68
+ }
69
+ const port = input.port ?? DEFAULT_SUPERVISOR_WEB_PORT;
70
+ try {
71
+ const handle = await startSupervisorWeb({
72
+ port,
73
+ ...(input.agentDir !== undefined ? { agentDir: input.agentDir } : {}),
74
+ ...(input.agentId !== undefined ? { agentId: input.agentId } : {}),
75
+ });
76
+ running = handle;
77
+ return { ok: true, url: handle.url, port: handle.port, alreadyRunning: false };
78
+ }
79
+ catch (err) {
80
+ const code = err?.code;
81
+ if (code === "EADDRINUSE") {
82
+ const existing = await probeSupervisorWeb(port);
83
+ if (existing !== undefined)
84
+ return { ok: true, url: existing, port, alreadyRunning: true };
85
+ return {
86
+ ok: false,
87
+ error: `端口 ${port} 已被占用(EADDRINUSE)—— 监督面没起来。\n` +
88
+ `可执行建议:① 换一个端口:/proactive web --port ${port + 1};` +
89
+ `② 或先停掉占用它的进程:lsof -nP -iTCP:${port} -sTCP:LISTEN`,
90
+ };
91
+ }
92
+ const detail = err instanceof Error ? err.message : String(err);
93
+ return { ok: false, error: `监督面启动失败(端口 ${port}):${detail}\n可执行建议:/proactive web --port 0(随机端口)` };
94
+ }
95
+ }
96
+ /**
97
+ * 停掉本进程内的看板(**释放端口**;幂等:本来没跑 → 返回 false)。
98
+ */
99
+ export async function stopSupervisorWeb() {
100
+ if (running === undefined)
101
+ return false;
102
+ const handle = running;
103
+ running = undefined;
104
+ await handle.close();
105
+ return true;
106
+ }
@@ -9,8 +9,9 @@
9
9
  * 着色由扩展层用宿主的 theme 加,宽度裁剪由扩展层做 → 本模块可被纯 Node 测试直接断言。
10
10
  */
11
11
  import { formatBudgetLine, formatConservationLine, formatEntrustmentConservationLine, formatStatusLine, formatSupervisorSummary, } from "./supervisor-state.js";
12
+ import { formatPeerDirectory } from "./peer.js";
12
13
  /** 毫秒 → `MM-DD HH:mm:ss`(本地时区)。 */
13
- function fmtTime(ts) {
14
+ export function formatSupervisorTime(ts) {
14
15
  const d = new Date(ts);
15
16
  const p = (n) => String(n).padStart(2, "0");
16
17
  return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
@@ -39,7 +40,7 @@ export function formatSupervisorPanel(state, opts = {}) {
39
40
  const len = t.bodyLen === undefined ? "" : ` · 正文 ${t.bodyLen} 字`;
40
41
  const digest = t.bodyDigest === undefined ? "" : ` · 摘要 ${t.bodyDigest}`;
41
42
  const peer = t.peer === undefined ? "" : ` · 对端 ${t.peer}`;
42
- lines.push(` ${fmtTime(t.ts)} ${t.summary}${len}${digest}${peer}`);
43
+ lines.push(` ${formatSupervisorTime(t.ts)} ${t.summary}${len}${digest}${peer}`);
43
44
  }
44
45
  }
45
46
  lines.push(`托付(${state.trusts.length})`);
@@ -83,3 +84,126 @@ export function formatSupervisorPanel(state, opts = {}) {
83
84
  export function formatSupervisorText(state, opts = {}) {
84
85
  return formatSupervisorPanel(state, opts).join("\n");
85
86
  }
87
+ /** 主动联系流水最多显示几条(缺省)。 */
88
+ const DEFAULT_CONTACT_LIMIT = 5;
89
+ /** 「在不在」的判定(**不猜**:心跳在 + 进程活着 = 在跑;装过但没跑 / 没装过 = 明说)。 */
90
+ function livenessVerdict(state) {
91
+ if (!state.status.present)
92
+ return "未安装(没有心跳 / 宿主状态文件)—— 助理从未常驻";
93
+ if (!state.status.running) {
94
+ const reason = state.status.notRunningReason === undefined ? "" : `(原因:${state.status.notRunningReason})`;
95
+ return `装过但当前没在运行${reason}`;
96
+ }
97
+ return "在跑(心跳在 + 进程活着)";
98
+ }
99
+ /** 当前模式(★干跑必须显式;被阻塞 / 预算耗尽一并点名)。 */
100
+ function runModeLine(state) {
101
+ if (!state.status.running)
102
+ return "—(值班进程没在跑 → 形态未知)";
103
+ const base = state.status.runMode === "dry-run"
104
+ ? "干跑(dry-run)—— 只演练,不会真的动手"
105
+ : state.status.runMode === "live"
106
+ ? "真跑(live)"
107
+ : "未知(心跳里没有形态记录)";
108
+ const extra = [];
109
+ if (state.budget.exhausted)
110
+ extra.push("预算耗尽");
111
+ if (state.blockers.length > 0)
112
+ extra.push(`被阻塞(${state.blockers.length} 项)`);
113
+ return extra.length === 0 ? base : `${base} · ${extra.join(" · ")}`;
114
+ }
115
+ /** 对端分布(**同一份目录**的数字:在线 = 忙碌 + 空闲;总数 = 在线 + 离线)。 */
116
+ function peerDistribution(peers) {
117
+ if (peers === undefined)
118
+ return "对端目录未初始化(协作层尚未起)";
119
+ const total = peers.peers.length;
120
+ const busy = peers.peers.filter((p) => p.status === "busy").length;
121
+ const idle = peers.peers.filter((p) => p.status === "idle").length;
122
+ const offline = peers.peers.filter((p) => p.status === "offline").length;
123
+ return `共 ${total} 个:在线 ${total - offline} · 忙碌 ${busy} · 空闲 ${idle} · 离线 ${offline}`;
124
+ }
125
+ /** 通道 → 人话(来源)。 */
126
+ function channelLabel(channel) {
127
+ if (channel === "session")
128
+ return "会话通道";
129
+ if (channel === "device")
130
+ return "设备通道";
131
+ return channel;
132
+ }
133
+ /** 联系结局 → 人话(结果)。 */
134
+ function outcomeLabel(outcome) {
135
+ switch (outcome) {
136
+ case "session":
137
+ return "已送达(会话)";
138
+ case "device":
139
+ return "已送达(设备)";
140
+ case "both":
141
+ return "已送达(会话+设备)";
142
+ case "skipped":
143
+ return "已跳过";
144
+ case "failed":
145
+ return "失败";
146
+ default:
147
+ return outcome;
148
+ }
149
+ }
150
+ /**
151
+ * 渲染交互面板的行(**只读**:没有任何可点的操作)。
152
+ *
153
+ * @param input 三份既有事实(监督聚合 / 对端目录 / 主动联系流水)。
154
+ */
155
+ export function formatInteractivePanel(input) {
156
+ const { state, peers, contacts } = input;
157
+ const contactLimit = input.contactLimit ?? DEFAULT_CONTACT_LIMIT;
158
+ const lines = [];
159
+ lines.push("监督面板(只读 · 数字与 status / peers / history / Web 看板同源)");
160
+ lines.push(`聚合于 ${formatSupervisorTime(state.generatedAt)} · 数据源 ${state.agentDir}`);
161
+ lines.push("");
162
+ // ---- ① 在不在 ----
163
+ lines.push("① 在不在");
164
+ lines.push(` 判定 ${livenessVerdict(state)}`);
165
+ lines.push(` 状态 ${formatSupervisorSummary(state)}`);
166
+ lines.push(` ${formatStatusLine(state)}`);
167
+ lines.push(` 模式 ${runModeLine(state)}`);
168
+ if (state.blockers.length === 0) {
169
+ lines.push(" 阻塞 无");
170
+ }
171
+ else {
172
+ const shown = state.blockers.slice(0, 3).map((b) => b.title).join(";");
173
+ lines.push(` 阻塞 ${state.blockers.length} 项:${shown}`);
174
+ }
175
+ lines.push(` 预算 ${formatBudgetLine(state)}`);
176
+ lines.push("");
177
+ // ---- ② 有多少 ----
178
+ lines.push("② 有多少(对端 · 与 /proactive peers --all 同源)");
179
+ lines.push(` 分布 ${peerDistribution(peers)}`);
180
+ if (peers === undefined) {
181
+ lines.push(" 名单 (无)");
182
+ }
183
+ else {
184
+ // 名单**逐字复用** `/proactive peers --all` 的渲染 → 两处不可能漂移。
185
+ for (const line of formatPeerDirectory(peers).split("\n"))
186
+ lines.push(line);
187
+ }
188
+ lines.push("");
189
+ // ---- ③ 有没有发生 ----
190
+ lines.push("③ 有没有发生(主动联系流水 · 同一份 contacts.jsonl)");
191
+ if (contacts.length === 0) {
192
+ lines.push(" 主动联系 从未联系过(清单为空)");
193
+ }
194
+ else {
195
+ const last = contacts[contacts.length - 1];
196
+ const target = last.targetAgentId === undefined ? "" : `(对象 ${last.targetAgentId})`;
197
+ lines.push(` 主动联系 共 ${contacts.length} 次 · 最近一次 ${formatSupervisorTime(last.ts)}${target}`);
198
+ const rows = contacts.slice(Math.max(0, contacts.length - contactLimit)).reverse();
199
+ lines.push(` 流水(最近 ${rows.length} 次)`);
200
+ for (const c of rows) {
201
+ const source = c.channels.length > 0 ? c.channels.map(channelLabel).join("+") : "无通道";
202
+ const to = c.targetAgentId ?? "(无对象)";
203
+ const reason = c.reason === undefined || c.reason === "" ? "" : `(${c.reason})`;
204
+ lines.push(` ${formatSupervisorTime(c.ts)} · ${source} · ${to} · ${outcomeLabel(c.outcome)}${reason}`);
205
+ }
206
+ }
207
+ lines.push(` 守恒 ${formatConservationLine(state)}`);
208
+ return lines;
209
+ }
package/manual/USAGE.html CHANGED
@@ -16,7 +16,7 @@ th{background:var(--code)}.warn{border-left:3px solid var(--warn);padding-left:1
16
16
  .ok{border-left:3px solid var(--ac);padding-left:12px;margin:12px 0}a{color:var(--ac)}
17
17
  </style></head><body>
18
18
  <h1>pi-proactive 使用手册</h1>
19
- <p class="v">版本 0.2.0-beta.7 | 跨项目协作 · 主动触发 · 常驻助理 | 本手册随 npm 包发布</p>
19
+ <p class="v">版本 0.2.0-beta.8 | 跨项目协作 · 主动触发 · 常驻助理 | 本手册随 npm 包发布</p>
20
20
 
21
21
  <h2>1. 这是什么</h2>
22
22
  <p>pi-proactive 是 Pi Coding Agent 的扩展插件,给它加上四件事:<b>跨进程/跨项目消息协作</b>、<b>空闲主动触发</b>、<b>常驻值班助理</b>、<b>可观测的协作状态</b>。</p>
@@ -36,6 +36,17 @@ th{background:var(--code)}.warn{border-left:3px solid var(--warn);padding-left:1
36
36
  <tr><td><b>模型工具面</b></td><td>让 agent 自己查、自己协作</td><td><code>list_peers</code> · <code>message_agent</code> · <code>schedule_task</code> · <code>proactive_query</code> · <code>proactive_schedule</code> · <code>proactive_peer</code> · <code>proactive_manage</code></td></tr>
37
37
  <tr><td><b>CLI</b></td><td>脚本化 / 监督</td><td><code>pi-proactive --help</code></td></tr></table>
38
38
 
39
+ <h2>3.5 App 模式(不想开终端就用它)</h2>
40
+ <pre><code>node ~/.pi/agent/npm/node_modules/pi-proactive/app/app.mjs start # 启动(起 pi + 注入插件 + 通知 + 面板)
41
+ node ~/.pi/agent/npm/node_modules/pi-proactive/app/app.mjs status # 一屏状态(助理在不在/对端多少/主动联系流水)
42
+ node ~/.pi/agent/npm/node_modules/pi-proactive/app/app.mjs describe # 三问:它是谁/在干什么/怎么让它做事
43
+ node ~/.pi/agent/npm/node_modules/pi-proactive/app/app.mjs guide # 引导(≤3 步,一步一问,随时可说"算了")
44
+ node ~/.pi/agent/npm/node_modules/pi-proactive/app/app.mjs stop # 停止(进程/socket/临时目录 七处零残留)</code></pre>
45
+ <table><tr><th>模式</th><th>说明</th></tr>
46
+ <tr><td>默认(专属 agent 根)</td><td>隔离干净,<b>但没有模型凭据 → 只能看不能做事</b>,App 会明说这一点</td></tr>
47
+ <tr><td><code>--shared</code></td><td>与宿主 pi 共用 <code>~/.pi/agent</code>(有凭据、能做事)→ 排障时先看这一点</td></tr></table>
48
+ <div class="warn"><b>Windows:</b>App 外壳暂未实现 Windows 分支(会明确报"尚未实现",不假装能跑)。</div>
49
+
39
50
  <h2>4. 常驻助理(7×24)</h2>
40
51
  <pre><code># 安装常驻(跨平台:macOS launchd · Linux systemd --user · Windows 计划任务)
41
52
  node ~/.pi/agent/npm/node_modules/pi-proactive/tools/daemon-install.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-proactive",
3
- "version": "0.2.0-beta.7",
3
+ "version": "0.2.0-beta.8",
4
4
  "description": "给 Pi 增加主动性:空闲时主动推进或提问、按时间点主动发起任务、多个会话之间对话协作。(专有软件:分发物不含源码,使用条款见包内 LICENSE)",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -141,25 +141,25 @@ description: >-
141
141
  <!-- BEGIN:GEN commands -->
142
142
  | 命令 | 参数形态 | 做什么 | 状态 |
143
143
  | --- | --- | --- | --- |
144
- | `/proactive status` | `无` | 总览:开关、空闲计时、待触发任务、对端在线、上次主动、本小时用量。 | 已实现 · `pi-proactive/src/commands.ts:632` |
145
- | `/proactive on` | `无` | 打开全部「代理自发」行为(空闲触发、模型自调度、跨会话自发请求)。 | 已实现 · `pi-proactive/src/commands.ts:668` |
146
- | `/proactive off` | `无` | 关闭全部「代理自发」行为;已排期任务保留但不再触发。 | 已实现 · `pi-proactive/src/commands.ts:669` |
147
- | `/proactive test` | `无` | 立即模拟一次触发(走完整「护栏 → 投递」链路),用于验证主动能力是否真的生效。 | 已实现 · `pi-proactive/src/commands.ts:758` |
148
- | `/proactive tasks` | `无` | 列出所有待触发任务(编号 / 类型 / 下次到期 / 归属 / 触发次数)。 | 已实现 · `pi-proactive/src/commands.ts:761` |
149
- | `/proactive cancel` | `<id>` | 取消一个任务。 | 已实现 · `pi-proactive/src/commands.ts:787` |
150
- | `/proactive idle` | `on\|off` | 打开 / 关闭空闲触发并设定阈值(on 可带时长参数,省略 = 用上次设定)。 | 已实现 · `pi-proactive/src/commands.ts:827` |
151
- | `/proactive peers` | `[--all\|--idle\|--name <子串>\|--detail <agentId>]` | 列出已注册的对端与在线状态(名字 / 忙闲 / 最后心跳);可加 --all / --idle / --name / --detail。 | 已实现 · `pi-proactive/src/commands.ts:689` |
152
- | `/proactive spawn` | `[名字]` | 创建一个对端会话(子进程 pi --mode rpc);本会话退出时一并终止。 | 已实现 · `pi-proactive/src/commands.ts:735` |
153
- | `/proactive in` | `<时长> <内容>` | 排一个延迟任务(默认只属于当前会话)。 | 已实现 · `pi-proactive/src/commands.ts:805` |
154
- | `/proactive at` | `<HH:MM> <内容>` | 排一个绝对时间任务。 | 已实现 · `pi-proactive/src/commands.ts:806` |
155
- | `/proactive every` | `<时长> <内容>` | 排一个周期任务。 | 已实现 · `pi-proactive/src/commands.ts:807` |
156
- | `/proactive help` | `无` | 打印命令速查表(命令表 / 配置键 / 最常见故障 / 详细文档路径)。 | 已实现 · `pi-proactive/src/commands.ts:842` |
157
- | `/proactive history` | `[peer\|me] [条数]` | 查看最近的主动行为记录(时间 / 类型 / 目标 / 结果 / 被护栏拦下的原因)。 | 已实现 · `pi-proactive/src/commands.ts:765` |
158
- | `/proactive doctor` | `[--json]` | 只读体检:逐项健康判定 + 处置建议(--json 机器可读);不打扰、不耗额度。 | 已实现 · `pi-proactive/src/commands.ts:640` |
159
- | `/proactive progress` | `无` | 项目进度 + ETA:区间 + 置信度 + 归因 + 阻塞项(不给单点数字;口径 = scripts/progress-clock.py)。 | 已实现 · `pi-proactive/src/commands.ts:648` |
160
- | `/proactive tell` | `<peer> <内容> [--kind ask\|task\|notify]` | 人侧给另一个会话下指令(--kind ask\|task\|notify,缺省 ask 要回话);回信落回本会话。 | 已实现 · `pi-proactive/src/commands.ts:755` |
161
- | `/proactive assistant` | `on\|off\|status\|restart` | 常驻助理(7×24)控制:on 打开(后台装,不阻塞会话)/ off 关闭(回执含残留终检)/ status 看宿主·进程·心跳·上次联系 / restart 重装。与终端 CLI 共用同一套安装/卸载实现。 | 已实现 · `pi-proactive/src/commands.ts:724` |
162
- | `/proactive config` | `[undo] [--json]` | 列出全部配置项(当前值 / 缺省值 / 取值范围 / 一句说明),先报警写错的项;config undo 撤销上一次配置改动。 | 已实现 · `pi-proactive/src/commands.ts:655` |
144
+ | `/proactive status` | `无` | 总览:开关、空闲计时、待触发任务、对端在线、上次主动、本小时用量。 | 已实现 · `pi-proactive/src/commands.ts:633` |
145
+ | `/proactive on` | `无` | 打开全部「代理自发」行为(空闲触发、模型自调度、跨会话自发请求)。 | 已实现 · `pi-proactive/src/commands.ts:669` |
146
+ | `/proactive off` | `无` | 关闭全部「代理自发」行为;已排期任务保留但不再触发。 | 已实现 · `pi-proactive/src/commands.ts:670` |
147
+ | `/proactive test` | `无` | 立即模拟一次触发(走完整「护栏 → 投递」链路),用于验证主动能力是否真的生效。 | 已实现 · `pi-proactive/src/commands.ts:806` |
148
+ | `/proactive tasks` | `无` | 列出所有待触发任务(编号 / 类型 / 下次到期 / 归属 / 触发次数)。 | 已实现 · `pi-proactive/src/commands.ts:809` |
149
+ | `/proactive cancel` | `<id>` | 取消一个任务。 | 已实现 · `pi-proactive/src/commands.ts:835` |
150
+ | `/proactive idle` | `on\|off` | 打开 / 关闭空闲触发并设定阈值(on 可带时长参数,省略 = 用上次设定)。 | 已实现 · `pi-proactive/src/commands.ts:875` |
151
+ | `/proactive peers` | `[--all\|--idle\|--name <子串>\|--detail <agentId>]` | 列出已注册的对端与在线状态(名字 / 忙闲 / 最后心跳);可加 --all / --idle / --name / --detail。 | 已实现 · `pi-proactive/src/commands.ts:690` |
152
+ | `/proactive spawn` | `[名字]` | 创建一个对端会话(子进程 pi --mode rpc);本会话退出时一并终止。 | 已实现 · `pi-proactive/src/commands.ts:783` |
153
+ | `/proactive in` | `<时长> <内容>` | 排一个延迟任务(默认只属于当前会话)。 | 已实现 · `pi-proactive/src/commands.ts:853` |
154
+ | `/proactive at` | `<HH:MM> <内容>` | 排一个绝对时间任务。 | 已实现 · `pi-proactive/src/commands.ts:854` |
155
+ | `/proactive every` | `<时长> <内容>` | 排一个周期任务。 | 已实现 · `pi-proactive/src/commands.ts:855` |
156
+ | `/proactive help` | `无` | 打印命令速查表(命令表 / 配置键 / 最常见故障 / 详细文档路径)。 | 已实现 · `pi-proactive/src/commands.ts:890` |
157
+ | `/proactive history` | `[peer\|me] [条数]` | 查看最近的主动行为记录(时间 / 类型 / 目标 / 结果 / 被护栏拦下的原因)。 | 已实现 · `pi-proactive/src/commands.ts:813` |
158
+ | `/proactive doctor` | `[--json]` | 只读体检:逐项健康判定 + 处置建议(--json 机器可读);不打扰、不耗额度。 | 已实现 · `pi-proactive/src/commands.ts:641` |
159
+ | `/proactive progress` | `无` | 项目进度 + ETA:区间 + 置信度 + 归因 + 阻塞项(不给单点数字;口径 = scripts/progress-clock.py)。 | 已实现 · `pi-proactive/src/commands.ts:649` |
160
+ | `/proactive tell` | `<peer> <内容> [--kind ask\|task\|notify]` | 人侧给另一个会话下指令(--kind ask\|task\|notify,缺省 ask 要回话);回信落回本会话。 | 已实现 · `pi-proactive/src/commands.ts:803` |
161
+ | `/proactive assistant` | `on\|off\|status\|restart` | 常驻助理(7×24)控制:on 打开(后台装,不阻塞会话)/ off 关闭(回执含残留终检)/ status 看宿主·进程·心跳·上次联系 / restart 重装。与终端 CLI 共用同一套安装/卸载实现。 | 已实现 · `pi-proactive/src/commands.ts:772` |
162
+ | `/proactive config` | `[undo] [--json]` | 列出全部配置项(当前值 / 缺省值 / 取值范围 / 一句说明),先报警写错的项;config undo 撤销上一次配置改动。 | 已实现 · `pi-proactive/src/commands.ts:656` |
163
163
  <!-- END:GEN commands -->
164
164
 
165
165
  > 命令面真源 = 实现:上表由 `scripts/gen-doc-tables.sh` 从扩展的 `SUBCOMMANDS` + `switch (sub)` 分派自动生成(**共 19 个命令**),**不要手改**。