@gavin-hu/my-pi-agent 0.18.0 → 0.20.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.
Files changed (324) hide show
  1. package/README.md +66 -52
  2. package/README.zh-CN.md +56 -46
  3. package/docs/extensions/README.md +34 -0
  4. package/docs/extensions/board.md +507 -0
  5. package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +774 -574
  6. package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
  7. package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
  8. package/docs/extensions/file-browse.md +561 -0
  9. package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
  10. package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
  11. package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
  12. package/{extensions/job/README.md → docs/extensions/job.md} +62 -52
  13. package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
  14. package/{extensions/plan/README.md → docs/extensions/plan.md} +440 -322
  15. package/{extensions/question/README.md → docs/extensions/question.md} +54 -31
  16. package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
  17. package/docs/extensions/session-bus.md +298 -0
  18. package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
  19. package/docs/extensions/team.md +254 -0
  20. package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
  21. package/{extensions/watch/README.md → docs/extensions/watch.md} +54 -36
  22. package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
  23. package/{extensions/wechat/README.md → docs/extensions/wechat.md} +260 -163
  24. package/{extensions/worktree/README.md → docs/extensions/worktree.md} +137 -79
  25. package/extensions/board/commands.ts +105 -0
  26. package/extensions/board/config.ts +36 -0
  27. package/extensions/board/format.ts +170 -0
  28. package/extensions/board/index.ts +40 -0
  29. package/extensions/board/input.ts +54 -0
  30. package/extensions/board/join.ts +42 -0
  31. package/extensions/board/page.ts +388 -0
  32. package/extensions/board/result.ts +108 -0
  33. package/extensions/board/rows.ts +48 -0
  34. package/extensions/board/schema.ts +90 -0
  35. package/extensions/board/seat.ts +28 -0
  36. package/extensions/board/snapshot.ts +42 -0
  37. package/extensions/board/tools.ts +444 -0
  38. package/extensions/board/transport/router.ts +91 -0
  39. package/extensions/board/transport/server.ts +138 -0
  40. package/extensions/board/types.ts +121 -0
  41. package/extensions/board/view/client.ts +51 -0
  42. package/extensions/board/view/dialog.ts +43 -0
  43. package/extensions/board/view/keys.ts +125 -0
  44. package/extensions/board/view/live.ts +231 -0
  45. package/extensions/board/view/names.ts +91 -0
  46. package/extensions/board/view/page.ts +118 -0
  47. package/extensions/board/view/render.ts +519 -0
  48. package/extensions/board/view/styles.ts +228 -0
  49. package/extensions/board/view/team.ts +327 -0
  50. package/extensions/bytefly/footer/budget.ts +6 -5
  51. package/extensions/bytefly/footer/collect/entries.ts +12 -4
  52. package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
  53. package/extensions/bytefly/footer/config.ts +19 -8
  54. package/extensions/bytefly/footer/price/price.ts +30 -8
  55. package/extensions/bytefly/footer/register.ts +5 -5
  56. package/extensions/bytefly/footer/render/format.ts +18 -0
  57. package/extensions/bytefly/footer/render/lines.ts +23 -7
  58. package/extensions/bytefly/footer/render/segments.ts +117 -31
  59. package/extensions/bytefly/footer/types.ts +29 -5
  60. package/extensions/bytefly/footer/unpriced.ts +42 -17
  61. package/extensions/bytefly/panel/commands.ts +67 -7
  62. package/extensions/bytefly/panel/filter.ts +68 -0
  63. package/extensions/bytefly/panel/format.ts +16 -15
  64. package/extensions/bytefly/panel/join.ts +56 -13
  65. package/extensions/bytefly/panel/prices.ts +43 -0
  66. package/extensions/bytefly/panel/settings-runtime.ts +0 -6
  67. package/extensions/bytefly/panel/settings.ts +75 -43
  68. package/extensions/bytefly/panel/tui/entries.ts +26 -8
  69. package/extensions/bytefly/panel/tui/frame.ts +130 -93
  70. package/extensions/bytefly/panel/tui/keys.ts +352 -0
  71. package/extensions/bytefly/panel/tui/list-view.ts +25 -46
  72. package/extensions/bytefly/panel/tui/panes.ts +76 -0
  73. package/extensions/bytefly/panel/tui/section-view.ts +126 -0
  74. package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
  75. package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
  76. package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
  77. package/extensions/bytefly/panel/tui.ts +306 -259
  78. package/extensions/bytefly/picker/discovery.ts +0 -2
  79. package/extensions/bytefly/picker/types.ts +0 -2
  80. package/extensions/bytefly/update/check.ts +39 -0
  81. package/extensions/bytefly/update/format.ts +15 -0
  82. package/extensions/bytefly/update/register.ts +73 -34
  83. package/extensions/bytefly/update/state.ts +6 -3
  84. package/extensions/check/commands.ts +2 -1
  85. package/extensions/context/tui.ts +1 -1
  86. package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
  87. package/extensions/file-browse/files/classify.ts +136 -0
  88. package/extensions/file-browse/files/files.ts +12 -0
  89. package/extensions/file-browse/files/index.ts +90 -0
  90. package/extensions/file-browse/files/list.ts +261 -0
  91. package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
  92. package/extensions/file-browse/files/tree.ts +101 -0
  93. package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
  94. package/extensions/file-browse/index.ts +4 -4
  95. package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
  96. package/extensions/file-browse/{router.ts → transport/router.ts} +21 -17
  97. package/extensions/file-browse/transport/server.ts +110 -0
  98. package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
  99. package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
  100. package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
  101. package/extensions/file-browse/view/emit.ts +51 -0
  102. package/extensions/file-browse/{html.ts → view/html.ts} +19 -18
  103. package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
  104. package/extensions/file-browse/{markdown.ts → view/markdown.ts} +18 -20
  105. package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +19 -14
  106. package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
  107. package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
  108. package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
  109. package/extensions/file-read/schema.ts +2 -1
  110. package/extensions/job/runtime.ts +4 -4
  111. package/extensions/memory/store.ts +12 -5
  112. package/extensions/plan/blocks.ts +23 -2
  113. package/extensions/plan/commands.ts +21 -16
  114. package/extensions/plan/feedback.ts +185 -52
  115. package/extensions/plan/format.ts +10 -3
  116. package/extensions/plan/index.ts +23 -5
  117. package/extensions/plan/plan-files.ts +88 -0
  118. package/extensions/plan/plan-index.ts +43 -9
  119. package/extensions/plan/plan-text.ts +33 -23
  120. package/extensions/plan/plans.ts +27 -34
  121. package/extensions/plan/policy.ts +30 -17
  122. package/extensions/plan/review.ts +3 -27
  123. package/extensions/plan/runtime.ts +52 -11
  124. package/extensions/plan/sections.ts +112 -0
  125. package/extensions/plan/steps.ts +3 -22
  126. package/extensions/plan/todos.ts +51 -3
  127. package/extensions/plan/tools/exit.ts +20 -95
  128. package/extensions/plan/tools/todo-seed.ts +4 -2
  129. package/extensions/plan/tools/write.ts +12 -1
  130. package/extensions/plan/tui.ts +5 -34
  131. package/extensions/plan/types.ts +15 -8
  132. package/extensions/plan/web-blocks.ts +89 -0
  133. package/extensions/plan/web-chrome.ts +3 -5
  134. package/extensions/plan/web-client.ts +179 -330
  135. package/extensions/plan/web-collab.ts +3 -3
  136. package/extensions/plan/web-comments.ts +18 -8
  137. package/extensions/plan/web-composer.ts +80 -0
  138. package/extensions/plan/web-draft.ts +40 -0
  139. package/extensions/plan/web-keys.ts +134 -0
  140. package/extensions/plan/web-outline.ts +192 -0
  141. package/extensions/plan/web-page.ts +4 -8
  142. package/extensions/plan/web-plan.ts +150 -0
  143. package/extensions/plan/web-router.ts +3 -58
  144. package/extensions/plan/web-settle.ts +194 -0
  145. package/extensions/plan/web-share-session.ts +24 -30
  146. package/extensions/plan/web-streams.ts +14 -14
  147. package/extensions/plan/web-tokens.ts +2 -2
  148. package/extensions/plan/web.ts +66 -120
  149. package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
  150. package/extensions/rewind/{review.ts → commands/review.ts} +15 -14
  151. package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
  152. package/extensions/rewind/{config.ts → config/config.ts} +1 -1
  153. package/extensions/rewind/index.ts +3 -3
  154. package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
  155. package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
  156. package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
  157. package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
  158. package/extensions/rewind/{format.ts → text/format.ts} +3 -3
  159. package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
  160. package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
  161. package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
  162. package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
  163. package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
  164. package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
  165. package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
  166. package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
  167. package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
  168. package/extensions/rewind/{status.ts → view/status.ts} +4 -4
  169. package/extensions/session-bus/commands.ts +17 -69
  170. package/extensions/session-bus/format.ts +22 -45
  171. package/extensions/session-bus/index.ts +13 -7
  172. package/extensions/session-bus/join-request.ts +40 -0
  173. package/extensions/session-bus/mail-watch.ts +134 -0
  174. package/extensions/session-bus/notice.ts +32 -4
  175. package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
  176. package/extensions/session-bus/protocol.ts +19 -49
  177. package/extensions/session-bus/resolve.ts +67 -0
  178. package/extensions/session-bus/result.ts +16 -51
  179. package/extensions/session-bus/rows.ts +11 -5
  180. package/extensions/session-bus/runtime.ts +220 -267
  181. package/extensions/session-bus/store.ts +55 -7
  182. package/extensions/session-bus/tools.ts +31 -66
  183. package/extensions/session-bus/tui.ts +5 -5
  184. package/extensions/session-bus/types.ts +23 -14
  185. package/extensions/subagent/agents.ts +9 -8
  186. package/extensions/subagent/commands.ts +2 -1
  187. package/extensions/subagent/invocation.ts +4 -2
  188. package/extensions/team/commands.ts +145 -0
  189. package/extensions/team/dispatch.ts +133 -0
  190. package/extensions/team/format.ts +119 -0
  191. package/extensions/team/index.ts +34 -0
  192. package/extensions/team/schema.ts +29 -0
  193. package/extensions/team/tools.ts +267 -0
  194. package/extensions/team/types.ts +65 -0
  195. package/extensions/todo/index.ts +4 -1
  196. package/extensions/todo/nudge.ts +8 -2
  197. package/extensions/web-access/fetch/page.ts +4 -1
  198. package/extensions/web-access/fetch/structured.ts +447 -0
  199. package/extensions/web-access/fetch/tool.ts +2 -1
  200. package/extensions/web-access/search/tool.ts +2 -1
  201. package/extensions/wechat/bridge.ts +106 -251
  202. package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
  203. package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
  204. package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
  205. package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
  206. package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
  207. package/extensions/wechat/dialog/channels.ts +136 -0
  208. package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
  209. package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
  210. package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
  211. package/extensions/wechat/index.ts +10 -10
  212. package/extensions/wechat/intake.ts +245 -0
  213. package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
  214. package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
  215. package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
  216. package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
  217. package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
  218. package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
  219. package/extensions/wechat/{state.ts → state/state.ts} +14 -2
  220. package/extensions/wechat/{format.ts → text/format.ts} +3 -3
  221. package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
  222. package/extensions/wechat/tools.ts +29 -12
  223. package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
  224. package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
  225. package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
  226. package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
  227. package/extensions/wechat/transport/outbound.ts +142 -0
  228. package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
  229. package/extensions/wechat/types.ts +10 -1
  230. package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
  231. package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
  232. package/extensions/worktree/commands.ts +24 -17
  233. package/extensions/worktree/exit.ts +12 -6
  234. package/extensions/worktree/git.ts +22 -14
  235. package/extensions/worktree/guard.ts +18 -290
  236. package/extensions/worktree/handoff.ts +10 -2
  237. package/extensions/worktree/index.ts +2 -1
  238. package/extensions/worktree/lifecycle.ts +2 -0
  239. package/extensions/worktree/registry.ts +20 -11
  240. package/extensions/worktree/root-tools.ts +13 -3
  241. package/extensions/worktree/runtime.ts +8 -2
  242. package/extensions/worktree/screen.ts +15 -8
  243. package/extensions/worktree/shell.ts +303 -0
  244. package/extensions/worktree/state.ts +1 -1
  245. package/extensions/worktree/status.ts +42 -9
  246. package/extensions/worktree/tools.ts +19 -13
  247. package/lib/README.md +11 -8
  248. package/lib/board/card.ts +479 -0
  249. package/lib/board/links.ts +141 -0
  250. package/lib/board/page-pointer.ts +310 -0
  251. package/lib/board/round-text.ts +81 -0
  252. package/lib/board/round.ts +94 -0
  253. package/lib/board/rules.ts +437 -0
  254. package/lib/board/session.ts +105 -0
  255. package/lib/board/store.ts +497 -0
  256. package/lib/board/view.ts +234 -0
  257. package/lib/errors.ts +12 -4
  258. package/lib/format.ts +35 -0
  259. package/lib/http/guard.ts +112 -0
  260. package/lib/http/index.ts +16 -5
  261. package/lib/http/serve.ts +237 -0
  262. package/lib/http/server.ts +12 -10
  263. package/lib/http/sink.ts +135 -0
  264. package/lib/http/token.ts +38 -0
  265. package/lib/plan-entry.ts +11 -0
  266. package/lib/policy.ts +25 -2
  267. package/lib/requirements.ts +6 -7
  268. package/lib/session-label.ts +64 -0
  269. package/lib/session-roster.ts +201 -0
  270. package/lib/session-tool.ts +184 -0
  271. package/lib/spawn.ts +37 -3
  272. package/lib/stray-fields.ts +45 -0
  273. package/lib/tool-names.ts +38 -2
  274. package/lib/tui/glyphs.ts +3 -2
  275. package/lib/web/README.md +57 -4
  276. package/lib/web/client-runtime.ts +123 -0
  277. package/lib/web/components.ts +5 -0
  278. package/lib/web/index.ts +4 -2
  279. package/lib/web/markup.ts +88 -1
  280. package/lib/web/names.ts +42 -0
  281. package/lib/web/runtime.ts +7 -1
  282. package/lib/web/tokens.ts +25 -2
  283. package/package.json +26 -14
  284. package/skills/next/SKILL.md +4 -3
  285. package/skills/project-docs/SKILL.md +133 -0
  286. package/skills/project-docs/references/audit.md +131 -0
  287. package/skills/project-docs/references/changelog.md +105 -0
  288. package/skills/project-docs/references/discovery.md +127 -0
  289. package/skills/project-docs/references/readme.md +80 -0
  290. package/skills/project-docs/references/release.md +84 -0
  291. package/skills/project-docs/references/roadmap.md +93 -0
  292. package/skills/project-docs/scripts/state.ts +316 -0
  293. package/skills/refactor/SKILL.md +100 -125
  294. package/skills/refactor/references/handoff.md +35 -0
  295. package/skills/refactor/references/signals.md +54 -0
  296. package/skills/refactor/scripts/inventory.ts +587 -0
  297. package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
  298. package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
  299. package/extensions/file-browse/README.md +0 -506
  300. package/extensions/file-browse/files.ts +0 -564
  301. package/extensions/file-browse/server.ts +0 -141
  302. package/extensions/job/process.ts +0 -35
  303. package/extensions/plan/split.ts +0 -87
  304. package/extensions/session-bus/README.md +0 -247
  305. package/extensions/session-bus/team.ts +0 -102
  306. package/extensions/wechat/outbound.ts +0 -100
  307. package/lib/plans/sections.ts +0 -172
  308. package/lib/plans/setting.ts +0 -52
  309. package/lib/plans/split.ts +0 -215
  310. package/lib/plans/store.ts +0 -19
  311. package/lib/plans/team.ts +0 -151
  312. package/skills/changelog/SKILL.md +0 -148
  313. package/skills/parallel-dev/SKILL.md +0 -150
  314. /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
  315. /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
  316. /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
  317. /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
  318. /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
  319. /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
  320. /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
  321. /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
  322. /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
  323. /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
  324. /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
