pi-watchdog-supervisor 0.1.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/.tmp/2026-07-08_pi_watchdog_extension_requirement.md +907 -0
- package/.tmp/2026-07-08_pi_watchdog_high_level_spec.md +192 -0
- package/.tmp/2026-07-08_pi_watchdog_task01_low_level_spec.md +234 -0
- package/.tmp/2026-07-08_pi_watchdog_task02_low_level_spec.md +158 -0
- package/.tmp/2026-07-08_pi_watchdog_task03_low_level_spec.md +175 -0
- package/.tmp/2026-07-08_pi_watchdog_task04_low_level_spec.md +151 -0
- package/.tmp/2026-07-08_pi_watchdog_task05_low_level_spec.md +116 -0
- package/.tmp/2026-07-08_pi_watchdog_task06_low_level_spec.md +69 -0
- package/.tmp/detect_approach.patch +253 -0
- package/README.md +176 -0
- package/examples/AGENTS.md +27 -0
- package/examples/watchdog-supervisor.json +12 -0
- package/package.json +39 -0
- package/prompts/watchdog-agent.md +36 -0
- package/skills/watchdog-supervisor/SKILL.md +35 -0
- package/src/checker.ts +54 -0
- package/src/collector.ts +223 -0
- package/src/commands.ts +159 -0
- package/src/config.ts +80 -0
- package/src/detector.ts +150 -0
- package/src/index.ts +94 -0
- package/src/integrations/gotgenes-subagents.ts +45 -0
- package/src/lmdebug.ts +128 -0
- package/src/normalize.ts +40 -0
- package/src/registry.ts +121 -0
- package/src/store.ts +137 -0
- package/src/tools.ts +289 -0
- package/src/types.ts +80 -0
- package/test/Screenshot_20260708_220337.png +0 -0
- package/test/checker.test.ts +118 -0
- package/test/collector.test.ts +201 -0
- package/test/config.test.ts +131 -0
- package/test/detector.test.ts +224 -0
- package/test/integration.test.ts +57 -0
- package/test/lmdebug.test.ts +114 -0
- package/test/normalize.test.ts +78 -0
- package/test/registry.test.ts +149 -0
- package/test/store.test.ts +176 -0
- package/test/tools.test.ts +315 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# pi-watchdog-supervisor — High-Level Tech Spec
|
|
2
|
+
|
|
3
|
+
- Date: 2026-07-08
|
|
4
|
+
- Target reader: RD / coding agent
|
|
5
|
+
- Source requirement: `.tmp/2026-07-08_pi_watchdog_extension_requirement.md`
|
|
6
|
+
- Status: v1 — ready for user review
|
|
7
|
+
- Perspective: SDD(Spec-Driven Development),本文件只定義交付階段、功能目標與驗收標準,不涉及 source-code 層實作細節。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. 需求摘要
|
|
12
|
+
|
|
13
|
+
Pi 的 task sub-agent 可能陷入重複工具迴圈(同 command、同輸出、無新 patch),目前需人工介入。本專案建立一個 Pi extension `pi-watchdog-supervisor`,以三層分工實現自動監督:
|
|
14
|
+
|
|
15
|
+
```txt
|
|
16
|
+
extension runtime = 確定性事件收集器 + stuck 偵測器(規則式,非 LLM)
|
|
17
|
+
watchdog sub-agent = 推理/摘要層,透過 watchdog_* tools 讀取狀態
|
|
18
|
+
main agent = 最終決策者(收到 alert 後決定是否送出救援訊息)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
預設行為:偵測 stuck → alert main agent(`main_only`)→ main agent 決定;**不**自動 kill、**不**自動 steer。
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 2. 架構決策(本 spec 新增)
|
|
26
|
+
|
|
27
|
+
### 2.1 觀察通道:子 session 自我回報 + 共享 state store
|
|
28
|
+
|
|
29
|
+
API 查證(見 §3)確認:Pi 核心沒有 sub-agent API,extension 只能在自己所在的 session 內運作。但 `@gotgenes/pi-subagents` 的子 agent 會**繼承父 session 的 extensions**,因此:
|
|
30
|
+
|
|
31
|
+
```txt
|
|
32
|
+
Task Sub-agent Session(子)
|
|
33
|
+
│ pi-watchdog-supervisor extension 也載入於此
|
|
34
|
+
│ ├─ 監聽「自己的」tool_call / tool_result 事件
|
|
35
|
+
│ ├─ 正規化 command、hash output
|
|
36
|
+
│ └─ 寫入共享 state store(以 parent session 為 key 的檔案)
|
|
37
|
+
│ ↓
|
|
38
|
+
Main Agent Session(父)
|
|
39
|
+
│ pi-watchdog-supervisor extension
|
|
40
|
+
│ ├─ 訂閱 subagents:* 生命週期事件(建立 target registry)
|
|
41
|
+
│ ├─ 讀取共享 store,彙整 ring buffer
|
|
42
|
+
│ ├─ 執行確定性 stuck 偵測 + cooldown
|
|
43
|
+
│ └─ 提供 watchdog_* tools 給 watchdog sub-agent
|
|
44
|
+
│ ↓
|
|
45
|
+
Watchdog Sub-agent Session
|
|
46
|
+
呼叫 watchdog_* tools → 產出 compact alert → main agent
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
此設計取代了需求文件 §12.3 的「僅追蹤本 session 可見 tool calls」的降級方案,直接解決「watchdog 無法觀察兄弟 sub-agent」的核心約束(§4),且不需輪詢完整對話 log,token 成本低。
|
|
50
|
+
|
|
51
|
+
### 2.2 資料流圖
|
|
52
|
+
|
|
53
|
+
```txt
|
|
54
|
+
子 agent tool_call 事件
|
|
55
|
+
│
|
|
56
|
+
▼
|
|
57
|
+
子 session extension instance(normalize + hash)
|
|
58
|
+
│ append
|
|
59
|
+
▼
|
|
60
|
+
共享 state store(JSONL / 檔案,key = parent session id)
|
|
61
|
+
│ read + aggregate
|
|
62
|
+
▼
|
|
63
|
+
父 session extension(ring buffer + 規則偵測 + cooldown)
|
|
64
|
+
│ watchdog_* tools
|
|
65
|
+
▼
|
|
66
|
+
watchdog sub-agent(推理、產出 alert)
|
|
67
|
+
│ watchdog_alert_main(pi.sendMessage / pi-intercom)
|
|
68
|
+
▼
|
|
69
|
+
main agent(決策:steer / restart / ignore)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 3. API 查證結果(解決需求文件 §22 open questions)
|
|
75
|
+
|
|
76
|
+
查證來源:Pi extensions 官方文件、`@gotgenes/pi-subagents` 與 `pi-intercom` 套件頁(accessed 2026-07-08,URL 見 §8)。
|
|
77
|
+
|
|
78
|
+
| Open Question | 查證結果 |
|
|
79
|
+
|---|---|
|
|
80
|
+
| Pi 事件名稱 | 已確認:`tool_call`(可 block)、`tool_result`、`tool_execution_start/update/end`、`agent_start/end`、`turn_start/end`、`session_start` 等 |
|
|
81
|
+
| Extension 能否送訊息進 session | 可以,但**僅限自己所在的 session**:`pi.sendMessage(..., { deliverAs: 'steer' \| 'followUp' \| 'nextTurn', triggerTurn })`、`pi.sendUserMessage()` |
|
|
82
|
+
| Pi 核心是否有 sub-agent API | **沒有**。Extension 只在單一 session context 內運作 → 必須依賴 `@gotgenes/pi-subagents` |
|
|
83
|
+
| `@gotgenes/pi-subagents` API 能力 | `getSubagentsService()`、`svc.spawn(type, prompt)`、`steer_subagent(agent_id, message)`、`get_subagent_result(agent_id, wait, verbose)`(verbose 可取完整對話 log);透過 `pi.events` 發出 `subagents:created/started/completed/failed/steered/compacted` 事件(含 agent id、type、token 數、status) |
|
|
84
|
+
| 跨 session 通訊 | `pi-intercom`:local broker + Unix socket,收件方 idle 時訊息會**觸發新 turn**;另有 subagent 專用 `contact_supervisor` tool(需 pi-subagents) |
|
|
85
|
+
| 父子關係識別 | 子 agent 繼承父的 skills/extensions;權限系統有 `subagents:child:session-created` 可作確定性子 session 偵測 |
|
|
86
|
+
|
|
87
|
+
### 仍未解之項(留給各 Task 的 low-level spec / 前置 spike)
|
|
88
|
+
|
|
89
|
+
- `SubagentsService` 完整 method 簽名(需讀套件 `src/service/service.ts` 原始碼)。→ Task02 spike
|
|
90
|
+
- `subagents:*` 事件是否含 per-tool-call 細節(推測不含,故採 §2.1 自我回報架構)。→ Task03 spike
|
|
91
|
+
- 子 session 內的 extension 如何取得 parent session id。→ Task03 spike
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 4. Task 拆分
|
|
96
|
+
|
|
97
|
+
對應需求文件 §17 Milestone 1–6。分類上全部屬於 `03. UI or Core Function`(新 extension 開發,無既有程式碼可 typing/refactor),測試併入各 Task 驗收。
|
|
98
|
+
|
|
99
|
+
### 估算基準
|
|
100
|
+
|
|
101
|
+
- `S` 基準 = Task01:extension 骨架 + 指令註冊 + config 載入,Pi 文件有完整範例、無外部整合風險,約 1x。
|
|
102
|
+
- `M` ≈ 2-3x:需整合第三方套件事件或跨多檔案,有中等不確定性。
|
|
103
|
+
- `L` ≈ 4-6x:跨 session 通道,最高的未知數與回歸風險。
|
|
104
|
+
- 倍數為規劃輔助,非時間保證。
|
|
105
|
+
|
|
106
|
+
### Task01 — Extension skeleton 與設定系統(S)
|
|
107
|
+
|
|
108
|
+
- 目標:建立 `pi-watchdog-supervisor` package(含 pi manifest);註冊 `/watchdog status|config|set|pause|resume|inspect` 指令;載入並合併 global(`~/.pi/agent/watchdog-supervisor/config.json`)與 project(`.pi/watchdog-supervisor.json`)config,project 覆蓋 global;定義 `WatchdogConfig` 預設值(含可設定的 rescueMessage)。
|
|
109
|
+
- 驗收:
|
|
110
|
+
- `pi -e ./src/index.ts` 啟動後 `/watchdog status` 顯示 extension 已載入。
|
|
111
|
+
- `/watchdog config` 正確反映 global+project 合併結果;`/watchdog set rescueMessage <msg>` 於當前 session 生效。
|
|
112
|
+
- config merge 有 unit test。
|
|
113
|
+
|
|
114
|
+
### Task02 — Sub-agent 發現與生命週期追蹤(M)
|
|
115
|
+
|
|
116
|
+
- 目標:以 optional peer dependency 整合 `@gotgenes/pi-subagents`;訂閱 `subagents:created/started/completed/failed/steered/compacted`;維護 target registry(`WatchdogTarget`);區分 watchdog / task agent;套件缺席時 extension 仍可載入並顯示降級狀態。
|
|
117
|
+
- 前置 spike:讀 `@gotgenes/pi-subagents` 原始碼確認 `SubagentsService` 簽名與事件 payload。
|
|
118
|
+
- 驗收:
|
|
119
|
+
- main session 有 2 個 task sub-agent + 1 個 watchdog sub-agent 時,`/watchdog status` 正確列出 3 個 target、kind 與 status。
|
|
120
|
+
- 未安裝 pi-subagents 時無錯誤,`/watchdog status` 顯示「subagent service unavailable」。
|
|
121
|
+
|
|
122
|
+
### Task03 — 事件收集:子 session 自我回報 + 共享 store(L)
|
|
123
|
+
|
|
124
|
+
- 目標:子 session 端 extension instance 監聽自身 `tool_call`/`tool_result`;實作 `normalizeCommand()`(trim、collapse spaces、strip ANSI)與 `hashOutput()`(strip ANSI、truncate、sha256、preview N 行);寫入以 parent session 為 key 的共享 state store;父 session 端讀取彙整為每 target 的 ring buffer(`maxEventsPerAgent`,預設 200),只存 hash 與 preview,不存完整輸出。
|
|
125
|
+
- 前置 spike:確認子 session 取得 parent session id 的方式;確認 `subagents:*` 事件是否已含 tool 細節(若有,可簡化架構)。
|
|
126
|
+
- 驗收:
|
|
127
|
+
- 執行中 task sub-agent 的 tool call(含 command key、output hash)出現在父 session 的事件 buffer 中(Task05 後可經 `watchdog_read_events()` 驗證)。
|
|
128
|
+
- `normalizeCommand()` / `hashOutput()` / ring buffer 有 unit test。
|
|
129
|
+
|
|
130
|
+
### Task04 — Stuck 偵測器(M)
|
|
131
|
+
|
|
132
|
+
- 目標:實作規則式偵測:同 command + 同 output hash ≥ `repeatThreshold`(3);同 typecheck error ≥ 2;`idleNoProgressSec`(300s)內無 edit/patch 但 tools 持續執行;read-only loop。輸出 `likelyStuck` + `confidence` + `reasons` + `evidence`。實作 per-target alert cooldown(60s)與同 evidence key 去重。
|
|
133
|
+
- 驗收:
|
|
134
|
+
- 模擬同一 `rg` 指令重複 3 次(同輸出)→ `likelyStuck === true`、confidence ≥ medium、evidence 含 `repeated_command`。
|
|
135
|
+
- cooldown 期間相同 evidence 不重複產生 alert。
|
|
136
|
+
- 各偵測規則與 cooldown 有 unit test。
|
|
137
|
+
|
|
138
|
+
### Task05 — Watchdog tools(M)
|
|
139
|
+
|
|
140
|
+
- 目標:以 `pi.registerTool` 註冊需求文件 §9 全部工具:`watchdog_list_targets`、`watchdog_read_events`、`watchdog_detect_stuck`、`watchdog_alert_main`(優先 `pi.sendMessage` 注入 main session;pi-intercom 為跨 session 備援;最後 fallback UI 通知)、`watchdog_steer_subagent`(預設 `dryRun=true`,實際 steer 走 pi-subagents `steer_subagent`)、`watchdog_config`。輸出 schema 依需求文件 §9 定義。
|
|
141
|
+
- 驗收:
|
|
142
|
+
- watchdog sub-agent 能呼叫 `watchdog_detect_stuck` 取得 evidence 並產出 §8.2 格式的 compact alert。
|
|
143
|
+
- `watchdog_alert_main` 的訊息實際出現在 main session。
|
|
144
|
+
- `watchdog_steer_subagent` 預設 dry-run 不實際送訊息。
|
|
145
|
+
|
|
146
|
+
### Task06 — Supervisor 工作流與文件(M)
|
|
147
|
+
|
|
148
|
+
- 目標:提供 watchdog agent prompt(`prompts/watchdog-agent.md`)、watchdog skill(§21)、範例 main-agent 指引(examples/AGENTS.md);實作 main-agent-driven 輪詢流程(需求文件 §14 Option A);README 與手動情境測試腳本(dummy loop agent 重複 `rg` 3 次)。
|
|
149
|
+
- 驗收:
|
|
150
|
+
- 端到端手動測試:dummy task sub-agent 進入迴圈 → watchdog 偵測 → main agent 收到 `[Watchdog Alert]` → main agent 可選擇送出 rescue message(人工確認送達)。
|
|
151
|
+
- Extension-driven tick(Option B)明確標記為 post-MVP,不在本次交付。
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 5. 釋出安全性
|
|
156
|
+
|
|
157
|
+
每個 Task 完成後皆可獨立釋出,不需等待後續 Task:
|
|
158
|
+
|
|
159
|
+
- Task01–03 只收集資料、不產生任何 alert 或介入,對現有工作流零影響。
|
|
160
|
+
- Task04–05 預設 `alertMode: "main_only"`、steer 預設 dry-run,不會自動介入 sub-agent。
|
|
161
|
+
- 整體可用 `enabled: false` 或 `/watchdog pause` 一鍵停用。
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 6. 風險表(依查證結果更新需求文件 §24)
|
|
166
|
+
|
|
167
|
+
| Risk | Impact | 變化 | Mitigation |
|
|
168
|
+
|---|---:|---|---|
|
|
169
|
+
| Pi 不暴露 sub-agent 內部 | Medium(原 High) | ↓ 已緩解 | 查證確認子 agent 繼承 extensions → 採自我回報架構(§2.1) |
|
|
170
|
+
| 子 session 無法識別 parent id | Medium | 新增 | Task03 前置 spike;備援:pi-intercom 註冊回報 |
|
|
171
|
+
| `@gotgenes/pi-subagents` API 與文件不符 | Medium | 新增 | Task02 spike 直接讀原始碼;adapter 隔離於 `integrations/` |
|
|
172
|
+
| Watchdog sub-agent 閒置 | Medium | 不變 | MVP 採 main-agent-driven(Option A),Option B 為 post-MVP |
|
|
173
|
+
| 誤報過多 | Medium | 不變 | cooldown、confidence 分級、main agent 最終審批 |
|
|
174
|
+
| 監控造成 token 浪費 | Low(原 Medium) | ↓ | 只存 hash + preview;watchdog 只讀 compact 資料 |
|
|
175
|
+
| 第三方 extension API 變動 | Medium | 不變 | adapter 隔離於 `integrations/` |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 7. Revision History
|
|
180
|
+
|
|
181
|
+
| Version | Change |
|
|
182
|
+
|---|---|
|
|
183
|
+
| v1 | 初版:API 查證結果 + 觀察通道架構決策 + Task01–06 拆分與驗收標準 |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 8. Reference
|
|
188
|
+
|
|
189
|
+
- reference01: https://pi.dev/docs/latest/extensions — 事件清單、registerTool/registerCommand、sendMessage/sendUserMessage、無 sub-agent API(accessed 2026-07-08)
|
|
190
|
+
- reference02: https://pi.dev/packages/%40gotgenes/pi-subagents — service API、subagents:* 事件、steering、extension 繼承(accessed 2026-07-08)
|
|
191
|
+
- reference03: https://pi.dev/packages/pi-intercom — broker 架構、turn 觸發、contact_supervisor(accessed 2026-07-08)
|
|
192
|
+
- reference04: `.tmp/2026-07-08_pi_watchdog_extension_requirement.md` — 原始需求文件
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Task01 — Extension Skeleton 與設定系統:Low-Level Detail Spec
|
|
2
|
+
|
|
3
|
+
- Date: 2026-07-08
|
|
4
|
+
- Target reader: coding agent / implementation engineer
|
|
5
|
+
- Source:
|
|
6
|
+
- High-level spec: `.tmp/2026-07-08_pi_watchdog_high_level_spec.md`(Task01)
|
|
7
|
+
- Requirement: `.tmp/2026-07-08_pi_watchdog_extension_requirement.md`(§7.3, §10, §11, §16, §17 Milestone 1, §19)
|
|
8
|
+
- Status: v1 — ready for user review
|
|
9
|
+
- Perspective: TDD(先寫失敗測試,再實作使其通過)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. 範圍
|
|
14
|
+
|
|
15
|
+
### In scope
|
|
16
|
+
|
|
17
|
+
- `pi-watchdog-supervisor` package 骨架(pi manifest、TypeScript、測試環境)。
|
|
18
|
+
- `WatchdogConfig` 型別、預設值、global + project config 載入與合併(project 覆蓋 global)。
|
|
19
|
+
- Session 層級的 runtime 狀態:`paused` 旗標、session-only `rescueMessage` 覆寫。
|
|
20
|
+
- 指令註冊:`/watchdog status`、`/watchdog config`、`/watchdog set rescueMessage <msg>`、`/watchdog pause`、`/watchdog resume`、`/watchdog inspect <targetId>`(stub)。
|
|
21
|
+
|
|
22
|
+
### Out of scope(後續 Task)
|
|
23
|
+
|
|
24
|
+
- sub-agent 發現(Task02)、事件收集(Task03)、stuck 偵測(Task04)、watchdog tools(Task05)。
|
|
25
|
+
- `/watchdog inspect` 只回覆「尚未實作,Task03 提供」的 stub 訊息。
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. 實作輸入(已查證的 API 事實)
|
|
30
|
+
|
|
31
|
+
- Extension 進入點:default export factory,`export default function (pi: ExtensionAPI) { ... }`,可為 async。型別來自 `@earendil-works/pi-coding-agent`。
|
|
32
|
+
- 開發載入:`pi -e ./src/index.ts`;extension 經 jiti 載入,TypeScript **免編譯**、可直接用 Node built-ins 與本地 npm 依賴。
|
|
33
|
+
- 指令註冊:`pi.registerCommand(name, { description, handler: async (args: string, ctx: ExtensionCommandContext) => void })`。
|
|
34
|
+
- 使用者輸出:`ctx.ui.notify(msg, "info" | ...)` 一行通知;互動前先檢查 `ctx.hasUI`(print/rpc 模式無 UI)。
|
|
35
|
+
- Package manifest:`package.json` 的 `pi` key,`{ "extensions": ["./src/index.ts"] }`;`keywords: ["pi-package"]` 供 gallery 曝光。
|
|
36
|
+
- Config 路徑(依需求文件 §11):
|
|
37
|
+
- Global: `~/.pi/agent/watchdog-supervisor/config.json`
|
|
38
|
+
- Project: `.pi/watchdog-supervisor.json`
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. 檔案結構(本 Task 建立)
|
|
43
|
+
|
|
44
|
+
```txt
|
|
45
|
+
./ # repo root = package root
|
|
46
|
+
├─ package.json # pi manifest + scripts
|
|
47
|
+
├─ tsconfig.json
|
|
48
|
+
├─ vitest.config.ts
|
|
49
|
+
├─ src/
|
|
50
|
+
│ ├─ index.ts # extension entry: 載入 config、註冊 commands
|
|
51
|
+
│ ├─ types.ts # WatchdogConfig 等型別
|
|
52
|
+
│ ├─ config.ts # DEFAULT_CONFIG、loadConfigFile、mergeConfig、getConfigPaths
|
|
53
|
+
│ ├─ state.ts # SessionState: paused / rescueMessage 覆寫
|
|
54
|
+
│ └─ commands.ts # registerWatchdogCommands(pi, store)
|
|
55
|
+
└─ test/
|
|
56
|
+
├─ config.test.ts
|
|
57
|
+
└─ state.test.ts
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
註:`detector.ts`、`events.ts`、`tools.ts`、`integrations/` 於後續 Task 依需求文件 §18 補齊。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 4. 資料模型(本 Task 實作部分)
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// src/types.ts
|
|
68
|
+
type WatchdogConfig = {
|
|
69
|
+
enabled: boolean; // default: true
|
|
70
|
+
rescueMessage: string; // default: 需求文件 §2.2 中文預設訊息
|
|
71
|
+
repeatThreshold: number; // default: 3
|
|
72
|
+
typecheckRepeatThreshold: number; // default: 2
|
|
73
|
+
idleNoProgressSec: number; // default: 300
|
|
74
|
+
cooldownSec: number; // default: 60
|
|
75
|
+
maxPreviewLines: number; // default: 20
|
|
76
|
+
maxEventsPerAgent: number; // default: 200
|
|
77
|
+
alertMode: 'main_only' | 'direct_subagent' | 'both'; // default: 'main_only'
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/state.ts — session runtime 狀態(不落地)
|
|
81
|
+
type SessionState = {
|
|
82
|
+
paused: boolean; // default: false
|
|
83
|
+
rescueMessageOverride?: string; // /watchdog set rescueMessage 設定
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
有效設定 = `DEFAULT_CONFIG ← global config ← project config ← session override`(右邊優先)。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 5. 實作步驟(TDD)
|
|
92
|
+
|
|
93
|
+
每步驟:先寫測試(紅)→ 實作(綠)→ 驗證指令。
|
|
94
|
+
|
|
95
|
+
### Step 1 — Package 骨架與工具鏈
|
|
96
|
+
|
|
97
|
+
- 建立 `package.json`:`"type": "module"`、`pi.extensions: ["./src/index.ts"]`、`keywords: ["pi-package"]`;devDependencies:`typescript`、`vitest`、`@earendil-works/pi-coding-agent`(型別用);scripts:`"test": "vitest run"`、`"type-check": "tsc --noEmit"`。
|
|
98
|
+
- 建立 `tsconfig.json`(ESM、strict)、`vitest.config.ts`。
|
|
99
|
+
- 驗證:`npm install` 成功;`npm run type-check` 通過;`npm test` 可執行(0 test files 亦可)。
|
|
100
|
+
|
|
101
|
+
### Step 2 — `types.ts` + `DEFAULT_CONFIG`
|
|
102
|
+
|
|
103
|
+
- 先寫 `test/config.test.ts` 失敗測試:
|
|
104
|
+
- `DEFAULT_CONFIG` 各欄位值符合 §4(repeatThreshold=3、idleNoProgressSec=300、alertMode='main_only'、rescueMessage 為預設中文訊息…)。
|
|
105
|
+
- 實作 `src/types.ts` 與 `src/config.ts` 的 `DEFAULT_CONFIG` 使測試通過。
|
|
106
|
+
|
|
107
|
+
### Step 3 — Config 載入與合併
|
|
108
|
+
|
|
109
|
+
- 先寫失敗測試(`test/config.test.ts`,用 temp dir 產生假 config 檔):
|
|
110
|
+
1. 無任何 config 檔 → 回傳 `DEFAULT_CONFIG`。
|
|
111
|
+
2. 僅 global → default 被 global 覆蓋,未指定欄位保留 default。
|
|
112
|
+
3. global + project → project 欄位覆蓋 global,兩者未指定欄位保留 default。
|
|
113
|
+
4. config 檔為無效 JSON → 忽略該檔並回傳 warning 訊息(不 throw、不中斷載入)。
|
|
114
|
+
5. config 含未知欄位 → 忽略未知欄位。
|
|
115
|
+
6. `getConfigPaths(projectDir)` 回傳正確 global/project 路徑(global 以 `os.homedir()` 展開)。
|
|
116
|
+
- 實作 `src/config.ts`:
|
|
117
|
+
- `getConfigPaths(projectDir: string): { globalPath: string; projectPath: string }`
|
|
118
|
+
- `loadConfigFile(path: string): { config: Partial<WatchdogConfig> | null; warning?: string }`(檔案不存在 → `null`,無 warning)
|
|
119
|
+
- `mergeConfig(...parts: Array<Partial<WatchdogConfig> | null | undefined>): WatchdogConfig`
|
|
120
|
+
- `loadEffectiveConfig(projectDir: string): { config: WatchdogConfig; warnings: string[] }`
|
|
121
|
+
- 設計約束:`loadConfigFile` / `mergeConfig` 為純函式(I/O 與合併分離),方便測試;不做 file watch,config 於 extension 載入時讀一次(`/reload` 可重讀)。
|
|
122
|
+
|
|
123
|
+
### Step 4 — Session 狀態
|
|
124
|
+
|
|
125
|
+
- 先寫失敗測試(`test/state.test.ts`):
|
|
126
|
+
1. 初始 `paused === false`、無 override。
|
|
127
|
+
2. `pause()` / `resume()` 切換 `paused`。
|
|
128
|
+
3. `setRescueMessage(msg)` 後 `getEffectiveRescueMessage(config)` 回傳 override;未設定時回傳 `config.rescueMessage`。
|
|
129
|
+
- 實作 `src/state.ts`(簡單 in-memory store,供 commands 與後續 Task 共用)。
|
|
130
|
+
|
|
131
|
+
### Step 5 — 指令註冊與 entry point
|
|
132
|
+
|
|
133
|
+
- 實作 `src/commands.ts` 的 `registerWatchdogCommands(pi, store)`:
|
|
134
|
+
|
|
135
|
+
| 指令 | 行為 |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `/watchdog status` | 顯示:extension 版本、`enabled`、`paused`、config 摘要、target 數(Task01 固定顯示 `targets: 0 (subagent discovery: Task02)`) |
|
|
138
|
+
| `/watchdog config` | 顯示有效 config 全文(JSON pretty print)+ 各值來源不需標註(post-MVP) |
|
|
139
|
+
| `/watchdog set rescueMessage <msg>` | 設定 session override;`<msg>` 為空 → 顯示用法提示 |
|
|
140
|
+
| `/watchdog pause` | `paused = true`,通知使用者 |
|
|
141
|
+
| `/watchdog resume` | `paused = false`,通知使用者 |
|
|
142
|
+
| `/watchdog inspect <id>` | 回覆 stub:`inspect not implemented yet (Task03)` |
|
|
143
|
+
|
|
144
|
+
- 指令採單一 `/watchdog` 入口 + 子指令解析(`args.trim().split(/\s+/)`),未知子指令 → 顯示可用子指令清單。
|
|
145
|
+
- 輸出方式:`ctx.ui.notify()`(單行);config 全文等多行內容在 `ctx.hasUI` 時用 notify 逐段或 `ctx.ui.custom`,無 UI 模式(print/rpc)改 `console.log`。以最簡可行為準,不做花式 TUI。
|
|
146
|
+
- 實作 `src/index.ts`:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
export default function watchdogSupervisor(pi: ExtensionAPI) {
|
|
150
|
+
// load config once at startup; surface warnings via notify on session_start
|
|
151
|
+
// create session state store
|
|
152
|
+
// registerWatchdogCommands(pi, store)
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- config warning(如無效 JSON)在 `session_start` 時以 `ctx.ui.notify(warning, "warning")` 顯示一次。
|
|
157
|
+
|
|
158
|
+
### Step 6 — 手動驗收
|
|
159
|
+
|
|
160
|
+
依 §7 驗證指令逐項執行。
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 6. 測試覆蓋清單
|
|
165
|
+
|
|
166
|
+
| 測試 | 檔案 | 類型 |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| DEFAULT_CONFIG 欄位與預設值 | test/config.test.ts | unit |
|
|
169
|
+
| 無 config 檔 → defaults | test/config.test.ts | unit |
|
|
170
|
+
| global 覆蓋 default | test/config.test.ts | unit |
|
|
171
|
+
| project 覆蓋 global(部分欄位) | test/config.test.ts | unit |
|
|
172
|
+
| 無效 JSON → warning、不 throw | test/config.test.ts | unit |
|
|
173
|
+
| 未知欄位被忽略 | test/config.test.ts | unit |
|
|
174
|
+
| getConfigPaths 路徑正確 | test/config.test.ts | unit |
|
|
175
|
+
| pause/resume 切換 | test/state.test.ts | unit |
|
|
176
|
+
| rescueMessage override 優先序 | test/state.test.ts | unit |
|
|
177
|
+
| 指令行為(status/config/set/pause/resume/inspect) | 手動(§7) | manual |
|
|
178
|
+
|
|
179
|
+
註:commands 與 pi API 的互動層薄、依賴 `ExtensionAPI` runtime,MVP 以手動驗收為主,不為其建 mock harness(避免單次使用的測試設施;若後續 Task 需要再引入)。
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 7. 驗收標準與驗證指令
|
|
184
|
+
|
|
185
|
+
全部通過才算完成(依 completion verification 規則):
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm run type-check # tsc --noEmit 無錯誤
|
|
189
|
+
npm test # vitest run 全綠
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
手動場景(在本 repo 內執行):
|
|
193
|
+
|
|
194
|
+
```txt
|
|
195
|
+
1. pi -e ./src/index.ts
|
|
196
|
+
2. /watchdog status
|
|
197
|
+
→ 顯示 extension 已載入、enabled=true、paused=false、targets: 0
|
|
198
|
+
3. /watchdog config
|
|
199
|
+
→ 顯示 DEFAULT_CONFIG 全文
|
|
200
|
+
4. 建立 .pi/watchdog-supervisor.json(如 {"repeatThreshold": 5})→ /reload → /watchdog config
|
|
201
|
+
→ repeatThreshold 顯示 5,其餘為 default
|
|
202
|
+
5. /watchdog set rescueMessage 測試訊息
|
|
203
|
+
→ /watchdog status 或 config 反映 session override
|
|
204
|
+
6. /watchdog pause → status 顯示 paused=true;/watchdog resume → paused=false
|
|
205
|
+
7. /watchdog inspect xyz
|
|
206
|
+
→ 顯示 Task03 stub 訊息
|
|
207
|
+
8. 將 .pi/watchdog-supervisor.json 改為無效 JSON → /reload
|
|
208
|
+
→ extension 仍載入,顯示 warning,config 回退 default+global
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 8. 風險與注意事項
|
|
214
|
+
|
|
215
|
+
- `ExtensionCommandContext` 的 UI 細節(notify 多行支援、custom component 簽名)以實際型別定義為準;若與本 spec 假設不符,以「最簡輸出」原則調整,不影響驗收條件。
|
|
216
|
+
- `/reload` 是否重新執行 extension factory(重讀 config)需在 Step 6 實測確認;若否,改於 `session_start` 重讀。
|
|
217
|
+
- 依 karpathy 原則:不做 config file watch、不做來源標註、不做多語系輸出 — 未被要求的功能一律不加。
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 9. Revision History
|
|
222
|
+
|
|
223
|
+
| Version | Change |
|
|
224
|
+
|---|---|
|
|
225
|
+
| v1 | 初版:TDD 步驟、config 合併規則、指令表、測試清單與手動驗收場景 |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 10. Reference
|
|
230
|
+
|
|
231
|
+
- reference01: https://pi.dev/docs/latest/extensions — entry point、registerCommand、ctx.ui、jiti 載入、`pi -e`(accessed 2026-07-08)
|
|
232
|
+
- reference02: https://pi.dev/docs/latest/packages — pi manifest、config/settings 路徑、local dev workflow(accessed 2026-07-08)
|
|
233
|
+
- reference03: `.tmp/2026-07-08_pi_watchdog_high_level_spec.md` — Task01 定義
|
|
234
|
+
- reference04: `.tmp/2026-07-08_pi_watchdog_extension_requirement.md` — §7.3, §10, §11, §16, §19
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Task02 — Sub-agent 發現與生命週期追蹤:Low-Level Detail Spec
|
|
2
|
+
|
|
3
|
+
- Date: 2026-07-08
|
|
4
|
+
- Source: `.tmp/2026-07-08_pi_watchdog_high_level_spec.md`(Task02);requirement §9.1, §10, §12.1
|
|
5
|
+
- Status: v1 — 使用者已指示直接實作,本文件同步作為實作依據與追溯紀錄
|
|
6
|
+
- Perspective: TDD
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Spike 結果(讀 `@gotgenes/pi-subagents@18.0.1` 原始碼,已完成)
|
|
11
|
+
|
|
12
|
+
`src/service/service.ts` 確認:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// 同步 accessor,service 發布於 globalThis Symbol.for("@gotgenes/pi-subagents:service")
|
|
16
|
+
getSubagentsService(): SubagentsService | undefined
|
|
17
|
+
|
|
18
|
+
interface SubagentsService {
|
|
19
|
+
spawn(type: string, prompt: string, options?: SpawnOptions): string;
|
|
20
|
+
getRecord(id: string): SubagentRecord | undefined;
|
|
21
|
+
listAgents(): SubagentRecord[]; // 最新在前
|
|
22
|
+
abort(id: string): boolean;
|
|
23
|
+
steer(id: string, message: string): Promise<boolean>;
|
|
24
|
+
waitForAll(): Promise<void>;
|
|
25
|
+
hasRunning(): boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SubagentRecord {
|
|
29
|
+
id: string; type: string; description: string;
|
|
30
|
+
status: SubagentStatus; // queued|running|completed|steered|aborted|stopped|error
|
|
31
|
+
result?: string; error?: string;
|
|
32
|
+
toolUses: number; startedAt: number; completedAt?: number;
|
|
33
|
+
lifetimeUsage: LifetimeUsage; compactionCount: number;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`src/observation/subagent-events-observer.ts` 確認事件 payload:
|
|
38
|
+
|
|
39
|
+
| Channel | Payload |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `subagents:created` | `{ id, type, description, isBackground: true }` |
|
|
42
|
+
| `subagents:started` | `{ id, type, description }` |
|
|
43
|
+
| `subagents:completed` / `subagents:failed` | `buildEventData(record)`(含 id;failed = status ∈ error/stopped/aborted) |
|
|
44
|
+
| `subagents:steered` | `{ id, message }` |
|
|
45
|
+
| `subagents:compacted` | `{ id, type, description, reason, tokensBefore, compactionCount }` |
|
|
46
|
+
|
|
47
|
+
另確認(Task03 可用,本 Task 不用):`subagents:child:session-created` payload 為 `{ sessionId, parentSessionId? }` — parent id 取得方式已解。
|
|
48
|
+
|
|
49
|
+
**重要推論**:`listAgents()` 已提供 status/toolUses/lastActive 基礎資料 → Task02 採「事件驅動 + 指令時 `listAgents()` 對帳」雙軌,不需自建完整狀態機。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. 範圍
|
|
54
|
+
|
|
55
|
+
### In scope
|
|
56
|
+
|
|
57
|
+
- `src/integrations/gotgenes-subagents.ts`:optional 整合 adapter(dynamic import + try/catch;未安裝時回傳 unavailable)。
|
|
58
|
+
- `src/registry.ts`:target registry — 從 `SubagentRecord` 映射為 `WatchdogTarget`,事件更新 `lastActiveAt`。
|
|
59
|
+
- kind 分類:`type` 或 `description` 含 `watchdog`(不分大小寫)→ `'watchdog'`,否則 `'task'`;無法判斷 → `'unknown'`。
|
|
60
|
+
- `/watchdog status` 改為列出 targets(id、name/description、kind、status、toolUses、lastActiveAt);service 缺席時顯示 `subagent service unavailable (install @gotgenes/pi-subagents)`。
|
|
61
|
+
- `@gotgenes/pi-subagents` 列為 optional peerDependency + devDependency(開發/驗收用)。
|
|
62
|
+
|
|
63
|
+
### Out of scope
|
|
64
|
+
|
|
65
|
+
- tool_call 級事件收集、共享 store(Task03);stuck 偵測(Task04);watchdog tools(Task05)。
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 3. 資料模型
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// src/types.ts 新增
|
|
73
|
+
type TargetKind = 'task' | 'watchdog' | 'unknown';
|
|
74
|
+
|
|
75
|
+
type WatchdogTarget = {
|
|
76
|
+
id: string;
|
|
77
|
+
name?: string; // record.description
|
|
78
|
+
kind: TargetKind;
|
|
79
|
+
status: 'queued' | 'running' | 'completed' | 'steered' | 'aborted' | 'stopped' | 'error';
|
|
80
|
+
toolCallCount: number; // record.toolUses
|
|
81
|
+
createdAt: number; // record.startedAt
|
|
82
|
+
lastActiveAt: number; // 事件時間戳更新;初始 = startedAt
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
映射規則 `toWatchdogTarget(record, now)` 為純函式,事件套用 `applyEvent(targets, channel, payload, now)` 亦為純函式(I/O 分離)。
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 4. 實作步驟(TDD)
|
|
91
|
+
|
|
92
|
+
### Step 1 — registry 純函式(紅→綠)
|
|
93
|
+
|
|
94
|
+
`test/registry.test.ts` 失敗測試:
|
|
95
|
+
|
|
96
|
+
1. `classifyKind`:type 含 watchdog → `'watchdog'`;description 含 Watchdog → `'watchdog'`;一般 type → `'task'`。
|
|
97
|
+
2. `toWatchdogTarget`:欄位映射正確(toolUses→toolCallCount、startedAt→createdAt/lastActiveAt)。
|
|
98
|
+
3. `applyEvent`:`subagents:started` 更新既有 target 的 status 與 lastActiveAt;未知 id 時建立 minimal target;`subagents:failed` → status 依 payload;payload 缺 id → 不變。
|
|
99
|
+
4. `syncFromRecords`:以 `listAgents()` 結果對帳 — 新 record 加入、既有 target 保留較新的 lastActiveAt。
|
|
100
|
+
|
|
101
|
+
實作 `src/registry.ts` 使通過。
|
|
102
|
+
|
|
103
|
+
### Step 2 — 整合 adapter
|
|
104
|
+
|
|
105
|
+
`src/integrations/gotgenes-subagents.ts`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
type SubagentsIntegration =
|
|
109
|
+
| { available: true; listAgents: () => SubagentRecordLike[]; }
|
|
110
|
+
| { available: false; reason: string };
|
|
111
|
+
|
|
112
|
+
// dynamic import in try/catch; getSubagentsService() 回 undefined 也算 unavailable
|
|
113
|
+
const connectSubagents = async (): Promise<SubagentsIntegration> => { ... }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- 型別以本地最小結構型別(`SubagentRecordLike`)宣告,不直接 import 套件型別到核心模組 — adapter 隔離(高階 spec 風險緩解)。
|
|
117
|
+
- 事件訂閱:以 `pi.events.on(channel, handler)` 訂閱 §1 表列 6 個 channel(實作時以 `@earendil-works/pi-coding-agent` 型別宣告為準;若 API 名稱不同,於回報中註明實際名稱)。
|
|
118
|
+
- 測試:unavailable 分支(import 失敗 / service undefined)回傳 reason;available 分支以 fake globalThis service 測試。
|
|
119
|
+
|
|
120
|
+
### Step 3 — 接線 `index.ts` + `/watchdog status`
|
|
121
|
+
|
|
122
|
+
- `index.ts`:載入時 `connectSubagents()`,建立 registry,訂閱事件 → `applyEvent`。
|
|
123
|
+
- `commands.ts` `status`:先 `syncFromRecords(listAgents())` 對帳,再輸出:
|
|
124
|
+
|
|
125
|
+
```txt
|
|
126
|
+
watchdog-supervisor loaded | enabled=true | paused=false
|
|
127
|
+
targets (3):
|
|
128
|
+
[task] a1b2c3 running tools=12 "explore campaign-list"
|
|
129
|
+
[task] d4e5f6 running tools=3 "fix type errors"
|
|
130
|
+
[watchdog] g7h8i9 running tools=1 "supervise task agents"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- service 缺席:`targets: unavailable — install @gotgenes/pi-subagents`。
|
|
134
|
+
|
|
135
|
+
### Step 4 — 驗證
|
|
136
|
+
|
|
137
|
+
- `npm run type-check`、`npm test` 全綠(既有 14 tests 不得回歸)。
|
|
138
|
+
- Smoke:`pi -e ./src/index.ts` 載入無錯誤(未裝 pi-subagents 時顯示降級訊息)。
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 5. 驗收標準
|
|
143
|
+
|
|
144
|
+
- [ ] `classifyKind` / `toWatchdogTarget` / `applyEvent` / `syncFromRecords` unit tests 通過。
|
|
145
|
+
- [ ] 未安裝(或 service 未發布)時 extension 正常載入,`/watchdog status` 顯示 unavailable 訊息。
|
|
146
|
+
- [ ] 安裝 pi-subagents 後:main session 有 2 task + 1 watchdog sub-agent 時 `/watchdog status` 正確列出 3 個 target 的 kind 與 status(手動場景,需 pi TUI)。
|
|
147
|
+
- [ ] type-check + 全部測試通過。
|
|
148
|
+
|
|
149
|
+
## 6. Revision History
|
|
150
|
+
|
|
151
|
+
| Version | Change |
|
|
152
|
+
|---|---|
|
|
153
|
+
| v1 | 初版:含 spike 結果(service 簽名、事件 payload、parent id 解法)與 TDD 步驟 |
|
|
154
|
+
|
|
155
|
+
## 7. Reference
|
|
156
|
+
|
|
157
|
+
- reference01: `@gotgenes/pi-subagents@18.0.1` npm tarball `src/service/service.ts`、`src/observation/subagent-events-observer.ts`、`src/lifecycle/{subagent-state,child-lifecycle}.ts`(accessed 2026-07-08)
|
|
158
|
+
- reference02: `.tmp/2026-07-08_pi_watchdog_high_level_spec.md` — Task02 定義
|