@zhuxixi/pi-agent-board 0.5.2 → 0.6.1

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +43 -5
  3. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +551 -0
  4. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +209 -0
  5. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +796 -0
  6. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +114 -0
  7. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +73 -0
  8. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +143 -0
  9. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +226 -0
  10. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +87 -0
  11. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +53 -0
  12. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +731 -0
  13. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +30 -0
  14. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +68 -0
  15. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +125 -0
  16. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +56 -0
  17. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +96 -0
  18. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +1 -1
  19. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +92 -0
  20. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +50 -0
  21. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +106 -0
  22. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +64 -0
  23. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +48 -0
  24. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +64 -0
  25. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +71 -0
  26. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +49 -0
  27. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +76 -0
  28. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +510 -0
  29. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +58 -0
  30. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +52 -0
  31. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +87 -0
  32. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +56 -0
  33. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +79 -0
  34. package/package.json +83 -81
  35. package/runner/job-runner.mjs +2 -2
  36. package/runner/pty-runner.mjs +626 -3
  37. package/runner/state-runner.mjs +3 -0
  38. package/runner/title-runner.mjs +1 -1
  39. package/scripts/release_helper.mjs +277 -0
  40. package/src/commands/agent-board.ts +47 -35
  41. package/src/commands/attach-decision.mjs +66 -0
  42. package/src/commands/attach-flow.ts +45 -39
  43. package/src/commands/bg.ts +9 -0
  44. package/src/core/code-refs.mjs +85 -33
  45. package/src/core/evidence.mjs +2 -2
  46. package/src/core/heuristics.mjs +75 -2
  47. package/src/core/host-coordination.mjs +182 -0
  48. package/src/core/host-crash.mjs +43 -3
  49. package/src/core/host-probe.mjs +196 -0
  50. package/src/core/launch-options.mjs +17 -0
  51. package/src/core/launch.mjs +35 -35
  52. package/src/core/locks.mjs +196 -1
  53. package/src/core/paths.mjs +24 -0
  54. package/src/core/store.mjs +164 -5
  55. package/src/core/types.mjs +17 -1
  56. package/src/core/warm-host-sweeper.mjs +150 -0
  57. package/src/index.ts +40 -3
  58. package/src/runtime/service.mjs +1071 -108
  59. package/src/ui/dashboard-decisions.mjs +55 -0
  60. package/src/ui/dashboard.ts +67 -13
  61. package/src/ui/pty-attach.ts +23 -5
