pi-proactive 0.2.0-beta.4 → 0.2.0-beta.6
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 +77 -0
- package/README.md +32 -14
- package/build/extensions/proactive.js +2 -1
- package/daemon/run-daemon.mjs +268 -0
- package/package.json +12 -3
- package/skills/pi-proactive/SKILL.md +67 -9
- package/tools/daemon-common.mjs +289 -0
- package/tools/daemon-install.mjs +488 -0
- package/tools/daemon-uninstall.mjs +302 -0
- package/tools/enumerate-residue.mjs +487 -0
- package/tools/verify-windows.mjs +358 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,83 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
### 0.2.0-beta.6 — 用户可见新增:跨平台常驻安装 + 独立枚举器
|
|
12
|
+
|
|
13
|
+
**新增能力(可直接用)**
|
|
14
|
+
- `tools/daemon-install.mjs` / `tools/daemon-uninstall.mjs` / `daemon/run-daemon.mjs`:
|
|
15
|
+
跨平台常驻助理安装/卸载/保活 —— macOS(launchd) · Linux(systemd --user) · **Windows(计划任务)**
|
|
16
|
+
(不再依赖 bash;agent 根取短路径,端点路径 <100B;幂等;卸载零残留)
|
|
17
|
+
- `tools/enumerate-residue.mjs`:跨项目残留枚举器(零依赖)—— 七字段 + pidAlive 三值 + expired/unreachable/inflight 三态
|
|
18
|
+
- `tools/verify-windows.mjs`:Windows 一键验证(W1–W6 逐项证据表,支持 `--selftest` 离线自检)
|
|
19
|
+
|
|
20
|
+
**文档更正**
|
|
21
|
+
- `--legacy-peer-deps` 仅在【混合安装目录】需要;干净空目录不需要(typebox 已随包 bundled)
|
|
22
|
+
- 未知配置项 → 整份配置回落默认值(fail-closed):一次拼错会连带关掉空闲触发
|
|
23
|
+
- `pi -g` 全局安装会多出第二份副本 → 宿主报 duplicate runtime modules;宿主实际加载的是 pi 包路径那份
|
|
24
|
+
|
|
25
|
+
**发布内容**
|
|
26
|
+
- 判据脚本 sha256 随版本登记于 `docs/project/cp010-evidence/PROVENANCE.md`
|
|
27
|
+
- 仍在验证中、尚未宣称通过:Windows 真机 W1–W6(需真机通道)、Linux systemd 真机
|
|
28
|
+
|
|
29
|
+
重要:未知配置项会【整份回落默认值】(fail-closed)
|
|
30
|
+
一个拼错的键会导致**整份配置回落默认**(例如 idleTrigger 回到"未开启")→ 一次拼错可能连带关掉空闲触发。
|
|
31
|
+
报错信息会指名该键(CONFIG_UNKNOWN_KEY),请按它修正而不是删配置。
|
|
32
|
+
|
|
33
|
+
### 安装路径(三者不等价,勿混用)
|
|
34
|
+
- **pi 包路径(宿主实际加载的那份)**:`cd ~/.pi/agent/npm && npm install pi-proactive@latest`
|
|
35
|
+
- 独立全局安装:会多出第二份副本 → 宿主报 duplicate runtime modules
|
|
36
|
+
- `--legacy-peer-deps` 仅在【混合安装目录】需要(该目录有与 pi-proactive 无关的 peer 冲突);干净空目录不需要(typebox 已 bundled)
|
|
37
|
+
|
|
38
|
+
### 升级指引(2026-10-31 实测校正)
|
|
39
|
+
本机的**实际全局根是 `~/.npm-global`**(`pi` 本身就在那里),而 `npm prefix -g` 报的是 `/usr/local`
|
|
40
|
+
→ 按通用文档写 `npm i -g pi-proactive` 会因 `/usr/local/lib/node_modules` 无写权限而失败。
|
|
41
|
+
**请用用户级写法**:
|
|
42
|
+
```bash
|
|
43
|
+
npm i -g --prefix ~/.npm-global pi-proactive@latest --legacy-peer-deps
|
|
44
|
+
# 或(pi 包存储方式)
|
|
45
|
+
npm install pi-proactive@latest --legacy-peer-deps
|
|
46
|
+
```
|
|
47
|
+
`--legacy-peer-deps` 是必需的:本机该目录存在无关的 peer 冲突。
|
|
48
|
+
|
|
49
|
+
## 未发布(Windows 交付切片·第一批)
|
|
50
|
+
|
|
51
|
+
### 新增
|
|
52
|
+
- **跨平台用户侧入口**(零第三方依赖 · 纯 Node ≥ 22.19 · 三平台同源):
|
|
53
|
+
- `tools/daemon-install.mjs` —— 一键安装:macOS **launchd** / Linux **systemd --user** / Windows **计划任务**
|
|
54
|
+
(`schtasks /Create /SC MINUTE /MO n ... /F`);三个平台分支复用既有 `daemon/launchd.plist` 与
|
|
55
|
+
`daemon/pi-proactive-daemon.service` 模板(模板本身未改,渲染时把 `ProgramArguments`/`ExecStart` 换成**直接调 node**)。
|
|
56
|
+
- `tools/daemon-uninstall.mjs` —— 对称卸载(三处归零:任务/单元、进程、目录)。
|
|
57
|
+
- `daemon/run-daemon.mjs` —— 跨平台保活监督器(心跳新鲜度 + 值班锁互斥 + 崩溃自愈;`daemon/run-daemon.sh` 保留供 unix 维护侧)。
|
|
58
|
+
- `tools/daemon-common.mjs` —— 三者共用的平台/路径/模板口径(同一口径只写一份)。
|
|
59
|
+
- `tools/verify-windows.mjs` —— Windows 验收脚本(W1..W6 逐项证据表;非 Windows 上跑只做离线自检,真机项标「未验证」)。
|
|
60
|
+
- Windows 特有硬约束落地:**短路径**(agent 根自动降到 < 100B 端点路径的候选)、**引号化**(含空格的 node/脚本路径逐个加引号)、
|
|
61
|
+
**幂等**(`/Query` 先探测 + `/F` 覆盖同名任务 → 重复安装不产生第二份任务/进程)。
|
|
62
|
+
|
|
63
|
+
### 修复
|
|
64
|
+
- 重复安装时「心跳不前进」:旧心跳来自**刚被停掉的进程** → 监督器误判「有人在值班」而白等 20s。
|
|
65
|
+
修复:心跳新鲜度增加**进程存活**判定(`heartbeatFresh(..., { checkPid: true })`)。
|
|
66
|
+
- launchd 重载竞态:`launchctl bootout` 是异步的 → 紧接着 `bootstrap` 可能返回 0 但随后被挂起的 bootout 摘掉(单元根本没加载)。
|
|
67
|
+
修复:bootout 后等目标**真正从域里消失**再 bootstrap,并加多次回退尝试。
|
|
68
|
+
|
|
69
|
+
### 证据来源
|
|
70
|
+
- `bash pi-proactive/tests/run.sh` → 63/64 文件通过(唯一失败文件 `enumeration-selfimpl.test.ts` 属于另一并行切片的**既存红**,
|
|
71
|
+
其所需实现文件 `tools/enumerate-residue.mjs` 在 git 全历史中都不存在,与本切片无关)。
|
|
72
|
+
- `node --experimental-strip-types --test pi-proactive/tests/windows-port.test.ts` → 13/13 通过(Windows 分支计划生成 / 短路径 / 引号化 / 幂等 / 监督器 / 打包契约)。
|
|
73
|
+
- `node --experimental-strip-types --test pi-proactive/tests/daemon-install-mjs-e2e.test.ts` → macOS launchd **真装 → 回执「已常驻」→ 三证据 → 重复安装幂等 → 真卸零残留** 全通过。
|
|
74
|
+
- **未验证**:Windows 真机(W1..W6 全部)—— 当前无可脚本化的 Windows 执行通道(见 `docs/project/windows-porting.md` §一/§二)。
|
|
75
|
+
|
|
76
|
+
## 0.2.0-beta.5 — 2026-10-31(hotfix)
|
|
77
|
+
|
|
78
|
+
### 修复
|
|
79
|
+
- **阻断级**:`pendingConfigError` 声明晚于模块级 `config = safeConfig()` → TDZ(暂时性死区)→
|
|
80
|
+
每次会话抛 `Cannot access 'pendingConfigError' before initialization`;**用户自定义配置被静默忽略**(一律回落 default);
|
|
81
|
+
若配置含拼错键,该分支同样抛错并逃出 → 整个扩展加载失败。修复:声明上移到模块级初始化之前。
|
|
82
|
+
- `typebox` 同时列入 peerDependencies 与 bundledDependencies(包内自带)→ 兼容 `--legacy-peer-deps` 安装的机器
|
|
83
|
+
(此前报 Cannot find package 'typebox')。
|
|
84
|
+
|
|
85
|
+
### 证据来源
|
|
86
|
+
由 ai-native 项目(跨项目协作)提供第一手证据(真实宿主加载 stderr + 产物源码行号),我方独立复核确认。
|
|
10
87
|
## 0.2.0-beta.4 — 2026-10-31
|
|
11
88
|
|
|
12
89
|
### 新增
|
package/README.md
CHANGED
|
@@ -73,24 +73,29 @@ pi --extension ./pi-proactive/extensions/proactive.ts
|
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
- `on` **立即回执「正在安装…」**(不等 launchd/systemd 冷启动)→ 装完用 `assistant status` 看结果;
|
|
76
|
-
- 会话内与终端 CLI
|
|
76
|
+
- 会话内与终端 CLI **共用同一套安装/卸载实现**(跨平台 `.mjs`:`tools/daemon-install.mjs` / `tools/daemon-uninstall.mjs`);
|
|
77
77
|
- 装不上时回执会给出**缺什么、怎么手动跑、怎么清理**三条可操作信息。
|
|
78
78
|
|
|
79
79
|
**在终端里开 / 关(等价路径)**:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
|
-
# 安装(幂等;macOS 用 launchd,Linux 用 systemd --user
|
|
83
|
-
|
|
82
|
+
# 安装(幂等;macOS 用 launchd,Linux 用 systemd --user,Windows 用计划任务)
|
|
83
|
+
node <安装目录>/tools/daemon-install.mjs
|
|
84
84
|
|
|
85
85
|
# 看看它昨晚干了什么 + 有什么等你确认
|
|
86
86
|
/proactive doctor
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
> **`.sh` 入口的适用范围**:`tools/daemon-install.sh` / `tools/daemon-uninstall.sh` **仅供 unix 维护侧使用**
|
|
90
|
+
> (保留是为了与既有运维流程兼容);**Windows 用户请用上面的 `.mjs` 入口**(`.sh` 依赖 bash,Windows 装不了)。
|
|
91
|
+
> 两个 `.mjs` 入口零第三方依赖、纯 Node(≥ 22.19),三平台同源。
|
|
92
|
+
|
|
89
93
|
- **默认 `enabled=false` + 干跑** → 装了不花钱、不投递任何任务;
|
|
90
94
|
- 开启后仍受**预算硬上限**约束(每小时/每日动作数;token 只做估算)→ 超限自动**降级为只观察**并记账;
|
|
91
95
|
- **不可逆动作绝不自动执行** → 进人工确认队列,早间交班时呈现;
|
|
92
|
-
- 形态:macOS `launchd`(KeepAlive)/ Linux `systemd --user`(Restart=always
|
|
93
|
-
- 自愈:崩溃自动重启、幂等认领、半成品不重放;卸载用 `tools/daemon-uninstall.
|
|
96
|
+
- 形态:macOS `launchd`(KeepAlive)/ Linux `systemd --user`(Restart=always)/ Windows **计划任务**(`schtasks /Create /SC MINUTE`,重复安装覆盖同名任务、不产生第二份);
|
|
97
|
+
- 自愈:崩溃自动重启、幂等认领、半成品不重放;卸载用 `tools/daemon-uninstall.mjs`(幂等、无残留);
|
|
98
|
+
- Windows 验收(逐项证据表 W1..W6):`node <安装目录>/tools/verify-windows.mjs`(非 Windows 上跑同一脚本只做离线自检,真机项标「未验证」)。
|
|
94
99
|
|
|
95
100
|
|
|
96
101
|
|
|
@@ -437,15 +442,15 @@ Usage: `/proactive <子命令>`。下表**逐条对应实现里实际注册的
|
|
|
437
442
|
<!-- BEGIN:GEN tools -->
|
|
438
443
|
| 工具 | 参数 | 作用 | 定义位置 |
|
|
439
444
|
| --- | --- | --- | --- |
|
|
440
|
-
| `schedule_task` | prompt(必填):任务提示词<br>intent(可选):投递意图,默认 wake(取值:wake \| interrupt \| queue \| notify)<br>askUser(可选):触发时是否向用户提问(仅本地)<br>when(可选):触发时间:时长(10m)/ 时刻(18:30)/ ISO 8601;缺省按 10m 处理 | 预约一个稍后触发的任务(真正落库到任务表,到点由调度器投递)。when 支持时长(10m)、时刻(18:30)或 ISO 时间;缺省按 10m 处理。 | `pi-proactive/extensions/proactive.ts:
|
|
441
|
-
| `message_agent` | to(必填):目标 agent 的名字或 agentId<br>kind(可选):消息语义(对方要不要回我);缺省 notify(取值:notify \| ask \| task \| reply \| broadcast)<br>subject(可选):标题<br>body(必填):正文<br>intent(可选):投递意图(怎么投给对方);缺省 wake(取值:wake \| interrupt \| queue \| notify)<br>threadId(可选):话题 / 会话 id(可选) | 给另一个正在运行的 agent 会话发消息(跨进程协作)。对端在线时走 socket 直达;不在线时消息落盘到对端邮箱,待其上线后处理。 | `pi-proactive/extensions/proactive.ts:
|
|
442
|
-
| `list_peers` | name(可选):名字子串(大小写敏感;也命中 agentId)。同名歧义时返回与 message_agent 报错完全一致的候选<br>status(可选):按在场状态过滤(online = 在线 = 空闲 ∪ 忙碌)(取值:online \| idle \| busy \| offline)<br>capability(可选):按能力标签过滤(如 message_agent)<br>includeOffline(可选):是否包含离线对端(缺省 false = 只给在线) | 列出本机可见的其它 agent 对端(名字 / agentId / 在场状态 / 心跳龄 / 能力标签 / 在途任务数 / 传输方式),可按名字、状态、能力过滤。**只读**:不产生任何对外行为。发消息前先查它(先查后发),别盲发 —— 离线对端现在打扰它不合适。 | `pi-proactive/extensions/proactive.ts:
|
|
443
|
-
| `proactive_query` | action(必填):查询动作(与 /proactive 子命令同名)(取值:peers \| history \| status \| tasks \| doctor \| help \| config \| supervisor)<br>name(可选):peers:名字子串过滤<br>all(可选):peers:是否包含离线对端<br>idle(可选):peers:只看空闲对端<br>detail(可选):peers:只看这一个 agentId<br>scope(可选):history:范围(对端 / 我)(取值:peer \| me)<br>peer(可选):history:按对端 agentId 过滤<br>limit(可选):history:条数(1-200 的整数)<br>json(可选):doctor / config:输出 JSON | 查询域:查对端(peers)/ 看主动行为历史(history)/ 看状态(status)/ 看任务(tasks)/ 只读体检(doctor)/ 看命令手册(help)/ 查配置清单(config)/ 看监督面(supervisor)。**只读**:不产生任何对外行为。 | `pi-proactive/extensions/proactive.ts:
|
|
444
|
-
| `progress_query` | | 查项目进度与 ETA:返回**区间 + 置信度 + 归因 + 阻塞项**(不给单点数字)。口径唯一(scripts/progress-clock.py),与人侧 `/proactive progress` 同源同口径、逐字一致。**只读**:不产生任何对外行为。 | `pi-proactive/extensions/proactive.ts:
|
|
445
|
-
| `proactive_schedule` | action(必填):排程动作(与 /proactive 子命令同名)(取值:in \| at \| every \| cancel)<br>when(可选):in/at/every:触发时间(时长 10m / 时刻 18:30)<br>content(可选):in/at/every:任务内容<br>id(可选):cancel:任务 id | 排程域:定时(in)/ 定点(at)/ 周期(every)/ 取消(cancel)。落到真实任务表,到点由调度器投递;与命令面 `/proactive in\|at\|every\|cancel` 同源。 | `pi-proactive/extensions/proactive.ts:
|
|
446
|
-
| `proactive_peer` | action(必填):对端动作(与 /proactive 子命令同名)(取值:tell \| spawn)<br>peer(可选):tell:目标对端(名字或 agentId)<br>content(可选):tell:指令内容<br>kind(可选):tell:指令类型(缺省 ask)(取值:ask \| task \| notify)<br>subject(可选):tell:标题<br>thread(可选):tell:线程 id<br>name(可选):spawn:对端名字 | 对端协作域:给对端下指令(tell)/ 创建对端会话(spawn,受同时存活数上限)。tell 的目标解析与回执三态与命令面 `/proactive tell` 完全同源。 | `pi-proactive/extensions/proactive.ts:
|
|
447
|
-
| `proactive_manage` | action(必填):管理动作(与 /proactive 子命令同名)(取值:on \| off \| idle \| test)<br>state(可选):idle:开 / 关(取值:on \| off) | 管理域:主动性总开关(on/off)、空闲触发开关(idle)、自检(test)。这些能力属 human-gated:调用只会回报「需人工确认」并给出可复制的人工指令,**不会**自行改动全局开关。 | `pi-proactive/extensions/proactive.ts:
|
|
448
|
-
| `proactive_assistant` | action(必填):常驻助理动作:on 打开 / off 关闭 / status 看状态 / restart 重装(与 /proactive assistant 同名)(取值:on \| off \| status \| restart) | 常驻助理(7×24)控制:on 打开 / off 关闭 / status 看状态 / restart 重装。与人侧 `/proactive assistant …` 同源同分派 → 逐字一致;安装**不阻塞**(立即回执「正在安装…」,结果用 status 查)。装上默认 enabled=false + 干跑 → 不花钱、不主动打扰。 | `pi-proactive/extensions/proactive.ts:
|
|
445
|
+
| `schedule_task` | prompt(必填):任务提示词<br>intent(可选):投递意图,默认 wake(取值:wake \| interrupt \| queue \| notify)<br>askUser(可选):触发时是否向用户提问(仅本地)<br>when(可选):触发时间:时长(10m)/ 时刻(18:30)/ ISO 8601;缺省按 10m 处理 | 预约一个稍后触发的任务(真正落库到任务表,到点由调度器投递)。when 支持时长(10m)、时刻(18:30)或 ISO 时间;缺省按 10m 处理。 | `pi-proactive/extensions/proactive.ts:648` |
|
|
446
|
+
| `message_agent` | to(必填):目标 agent 的名字或 agentId<br>kind(可选):消息语义(对方要不要回我);缺省 notify(取值:notify \| ask \| task \| reply \| broadcast)<br>subject(可选):标题<br>body(必填):正文<br>intent(可选):投递意图(怎么投给对方);缺省 wake(取值:wake \| interrupt \| queue \| notify)<br>threadId(可选):话题 / 会话 id(可选) | 给另一个正在运行的 agent 会话发消息(跨进程协作)。对端在线时走 socket 直达;不在线时消息落盘到对端邮箱,待其上线后处理。 | `pi-proactive/extensions/proactive.ts:693` |
|
|
447
|
+
| `list_peers` | name(可选):名字子串(大小写敏感;也命中 agentId)。同名歧义时返回与 message_agent 报错完全一致的候选<br>status(可选):按在场状态过滤(online = 在线 = 空闲 ∪ 忙碌)(取值:online \| idle \| busy \| offline)<br>capability(可选):按能力标签过滤(如 message_agent)<br>includeOffline(可选):是否包含离线对端(缺省 false = 只给在线) | 列出本机可见的其它 agent 对端(名字 / agentId / 在场状态 / 心跳龄 / 能力标签 / 在途任务数 / 传输方式),可按名字、状态、能力过滤。**只读**:不产生任何对外行为。发消息前先查它(先查后发),别盲发 —— 离线对端现在打扰它不合适。 | `pi-proactive/extensions/proactive.ts:767` |
|
|
448
|
+
| `proactive_query` | action(必填):查询动作(与 /proactive 子命令同名)(取值:peers \| history \| status \| tasks \| doctor \| help \| config \| supervisor)<br>name(可选):peers:名字子串过滤<br>all(可选):peers:是否包含离线对端<br>idle(可选):peers:只看空闲对端<br>detail(可选):peers:只看这一个 agentId<br>scope(可选):history:范围(对端 / 我)(取值:peer \| me)<br>peer(可选):history:按对端 agentId 过滤<br>limit(可选):history:条数(1-200 的整数)<br>json(可选):doctor / config:输出 JSON | 查询域:查对端(peers)/ 看主动行为历史(history)/ 看状态(status)/ 看任务(tasks)/ 只读体检(doctor)/ 看命令手册(help)/ 查配置清单(config)/ 看监督面(supervisor)。**只读**:不产生任何对外行为。 | `pi-proactive/extensions/proactive.ts:804` |
|
|
449
|
+
| `progress_query` | | 查项目进度与 ETA:返回**区间 + 置信度 + 归因 + 阻塞项**(不给单点数字)。口径唯一(scripts/progress-clock.py),与人侧 `/proactive progress` 同源同口径、逐字一致。**只读**:不产生任何对外行为。 | `pi-proactive/extensions/proactive.ts:842` |
|
|
450
|
+
| `proactive_schedule` | action(必填):排程动作(与 /proactive 子命令同名)(取值:in \| at \| every \| cancel)<br>when(可选):in/at/every:触发时间(时长 10m / 时刻 18:30)<br>content(可选):in/at/every:任务内容<br>id(可选):cancel:任务 id | 排程域:定时(in)/ 定点(at)/ 周期(every)/ 取消(cancel)。落到真实任务表,到点由调度器投递;与命令面 `/proactive in\|at\|every\|cancel` 同源。 | `pi-proactive/extensions/proactive.ts:856` |
|
|
451
|
+
| `proactive_peer` | action(必填):对端动作(与 /proactive 子命令同名)(取值:tell \| spawn)<br>peer(可选):tell:目标对端(名字或 agentId)<br>content(可选):tell:指令内容<br>kind(可选):tell:指令类型(缺省 ask)(取值:ask \| task \| notify)<br>subject(可选):tell:标题<br>thread(可选):tell:线程 id<br>name(可选):spawn:对端名字 | 对端协作域:给对端下指令(tell)/ 创建对端会话(spawn,受同时存活数上限)。tell 的目标解析与回执三态与命令面 `/proactive tell` 完全同源。 | `pi-proactive/extensions/proactive.ts:875` |
|
|
452
|
+
| `proactive_manage` | action(必填):管理动作(与 /proactive 子命令同名)(取值:on \| off \| idle \| test)<br>state(可选):idle:开 / 关(取值:on \| off) | 管理域:主动性总开关(on/off)、空闲触发开关(idle)、自检(test)。这些能力属 human-gated:调用只会回报「需人工确认」并给出可复制的人工指令,**不会**自行改动全局开关。 | `pi-proactive/extensions/proactive.ts:914` |
|
|
453
|
+
| `proactive_assistant` | action(必填):常驻助理动作:on 打开 / off 关闭 / status 看状态 / restart 重装(与 /proactive assistant 同名)(取值:on \| off \| status \| restart) | 常驻助理(7×24)控制:on 打开 / off 关闭 / status 看状态 / restart 重装。与人侧 `/proactive assistant …` 同源同分派 → 逐字一致;安装**不阻塞**(立即回执「正在安装…」,结果用 status 查)。装上默认 enabled=false + 干跑 → 不花钱、不主动打扰。 | `pi-proactive/extensions/proactive.ts:926` |
|
|
449
454
|
<!-- END:GEN tools -->
|
|
450
455
|
|
|
451
456
|
> **模型工具面真源 = 实现**:上表由 `scripts/gen-doc-tables.sh` 从 `pi-proactive/extensions/proactive.ts` 的
|
|
@@ -725,3 +730,16 @@ agents/<ref>.md # agent 定义
|
|
|
725
730
|
| 长时驻留(>10 分钟) | 长时间运行下的资源 / 孤儿进程 / 内存行为 | **未验证**(实测每个宿主生命周期 ≤ 4 分钟) |
|
|
726
731
|
| `every` 小时级节拍 | 小时级运行下的节拍漂移是否累积 | **未验证**(短时实测节拍正常) |
|
|
727
732
|
| 门禁 G11(文档↔实现一致性断言) | 机器化的文档↔实现一致性门禁 | **已存在**(判据在 `scripts/merge-gate.sh` 的 G11 段,默认零容忍;含**反向断言**:文档的否定式声明若指向仓库里已存在的对象 → 判为违规;回归自测在 `scripts/tests/gate-doc-consistency.test.sh`) |
|
|
733
|
+
|
|
734
|
+
### 升级
|
|
735
|
+
|
|
736
|
+
```bash
|
|
737
|
+
# ① pi 包路径(宿主实际加载的就是这一份 —— settings.json 记 npm:pi-proactive)
|
|
738
|
+
cd ~/.pi/agent/npm && npm install pi-proactive@latest
|
|
739
|
+
# ② 独立全局安装(会多出第二份副本 → 宿主会报 duplicate runtime modules,慎用)
|
|
740
|
+
npm i -g --prefix ~/.npm-global pi-proactive@latest
|
|
741
|
+
# ③ --legacy-peer-deps 只在【混合安装目录】才需要(该目录有与 pi-proactive 无关的 peer 冲突);
|
|
742
|
+
# 在干净空目录里【不需要】(typebox 已随包 bundled)
|
|
743
|
+
# ⚠️ 不要用 npm i -g 覆盖宿主加载的那一份 —— 那是两个不同的副本
|
|
744
|
+
# ⚠️ 未知配置项会让整份配置回落默认值(fail-closed):一次拼错会连带关掉空闲触发
|
|
745
|
+
```
|
|
@@ -200,6 +200,8 @@ let currentStore;
|
|
|
200
200
|
let currentScheduler;
|
|
201
201
|
/** 当前会话 id(宿主 `ctx.sessionManager.getSessionId()`)→ **跨进程重启稳定**(session 作用域归属依据)。 */
|
|
202
202
|
let currentSessionId;
|
|
203
|
+
// TDZ 修复:必须早于模块级 config 初始化(会调用 safeConfig)声明
|
|
204
|
+
let pendingConfigError;
|
|
203
205
|
let config = safeConfig();
|
|
204
206
|
let tracker = new ActivityTracker({ idleDetect: config.idleDetect });
|
|
205
207
|
let guard = new Guard(config);
|
|
@@ -294,7 +296,6 @@ let lastPeerWatchAt = 0;
|
|
|
294
296
|
* 通道限制:配置加载发生在宿主给出 `ctx` 之前 → 先存起来,
|
|
295
297
|
* 由 `session_start`(拿得到 `ctx.ui.notify`)呈现给用户(stderr 亦有一份)。
|
|
296
298
|
*/
|
|
297
|
-
let pendingConfigError;
|
|
298
299
|
function safeConfig() {
|
|
299
300
|
try {
|
|
300
301
|
pendingConfigError = undefined;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pi-proactive 常驻助理的**跨平台保活监督器**(supervisor)。
|
|
4
|
+
*
|
|
5
|
+
* 角色:**同时**充当两个宿主里的「保活容器」——
|
|
6
|
+
* ① 系统宿主:macOS launchd / Linux systemd --user / **Windows 计划任务** 跑的就是它;
|
|
7
|
+
* ② 另一实例(重复安装 / 手工起)跑的是同一个脚本 → 谁先拿到「值班锁」谁当值班进程。
|
|
8
|
+
*
|
|
9
|
+
* 为什么必须这样(形态选型 §2.5):
|
|
10
|
+
* - 「装了服务」≠「有常驻」:进程崩了 / 被回收 → 助理就没了;
|
|
11
|
+
* - 所以:**宿主没了,另一个实例立刻接管**(助理不会随宿主一起消失);
|
|
12
|
+
* - 判定(**可判定**,不猜):
|
|
13
|
+
* · 心跳新鲜(< STALE_MS)→ 已有实例在值班 → 本实例待命;
|
|
14
|
+
* · 心跳陈旧/缺失 → 抢 `daemon.lock`(mkdir 原子锁)→ 拿到锁的成为值班进程。
|
|
15
|
+
*
|
|
16
|
+
* 与旧版 `daemon/run-daemon.sh` 的差异:**同一份逻辑的跨平台实现**(Windows 用户装不到 bash)。
|
|
17
|
+
* 行为口径保持一致(心跳 5s 周期 → 20s 判定陈旧;锁目录原子创建;崩溃后自动重启)。
|
|
18
|
+
*
|
|
19
|
+
* 用法(由安装器生成调用参数):
|
|
20
|
+
* node run-daemon.mjs --agent-dir <dir> --agent-id <id> --entry <daemon.ts|daemon.js> \
|
|
21
|
+
* --log-dir <dir> [--node <nodeBin>] [--work-dir <dir>]
|
|
22
|
+
*/
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { spawn } from "node:child_process";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
27
|
+
|
|
28
|
+
/** 心跳陈旧阈值(src/daemon.ts 的心跳周期是 5s → 20s 没更新即判定没在跑)。 */
|
|
29
|
+
export const STALE_MS = 20_000;
|
|
30
|
+
/** 待命/重启的轮询间隔。 */
|
|
31
|
+
export const LOOP_INTERVAL_MS = 3_000;
|
|
32
|
+
|
|
33
|
+
/** 解析命令行参数。 */
|
|
34
|
+
export function parseArgs(argv) {
|
|
35
|
+
const o = {
|
|
36
|
+
agentDir: "",
|
|
37
|
+
agentId: "",
|
|
38
|
+
entry: "",
|
|
39
|
+
logDir: "",
|
|
40
|
+
node: process.execPath,
|
|
41
|
+
workDir: "",
|
|
42
|
+
};
|
|
43
|
+
for (let i = 0; i < argv.length; i++) {
|
|
44
|
+
const a = argv[i];
|
|
45
|
+
const next = () => argv[++i] ?? "";
|
|
46
|
+
if (a === "--agent-dir") o.agentDir = next();
|
|
47
|
+
else if (a === "--agent-id") o.agentId = next();
|
|
48
|
+
else if (a === "--entry") o.entry = next();
|
|
49
|
+
else if (a === "--log-dir") o.logDir = next();
|
|
50
|
+
else if (a === "--node") o.node = next();
|
|
51
|
+
else if (a === "--work-dir") o.workDir = next();
|
|
52
|
+
}
|
|
53
|
+
if (o.logDir) o.logDir = path.resolve(o.logDir);
|
|
54
|
+
return o;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 心跳文件路径(与 `src/daemon-config.ts` 同口径)。 */
|
|
58
|
+
export function heartbeatPathFor(agentDir) {
|
|
59
|
+
return path.join(agentDir, "proactive", "daemon", "heartbeat.json");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 值班锁目录。 */
|
|
63
|
+
export function lockPathFor(agentDir) {
|
|
64
|
+
return path.join(agentDir, "proactive", "daemon", "daemon.lock");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 读心跳(坏数据 → null;巡检脚本不能被脏输入搞崩)。 */
|
|
68
|
+
export function readHeartbeat(file) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 心跳是否新鲜(= 已经有实例在值班)。
|
|
78
|
+
*
|
|
79
|
+
* `opts.checkPid`:心跳里的进程**已经不在**时不算新鲜 —— 否则「刚被杀掉的旧心跳」
|
|
80
|
+
* 会让新监督器白等 20s(重启延迟);这正是重复安装时「心跳不前进」的根因。
|
|
81
|
+
*
|
|
82
|
+
* @param {string} file
|
|
83
|
+
* @param {number} [nowMs]
|
|
84
|
+
* @param {number} [staleMs]
|
|
85
|
+
* @param {{ checkPid?: boolean }} [opts]
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
export function heartbeatFresh(file, nowMs = Date.now(), staleMs = STALE_MS, opts = {}) {
|
|
89
|
+
const hb = readHeartbeat(file);
|
|
90
|
+
if (!hb || typeof hb.ts !== "number") return false;
|
|
91
|
+
if (nowMs - hb.ts >= staleMs) return false;
|
|
92
|
+
if (opts.checkPid && typeof hb.pid === "number" && hb.pid > 0) {
|
|
93
|
+
try {
|
|
94
|
+
process.kill(hb.pid, 0);
|
|
95
|
+
} catch {
|
|
96
|
+
return false; // 心跳是死的(进程已退出)→ 立刻接管
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 抢值班锁(mkdir 是原子的 → 两个监督器同时启动也只有一个能当值班进程)。
|
|
104
|
+
*
|
|
105
|
+
* 锁可能是上次崩溃留下的 → 锁里的 pid 不在了就清掉重抢。
|
|
106
|
+
*/
|
|
107
|
+
export function acquireLock(lockDir) {
|
|
108
|
+
const pidFile = path.join(lockDir, "pid");
|
|
109
|
+
try {
|
|
110
|
+
fs.mkdirSync(lockDir);
|
|
111
|
+
} catch {
|
|
112
|
+
// 已存在 → 看锁主人还活着没
|
|
113
|
+
let owner = 0;
|
|
114
|
+
try {
|
|
115
|
+
owner = Number(fs.readFileSync(pidFile, "utf8").trim()) || 0;
|
|
116
|
+
} catch {
|
|
117
|
+
owner = 0;
|
|
118
|
+
}
|
|
119
|
+
if (owner > 0) {
|
|
120
|
+
try {
|
|
121
|
+
process.kill(owner, 0);
|
|
122
|
+
return false; // 主人还活着 → 不抢
|
|
123
|
+
} catch {
|
|
124
|
+
// 主人已死 → 清理后重抢
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
fs.mkdirSync(lockDir);
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
fs.writeFileSync(pidFile, String(process.pid));
|
|
140
|
+
} catch {
|
|
141
|
+
/* 写不进锁里的 pid 不致命(只是失去「主人已死」的自愈线索) */
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 释放值班锁。 */
|
|
147
|
+
export function releaseLock(lockDir) {
|
|
148
|
+
try {
|
|
149
|
+
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
150
|
+
} catch {
|
|
151
|
+
/* 忽略 */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** 拉起值班进程的命令行(.ts 入口需要类型擦除)。 */
|
|
156
|
+
export function daemonCommand(entry, agentDir) {
|
|
157
|
+
const args = entry.endsWith(".ts") ? ["--experimental-strip-types", entry] : [entry];
|
|
158
|
+
return [...args, "--agent-dir", agentDir];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 睡一会儿(可被打断)。 */
|
|
162
|
+
function sleep(ms) {
|
|
163
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 监督器主循环。
|
|
168
|
+
*
|
|
169
|
+
* @param opts.agentDir agent 根(心跳/锁/日志都落在这里)
|
|
170
|
+
* @param opts.entry 值班进程入口(`src/daemon.ts` 或 `build/src/daemon.js`)
|
|
171
|
+
* @param opts.logDir 日志目录(stdout/stderr 落盘)
|
|
172
|
+
* @param opts.signal 收到该信号即退出(清理子进程 + 释放锁)
|
|
173
|
+
*/
|
|
174
|
+
export async function runSupervisor(opts) {
|
|
175
|
+
const agentDir = opts.agentDir;
|
|
176
|
+
const nodeBin = opts.nodeBin || process.execPath;
|
|
177
|
+
const hbFile = heartbeatPathFor(agentDir);
|
|
178
|
+
const lockDir = lockPathFor(agentDir);
|
|
179
|
+
const logDir = opts.logDir || path.join(agentDir, "proactive", "daemon");
|
|
180
|
+
let child;
|
|
181
|
+
let stopping = false;
|
|
182
|
+
|
|
183
|
+
const stop = () => {
|
|
184
|
+
stopping = true;
|
|
185
|
+
if (child && child.exitCode === null) {
|
|
186
|
+
try {
|
|
187
|
+
child.kill("SIGTERM");
|
|
188
|
+
} catch {
|
|
189
|
+
/* 忽略 */
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
process.on(opts.signal ?? "SIGTERM", stop);
|
|
194
|
+
|
|
195
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
196
|
+
fs.mkdirSync(path.dirname(hbFile), { recursive: true });
|
|
197
|
+
|
|
198
|
+
while (!stopping) {
|
|
199
|
+
if (heartbeatFresh(hbFile, Date.now(), STALE_MS, { checkPid: true })) {
|
|
200
|
+
// 已有实例在值班 → 本实例待命,不重复拉起(避免双开 → 避免重复打扰)
|
|
201
|
+
await sleep(LOOP_INTERVAL_MS);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (acquireLock(lockDir)) {
|
|
205
|
+
const out = fs.openSync(path.join(logDir, "daemon.out.log"), "a");
|
|
206
|
+
const err = fs.openSync(path.join(logDir, "daemon.err.log"), "a");
|
|
207
|
+
try {
|
|
208
|
+
child = spawn(nodeBin, daemonCommand(opts.entry, agentDir), {
|
|
209
|
+
cwd: opts.workDir || agentDir,
|
|
210
|
+
stdio: ["ignore", out, err],
|
|
211
|
+
env: { ...process.env, PI_PROACTIVE_AGENT_ID: process.env.PI_PROACTIVE_AGENT_ID ?? "" },
|
|
212
|
+
});
|
|
213
|
+
await new Promise((resolve) => {
|
|
214
|
+
child?.once("exit", () => resolve());
|
|
215
|
+
child?.once("error", () => resolve());
|
|
216
|
+
});
|
|
217
|
+
} finally {
|
|
218
|
+
child = undefined;
|
|
219
|
+
try {
|
|
220
|
+
fs.closeSync(out);
|
|
221
|
+
fs.closeSync(err);
|
|
222
|
+
} catch {
|
|
223
|
+
/* 忽略 */
|
|
224
|
+
}
|
|
225
|
+
// 值班进程退出 → 释放锁,稍后重试(崩溃自愈)
|
|
226
|
+
releaseLock(lockDir);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (!stopping) await sleep(LOOP_INTERVAL_MS);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (child && child.exitCode === null) {
|
|
233
|
+
try {
|
|
234
|
+
child.kill("SIGTERM");
|
|
235
|
+
} catch {
|
|
236
|
+
/* 忽略 */
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
releaseLock(lockDir);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ------------------------------------------------------------------ 入口 ----
|
|
243
|
+
|
|
244
|
+
const invokedDirectly =
|
|
245
|
+
process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
246
|
+
|
|
247
|
+
if (invokedDirectly) {
|
|
248
|
+
const args = parseArgs(process.argv.slice(2));
|
|
249
|
+
if (!args.agentDir || !args.entry) {
|
|
250
|
+
console.error("用法:node run-daemon.mjs --agent-dir <dir> --agent-id <id> --entry <daemonEntry> --log-dir <dir>");
|
|
251
|
+
process.exit(2);
|
|
252
|
+
}
|
|
253
|
+
if (args.workDir) {
|
|
254
|
+
try {
|
|
255
|
+
process.chdir(args.workDir);
|
|
256
|
+
} catch {
|
|
257
|
+
/* 工作目录不存在不影响值班 */
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
process.env.PI_PROACTIVE_AGENT_ID = args.agentId || process.env.PI_PROACTIVE_AGENT_ID || "";
|
|
261
|
+
await runSupervisor({
|
|
262
|
+
agentDir: args.agentDir,
|
|
263
|
+
entry: args.entry,
|
|
264
|
+
logDir: args.logDir,
|
|
265
|
+
nodeBin: args.node,
|
|
266
|
+
workDir: args.workDir,
|
|
267
|
+
});
|
|
268
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-proactive",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.6",
|
|
4
4
|
"description": "给 Pi 增加主动性:空闲时主动推进或提问、按时间点主动发起任务、多个会话之间对话协作。(专有软件:分发物不含源码,使用条款见包内 LICENSE)",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
@@ -23,11 +23,17 @@
|
|
|
23
23
|
"README.md",
|
|
24
24
|
"CHANGELOG.md",
|
|
25
25
|
"LICENSE",
|
|
26
|
+
"tools/daemon-install.mjs",
|
|
27
|
+
"tools/daemon-uninstall.mjs",
|
|
28
|
+
"tools/daemon-common.mjs",
|
|
29
|
+
"tools/verify-windows.mjs",
|
|
30
|
+
"tools/enumerate-residue.mjs",
|
|
26
31
|
"tools/daemon-install.sh",
|
|
27
32
|
"tools/daemon-uninstall.sh",
|
|
28
33
|
"daemon/",
|
|
29
34
|
"build/cli.mjs",
|
|
30
|
-
"tools/cli.mjs"
|
|
35
|
+
"tools/cli.mjs",
|
|
36
|
+
"daemon/run-daemon.mjs"
|
|
31
37
|
],
|
|
32
38
|
"pi": {
|
|
33
39
|
"extensions": [
|
|
@@ -52,5 +58,8 @@
|
|
|
52
58
|
},
|
|
53
59
|
"bin": {
|
|
54
60
|
"pi-proactive": "./build/cli.mjs"
|
|
55
|
-
}
|
|
61
|
+
},
|
|
62
|
+
"bundledDependencies": [
|
|
63
|
+
"typebox"
|
|
64
|
+
]
|
|
56
65
|
}
|
|
@@ -21,15 +21,15 @@ description: >-
|
|
|
21
21
|
<!-- BEGIN:GEN tools -->
|
|
22
22
|
| 工具 | 参数 | 作用 | 定义位置 |
|
|
23
23
|
| --- | --- | --- | --- |
|
|
24
|
-
| `schedule_task` | prompt(必填):任务提示词<br>intent(可选):投递意图,默认 wake(取值:wake \| interrupt \| queue \| notify)<br>askUser(可选):触发时是否向用户提问(仅本地)<br>when(可选):触发时间:时长(10m)/ 时刻(18:30)/ ISO 8601;缺省按 10m 处理 | 预约一个稍后触发的任务(真正落库到任务表,到点由调度器投递)。when 支持时长(10m)、时刻(18:30)或 ISO 时间;缺省按 10m 处理。 | `pi-proactive/extensions/proactive.ts:
|
|
25
|
-
| `message_agent` | to(必填):目标 agent 的名字或 agentId<br>kind(可选):消息语义(对方要不要回我);缺省 notify(取值:notify \| ask \| task \| reply \| broadcast)<br>subject(可选):标题<br>body(必填):正文<br>intent(可选):投递意图(怎么投给对方);缺省 wake(取值:wake \| interrupt \| queue \| notify)<br>threadId(可选):话题 / 会话 id(可选) | 给另一个正在运行的 agent 会话发消息(跨进程协作)。对端在线时走 socket 直达;不在线时消息落盘到对端邮箱,待其上线后处理。 | `pi-proactive/extensions/proactive.ts:
|
|
26
|
-
| `list_peers` | name(可选):名字子串(大小写敏感;也命中 agentId)。同名歧义时返回与 message_agent 报错完全一致的候选<br>status(可选):按在场状态过滤(online = 在线 = 空闲 ∪ 忙碌)(取值:online \| idle \| busy \| offline)<br>capability(可选):按能力标签过滤(如 message_agent)<br>includeOffline(可选):是否包含离线对端(缺省 false = 只给在线) | 列出本机可见的其它 agent 对端(名字 / agentId / 在场状态 / 心跳龄 / 能力标签 / 在途任务数 / 传输方式),可按名字、状态、能力过滤。**只读**:不产生任何对外行为。发消息前先查它(先查后发),别盲发 —— 离线对端现在打扰它不合适。 | `pi-proactive/extensions/proactive.ts:
|
|
27
|
-
| `proactive_query` | action(必填):查询动作(与 /proactive 子命令同名)(取值:peers \| history \| status \| tasks \| doctor \| help \| config \| supervisor)<br>name(可选):peers:名字子串过滤<br>all(可选):peers:是否包含离线对端<br>idle(可选):peers:只看空闲对端<br>detail(可选):peers:只看这一个 agentId<br>scope(可选):history:范围(对端 / 我)(取值:peer \| me)<br>peer(可选):history:按对端 agentId 过滤<br>limit(可选):history:条数(1-200 的整数)<br>json(可选):doctor / config:输出 JSON | 查询域:查对端(peers)/ 看主动行为历史(history)/ 看状态(status)/ 看任务(tasks)/ 只读体检(doctor)/ 看命令手册(help)/ 查配置清单(config)/ 看监督面(supervisor)。**只读**:不产生任何对外行为。 | `pi-proactive/extensions/proactive.ts:
|
|
28
|
-
| `progress_query` | | 查项目进度与 ETA:返回**区间 + 置信度 + 归因 + 阻塞项**(不给单点数字)。口径唯一(scripts/progress-clock.py),与人侧 `/proactive progress` 同源同口径、逐字一致。**只读**:不产生任何对外行为。 | `pi-proactive/extensions/proactive.ts:
|
|
29
|
-
| `proactive_schedule` | action(必填):排程动作(与 /proactive 子命令同名)(取值:in \| at \| every \| cancel)<br>when(可选):in/at/every:触发时间(时长 10m / 时刻 18:30)<br>content(可选):in/at/every:任务内容<br>id(可选):cancel:任务 id | 排程域:定时(in)/ 定点(at)/ 周期(every)/ 取消(cancel)。落到真实任务表,到点由调度器投递;与命令面 `/proactive in\|at\|every\|cancel` 同源。 | `pi-proactive/extensions/proactive.ts:
|
|
30
|
-
| `proactive_peer` | action(必填):对端动作(与 /proactive 子命令同名)(取值:tell \| spawn)<br>peer(可选):tell:目标对端(名字或 agentId)<br>content(可选):tell:指令内容<br>kind(可选):tell:指令类型(缺省 ask)(取值:ask \| task \| notify)<br>subject(可选):tell:标题<br>thread(可选):tell:线程 id<br>name(可选):spawn:对端名字 | 对端协作域:给对端下指令(tell)/ 创建对端会话(spawn,受同时存活数上限)。tell 的目标解析与回执三态与命令面 `/proactive tell` 完全同源。 | `pi-proactive/extensions/proactive.ts:
|
|
31
|
-
| `proactive_manage` | action(必填):管理动作(与 /proactive 子命令同名)(取值:on \| off \| idle \| test)<br>state(可选):idle:开 / 关(取值:on \| off) | 管理域:主动性总开关(on/off)、空闲触发开关(idle)、自检(test)。这些能力属 human-gated:调用只会回报「需人工确认」并给出可复制的人工指令,**不会**自行改动全局开关。 | `pi-proactive/extensions/proactive.ts:
|
|
32
|
-
| `proactive_assistant` | action(必填):常驻助理动作:on 打开 / off 关闭 / status 看状态 / restart 重装(与 /proactive assistant 同名)(取值:on \| off \| status \| restart) | 常驻助理(7×24)控制:on 打开 / off 关闭 / status 看状态 / restart 重装。与人侧 `/proactive assistant …` 同源同分派 → 逐字一致;安装**不阻塞**(立即回执「正在安装…」,结果用 status 查)。装上默认 enabled=false + 干跑 → 不花钱、不主动打扰。 | `pi-proactive/extensions/proactive.ts:
|
|
24
|
+
| `schedule_task` | prompt(必填):任务提示词<br>intent(可选):投递意图,默认 wake(取值:wake \| interrupt \| queue \| notify)<br>askUser(可选):触发时是否向用户提问(仅本地)<br>when(可选):触发时间:时长(10m)/ 时刻(18:30)/ ISO 8601;缺省按 10m 处理 | 预约一个稍后触发的任务(真正落库到任务表,到点由调度器投递)。when 支持时长(10m)、时刻(18:30)或 ISO 时间;缺省按 10m 处理。 | `pi-proactive/extensions/proactive.ts:648` |
|
|
25
|
+
| `message_agent` | to(必填):目标 agent 的名字或 agentId<br>kind(可选):消息语义(对方要不要回我);缺省 notify(取值:notify \| ask \| task \| reply \| broadcast)<br>subject(可选):标题<br>body(必填):正文<br>intent(可选):投递意图(怎么投给对方);缺省 wake(取值:wake \| interrupt \| queue \| notify)<br>threadId(可选):话题 / 会话 id(可选) | 给另一个正在运行的 agent 会话发消息(跨进程协作)。对端在线时走 socket 直达;不在线时消息落盘到对端邮箱,待其上线后处理。 | `pi-proactive/extensions/proactive.ts:693` |
|
|
26
|
+
| `list_peers` | name(可选):名字子串(大小写敏感;也命中 agentId)。同名歧义时返回与 message_agent 报错完全一致的候选<br>status(可选):按在场状态过滤(online = 在线 = 空闲 ∪ 忙碌)(取值:online \| idle \| busy \| offline)<br>capability(可选):按能力标签过滤(如 message_agent)<br>includeOffline(可选):是否包含离线对端(缺省 false = 只给在线) | 列出本机可见的其它 agent 对端(名字 / agentId / 在场状态 / 心跳龄 / 能力标签 / 在途任务数 / 传输方式),可按名字、状态、能力过滤。**只读**:不产生任何对外行为。发消息前先查它(先查后发),别盲发 —— 离线对端现在打扰它不合适。 | `pi-proactive/extensions/proactive.ts:767` |
|
|
27
|
+
| `proactive_query` | action(必填):查询动作(与 /proactive 子命令同名)(取值:peers \| history \| status \| tasks \| doctor \| help \| config \| supervisor)<br>name(可选):peers:名字子串过滤<br>all(可选):peers:是否包含离线对端<br>idle(可选):peers:只看空闲对端<br>detail(可选):peers:只看这一个 agentId<br>scope(可选):history:范围(对端 / 我)(取值:peer \| me)<br>peer(可选):history:按对端 agentId 过滤<br>limit(可选):history:条数(1-200 的整数)<br>json(可选):doctor / config:输出 JSON | 查询域:查对端(peers)/ 看主动行为历史(history)/ 看状态(status)/ 看任务(tasks)/ 只读体检(doctor)/ 看命令手册(help)/ 查配置清单(config)/ 看监督面(supervisor)。**只读**:不产生任何对外行为。 | `pi-proactive/extensions/proactive.ts:804` |
|
|
28
|
+
| `progress_query` | | 查项目进度与 ETA:返回**区间 + 置信度 + 归因 + 阻塞项**(不给单点数字)。口径唯一(scripts/progress-clock.py),与人侧 `/proactive progress` 同源同口径、逐字一致。**只读**:不产生任何对外行为。 | `pi-proactive/extensions/proactive.ts:842` |
|
|
29
|
+
| `proactive_schedule` | action(必填):排程动作(与 /proactive 子命令同名)(取值:in \| at \| every \| cancel)<br>when(可选):in/at/every:触发时间(时长 10m / 时刻 18:30)<br>content(可选):in/at/every:任务内容<br>id(可选):cancel:任务 id | 排程域:定时(in)/ 定点(at)/ 周期(every)/ 取消(cancel)。落到真实任务表,到点由调度器投递;与命令面 `/proactive in\|at\|every\|cancel` 同源。 | `pi-proactive/extensions/proactive.ts:856` |
|
|
30
|
+
| `proactive_peer` | action(必填):对端动作(与 /proactive 子命令同名)(取值:tell \| spawn)<br>peer(可选):tell:目标对端(名字或 agentId)<br>content(可选):tell:指令内容<br>kind(可选):tell:指令类型(缺省 ask)(取值:ask \| task \| notify)<br>subject(可选):tell:标题<br>thread(可选):tell:线程 id<br>name(可选):spawn:对端名字 | 对端协作域:给对端下指令(tell)/ 创建对端会话(spawn,受同时存活数上限)。tell 的目标解析与回执三态与命令面 `/proactive tell` 完全同源。 | `pi-proactive/extensions/proactive.ts:875` |
|
|
31
|
+
| `proactive_manage` | action(必填):管理动作(与 /proactive 子命令同名)(取值:on \| off \| idle \| test)<br>state(可选):idle:开 / 关(取值:on \| off) | 管理域:主动性总开关(on/off)、空闲触发开关(idle)、自检(test)。这些能力属 human-gated:调用只会回报「需人工确认」并给出可复制的人工指令,**不会**自行改动全局开关。 | `pi-proactive/extensions/proactive.ts:914` |
|
|
32
|
+
| `proactive_assistant` | action(必填):常驻助理动作:on 打开 / off 关闭 / status 看状态 / restart 重装(与 /proactive assistant 同名)(取值:on \| off \| status \| restart) | 常驻助理(7×24)控制:on 打开 / off 关闭 / status 看状态 / restart 重装。与人侧 `/proactive assistant …` 同源同分派 → 逐字一致;安装**不阻塞**(立即回执「正在安装…」,结果用 status 查)。装上默认 enabled=false + 干跑 → 不花钱、不主动打扰。 | `pi-proactive/extensions/proactive.ts:926` |
|
|
33
33
|
<!-- END:GEN tools -->
|
|
34
34
|
|
|
35
35
|
> 工具面真源 = 实现:上表由 `scripts/gen-doc-tables.sh` 从扩展的 `registerTool` 注册项与参数 schema 自动生成,**不要手改**。
|
|
@@ -185,3 +185,61 @@ description: >-
|
|
|
185
185
|
|
|
186
186
|
补充:消息回执是 `queued` → 尚未送达(见陷阱 1);`/proactive test` 可强制走一次投递链路自检
|
|
187
187
|
(不受总开关与冷却影响)。
|
|
188
|
+
|
|
189
|
+
## 数字员工与托付(用户说人话即可,先理解意图再选能力)
|
|
190
|
+
|
|
191
|
+
> 这一节是【模型用的说明书】:用户会用自然语言提要求,你要把人话映射到下面的能力。
|
|
192
|
+
|
|
193
|
+
### 常驻数字员工("让它一直在 / 别老烦我 / 它现在在干嘛")
|
|
194
|
+
```
|
|
195
|
+
/proactive assistant on 打开常驻数字员工(装常驻进程 + 注册系统通知;回执含"已常驻"与三证据)
|
|
196
|
+
/proactive assistant status 看它是否在跑:宿主 / 进程 / 心跳龄期 / 上次联系 / 预算余量
|
|
197
|
+
/proactive assistant off 关闭(残留终检为空:任务 0 / 进程 0 / 单元 0)
|
|
198
|
+
/proactive assistant restart 重启(等价再跑一次 on)
|
|
199
|
+
```
|
|
200
|
+
人话映射示例:
|
|
201
|
+
"让它以后一直帮我盯着" → assistant on | "它现在在干嘛" → assistant status | "别老烦我了" → assistant off
|
|
202
|
+
★ 不要让用户手改配置文件、不要让用户装系统服务 —— 这些由上面一条命令完成。
|
|
203
|
+
|
|
204
|
+
### 托付("帮我盯着 X,Y 发生时告诉我")
|
|
205
|
+
托付(trust)相关能力的实现已在仓内,但**尚未接到命令分派** —— 在接入并通过独立验收前,
|
|
206
|
+
命令面不宣称它可用(宣称了却调不出来,等于给用户一个不存在的入口)。
|
|
207
|
+
人话映射示例:
|
|
208
|
+
"帮我盯着 CI 变绿" → trust add(并回执"记下了,我会在变绿时告诉你")
|
|
209
|
+
"我托付的事怎么样了" → trust list | "这个不用盯了" → trust cancel
|
|
210
|
+
"它找我啥了" → inbox | "问一下它进度" → ask
|
|
211
|
+
★ 托付里的"需要人担责的决定"必须回到人(human-gated),不要自行结账。
|
|
212
|
+
|
|
213
|
+
### 三层主动性(用户想要"它自己动起来"时,说清是哪一层)
|
|
214
|
+
```
|
|
215
|
+
L-B 托付型 我明确说"盯着 X" → trust add
|
|
216
|
+
L-C 目标驱动 我给目标("把发布流程跑通") → goal 相关命令,由它自己拆解推进、遇阻才问
|
|
217
|
+
L-D 自主推进 它从未竟目标里自己发现下一步 → 由它自己推进;不可逆/对外动作仍需人确认
|
|
218
|
+
```
|
|
219
|
+
★ 空闲触发的主动是【浅层】:默认静默,只在确有价值且可等时开口;空转的主动是负价值。
|
|
220
|
+
|
|
221
|
+
### 监督("它今天对我做了什么")
|
|
222
|
+
```
|
|
223
|
+
会话内 状态栏有摘要;pi-proactive web 起本地看板(127.0.0.1,只读)
|
|
224
|
+
/proactive progress 进度与 ETA:区间 + 置信度 + 归因 + 第 3 层阻塞项(【无单点 ETA】)
|
|
225
|
+
/proactive history 血缘与应答记录(触发→投递→回应→结账 四段)
|
|
226
|
+
/proactive doctor 巡检健康与告警
|
|
227
|
+
```
|
|
228
|
+
★ 监督面是【看】的;改配置请回会话里用自然语言,不要在页面里改。
|
|
229
|
+
|
|
230
|
+
### 配置(三层作用域,改配置前先知道值来自哪一层)
|
|
231
|
+
```
|
|
232
|
+
会话级 > agent 级 > 全局默认(每层只覆盖显式声明的键)
|
|
233
|
+
★ 安全类键(预算上限 / 权限阶梯 / 来源校验 / 联系白名单)取【最严】——局部不得放宽
|
|
234
|
+
/proactive config 看全部配置项与当前值、默认值、取值范围
|
|
235
|
+
```
|
|
236
|
+
人话映射示例:"把空闲时间改成 20 分钟" → 改对应配置项 | "它多久找我一次" → 看 idleTrigger.afterMs
|
|
237
|
+
★ 拼错或未知的配置项会被指名并给出最相近键 —— 不要绕过它。
|
|
238
|
+
|
|
239
|
+
## 不要做的事(模型侧)
|
|
240
|
+
```
|
|
241
|
+
× 不要让用户去装系统服务、改配置文件、开终端 —— 门槛最低的形态是"在 pi-agent 里说人话"
|
|
242
|
+
× 不要在用户没表达托付时替他登记托付(那是替他做决定)
|
|
243
|
+
× 不要把"待办/想法"说成"已达成" —— 未达成必须显式说明
|
|
244
|
+
× 不要用单点时间承诺,用区间 + 置信度 + 阻塞项
|
|
245
|
+
```
|