@@ -11,8 +11,9 @@ import { createHash, randomBytes } from "node:crypto";
11
11
  import { mkdir, link, readdir, rename, rm, rmdir, writeFile } from "node:fs/promises";
12
12
  import { dirname, join } from "node:path";
13
13
  import { asRecord, readJson } from "../../lib/config.ts";
14
- import { claimsConflict } from "./protocol.ts";
14
+ import { claimsConflict } from "./overlap.ts";
15
15
  import type { BusMessage, ClaimConflict, ClaimRecord, SessionRecord } from "./types.ts";
16
+ import { INBOX_DIR_NAME, SESSIONS_DIR_NAME } from "../../lib/session-roster.ts";
16
17
 
17
18
  const DIR_MODE = 0o700;
18
19
  const FILE_MODE = 0o600;
@@ -27,6 +28,8 @@ export interface BusStore {
27
28
  ensure(): Promise<void>;
28
29
  writeSession(record: SessionRecord): Promise<void>;
29
30
  sessions(): Promise<SessionRecord[]>;
31
+ /** One session's record, or undefined when it is not on disk. */
32
+ session(id: string): Promise<SessionRecord | undefined>;
30
33
  removeSession(id: string): Promise<void>;
31
34
  writeMessage(target: string, message: BusMessage): Promise<void>;
32
35
  /** Read a recipient's messages without consuming them, oldest first. */
@@ -44,12 +47,36 @@ export interface BusStore {
44
47
  releaseAll(owner: string): Promise<string[]>;
45
48
  prune(input: {
46
49
  now: number;
50
+ /** The sessions known to be live when the caller read the bus. */
47
51
  activeIds: ReadonlySet<string>;
52
+ /**
53
+ * Records whose process the caller found gone: they keep their row until their
54
+ * heartbeat ages out, and own no path and no mail in the meantime.
55
+ */
56
+ goneIds?: ReadonlySet<string>;
48
57
  sessionTtlMs: number;
49
58
  messageTtlMs: number;
50
59
  }): Promise<void>;
51
60
  }
52
61
 
62
+ /**
63
+ * Release each path through `release`, reporting the ones this session did not
64
+ * hold. One copy, because `release` and `leave` answer the same two lists, and a
65
+ * second loop is how the two answers drift.
66
+ */
67
+ export async function releaseClaimed(
68
+ paths: readonly string[],
69
+ release: (path: string) => Promise<boolean>,
70
+ ): Promise<{ released: string[]; notOwned: string[] }> {
71
+ const released: string[] = [];
72
+ const notOwned: string[] = [];
73
+ for (const path of paths) {
74
+ if (await release(path)) released.push(path);
75
+ else notOwned.push(path);
76
+ }
77
+ return { released, notOwned };
78
+ }
79
+
53
80
  const SHA = (value: string): string => createHash("sha256").update(value).digest("hex").slice(0, 32);
54
81
 
55
82
  function isSession(value: unknown): value is SessionRecord {
@@ -63,10 +90,18 @@ function isSession(value: unknown): value is SessionRecord {
63
90
  typeof entry.repoRoot === "string" &&
64
91
  typeof entry.mode === "string" &&
65
92
  typeof entry.startedAt === "number" &&
66
- typeof entry.lastSeen === "number"
93
+ typeof entry.lastSeen === "number" &&
94
+ (entry.startToken === undefined || typeof entry.startToken === "string") &&
95
+ (entry.watching === undefined || isWatchingState(entry.watching))
67
96
  );
68
97
  }
69
98
 
99
+ /** The optional listening field, validated only when it is there. */
100
+ function isWatchingState(value: unknown): boolean {
101
+ const entry = asRecord(value);
102
+ return entry !== undefined && typeof entry.armed === "boolean" && typeof entry.wake === "boolean";
103
+ }
104
+
70
105
  function isMessage(value: unknown): value is BusMessage {
71
106
  const entry = asRecord(value);
72
107
  if (!entry) return false;
@@ -136,8 +171,8 @@ async function dropIfEmpty(dir: string): Promise<void> {
136
171
  /** Create a store rooted at one repository's bus directory. */
137
172
  export function createBusStore(options: BusStoreOptions): BusStore {
138
173
  const { dir } = options;
139
- const sessionsDir = join(dir, "sessions");
140
- const inboxRoot = join(dir, "inbox");
174
+ const sessionsDir = join(dir, SESSIONS_DIR_NAME);
175
+ const inboxRoot = join(dir, INBOX_DIR_NAME);
141
176
  const claimsDir = join(dir, "claims");
142
177
 
143
178
  const sessionFile = (id: string): string => join(sessionsDir, `${SHA(id)}.json`);
@@ -270,6 +305,8 @@ export function createBusStore(options: BusStoreOptions): BusStore {
270
305
 
271
306
  sessions: readSessions,
272
307
 
308
+ session: async (id) => readTyped(sessionFile(id), isSession),
309
+
273
310
  async removeSession(id) {
274
311
  await rm(sessionFile(id), { force: true });
275
312
  await dropIfEmpty(inboxDir(id));
@@ -305,16 +342,27 @@ export function createBusStore(options: BusStoreOptions): BusStore {
305
342
  return released;
306
343
  },
307
344
 
308
- async prune({ now: at, activeIds, sessionTtlMs, messageTtlMs }) {
345
+ async prune({ now: at, activeIds, goneIds, sessionTtlMs, messageTtlMs }) {
346
+ // The caller's set can lag: a peer that joined and claimed since that read is
347
+ // live on disk, and its claims are its own. This read is the answer, so the
348
+ // set only adds to what it found rather than deciding who survives — except for
349
+ // a record whose process is gone, which the caller knows and the file cannot.
350
+ // That record keeps its row until the heartbeat ages out and owns nothing.
351
+ const live = new Set(activeIds);
352
+ const gone = new Set(goneIds ?? []);
309
353
  for (const session of await readSessions()) {
310
354
  if (at - session.lastSeen > sessionTtlMs) {
311
355
  await rm(sessionFile(session.id), { force: true });
312
356
  await dropIfEmpty(inboxDir(session.id));
313
- activeIds = new Set([...activeIds].filter((id) => id !== session.id));
357
+ live.delete(session.id);
358
+ } else if (gone.has(session.id)) {
359
+ live.delete(session.id);
360
+ } else {
361
+ live.add(session.id);
314
362
  }
315
363
  }
316
364
  for (const claim of await readClaims()) {
317
- if (!activeIds.has(claim.owner)) await rm(claimFile(claim.path), { force: true });
365
+ if (!live.has(claim.owner)) await rm(claimFile(claim.path), { force: true });
318
366
  }
319
367
  for (const name of await readdir(inboxRoot).catch(() => [] as string[])) {
320
368
  const directory = join(inboxRoot, name);
@@ -2,10 +2,10 @@
2
2
  * The `session` tool.
3
3
  *
4
4
  * One action-shaped tool keeps the prompt surface small: status, send, inbox,
5
- * claim, release, join, leave, watch, name. The runtime owns file I/O and
6
- * lifecycle; this module only validates the action, calls the runtime, and hands
7
- * the result to the shared presentation module — one collapsed line, or the
8
- * board's rows on expand.
5
+ * claim, release, join. The runtime owns file I/O and lifecycle; this
6
+ * module only validates the action, calls the runtime, and hands the result to
7
+ * the shared presentation module — one collapsed line, or the board's rows on
8
+ * expand.
9
9
  */
10
10
 
11
11
  import { StringEnum } from "@earendil-works/pi-ai";
@@ -14,16 +14,7 @@ import { Type, type Static } from "typebox";
14
14
  import { reuseText, toolDetails } from "../../lib/tui/chrome.ts";
15
15
  import { errorResult, textResult } from "../../lib/tool-result.ts";
16
16
  import { callLine, detailsOrLine } from "../../lib/tui/tool.ts";
17
- import {
18
- formatClaim,
19
- formatInbox,
20
- formatJoin,
21
- formatName,
22
- formatRelease,
23
- formatSend,
24
- formatStatus,
25
- formatWatch,
26
- } from "./format.ts";
17
+ import { formatClaim, formatInbox, formatJoin, formatRelease, formatSend, formatStatus } from "./format.ts";
27
18
  import { normalizeAction, TOOL_NAME } from "./protocol.ts";
28
19
  import type { SessionBus } from "./runtime.ts";
29
20
  import { collapsedLine, resultSections, SessionResult } from "./result.ts";
@@ -32,8 +23,8 @@ import type { SessionDetails } from "./types.ts";
32
23
  export { TOOL_NAME };
33
24
 
34
25
  const SessionParams = Type.Object({
35
- action: StringEnum(["status", "send", "inbox", "claim", "release", "join", "leave", "watch", "name"] as const, {
36
- description: "status, send, inbox, claim, release, join, leave, watch, or name.",
26
+ action: StringEnum(["status", "send", "inbox", "claim", "release", "join"] as const, {
27
+ description: "status, send, inbox, claim, release, or join.",
37
28
  }),
38
29
  to: Type.Optional(Type.String({ description: "Target session label/id, or * for an event broadcast." })),
39
30
  text: Type.Optional(Type.String({ description: "Message text, at most 4000 characters." })),
@@ -50,19 +41,16 @@ const SessionParams = Type.Object({
50
41
  ),
51
42
  wake: Type.Optional(
52
43
  Type.Boolean({
53
- description:
54
- "With watch: set the mode — true raises one turn per burst, false waits for the next turn, omitted keeps it.",
44
+ description: "With join: mail raises one turn per burst once the agent is idle; omitted waits for the next turn.",
55
45
  }),
56
46
  ),
57
- stop: Type.Optional(Type.Boolean({ description: "With watch: turn this session's inbox watch off." })),
58
- team: Type.Optional(
59
- Type.String({
60
- description: "With join/leave: the split's name — the plan file name without `.md`.",
47
+ listen: Type.Optional(
48
+ Type.Boolean({
49
+ description: "With join: false joins without listening, so mail waits in the inbox until `inbox` reads it.",
61
50
  }),
62
51
  ),
63
- unit: Type.Optional(Type.String({ description: "With join/leave: the unit to claim or release, e.g. `core`." })),
64
52
  label: Type.Optional(
65
- Type.String({ description: "With name: what other sessions call this one, e.g. `billing-lead`." }),
53
+ Type.String({ description: "With join: what other sessions call this one, e.g. `billing-lead`." }),
66
54
  ),
67
55
  });
68
56
 
@@ -74,19 +62,14 @@ type SessionArgs = Static<typeof SessionParams>;
74
62
  * identity of the call.
75
63
  */
76
64
  function callSubject(action: string, args: Record<string, unknown>): string {
77
- if (action === "name") {
65
+ if (action === "join") {
78
66
  const label = typeof args.label === "string" ? args.label.trim() : "";
79
- return label ? `name ${label}` : "name";
67
+ return label ? `join ${label}` : "join";
80
68
  }
81
69
  if (action === "send") {
82
70
  const to = typeof args.to === "string" ? args.to : "";
83
71
  return to ? `send ${to}` : "send";
84
72
  }
85
- if (action === "join" || action === "leave") {
86
- const unit = typeof args.unit === "string" ? args.unit : "";
87
- const team = typeof args.team === "string" ? args.team : "";
88
- return [action, unit, team && !unit ? team : ""].filter(Boolean).join(" ");
89
- }
90
73
  if (action !== "claim" && action !== "release") return action;
91
74
  const paths = Array.isArray(args.paths) ? args.paths.filter((path): path is string => typeof path === "string") : [];
92
75
  const [first, ...rest] = paths;
@@ -99,16 +82,14 @@ export function registerTools(pi: ExtensionAPI, bus: SessionBus): void {
99
82
  name: TOOL_NAME,
100
83
  label: "Session bus",
101
84
  description:
102
- "Coordinate parallel Pi sessions on this repository. Actions: `status` (sessions, unread, claims, joined units), " +
85
+ "Coordinate parallel Pi sessions on this repository. Actions: `status` (sessions, unread, claims), " +
103
86
  "`send` (to a label/id, or `*` for an event), `inbox` (consume messages), `claim`/`release` (paths), " +
104
- "`join`/`leave` (a unit of an approved split, by `team` and `unit`), " +
105
- "`watch` (report new mail at your next turn, or raise one with `wake`; `stop` turns it off). " +
106
- "Delivery is pull-based unless this session arms `watch`.",
107
- promptSnippet: "Coordinate parallel sessions: status, send, inbox, claim, release, join, leave, watch, name.",
87
+ "`join` (get on the bus under a label, and listen for mail unless `listen: false`; `wake` raises a turn). " +
88
+ "Delivery is pull-based unless this session joined and is listening.",
89
+ promptSnippet: "Coordinate parallel sessions: status, send, inbox, claim, release, join.",
108
90
  promptGuidelines: [
109
- "Check `session inbox` when starting or joining a coordinated turn.",
91
+ "Check `session inbox` when starting a coordinated turn.",
110
92
  "Claim the paths you will edit before changing them; release them when done.",
111
- "When a plan was approved with a split, `join` its unit once at the start of the work and `leave` it when merged.",
112
93
  'Send `kind: "event"` for contract, build, or test changes another session needs.',
113
94
  "Never put credentials or secrets in a session-bus message.",
114
95
  ],
@@ -161,38 +142,22 @@ export function registerTools(pi: ExtensionAPI, bus: SessionBus): void {
161
142
  });
162
143
  }
163
144
  case "join": {
164
- const result = await bus.join({ team: args.team, unit: args.unit });
165
- return textResult(formatJoin(result.team, result.unit, result.claimed, result.conflicts), {
166
- action,
167
- team: result.team,
168
- unit: result.unit,
169
- claimed: result.claimed,
170
- conflicts: result.conflicts,
145
+ const label = typeof args.label === "string" ? args.label.trim() : "";
146
+ const joined = await bus.join({
147
+ ...(label === "" ? {} : { label }),
148
+ ...(args.wake === undefined ? {} : { wake: args.wake }),
149
+ ...(args.listen === undefined ? {} : { listen: args.listen }),
171
150
  });
172
- }
173
- case "leave": {
174
- const result = await bus.leave({ team: args.team, unit: args.unit });
175
- return textResult(formatRelease(result.released, result.notOwned), {
151
+ // A label is the bus's name and Pi's session name in one act, so the footer,
152
+ // `/resume`, and the record a peer addresses agree. A bare join derives its
153
+ // label from the name Pi already shows and must not rename it back.
154
+ if (label !== "") pi.setSessionName(joined.label);
155
+ return textResult(formatJoin(joined.label, joined.watching), {
176
156
  action,
177
- released: result.released,
178
- notOwned: result.notOwned,
179
- ...(args.team === undefined ? {} : { team: args.team }),
180
- ...(args.unit === undefined ? {} : { unit: args.unit }),
157
+ label: joined.label,
158
+ watching: joined.watching,
181
159
  });
182
160
  }
183
- case "watch": {
184
- const watching = await bus.watch({ wake: args.wake, stop: args.stop });
185
- return textResult(formatWatch(watching), { action, watching });
186
- }
187
- case "name": {
188
- const label = typeof args.label === "string" ? args.label.trim() : "";
189
- if (!label) return errorResult(action, 'name requires a label, e.g. "billing-lead".');
190
- const named = await bus.rename(label);
191
- // The bus's label and Pi's session name are one identity: the footer,
192
- // `/resume`, and the record a peer addresses have to agree.
193
- pi.setSessionName(named);
194
- return textResult(formatName(named), { action, label: named });
195
- }
196
161
  }
197
162
  } catch (error) {
198
163
  return errorResult(action, error instanceof Error ? error.message : String(error));
@@ -17,7 +17,7 @@ import { type DetailContents, DockListScreen } from "../../lib/tui/dock-screen.t
17
17
  import { SEPARATORS } from "../../lib/tui/glyphs.ts";
18
18
  import { keyValueRow } from "../../lib/tui/rows.ts";
19
19
  import { paint } from "../../lib/tui/tokens.ts";
20
- import { shortId } from "./format.ts";
20
+ import { idSuffix } from "./format.ts";
21
21
  import {
22
22
  type SessionRow,
23
23
  claimRowLine,
@@ -48,7 +48,7 @@ const SCOPE_TITLE: Readonly<Record<BoardScope, string>> = {
48
48
  };
49
49
 
50
50
  const SCOPE_EMPTY: Readonly<Record<BoardScope, string>> = {
51
- sessions: "No sessions on this bus.",
51
+ sessions: "No sessions on this bus yet. Press w to join and listen.",
52
52
  inbox: "No unread mail.",
53
53
  claims: "No claims.",
54
54
  };
@@ -76,7 +76,7 @@ function paneFacts(theme: Theme, row: SessionRow, now: number, width: number): s
76
76
  const labelWidth = 10;
77
77
  return [
78
78
  keyValueRow(theme, width, { label: "mode", labelWidth, value: sanitizeLine(row.session.mode) }),
79
- keyValueRow(theme, width, { label: "id", labelWidth, value: shortId(row.session.id) }),
79
+ keyValueRow(theme, width, { label: "id", labelWidth, value: idSuffix(row.session.id) }),
80
80
  keyValueRow(theme, width, {
81
81
  label: "last seen",
82
82
  labelWidth,
@@ -96,7 +96,7 @@ function boardKey(board: BoardSnapshot | undefined): string {
96
96
  return [
97
97
  ...board.status.sessions.map(
98
98
  (session) =>
99
- `${session.id}:${session.label}:${session.unread}:${session.lastSeen}:${session.mode}:${session.cwd}`,
99
+ `${session.id}:${session.label}:${session.unread}:${session.lastSeen}:${session.mode}:${session.cwd}:${session.state}`,
100
100
  ),
101
101
  ...board.status.claims.map((claim) => `${claim.path}:${claim.owner}:${claim.ownerLabel}:${claim.at}`),
102
102
  board.status.watching ? `${board.status.watching.armed}:${board.status.watching.wake}` : "",
@@ -229,7 +229,7 @@ export class SessionListComponent extends DockListScreen<BoardRow> {
229
229
  switch (this.scope) {
230
230
  case "sessions": {
231
231
  const sessions = rows.flatMap((row) => (row.kind === "session" ? [row.row] : []));
232
- return summaryCells(this.theme, sessions, watching);
232
+ return summaryCells(this.theme, sessions, watching, this.board?.status.onBus !== false);
233
233
  }
234
234
  case "inbox": {
235
235
  const cells = [paint(this.theme, "meta", count(rows.length, "unread message"))];
@@ -5,6 +5,7 @@
5
5
  * `store.ts` and the lifecycle in `runtime.ts`.
6
6
  */
7
7
 
8
+ import type { RosterState } from "../../lib/session-roster.ts";
8
9
  import type { MessageKind, SessionMode } from "./protocol.ts";
9
10
 
10
11
  /** One registered Pi session. */
@@ -12,6 +13,14 @@ export interface SessionRecord {
12
13
  id: string;
13
14
  label: string;
14
15
  pid: number;
16
+ /**
17
+ * That process's start time, when the host can name one.
18
+ *
19
+ * It is what tells the record's process apart from an unrelated one that has since
20
+ * been given the same pid, so `gone` does not have to wait for the heartbeat to age.
21
+ * Absent on a platform without `ps`, and on a record written before this existed.
22
+ */
23
+ startToken?: string;
15
24
  /** Working directory of the session; the worktree root when there is one. */
16
25
  cwd: string;
17
26
  /** Repository root the session belongs to, for relative claims. */
@@ -20,6 +29,14 @@ export interface SessionRecord {
20
29
  mode: SessionMode;
21
30
  startedAt: number;
22
31
  lastSeen: number;
32
+ /**
33
+ * Whether this session is listening for mail, written when it joins.
34
+ *
35
+ * Absent means "not listening": a record written before this existed and a session
36
+ * that stopped listening read the same way, which is the honest answer for a peer
37
+ * deciding whether mail will reach it.
38
+ */
39
+ watching?: { armed: boolean; wake: boolean };
23
40
  }
24
41
 
25
42
  /** One message waiting in a recipient's inbox. */
@@ -47,15 +64,10 @@ export interface ClaimRecord {
47
64
  /** What `session status` returns. */
48
65
  export interface SessionStatus {
49
66
  me: { id: string; label: string };
50
- sessions: Array<SessionRecord & { unread: number }>;
67
+ /** False while this session has only read the bus: it joins by naming itself. */
68
+ onBus: boolean;
69
+ sessions: Array<SessionRecord & { unread: number; state: RosterState }>;
51
70
  claims: ClaimRecord[];
52
- /** The newest split in this repository, and who holds each unit, on an explicit status call. */
53
- units?: {
54
- team: string;
55
- joined: number;
56
- total: number;
57
- rows: Array<{ unit: string; lead?: boolean; holder?: string }>;
58
- };
59
71
  /** Present only when this session armed its inbox watch. */
60
72
  watching?: { armed: boolean; wake: boolean };
61
73
  }
@@ -78,10 +90,7 @@ export interface BoardSnapshot {
78
90
 
79
91
  /** Structured result carried in the `session` tool's details. */
80
92
  export interface SessionDetails {
81
- action: "status" | "send" | "inbox" | "claim" | "release" | "join" | "leave" | "watch" | "name";
82
- /** With join/leave: the split and the unit, so a resumed transcript still says which. */
83
- team?: string;
84
- unit?: string;
93
+ action: "status" | "send" | "inbox" | "claim" | "release" | "join";
85
94
  status?: SessionStatus;
86
95
  delivered?: string[];
87
96
  messageId?: string;
@@ -90,9 +99,9 @@ export interface SessionDetails {
90
99
  conflicts?: ClaimConflict[];
91
100
  released?: string[];
92
101
  notOwned?: string[];
93
- /** Whether the inbox watch is on, and whether it raises a turn. */
102
+ /** Whether the inbox watch is on, and whether it raises a turn; `join` reports it. */
94
103
  watching?: { armed: boolean; wake: boolean };
95
- /** With name: the label the bus settled on, which is what this session is called. */
104
+ /** With join: the label the bus settled on, which is what this session is called. */
96
105
  label?: string;
97
106
  error?: string;
98
107
  }
@@ -16,6 +16,7 @@
16
16
  import * as fs from "node:fs";
17
17
  import * as path from "node:path";
18
18
  import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
19
+ import { READ_DOC_TOOL, WEB_FETCH_TOOL, WEB_SEARCH_TOOL } from "../../lib/tool-names.ts";
19
20
 
20
21
  /** Where an agent definition came from. */
21
22
  export type AgentSource = "builtin" | "user" | "project";
@@ -44,7 +45,7 @@ export const BUILTIN_AGENTS: readonly AgentConfig[] = [
44
45
  {
45
46
  name: "explorer",
46
47
  description: "Fast codebase recon that returns compressed context for handoff to other agents",
47
- tools: ["read", "grep", "find", "ls"],
48
+ tools: ["read", "grep", "find", "ls", READ_DOC_TOOL],
48
49
  source: "builtin",
49
50
  systemPrompt: `You are an explorer. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
50
51
 
@@ -94,7 +95,7 @@ Which file to look at first and why.`,
94
95
  name: "planner",
95
96
  description:
96
97
  "Delegated implementation planning in an isolated context (headless/chain; use plan mode for interactive planning)",
97
- tools: ["read", "grep", "find", "ls"],
98
+ tools: ["read", "grep", "find", "ls", READ_DOC_TOOL],
98
99
  source: "builtin",
99
100
  systemPrompt: `You are a delegated planning specialist working in an isolated context. The caller hands you planning when it wants a plan produced without adding exploration to its own transcript — for example an explorer → planner → worker chain, or a headless run.
100
101
 
@@ -134,7 +135,7 @@ Keep the plan concrete. The worker agent will execute it verbatim.`,
134
135
  {
135
136
  name: "reviewer",
136
137
  description: "Code review specialist for quality and security analysis",
137
- tools: ["read", "grep", "find", "ls", "bash"],
138
+ tools: ["read", "grep", "find", "ls", READ_DOC_TOOL, "bash"],
138
139
  source: "builtin",
139
140
  systemPrompt: `You are a senior code reviewer. Analyze code for quality, security, and maintainability.
140
141
 
@@ -168,7 +169,7 @@ Be specific with file paths and line numbers.`,
168
169
  {
169
170
  name: "worker",
170
171
  description: "General-purpose subagent with full capabilities and an isolated context window",
171
- tools: ["read", "write", "edit", "bash", "grep", "find", "ls"],
172
+ tools: ["read", "write", "edit", "bash", "grep", "find", "ls", READ_DOC_TOOL],
172
173
  source: "builtin",
173
174
  systemPrompt: `You are a worker agent with full capabilities. You operate in an isolated context window to handle delegated tasks without polluting the main conversation.
174
175
 
@@ -192,7 +193,7 @@ If handing off to another agent (e.g. reviewer), include:
192
193
  {
193
194
  name: "researcher",
194
195
  description: "Web research specialist that returns sourced findings on external questions",
195
- tools: ["read", "grep", "find", "ls", "web_search", "web_fetch"],
196
+ tools: ["read", "grep", "find", "ls", READ_DOC_TOOL, WEB_SEARCH_TOOL, WEB_FETCH_TOOL],
196
197
  source: "builtin",
197
198
  systemPrompt: `You are a research specialist. You investigate questions using web_search and web_fetch, then return findings another agent can act on.
198
199
 
@@ -228,7 +229,7 @@ What the caller should do with this, in 2-3 sentences.`,
228
229
  {
229
230
  name: "tester",
230
231
  description: "Writes or updates tests for a change and runs them",
231
- tools: ["read", "write", "edit", "grep", "find", "ls", "bash"],
232
+ tools: ["read", "write", "edit", "grep", "find", "ls", READ_DOC_TOOL, "bash"],
232
233
  source: "builtin",
233
234
  systemPrompt: `You are a test engineer. You add or update tests for a change and run them.
234
235
 
@@ -260,7 +261,7 @@ What is still untested and why.`,
260
261
  {
261
262
  name: "debugger",
262
263
  description: "Root-cause analysis for a failing test or bug, read-only plus running tests",
263
- tools: ["read", "grep", "find", "ls", "bash"],
264
+ tools: ["read", "grep", "find", "ls", READ_DOC_TOOL, "bash"],
264
265
  source: "builtin",
265
266
  systemPrompt: `You are a debugging specialist. You find the root cause of a failure from evidence, not guesses.
266
267
 
@@ -295,7 +296,7 @@ What else the bug or the fix could affect.`,
295
296
  {
296
297
  name: "documenter",
297
298
  description: "Updates docs, READMEs, and changelog entries to match the code",
298
- tools: ["read", "write", "edit", "grep", "find", "ls", "bash"],
299
+ tools: ["read", "write", "edit", "grep", "find", "ls", READ_DOC_TOOL, "bash"],
299
300
  source: "builtin",
300
301
  systemPrompt: `You are a documentation specialist. You keep docs accurate and consistent with the code.
301
302
 
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
10
+ import { planModeRefusal } from "../../lib/plan-entry.ts";
10
11
  import { isPlanModeEnabled } from "../../lib/plan-state.ts";
11
12
  import { withRailsSuppressed } from "../../lib/rails.ts";
12
13
  import type { BackgroundRuntime } from "./background.ts";
@@ -20,7 +21,7 @@ async function openAgents(
20
21
  ctx: ExtensionCommandContext,
21
22
  ): Promise<void> {
22
23
  if (isPlanModeEnabled(ctx.sessionManager.getBranch())) {
23
- ctx.ui.notify("Plan mode is read-only. Approve the plan before managing background runs.", "warning");
24
+ ctx.ui.notify(planModeRefusal("managing background runs"), "warning");
24
25
  return;
25
26
  }
26
27
 
@@ -6,13 +6,15 @@
6
6
  * import for invocation concerns.
7
7
  */
8
8
 
9
+ import { WORKSPACE_READERS } from "../../lib/policy.ts";
10
+ import { WEB_FETCH_TOOL, WEB_SEARCH_TOOL } from "../../lib/tool-names.ts";
9
11
  import type { AgentConfig } from "./agents.ts";
10
12
  import type { DispatchDefaults } from "./types.ts";
11
13
 
12
14
  export { getPiInvocation, type PiInvocation } from "../../lib/pi-invocation.ts";
13
15
 
14
- /** Tools a read-only delegation may use: structured readers and web readers only. */
15
- export const READ_ONLY_TOOLS = ["read", "grep", "find", "ls", "web_search", "web_fetch"] as const;
16
+ /** Tools a read-only delegation may use: structured readers, documents, and web readers only. */
17
+ export const READ_ONLY_TOOLS = [...WORKSPACE_READERS, WEB_SEARCH_TOOL, WEB_FETCH_TOOL] as const;
16
18
 
17
19
  /** Appended to a read-only agent's system prompt; the `--tools` list is the real gate. */
18
20
  export const READ_ONLY_NOTE =