@@ -0,0 +1,87 @@
1
+ # issue #87 spec:legacy 死 host 安全回收(resolver 自愈)
2
+
3
+ 日期:2026-09-08 · 状态:待用户确认
4
+
5
+ ## 背景与问题
6
+
7
+ v0.5.x 时代(无 instanceId 协议)的 legacy PTY host,当 runner 进程被异常杀死(SIGKILL / 承载终端关闭)时 host.json 永远停在 `state: "alive"`(或 `"starting"`)。v0.6.0 的 attach resolver 对 legacy host 执行「never recovered」(spec §10.1 保守决策),probe 失败直接 pending,同时 `hostActive`(纯磁盘状态)让 ensureHost 拒绝重新 claim——三层叠加成死锁,attach 永久失败。本机实录 6 个 view 处于该状态。
8
+
9
+ ## 设计目标
10
+
11
+ resolver 对「pid 可验证已死 + endpoint 不可达」的 legacy host 自动 finalize 为 `exited` 并走正常 ensure/claim 自愈,消除死锁;不满足安全条件时保持现有 pending 行为(不推翻 spec 的保守原意)。
12
+
13
+ ## 核心设计
14
+
15
+ ### D1:纯决策函数 `canFinalizeLegacyHost`(host-coordination.mjs)
16
+
17
+ ```js
18
+ /**
19
+ * @param {{ host: HostStatus|null, hostPid: number|null, hostPidAlive: boolean,
20
+ * probeClassification: string }} input
21
+ * @returns {boolean}
22
+ */
23
+ ```
24
+
25
+ 返回 true 当且仅当四条件同时成立:
26
+ 1. `host.instanceId == null`(legacy)
27
+ 2. `host.state ∈ {"starting", "alive"}`(stopping 由既有恢复路径 L907-933 覆盖,不重复处理)
28
+ 3. `hostPid != null && hostPidAlive === false` —— pid 已死是核心安全证据;pid 复用只会让 isAlive=true → 走保守分支,方向安全
29
+ 4. `probeClassification ∈ {"missing", "stale"}` —— missing=ENOENT(socket/pipe 不存在);stale=ECONNREFUSED+isSocket(进程死、socket 文件残留)。`unknown`/`occupied`/`starting` 一律不回收
30
+
31
+ 纯函数、零副作用、零注入,真值表可全枚举测试。
32
+
33
+ ### D2:resolver legacy 分支改造(service.mjs `resolveAttachTargetInner`)
34
+
35
+ - pid 解析复用 loadRow 同款 fallback:`Object.hasOwn(host, "runnerPid") ? host.runnerPid : readHostPid(root, viewId)`(legacy 的 pid 在 host-pid.json 镜像)
36
+ - 现 L963 `else if (legacy) return pending(...)` 改为:
37
+ - `canFinalizeLegacyHost(...)` 为 true → finalize(见 D3)→ `continue`(下一轮 row 重载 hostActive=false → 走正常 ensure/claim)
38
+ - 否则保持原 pending(行为不变)
39
+ - `state === "starting"` 的 legacy(L936 `withinGrace` 恒 true 的等死分支):在 grace 等待分支内同样先查 `canFinalizeLegacyHost`(该分支 probe 已执行,classification 可得),满足即 finalize + continue,不再等 grace 到期
40
+
41
+ ### D3:finalize 动作(service.mjs 内联,写路径唯一)
42
+
43
+ `writeHost(root, viewId, { ...host, state: "exited", endedAt: now, lastSeenAt: now, error: "legacy host finalized: runner pid dead" })` + `appendDiagnostic({ source: "service", level: "info", code: "legacy_host_finalized", ... })`。
44
+
45
+ - legacy 无 instanceId,不存在并发 owner,无需 fencing(与新协议 updateOwnedHost 路径区分);竞态窗口由 host-start lease 串行化兜底(claim 走全新 instanceId,与 exited 记录不冲突)。
46
+ - 选 `exited` 而非 `failed`:进程是正常死亡语义(被外部杀死),failed 在现有代码里语义是"spawn/启动失败"(#86 场景),exited 与 v0.5.x runner 自然退出时写的状态一致,下游(canReplaceHost/ensure)对两者处理相同。
47
+
48
+ ### D4:平台兼容性
49
+
50
+ 不引入任何文件存在性检查(Windows 命名管道 existsSync 不可用,#45);证据只来自 probe classification(connect+hello 是唯一权威,spec §7.1)+ isAlive(kill(pid,0) 跨平台)。不碰 prewarm keypress 路径(2s TTL 纪律)。
51
+
52
+ ## 非目标(明确排除)
53
+
54
+ - **升级迁移扫描**(issue 建议 2):resolver 自愈后功能冗余(下次 attach 自然恢复);若后续要 dashboard 行状态立刻正确可单开 issue。
55
+ - **UI 兜底提示**(issue 建议 3):永久 pending 状态被消除后无存在意义。
56
+ - **stopping 状态 legacy**:已有恢复路径覆盖(L907-933 对 instanceId != null 生效;legacy stopping 的 `staleStop` 判定要求 instanceId != null——属现存另一个小缺口,本次不扩范围,记为遗留观察项)。
57
+
58
+ ## 可测性拆分设计
59
+
60
+ | 单元 | 位置 | 性质 | 测法 |
61
+ |---|---|---|---|
62
+ | `canFinalizeLegacyHost` | host-coordination.mjs 新增导出 | 纯函数 | 真值表单测:4 条件 × 关键组合(全满足/缺 pid/pid 活/unknown/stopping/新协议 host) |
63
+ | pid fallback 解析 | service.mjs resolver 内联(复用 store.readHostPid) | 副作用隔离 | 集成测试造 legacy host.json(无 runnerPid 属性)+ host-pid.json |
64
+ | finalize + 自愈闭环 | service.mjs resolver | 集成 | 注入 scriptProbe(missing) + 死 pid → 断言 host.json 落 exited、diagnostic 写入、resolver 继续 claim 新 host |
65
+ | 保守分支 | 同上 | 集成 | pid 活(用 process.pid)→ 断言 pending 且不写盘 |
66
+
67
+ 测试基建现成:`resolverService` + `scriptProbe` + `aliveHost` fixtures + `instantSleep`(test/host-resolver.test.mjs 模式)。`aliveHost` fixture 需支持造 legacy host(无 instanceId、无 runnerPid 属性 + host-pid.json 镜像),必要时加 `legacyHost` fixture helper。
68
+
69
+ ## 验收矩阵
70
+
71
+ | ID | 功能点 | 验收方式 | 具体验证 | 通过标准 |
72
+ |----|--------|----------|----------|----------|
73
+ | A1 | canFinalizeLegacyHost 决策正确性 | 自动化(unit) | `node --test test/host-coordination.test.mjs` | 真值表全组合通过 |
74
+ | A2 | legacy alive + pid 死 + missing → 自愈闭环 | 自动化(integration) | `node --test test/host-resolver.test.mjs` | host.json 落 exited、diagnostic 有 legacy_host_finalized、resolver 成功 claim 新 instance |
75
+ | A3 | legacy alive + pid 活 → 不回收 | 自动化(integration) | 同上 | pending 返回、host.json 未被改写 |
76
+ | A4 | legacy starting + pid 死 → 不等 grace 即回收 | 自动化(integration) | 同上 | 不 sleep 到 deadline 即完成回收 + claim |
77
+ | A5 | probe unknown → 不回收 | 自动化(integration) | 同上 | pending、不写盘 |
78
+ | A6 | 全量回归 | 自动化(static/build) | `npm test` + `npm run typecheck` | 568+ 全绿、无新类型错误 |
79
+ | U1 | 本机 6 个真实卡死 legacy view 实测 | 用户实测 | 运行副本 checkout PR 分支 → 重启 pi → board 对卡死 view 按 enter | 全部自动拉起新 host 可正常 attach,无 manual restart 提示 |
80
+
81
+ U1 必须用户执行(重启 pi 会断开实现 session)。执行时机:PR 合并前。
82
+
83
+ ## 风险与降级
84
+
85
+ - pid 复用误判方向恒为保守(不回收),不会误杀;
86
+ - finalize 后若 claim 失败(如 PTY 不可用),行为与现有 ensure 失败路径一致(pending + 原因),无新增失败模式;
87
+ - Windows 无 legacy named-pipe 实测环境(U2 类)——设计只依赖平台无关的 probe/isAlive,风险评估为低,最终报告标注未实测。
@@ -0,0 +1,56 @@
1
+ # issue #86 spec:spawn 异步 error 兜底(spawnDetached 统一封装)
2
+
3
+ 日期:2026-09-08 · 状态:已授权全自动推进(用户 2026-09-08 决策)
4
+
5
+ ## 背景与问题
6
+
7
+ `launch.mjs` 4 个启动函数与 `pty-attach.ts openExternalTarget()` 的 spawn 均为 `spawn(...)` + `unref()` 无 `'error'` listener。spawn 启动失败走异步 `'error'` 事件,EventEmitter 无 listener 的 error 直接 throw → uncaughtException → 整个 pi 宿主进程退出。实录:WSL2 上一次 node 二进制瞬时 ENOENT 直接带崩 pi(host.json 已正确落 failed,但进程没活下来走重试)。
8
+
9
+ ## 核心设计
10
+
11
+ ### D1:launch.mjs 新增内部 helper `spawnDetached`
12
+
13
+ ```js
14
+ function spawnDetached(command, args, cwd) {
15
+ const child = spawn(command, args, { cwd, detached: true, stdio: "ignore", env: process.env, windowsHide: true });
16
+ child.on("error", () => {}); // 接住异步启动失败;调用方按 pid==null 记 failed,宿主不崩
17
+ child.unref();
18
+ return child;
19
+ }
20
+ ```
21
+
22
+ `launchRun` / `launchHost` / `launchTitle` / `launchAutoState` 四处收敛到该 helper。helper 不导出(模块内部实现细节,测试通过公开入口行为验证)。
23
+
24
+ ### D2:openExternalTarget 三分支补 error listener
25
+
26
+ darwin/win32/其他三分支统一为 `const child = spawn(...); child.on("error", () => {}); child.unref();`。fire-and-forget 语义与返回值不变(打开链接是 best-effort,失败静默)。参照同文件 L847-848 xclip 既有正确模式。
27
+
28
+ ## 非目标
29
+
30
+ - runner/*.mjs 内的 spawn(跑在分离 runner 进程里,崩了不拖垮 pi,issue 已明确排除);
31
+ - 顶层 `process.on("uncaughtException")` 兜底(会掩盖未知错误,不引入);
32
+ - 改变任何返回值/状态机语义。
33
+
34
+ ## 可测性拆分设计
35
+
36
+ | 单元 | 性质 | 测法 |
37
+ |---|---|---|
38
+ | spawnDetached 异步 error 兜底 | 副作用隔离(仅 child_process) | integration:注入不存在 node 路径,断言返回 pid:null 且进程存活(uncaughtException listener 不触发;node:test 进程崩=测试天然失败) |
39
+ | 4 个公开入口行为不变 | 公开 API | 复用现有 4 测试 + 新增 ENOENT 用例覆盖 launchRun/launchHost/launchTitle/launchAutoState |
40
+ | openExternalTarget | UI 层(c8 阈值外,惯例冒烟保护) | 静态验证 error listener 存在;不单加测试 |
41
+
42
+ ## 验收矩阵
43
+
44
+ | ID | 功能点 | 验收方式 | 具体验证 | 通过标准 |
45
+ |----|--------|----------|----------|----------|
46
+ | A1 | spawn 失败不带崩进程(4 入口) | 自动化(integration) | `node --test test/launch.test.mjs` | 注入 `/nonexistent/node-ENOENT-test` 后各入口正常返回 pid:null,进程存活 |
47
+ | A2 | 既有行为回归 | 自动化(integration) | `node --test test/launch.test.mjs` | 原 4 测试全绿 |
48
+ | A3 | openExternalTarget error 兜底 | 自动化(static) | 代码审查 + grep 断言 | 三分支均有 error listener |
49
+ | A4 | 全量回归 | 自动化(static/build) | `npm test` + `npm run typecheck` | 568+ 全绿、无类型错误 |
50
+ | U1 | issue 复现脚本对照 | 自动化替代(A1 等价) | issue 自带复现脚本逻辑已并入 A1 | 见 A1 |
51
+
52
+ U1 说明:issue 的复现脚本本质是"注入坏 node 路径 + uncaughtException 监听",A1 测试完全等价覆盖,故不需要独立用户实测。无 U 类纯人工项。
53
+
54
+ ## 风险
55
+
56
+ 极低:纯增量兜底,不改成功路径任何行为;失败路径从"进程崩"变为"走既有 pid==null 记 failed 重试路径"。
@@ -0,0 +1,79 @@
1
+ # issue #90 spec:defaultModel 失效防护(launch 前校验 + child exit 归因)
2
+
3
+ 日期:2026-09-08 · 状态:已授权全自动推进(用户 2026-09-08 决策)
4
+
5
+ ## 背景与问题
6
+
7
+ session 创建时记录的 `meta.defaultModel` 失效后(provider 前缀改名/移除),attach 走「launchHost → child --model 失效 → exit 1 → host exited → 再 attach 再 launch」死循环(实录单 view 数百次 launch_host)。且 runner child exit 不写 error 字段(pty-runner.mjs L217),host.json 只有 exitCode:1 无任何归因线索。
8
+
9
+ ## 核心设计
10
+
11
+ ### D1:launch 前模型校验(断死循环)
12
+
13
+ 1. **core 导出匹配函数**(`src/core/launch-options.mjs`):
14
+ ```js
15
+ /**
16
+ * Whether a stored model reference resolves to a currently-available model.
17
+ * Same rule as the dashboard launch picker: case-insensitive exact "provider/id".
18
+ * @param {string|null|undefined} modelRef
19
+ * @param {Array<{provider: string, id: string}>|undefined|null} availableModels
20
+ * @returns {boolean} true when modelRef is empty/null (no constraint) or matched.
21
+ */
22
+ export function modelRefAvailable(modelRef, availableModels)
23
+ ```
24
+ `modelRef` 空 → true(无校验对象);`availableModels` 空/undefined → true(调用方无法判断时保守放行,不制造新阻塞)。
25
+
26
+ 2. **service 注入**:`createService` 新增 `opts.availableModels`(`() => Array<{provider,id}>`,每次调用实时取值;默认 undefined → 跳过校验)。`src/index.ts` serviceFor 与 `src/commands/agent-board.ts` flag 路径注入 `() => ctx.modelRegistry.getAvailable()`(try/catch → undefined,对齐 agent-board.ts L75-79 现有防御模式)。
27
+
28
+ 3. **统一校验 helper**(service.mjs 模块内):`validateViewModelMeta(meta) → null | string`(null=通过;string=错误消息)。错误消息明确可行动:`Model "X" configured for this session is no longer available — update the view's model or clear defaultModel, then retry attach.`
29
+
30
+ 4. **两个校验点**(service.mjs 仅有的 host spawn 路径):
31
+ - `startHostUnderLease`:在 claim 之前校验(首选;若代码结构上 claim 已发生,则落 failed 清理后返回)→ `{ ok: false, error }`;
32
+ - `adoptClaimedHost`:claim 是既有废弃记录 → 校验失败时 `updateOwnedHost` 落 failed(error 带模型消息)+ 返回 pending。
33
+ - resolver 链路无需改动:ensureHostImpl 的 `{ ok:false, error }` 走现有 `pending(sessionFile, res.error)` → attach-flow notify 展示(渠道现成)。
34
+
35
+ 5. **dashboard.ts L1738 `findLaunchModelByRef` 改为复用 core 导出**(单一事实源;LaunchModel 结构类型 JSDoc 化)。
36
+
37
+ ### D2:child exit 错误归因(补线索)
38
+
39
+ 1. **纯函数**(`src/core/heuristics.mjs` 新增导出):`lastVisibleLogLine(text, maxLen = 200)`——strip ANSI(CSI `\x1b\[[0-9;?]*[ -/]*[@-~]` + OSC `\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)` + 杂项转义),按行切分,返回最后一个非空可见行(`\r` 处理:取每行最后一个 `\r` 段),截断 maxLen。空输入/全空 → null。
40
+ 2. **runner 接线**(`runner/pty-runner.mjs` child exit 回调 L213-223):`exitCode != null && exitCode !== 0` 时 best-effort(try/catch 包裹,绝不影响 exit 路径)读 screen.log 尾部 ~8KB(openSync/readSync seek 尾部,不整文件读)→ `lastVisibleLogLine` → 非 null 则 `update({ ..., error })`。exitCode=0 不改 error。
41
+
42
+ ## 非目标
43
+
44
+ - **失败退避**(issue 建议 2):D1 已断 spawn 死循环;通用退避需跨 claim 持久计数,复杂度/收益不划算,未来场景再单开;
45
+ - **run 路径模型校验**(launchRun 的 model):detach run 失败是一次性的,不循环——观察项;
46
+ - **UI 改动**:notify 渠道现成;
47
+ - 自动 fallback 到其他模型:静默换模型会让用户不知 session 行为已变,fail-fast + 明确提示更安全(issue 建议 1 的两个选项中选 fail-fast)。
48
+
49
+ ## 可测性拆分设计
50
+
51
+ | 单元 | 位置 | 性质 | 测法 |
52
+ |---|---|---|---|
53
+ | `modelRefAvailable` | launch-options.mjs | 纯函数 | 真值表:大小写/null/空列表/部分匹配不匹配 |
54
+ | `validateViewModelMeta` + ensure 路径 | service.mjs | 副作用隔离(注入 availableModels + launchHost spy) | integration:失效模型 → ok:false + launchHost 零调用 + 无 starting claim 残留;有效/null/未注入三对照 |
55
+ | adopt 路径校验 | service.mjs | 同上 | integration:废弃 claim + 失效模型 → 落 failed + 不 spawn |
56
+ | `lastVisibleLogLine` | heuristics.mjs | 纯函数 | 单测:ANSI/OSC/`\r`/空行/截断/空输入 |
57
+ | runner exit 归因 | pty-runner.mjs | 集成 | pty-runner.integration.test.mjs 模式:fake child exit 1 + 预置 screen.log → host.json.error 含错误行;exit 0 → error 不变 |
58
+
59
+ ## 验收矩阵
60
+
61
+ | ID | 功能点 | 验收方式 | 具体验证 | 通过标准 |
62
+ |----|--------|----------|----------|----------|
63
+ | A1 | modelRefAvailable 匹配规则 | 自动化(unit) | `node --test test/launch-options.test.mjs`(或既有对应文件) | 真值表全过 |
64
+ | A2 | 失效模型 attach 不再 spawn | 自动化(integration) | `node --test test/host-resolver.test.mjs` / service 测试 | ok:false、launchHost spy 零调用、无 starting claim 残留、error 消息含模型名 |
65
+ | A3 | 有效模型/null 模型回归 | 自动化(integration) | 同上 | 正常 launch(claim → spawn) |
66
+ | A4 | 未注入 availableModels 向后兼容 | 自动化(integration) | 同上 | 跳过校验正常 launch |
67
+ | A5 | adopt 路径失效模型 | 自动化(integration) | 同上 | 落 failed + 不 spawn |
68
+ | A6 | lastVisibleLogLine 提取 | 自动化(unit) | heuristics 测试 | ANSI/截断/空行用例全过 |
69
+ | A7 | runner exit≠0 归因 | 自动化(integration) | runner 测试 | host.json.error 含 screen.log 尾部错误行;exit 0 不写 |
70
+ | A8 | 全量回归 | 自动化(static/build) | `npm test` + `npm run typecheck` | 608+ 全绿 |
71
+ | U1 | 真实失效 view 实测 | 用户实测 | 合并后:view_539a5e9e20 恢复失效 defaultModel=glm/glm-5.3 → attach → 观察 notify 与 diagnostics | notify 明确提示模型失效;diagnostics 不再累积 launch_host;改回有效模型后 attach 成功 |
72
+
73
+ U1 需重启 pi(git 包不热重载),合并后由用户执行并回 issue 记录。
74
+
75
+ ## 风险与降级
76
+
77
+ - availableModels 为空数组时保守放行(不制造新阻塞)——校验仅在能确定"失效"时拦截;
78
+ - 校验失败路径每次 attach 仅一次文件级操作,无进程开销;
79
+ - D2 全 try/catch best-effort,exit 路径行为不变。
package/package.json CHANGED
@@ -1,83 +1,85 @@
1
1
  {
2
- "name": "@zhuxixi/pi-agent-board",
3
- "version": "0.5.2",
4
- "description": "Agent-board dashboard for Pi: dispatch, monitor, peek/reply, and attach to background Pi sessions.",
5
- "type": "module",
6
- "main": "./index.ts",
7
- "exports": "./index.ts",
8
- "files": [
9
- "index.ts",
10
- "src",
11
- "scripts",
12
- "runner",
13
- "README.md",
14
- "VERIFY.md",
15
- "LICENSE",
16
- "docs",
17
- "PRD.md",
18
- "IMPLEMENTATION_PLAN.md",
19
- "PROGRESS.md"
20
- ],
21
- "keywords": [
22
- "pi-package",
23
- "pi-extension",
24
- "pi",
25
- "pi-coding-agent",
26
- "agent-board",
27
- "background-agents",
28
- "dashboard"
29
- ],
30
- "license": "MIT",
31
- "repository": {
32
- "type": "git",
33
- "url": "git+https://github.com/zhuxixi/pi-agent-board.git"
34
- },
35
- "bugs": {
36
- "url": "https://github.com/zhuxixi/pi-agent-board/issues"
37
- },
38
- "homepage": "https://github.com/zhuxixi/pi-agent-board#readme",
39
- "publishConfig": {
40
- "access": "public"
41
- },
42
- "engines": {
43
- "node": ">=20"
44
- },
45
- "pi": {
46
- "extensions": [
47
- "./src/index.ts"
48
- ],
49
- "video": "https://github.com/zhuxixi/pi-agent-board/raw/refs/heads/main/assets/demo.mp4",
50
- "image": "https://github.com/zhuxixi/pi-agent-board/raw/refs/heads/main/assets/banner.png"
51
- },
52
- "scripts": {
53
- "postinstall": "node scripts/patch-vulns.mjs",
54
- "typecheck": "tsc --noEmit",
55
- "test": "node --test test/*.test.mjs",
56
- "test:coverage": "c8 node --test test/*.test.mjs",
57
- "pack:dry": "npm pack --dry-run",
58
- "verify": "npm run typecheck && npm test && npm run test:coverage && npm run pack:dry"
59
- },
60
- "peerDependencies": {
61
- "@earendil-works/pi-coding-agent": "*",
62
- "@earendil-works/pi-tui": "*"
63
- },
64
- "peerDependenciesMeta": {
65
- "@earendil-works/pi-coding-agent": {
66
- "optional": true
67
- },
68
- "@earendil-works/pi-tui": {
69
- "optional": true
70
- }
71
- },
72
- "devDependencies": {
73
- "@earendil-works/pi-coding-agent": "0.79.8",
74
- "@earendil-works/pi-tui": "0.79.8",
75
- "@types/node": "^25.9.1",
76
- "c8": "^12.0.0",
77
- "typescript": "^5.9.3"
78
- },
79
- "dependencies": {
80
- "@xterm/headless": "^6.0.0",
81
- "node-pty": "^1.1.0"
82
- }
2
+ "name": "@zhuxixi/pi-agent-board",
3
+ "version": "0.6.1",
4
+ "description": "Agent-board dashboard for Pi: dispatch, monitor, peek/reply, and attach to background Pi sessions.",
5
+ "type": "module",
6
+ "main": "./index.ts",
7
+ "exports": "./index.ts",
8
+ "files": [
9
+ "CHANGELOG.md",
10
+ "IMPLEMENTATION_PLAN.md",
11
+ "LICENSE",
12
+ "PRD.md",
13
+ "PROGRESS.md",
14
+ "README.md",
15
+ "VERIFY.md",
16
+ "docs",
17
+ "index.ts",
18
+ "runner",
19
+ "scripts",
20
+ "src"
21
+ ],
22
+ "keywords": [
23
+ "pi-package",
24
+ "pi-extension",
25
+ "pi",
26
+ "pi-coding-agent",
27
+ "agent-board",
28
+ "background-agents",
29
+ "dashboard"
30
+ ],
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/zhuxixi/pi-agent-board.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/zhuxixi/pi-agent-board/issues"
38
+ },
39
+ "homepage": "https://github.com/zhuxixi/pi-agent-board#readme",
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "engines": {
44
+ "node": ">=20"
45
+ },
46
+ "pi": {
47
+ "extensions": [
48
+ "./src/index.ts"
49
+ ],
50
+ "video": "https://github.com/zhuxixi/pi-agent-board/raw/refs/heads/main/assets/demo.mp4",
51
+ "image": "https://github.com/zhuxixi/pi-agent-board/raw/refs/heads/main/assets/banner.png"
52
+ },
53
+ "scripts": {
54
+ "postinstall": "node scripts/patch-vulns.mjs",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "node --test test/*.test.mjs",
57
+ "test:coverage": "c8 node --test test/*.test.mjs",
58
+ "pack:dry": "npm pack --dry-run",
59
+ "verify": "npm run typecheck && npm test && npm run test:coverage && npm run pack:dry",
60
+ "changelog": "node scripts/release_helper.mjs"
61
+ },
62
+ "peerDependencies": {
63
+ "@earendil-works/pi-coding-agent": "*",
64
+ "@earendil-works/pi-tui": "*"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "@earendil-works/pi-coding-agent": {
68
+ "optional": true
69
+ },
70
+ "@earendil-works/pi-tui": {
71
+ "optional": true
72
+ }
73
+ },
74
+ "devDependencies": {
75
+ "@earendil-works/pi-coding-agent": "0.79.8",
76
+ "@earendil-works/pi-tui": "0.79.8",
77
+ "@types/node": "^25.9.1",
78
+ "c8": "^12.0.0",
79
+ "typescript": "^5.9.3"
80
+ },
81
+ "dependencies": {
82
+ "@xterm/headless": "^6.0.0",
83
+ "node-pty": "^1.1.0"
84
+ }
83
85
  }
@@ -86,7 +86,7 @@ function main() {
86
86
  cwd: config.cwd,
87
87
  stdio: ["ignore", "pipe", "pipe"],
88
88
  windowsHide: true,
89
- env: process.env,
89
+ env: { ...process.env, AGENT_BOARD_NO_SWEEP: "1" },
90
90
  });
91
91
 
92
92
  status.pid = worker.pid ?? null;
@@ -460,7 +460,7 @@ async function maybeModelSummary(config, status) {
460
460
  function runOneShot(command, args, timeoutMs = 20000) {
461
461
  return new Promise((resolve) => {
462
462
  let out = "";
463
- const child = spawn(command, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
463
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "ignore"], windowsHide: true, env: { ...process.env, AGENT_BOARD_NO_SWEEP: "1" } });
464
464
  let buf = "";
465
465
  child.stdout.on("data", (c) => {
466
466
  buf += c.toString();