@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
@@ -1,92 +1,63 @@
1
1
  # todo — a TodoWrite-style task list for Pi
2
2
 
3
- A whole-list task tracker for Pi, modelled on Claude Code's `TodoWrite`. The
4
- model sends the complete list on every call; the previous list is discarded, so
5
- there are no ids to juggle and no add/toggle bookkeeping. The list follows the
6
- active session branch and is restored on `/resume` and `/tree`. When a run is
7
- about to end after mutating work without a todo update, the extension injects
8
- one short reminder and forces a continuation, so the list does not silently
9
- drift behind the work.
10
-
11
- <img src="../../assets/todo.png" alt="The /todos screen: a Todos title rule, a 3/7 completed summary, and the list with check, spinner, and circle glyphs" width="100%">
12
-
13
- ```
3
+ A whole-list task tracker for Pi, modelled on Claude Code's `TodoWrite`: the
4
+ model sends the complete list on every call, so there are no ids to juggle and
5
+ no add/toggle bookkeeping. The list follows the active session branch and is
6
+ rebuilt on `/resume` and `/tree`, and the extension keeps it honest by asking for
7
+ one update when work lands without one.
8
+
9
+ <img src="../../assets/screens/todo.png" alt="The /todos screen: a Todos title rule, a 3/7 completed summary, and the list with check, spinner, and circle glyphs" width="100%">
10
+
11
+ ## Capabilities
12
+
13
+ Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
14
+
15
+ | Capability | How | What you get | Status |
16
+ |---|---|---|---|
17
+ | Whole-list tracking | `todo` | One call replaces the list; an empty list clears it. | Shipped |
18
+ | Live progress rail | the `todo` widget | Progress and the active item, one line above the editor. | Shipped |
19
+ | Atomic validation | `todo` validation | At most one `in_progress`, at most 50 items, unique non-empty content, and a known status; a violation returns an error with the unchanged list. | Shipped |
20
+ | Terminal-safe normalization | sanitized `content` and `activeForm` | Control characters, newlines, tabs, and repeated spaces collapse to one terminal-safe line before the list is stored. | Shipped |
21
+ | Compact model result | the `todo` result | The model gets progress and the active item, not an echo of the whole list, so every update does not repeat the table. | Shipped |
22
+ | Transcript rendering | the `todo` transcript block | The call line carries progress; the collapsed rows lead with active work, the expanded rows keep submission order, and a cap ends with `… N more`. | Shipped |
23
+ | Full list on demand | `/todos` | A scrollable screen in the order the model submitted. | Shipped |
24
+ | Headless listing | `/todos` outside `tui` | RPC, print, and JSON modes get the same list as a notification, with no terminal screen. | Shipped |
25
+ | Branch-following state | tool-result `details` | The list survives `/resume` and `/tree`, and abandoned branches never leak in. | Shipped |
26
+ | Lag reminder | the `agent_before_settle` nudge | One hidden reminder and one forced continuation when mutating work lands without a todo update. | Shipped |
27
+ | Structured result | `structuredContent` | Codemode scripts read the list as data. | Shipped |
28
+
29
+ ## Quickstart
30
+
31
+ ```bash
14
32
  pi --extension ./extensions/todo # load just this extension
15
33
  pi -e . # load the whole @gavin-hu/my-pi-agent package
16
- pi install ./ # install the package
34
+ pi install ./ # install the package for a user
17
35
  ```
18
36
 
19
- ## Tool rendering
20
-
21
- How the `todo` call and its result look in the transcript — the call line as a headline and the body indented under it:
22
-
23
- <img src="../../assets/tools/todo.png" alt="The todo tool's result: the call line and an indented body with the progress line and the task rows below it, each led by a check, spinner, or circle glyph" width="100%">
24
-
25
- ## What it does
26
-
27
- - Registers one model-callable tool, `todo`, that **replaces** the task list.
28
- An empty list clears it. The tool result is a compact summary — progress plus
29
- the current item — not an echo of the list the model just sent.
30
- - Each item has `content`, a `status` (`pending` | `in_progress` |
31
- `completed`), and an optional `activeForm` ("Running tests") shown while the
32
- item is in progress.
33
- - Shows a persistent widget above the editor while the list has unfinished
34
- work (hidden when empty or fully completed), and a scrollable `/todos`
35
- screen on demand. It is the bottom rail, below the [`goal`](../goal/) widget
36
- (`Goal / Todos`).
37
- - Stores the list in tool-result `details`, so it follows the active session
38
- branch and survives `/resume` and `/tree` — abandoned branches never leak
39
- into the current list, and stored lists are re-sanitized on replay.
40
- - Keeps the list honest: when a run is about to settle after a successful
41
- mutating tool call (`write`/`edit`/`bash`/`powershell`, or a tool declaring
42
- `readOnlyHint: false`) without a todo update, it injects one hidden reminder
43
- and forces a single continuation. A failed or blocked call changed nothing, so
44
- it never triggers one. It fires while the list has unfinished items, and —
45
- when no list exists at all — only after three such calls in one turn, at least
46
- one of them a file write (`write`/`edit`, or a tool declaring
47
- `readOnlyHint: false`), so repeated `bash` inspection is not mistaken for
48
- unplanned work. A list the model wrote or cleared during the turn is left
49
- alone. At most one reminder per user turn, and it is dropped on the next user
50
- turn, so a reader can never trigger it.
51
- - Declares an `outputSchema` and returns matching `structuredContent`
52
- (`{ todos, action, error? }`), so codemode/scripts can read the list as data.
53
-
54
- The widget is a one-line summary — progress plus the current item — so the
55
- active task is always visible without expanding the list:
56
-
57
- ```
58
- Todos · 1/3 · Writing the tests
37
+ ```text
38
+ /todos # show the list, and its progress
59
39
  ```
60
40
 
61
- `/todos` is the expanded view and keeps the model's list order (use it to see
62
- the plan as written).
41
+ The model calls `todo` with the complete list; the widget appears above the
42
+ editor while anything is unfinished.
63
43
 
64
- In the transcript, the call line carries progress only (`todo → 1/4`); the
65
- result keeps the `✓` / `◒` / `◌` rows, directly under the call line. The item
66
- text lives on the rows, so the call line never repeats it. Collapsed, it leads
67
- with active work; expanded, it keeps the submitted order:
44
+ ## Tool
68
45
 
69
- ```
70
- todo → 1/4
71
- ◒ Writing tests
72
- ◌ Ship it
73
- ◌ Write docs
74
- ✓ Write schema
75
- ```
46
+ <img src="../../assets/tools/todo.png" alt="The todo tool's result: the call line and an indented body with the progress line and the task rows below it, each led by a check, spinner, or circle glyph" width="100%">
76
47
 
77
- Progress lives on the call line, so the rows have no footer; the persistent
78
- widget and `/todos` still show the count. A capped collapsed rail ends with
79
- `… N more` and the `app.tools.expand` hint.
48
+ `todo` — `exposure: "direct"` (the default), `executionMode: "sequential"`,
49
+ read-write, closed-world, idempotent. It replaces the list; an empty list clears
50
+ it. The result is a compact summary — progress plus the current item — not an
51
+ echo of the list the model just sent.
80
52
 
81
- ## Tool
53
+ | Parameter | Type | Meaning |
54
+ |---|---|---|
55
+ | `todos` | array, required | The complete list. Pass `[]` to clear. |
56
+ | `todos[].content` | string | Imperative description, e.g. `"Write the parser tests"`. |
57
+ | `todos[].status` | `pending` \| `in_progress` \| `completed` | Where the item stands. |
58
+ | `todos[].activeForm` | string | Present-continuous label, required when the item is `in_progress` ("Running tests"). |
82
59
 
83
- | Field | Value |
84
- |---|---|
85
- | `name` | `todo` |
86
- | `todos` | The complete list. Pass `[]` to clear. |
87
- | `todos[].content` | Imperative description, e.g. `"Write the parser tests"`. |
88
- | `todos[].status` | `pending`, `in_progress`, or `completed`. |
89
- | `todos[].activeForm` | Present-continuous label, required when the item is `in_progress`. |
60
+ Result (`details` / `structuredContent`): `{ todos, action, error? }`.
90
61
 
91
62
  Validation rules (a violation leaves the list unchanged):
92
63
 
@@ -108,20 +79,45 @@ unknown `status` or a wrong argument type is rejected by Pi before `execute`
108
79
  runs, so the host's validation message is returned instead; the list is still
109
80
  unchanged.
110
81
 
111
- ```
82
+ ```text
112
83
  todo({ todos: [{ content: "Write the parser tests", status: "in_progress", activeForm: "Writing the parser tests" }] })
113
84
  todo({ todos: [] }) // clear
114
85
  ```
115
86
 
116
- ### `/todos` command
87
+ The widget is a one-line summary — progress plus the current item — so the
88
+ active task is always visible without expanding the list:
89
+
90
+ ```text
91
+ Todos · 1/3 · Writing the tests
92
+ ```
93
+
94
+ In the transcript, the call line carries progress only (`todo → 1/4`); the
95
+ result keeps the `✓` / `◒` / `◌` rows, directly under the call line. The item
96
+ text lives on the rows, so the call line never repeats it. Collapsed, it leads
97
+ with active work; expanded, it keeps the submitted order:
98
+
99
+ ```text
100
+ todo → 1/4
101
+ ◒ Writing tests
102
+ ◌ Ship it
103
+ ◌ Write docs
104
+ ✓ Write schema
105
+ ```
106
+
107
+ Progress lives on the call line, so the rows have no footer; the persistent
108
+ widget and `/todos` still show the count. A capped collapsed rail ends with
109
+ `… N more` and the `app.tools.expand` hint.
110
+
111
+ ## Commands
117
112
 
118
113
  | Command | What it does |
119
114
  |---|---|
120
115
  | `/todos` | Show the current list. An interactive, scrollable screen in `tui` mode; a notification elsewhere. |
121
116
 
122
- On the `/todos` screen: `↑`/`↓` or `j`/`k` scroll one row, `PgUp`/`PgDn` page,
123
- `g`/`G` or `Home`/`End` jump to the ends, and `Esc` (or `Ctrl-C`) closes. The
124
- window grows and shrinks with the terminal height, so the footer hint stays
117
+ `/todos` is the expanded view and keeps the model's list order, so it shows the
118
+ plan as written. On the screen: `↑`/`↓` or `j`/`k` scroll one row, `PgUp`/`PgDn`
119
+ page, `g`/`G` or `Home`/`End` jump to the ends, and `Esc` (or `Ctrl-C`) closes.
120
+ The window grows and shrinks with the terminal height, so the footer hint stays
125
121
  visible.
126
122
 
127
123
  The TUI label is status-dependent: an `in_progress` row shows its `activeForm`
@@ -135,13 +131,14 @@ The tool works in every mode. The persistent widget and the `/todos` screen
135
131
  require interactive (`tui`) mode; RPC and non-interactive runs simply get the
136
132
  tool result. The lag reminder is mode-independent, because it rides the agent
137
133
  boundary and not the terminal UI. While any dock screen is open (`/todos`,
138
- `/jobs`, `/rewind`, or the `question` questionnaire), the above-editor
139
- rails are hidden and return on close.
134
+ `/jobs`, `/rewind`, or the `question` questionnaire), the above-editor rails are
135
+ hidden and return on close.
140
136
 
141
137
  ## Configuration
142
138
 
143
- The widget presentation is configurable from the `todo` section of the shared
144
- `bytefly.json` (global document, then project document, project wins):
139
+ One key, in the `todo` section of the shared `bytefly.json` (global document,
140
+ then project document, project wins; resolution rules in
141
+ [`../configuration.md`](../configuration.md#todo--the-todo-widget)):
145
142
 
146
143
  | Key | Default | Meaning |
147
144
  |---|---|---|
@@ -201,7 +198,7 @@ depends on config.
201
198
  a goal update would sink the list; the list subscribes on `pi.events` and
202
199
  re-runs `setWidget` whenever a rail above it changes, keeping the stack
203
200
  `Goal / Todos`. It never announces, so the chain cannot ping-pong. See
204
- [`lib/rails.ts`](../../lib/rails.ts) and [`goal`](../goal/).
201
+ [`lib/rails.ts`](../../lib/rails.ts) and [`goal`](./goal.md).
205
202
  - **The reminder is a bounded nudge, not a nag.** A prompt guideline cannot
206
203
  guarantee a timely update, so the extension backs it with one forced
207
204
  continuation. `tool_execution_end` counts a successful call by an explicit
@@ -237,8 +234,7 @@ depends on config.
237
234
  | File | Responsibility |
238
235
  |---|---|
239
236
  | `index.ts` | Factory: wire the runtime, tool, command, reminder events, and session events. |
240
- | `types.ts` | `Todo`, `TodoStatus`, `TodoDetails`. |
241
- | `types.ts` | `Todo` and `TodoDetails`; re-exports `TodoStatus` / `TODO_STATUSES` from `lib/todo-state.ts`. |
237
+ | `types.ts` | `Todo`, `TodoDetails`; re-exports `TodoStatus` / `TODO_STATUSES` from `lib/todo-state.ts`. |
242
238
  | `schema.ts` | TypeBox parameters; the parser and limits live in `lib/todo-state.ts`. |
243
239
  | `state.ts` | Status queries over the replayed list (pure). |
244
240
  | `nudge.ts` | Lag detection: mutating and file-write classification, due check, reminder text (pure). |
@@ -248,3 +244,4 @@ depends on config.
248
244
  | `runtime.ts` | Session-scoped state, lag tracking, and widget synchronization. |
249
245
  | `tools.ts` | `todo` tool registration and rendering. |
250
246
  | `commands.ts` | `/todos`. |
247
+ | `tools.test.ts`, `runtime.test.ts`, `ui.test.ts` | The capability suites, declared in `test/suite-manifest.ts`. |
@@ -6,41 +6,56 @@ the TUI, reported to the model at the next turn, or — when a watcher opts into
6
6
  `wake` — raise one triggered turn as soon as the agent is idle, including right
7
7
  after a running turn settles.
8
8
 
9
- ```
9
+ ## Capabilities
10
+
11
+ Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
12
+
13
+ ### Watching
14
+
15
+ | Capability | How | What you get | Status |
16
+ |---|---|---|---|
17
+ | Tail a source | `watch add` | A file is followed from its current end unless `fromStart` is set, and a shrinking file is re-read from the start; a command's merged stdout/stderr is streamed. | Shipped |
18
+ | Line matching | `watch add` | An include pattern with an optional exclude list, case-insensitive unless `caseSensitive` is set. | Shipped |
19
+ | Match delivery | `watch add`, `watch matches` | Pending matches are reported to the model at the start of the next turn. | Shipped |
20
+ | Wake turn | `watch add`, the `wakeOnMatch` key | One triggered turn as soon as the agent is idle, including right after a running turn settles. | Shipped |
21
+ | Desktop alert | `my-pi-agent/watch-match`, `notify.enabled`, `notify.matchFound` | A wake match leaves the terminal as an OS notification. | Shipped |
22
+ | Live status | `watch add`, the `showStatus` key | A `⊚ N` running chip and a `! N` unreported-match chip, plus one alert per unreported burst. | Shipped |
23
+ | Command outcome | `watch status`, `watch list` | A command watcher reports its exit code or error, including the captured signal, instead of only "stopped". | Shipped |
24
+ | Liveness detail | `watch list`, `watch status` | A running watcher names its pid and elapsed time; list rows sort running watchers first, newest next. | Shipped |
25
+ | Transcript rendering | the `watch` transcript block | The outcome line, at most eight rows, and a `(ctrl+o to expand)` hint keep the transcript compact. | Shipped |
26
+ | Sanitized output | `watch matches`, `/watch matches` | Model- and remote-authored output is stripped of control characters before it reaches a terminal or a notification. | Shipped |
27
+
28
+ ### Reading and managing
29
+
30
+ | Capability | How | What you get | Status |
31
+ |---|---|---|---|
32
+ | Watcher inventory | `watch list`, `watch status`, `/watch list` | Each watcher's source, state, and line and match counts, singly or as a table. | Shipped |
33
+ | Matched lines | `watch matches`, `/watch matches` | The matching lines, sanitized and read from a bounded history. | Shipped |
34
+ | Raw output | `watch logs`, `/watch logs` | The merged output tail, for checking a pattern that matches nothing. | Shipped |
35
+ | Watcher screen | `/watch` | A dock screen with the watcher list, the focused watcher's detail, and its matches pane. | Shipped |
36
+ | Stopping and cleanup | `watch stop`, `watch clear`, `/watch stop`, `/watch clear` | A running watcher stops, stopped ones are removed, and `all` takes the running ones too. | Shipped |
37
+
38
+ ## Quickstart
39
+
40
+ ```bash
10
41
  pi --extension ./extensions/watch # load just this extension
11
42
  pi -e . # load the whole @gavin-hu/my-pi-agent package
12
43
  pi install ./ # install the package
13
44
  ```
14
45
 
15
- ## Tool rendering
46
+ ```text
47
+ watch add { "path": "server.log", "pattern": "ERROR|FATAL", "label": "server" }
48
+ /watch
49
+ ```
16
50
 
17
- How the `watch` call and its result look in the transcript — the call line as a headline and the body indented under it:
51
+ ## Tool
18
52
 
19
53
  <img src="../../assets/tools/watch.png" alt="The watch tool's result: the call line and an indented body with a running file watcher and an exited command watcher" width="100%">
20
54
 
21
- ## What it does
22
-
23
- - Registers one model-callable tool, `watch`, with actions `add`, `list`,
24
- `status`, `matches`, `logs`, `stop`, and `clear`.
25
- - Tails a text file by polling it, or a shell command by streaming its merged
26
- stdout/stderr. A file is followed from its current end unless `fromStart` is
27
- set; a shrinking file (rotation/truncation) is re-read from the start.
28
- - Matches with a regular expression and an optional exclude list,
29
- case-insensitive by default and case-sensitive with `caseSensitive: true`.
30
- - Shows a `⊚ N` running-watcher chip and a `! N` unreported-match chip, raises a
31
- `ctx.ui.notify` alert once per unreported burst, and offers a `/watch` screen.
32
- - Reports pending matches to the model at the start of the next turn, or wakes a
33
- turn for a watcher added with `wake: true` as soon as the agent is idle. A wake
34
- that arrives mid-turn is raised once that run settles. A wake also leaves the
35
- terminal: the
36
- match is announced on `my-pi-agent/watch-match`, which bytefly's
37
- [notify](../bytefly/README.md#notifications) capability turns into a desktop
38
- notification when `notify.enabled` and `notify.matchFound` are on.
39
- - Watchers are session-only: `shutdown` flushes a partial line, sends SIGTERM to
40
- every command's process tree (escalating to SIGKILL after a short grace), and
41
- clears the chips.
42
-
43
- ## Tool
55
+ `watch` — `exposure: "direct"` (the default), `executionMode: "sequential"`,
56
+ read-write, destructive, open-world, not idempotent. One tool with seven
57
+ actions; a watcher outlives the call, so `add` returns an id immediately and the
58
+ rest read it back.
44
59
 
45
60
  | Field | Value |
46
61
  |---|---|
@@ -104,15 +119,10 @@ capped at eight rows, minus the selection-marker column (`listRow`'s
104
119
  render the tail of the sanitized lines, with the hidden count noted above and
105
120
  `(ctrl+o to expand)`.
106
121
 
107
- ### Command: /watch
108
-
109
- <img src="../../assets/watch.png" alt="The /watch screen: a Watch title rule, a `2 watchers · 1 running · 3 matches` summary, a selected file watcher and a finished command watcher, the focused watcher's path and pattern with its line and match counts, and the key hints" width="100%">
122
+ ## Commands
110
123
 
111
- A bare `/watch` opens a dock screen listing watchers. `j`/`k` (and the wheel)
112
- move, `Enter`/`l` opens a watcher's matches, `x`/`K` stops a running watcher
113
- behind a `y/N` confirm, `d` clears stopped ones behind a `y/N` confirm, `Esc`
114
- closes, and `g`/`G` and PgUp/PgDn scroll the matches pane. Subcommands work in
115
- every mode:
124
+ `/watch` manages watchers in every mode; a bare `/watch` opens the screen in the
125
+ TUI and prints the list everywhere else.
116
126
 
117
127
  | Command | Effect |
118
128
  |---|---|
@@ -123,6 +133,13 @@ every mode:
123
133
  | `/watch stop <id>` | Stop a watcher. |
124
134
  | `/watch clear [all]` | Remove stopped watchers, or every watcher with `all`. |
125
135
 
136
+ <img src="../../assets/screens/watch.png" alt="The /watch screen: a Watch title rule, a `2 watchers · 1 running · 3 matches` summary, a selected file watcher and a finished command watcher, the focused watcher's path and pattern with its line and match counts, and the key hints" width="100%">
137
+
138
+ A bare `/watch` opens a dock screen listing watchers. `j`/`k` (and the wheel)
139
+ move, `Enter`/`l` opens a watcher's matches, `x`/`K` stops a running watcher
140
+ behind a `y/N` confirm, `d` clears stopped ones behind a `y/N` confirm, `Esc`
141
+ closes, and `g`/`G` and PgUp/PgDn scroll the matches pane.
142
+
126
143
  ## Behaviour by mode
127
144
 
128
145
  - **tui** — the status chips, the match alert, and the `/watch` dock screen.
@@ -132,8 +149,9 @@ every mode:
132
149
 
133
150
  ## Configuration
134
151
 
135
- The `watch` section of the shared `bytefly.json` (global then project). Every
136
- value is clamped; a missing or malformed document is ignored.
152
+ The `watch` section of the shared `bytefly.json`; resolution rules in
153
+ [`../configuration.md`](../configuration.md#watch--file-and-command-watchers).
154
+ Every value is clamped; a missing or malformed document is ignored.
137
155
 
138
156
  | Key | Default | Meaning |
139
157
  |---|---|---|
@@ -3,53 +3,57 @@
3
3
  One extension, two tools, keyless and dependency-free (native `fetch`, no
4
4
  `curl`): `web_search` runs general web search through a pluggable provider
5
5
  (keyless DuckDuckGo by default; optional SearXNG or Brave), and `web_fetch`
6
- reads a URL as text. Both are `direct` and active by default, and
7
- read the `web-access` section of the shared `bytefly.json`.
6
+ reads a URL as text. Both are `direct`, active on registration, and read the
7
+ `web-access` section of the shared `bytefly.json`.
8
8
 
9
- ```
9
+ ## Capabilities
10
+
11
+ Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
12
+
13
+ | Capability | How | What you get | Status |
14
+ |---|---|---|---|
15
+ | Web search | `web_search` | Titles, URLs, snippets, and a direct answer when the provider has one. | Shipped |
16
+ | Page reading | `web_fetch` | A URL's readable text, with links kept as `[text](url)`. | Shipped |
17
+ | Structured bodies | `web_fetch` | A JSON, CSV, or XML body arrives as an aligned table or an indented tree instead of the raw blob. | Unreleased |
18
+ | Write-style fetch | `web_fetch` (`method`, `headers`, `body`) | POST with custom headers and a bounded body, so form and GraphQL endpoints are reachable; a body implies POST. | Shipped |
19
+ | Paged output | `web_fetch`, `startIndex`, `maxChars` | A long page arrives in budgeted slices and names the offset that continues it. | Shipped |
20
+ | Passage search | `web_fetch`, `find`, `contextChars` | Matching passages instead of the whole page, with a note when a cap hid the rest. | Shipped |
21
+ | Private-address refusal | `web_fetch`, `fetch.allowPrivateHosts` | Loopback, private, link-local, and metadata targets are refused on the first request and on every redirect hop. | Shipped |
22
+ | Charset-aware decoding | `web_fetch` | A GBK/GB18030 or `<meta charset>` page reads as text instead of replacement characters. | Shipped |
23
+ | Page cache | `web_fetch`, `refresh`, `fetch.cacheEnabled` | Paging and `find` reuse one fetch, and `refresh: true` bypasses a stale entry. | Shipped |
24
+ | PDF extraction | `web_fetch`, `fetch.pdfEnabled` | A PDF becomes text once the optional `unpdf` package is installed. | Shipped |
25
+ | Provider choice | `search.provider`, `search.endpoint`, `search.apiKeyEnv` | The keyless default works with no configuration; SearXNG or Brave add full result lists. | Shipped |
26
+ | Structured result | `nextIndex`, `matchesTruncated`, `cached` | A caller can continue paging and tell a capped match set from a complete one without parsing the text. | Shipped |
27
+ | Politeness throttle | `minIntervalMs` | Search requests are spaced instead of hammering a provider on a rapid series of calls. | Shipped |
28
+
29
+ ## Quickstart
30
+
31
+ ```bash
10
32
  pi --extension ./extensions/web-access # load just this extension
11
33
  pi -e . # load the whole @gavin-hu/my-pi-agent package
12
34
  pi install ./ # or install the package
13
35
  ```
14
36
 
15
- ## Tool rendering
37
+ ```text
38
+ web_search({ query: "pi coding agent" }) # keyless, no configuration
39
+ web_fetch({ url: "https://pi.dev/" }) # the page as readable text
40
+ ```
16
41
 
17
- How the `web_search` and `web_fetch` calls and their results look in the transcript — the call line as a headline and the body indented under it:
42
+ ## Tools
18
43
 
19
44
  <img src="../../assets/tools/web_search.png" alt="The web_search tool's call and result: the call line and an indented body with the query and the ranked results" width="100%">
20
45
 
21
46
  <img src="../../assets/tools/web_fetch.png" alt="The web_fetch tool's call and result: the call line and an indented body with the URL, title, and fetched text preview" width="100%">
22
47
 
23
- ## What it does
24
-
25
- - Registers `web_search`, general web search through a pluggable provider:
26
- keyless DuckDuckGo by default, or a self-hosted SearXNG instance / keyed Brave
27
- search. Returns titles, URLs, snippets, and a direct answer when available,
28
- always with no configuration required.
29
- - Registers `web_fetch`, which fetches a URL (GET or POST) and
30
- returns readable text with links kept as `[text](url)`.
31
- - Decodes by the byte-order mark, the declared `Content-Type` charset, or the
32
- page's own `<meta charset>`, so a GBK/GB18030 page reads as text instead of
33
- replacement characters.
34
- - Pages long output and reports the next `startIndex` (`nextIndex` in
35
- `structuredContent`); `find` returns matching passages instead of the whole
36
- page, and says when the match cap hid further hits.
37
- - Re-validates every redirect hop against private/internal addresses, and
38
- refuses transport headers and oversized bodies.
39
- - Optionally extracts PDFs (`unpdf`); the package loads lazily and is never
40
- required.
41
- - Caches fetched pages in one in-process cache per registration, so paging and
42
- `find` do not refetch; the cache is cleared on `session_start` and
43
- `session_shutdown`.
44
-
45
- ## Tools
48
+ Both tools are `direct` (the default) and active on registration; neither needs
49
+ configuration to run.
46
50
 
47
51
  | Tool | What it does |
48
52
  |---|---|
49
53
  | `web_search` | General web search; keyless by default, SearXNG or Brave optional. |
50
54
  | `web_fetch` | Fetch a URL and return readable text, pageable and searchable. |
51
55
 
52
- ```
56
+ ```text
53
57
  web_search({ query: "pi coding agent" })
54
58
  web_fetch({ url: "https://pi.dev/" })
55
59
  web_fetch({ url: "https://pi.dev/", find: ["documentation", "extensions"] })
@@ -68,7 +72,7 @@ flight it draws a railed `Searching… 8s` or `Fetching example.com… 3s` line,
68
72
  a finished call appends its duration. A failed page or a host-rejected call
69
73
  renders one sanitized `Error:` line in the error role.
70
74
 
71
- ```
75
+ ```text
72
76
  web_search "pi coding agent" (max 8)
73
77
 
74
78
  via searxng · 8 results · 2.1s
@@ -80,7 +84,7 @@ Answer: Pi is a coding agent harness …
80
84
  +3 more (ctrl+o to expand)
81
85
  ```
82
86
 
83
- ```
87
+ ```text
84
88
  web_fetch https://pi.dev/
85
89
  Pi — a coding-agent component · 200 · 12k chars (cached) · 3.4s
86
90
  ```
@@ -90,9 +94,10 @@ which URL supplied the text.
90
94
 
91
95
  ## Configuration
92
96
 
93
- Merged from the `web-access` section of the global `bytefly.json` and the
94
- project `<cwd>/.pi/bytefly.json` (project wins). Missing or malformed documents
95
- and unknown keys are ignored.
97
+ The `web-access` section of the shared `bytefly.json`: `search` configures
98
+ `web_search`, `fetch` configures `web_fetch`. Resolution rules are in
99
+ [`../configuration.md#web-access--search-and-fetch`](../configuration.md#web-access--search-and-fetch),
100
+ which also lists every key with its default and accepted values.
96
101
 
97
102
  ```jsonc
98
103
  // bytefly.json → "web-access"
@@ -183,7 +188,7 @@ whether it came from an explicit choice, the `auto` rule, or the keyless default
183
188
  the API-key variable when the provider takes one and no literal `apiKey` is set,
184
189
  and whether that variable is present; and, only on an explicit `refresh`, whether
185
190
  the configured `endpoint` answers. The key is never reported, only its variable
186
- name. See [`bytefly`](../bytefly/README.md#what-it-does) for the report.
191
+ name. See [`bytefly`](./bytefly.md#capabilities) for the report.
187
192
 
188
193
  | Parameter | Type | Notes |
189
194
  |---|---|---|
@@ -199,11 +204,16 @@ web_search({ query: "python list comprehension", maxResults: 3 })
199
204
  ## `web_fetch`
200
205
 
201
206
  GETs (or POSTs) an http(s) URL and returns readable text. HTML becomes
202
- plain text with links kept as `[text](url)`; JSON/text/XML is returned as-is;
203
- PDFs are text-extracted when the optional `unpdf` package is installed; other
204
- binary content is reported by type and size. Redirects are followed manually and
205
- **every hop is re-validated**, so a public URL cannot redirect into a private
206
- address. Loopback, private, link-local, and cloud-metadata addresses are refused.
207
+ plain text with links kept as `[text](url)`; a JSON, CSV, or XML body becomes a
208
+ table or an indented tree ([Structured bodies](#structured-bodies)); other textual
209
+ content is returned as-is; PDFs are text-extracted when the optional `unpdf`
210
+ package is installed; other binary content is reported by type and size. Bodies
211
+ are decoded by the
212
+ byte-order mark, the declared `Content-Type` charset, or the page's own
213
+ `<meta charset>`, so a GBK/GB18030 page reads as text instead of replacement
214
+ characters. Redirects are followed manually and **every hop is re-validated**, so
215
+ a public URL cannot redirect into a private address. Loopback, private,
216
+ link-local, and cloud-metadata addresses are refused.
207
217
 
208
218
  | Parameter | Type | Notes |
209
219
  |---|---|---|
@@ -237,6 +247,32 @@ web_fetch({ url: "https://pi.dev/", find: ["documentation", "extensions"] })
237
247
  web_fetch({ url: "https://api.example/graphql", method: "POST", headers: { "Content-Type": "application/json" }, body: '{"query":"{ viewer { login } }"}' })
238
248
  ```
239
249
 
250
+ ### Structured bodies
251
+
252
+ A JSON, CSV, or XML body is rendered as text instead of the blob it downloaded: an
253
+ array of records becomes a table with the union of their keys and padded columns,
254
+ an object becomes indented `key: value` lines, comma-separated rows become the same
255
+ table, and XML becomes an indented tree with attributes as `@name`. The declared
256
+ content type decides first; a body that declares nothing usable (`text/plain`) is
257
+ sniffed only when it has a shape — an opening brace, a uniform comma count, a root
258
+ tag — and anything that does not parse keeps its raw form, so a malformed document
259
+ is never made harder to read than it already was.
260
+
261
+ ```text
262
+ URL: https://api.example/users
263
+ Status: 200 (application/json)
264
+
265
+ id name role
266
+ 1 Ada admin
267
+ 2 Grace user
268
+ ```
269
+
270
+ Rendering happens once per fetch, before the result is cached, so `startIndex`,
271
+ `find`, and a second call all read the same string. Two bounds keep it honest: a
272
+ body past `MAX_SOURCE_CHARS` is returned raw, because a paged raw body beats a
273
+ rendered one, and the rendered text stops at `MAX_RENDERED_CHARS` with a note saying
274
+ so — both constants live in `fetch/structured.ts`.
275
+
240
276
  ### Optional dependencies
241
277
 
242
278
  PDF extraction needs a package that is not installed by default; it is loaded
@@ -259,6 +295,11 @@ the content type and size.
259
295
  - **Heuristic extraction**: `web_fetch` strips scripts/styles/nav/footer and
260
296
  prefers `<main>`/`<article>`, but is not a full readability engine. PDF
261
297
  extraction is optional (`unpdf`) and off unless installed.
298
+ - **A structured body is rendered by a reader, not a parser.** JSON, CSV, and XML
299
+ are read well enough to show a shape — CSV by RFC 4180 quoting, XML by balancing
300
+ tags — but a body it cannot parse keeps its raw form rather than a partial one,
301
+ and the `text/plain` sniff is a heuristic: a page of prose with a uniform comma
302
+ in every line reads as a one-row table.
262
303
  - **`find` output is budgeted.** With `maxMatches: 50` and `contextChars: 2000`
263
304
  the passages alone would exceed `maxOutputChars`; the result shows the passages
264
305
  that fit and names the knob to change.
@@ -296,11 +337,11 @@ the content type and size.
296
337
  `config.ts`, `schema.ts`, `types.ts`, `format.ts`, and `tool.ts`, and do not
297
338
  import each other; `index.ts` only registers. Shared text and HTML helpers
298
339
  live at the top level (`text.ts`, `html.ts`, `transcript.ts`, `http.ts`).
299
- - **Shared transport.** [`http.ts`](./http.ts) holds the fetch runner
340
+ - **Shared transport.** [`http.ts`](../../extensions/web-access/http.ts) holds the fetch runner
300
341
  (`HttpRunner`, `createFetchRunner`, HTTP error types), used by both halves and
301
342
  injectable for tests. It reads the body through a byte-counting stream, so
302
343
  `maxBytes` stops a chunked or compressed response before it is buffered whole.
303
- - **Charset-aware decoding.** [`charset.ts`](./charset.ts) is the one place a
344
+ - **Charset-aware decoding.** [`charset.ts`](../../extensions/web-access/charset.ts) is the one place a
304
345
  byte body becomes text: BOM first, then the declared `Content-Type` charset,
305
346
  then the page's own `<meta charset>`, then UTF-8. An unknown label falls back
306
347
  to UTF-8 instead of failing the fetch.
@@ -311,9 +352,9 @@ the content type and size.
311
352
  lazily and falls back to a binary note when it is absent.
312
353
  - **Per-hop SSRF validation.** Redirects are followed with `redirect: "manual"`
313
354
  under one shared deadline, re-resolving and re-checking each `Location`.
314
- - **Pluggable search providers.** [`registry.ts`](./search/registry.ts) holds a
355
+ - **Pluggable search providers.** [`registry.ts`](../../extensions/web-access/search/registry.ts) holds a
315
356
  value-only provider list (`duckduckgo`, `searxng`, `brave`) and a pure
316
- `resolveProvider`; [`search.ts`](./search/search.ts) owns selection, key
357
+ `resolveProvider`; [`search.ts`](../../extensions/web-access/search/search.ts) owns selection, key
317
358
  resolution, a per-instance throttle, and error mapping. A provider with no
318
359
  answer and no results reports `provider: "none"`.
319
360
  - **Find-in-page.** `exact` is case-sensitive, `insensitive` lowercases both
@@ -328,17 +369,17 @@ the content type and size.
328
369
  truncation note against `maxChars`, so a paged page always carries its
329
370
  `startIndex` hint inside the limit. `formatMatches` applies the same rule to
330
371
  `find`: it includes the passages that fit and names the knob that hid the rest.
331
- - **Code points without a copy.** [`text.ts`](./text.ts) counts and slices by
372
+ - **Code points without a copy.** [`text.ts`](../../extensions/web-access/text.ts) counts and slices by
332
373
  walking the string in place, so a multi-megabyte page is never expanded into an
333
374
  array of one-character strings on every call.
334
375
  - **One JSON boundary per concern.** `search/json.ts` owns `requestJson` (status
335
376
  check and parse error) and `collectResults` (title + http(s) URL + de-dupe), so
336
377
  the three providers cannot drift on either.
337
- - **Page cache.** [`fetch/cache.ts`](./fetch/cache.ts) is a factory
378
+ - **Page cache.** [`fetch/cache.ts`](../../extensions/web-access/fetch/cache.ts) is a factory
338
379
  (`createPageCache`) holding an LRU/TTL map keyed by requested URL, so paging and
339
380
  find are a single fetch; entries evict by count and total bytes, and one
340
381
  instance belongs to one registration.
341
- - **Transcript sanitization and reuse.** [`transcript.ts`](./transcript.ts)
382
+ - **Transcript sanitization and reuse.** [`transcript.ts`](../../extensions/web-access/transcript.ts)
342
383
  holds `oneLine`, shared by both tools to collapse untrusted web text to one
343
384
  sanitized, optionally clipped line; the renderers reuse
344
385
  `context.lastComponent` instead of allocating each render.
@@ -357,6 +398,7 @@ the content type and size.
357
398
  | `search/tool.ts` | `web_search` definition, activation, rendering |
358
399
  | `search/provider.ts` | Provider interface (`SearchProvider`, `ProviderContext`) |
359
400
  | `search/registry.ts` | Value-only provider list, `resolveProvider`, labels |
401
+ | `search/facts.ts` | What this session resolved to, published for `doctor` (`providerFacts`) |
360
402
  | `search/config.ts` | Search config type, defaults, clamping, API-key resolution |
361
403
  | `search/schema.ts` | Params/output schema, `resolveRequest()` |
362
404
  | `search/search.ts` | Provider resolution, throttle, and outcome mapping |
@@ -368,10 +410,10 @@ the content type and size.
368
410
  | `fetch/tool.ts` | `web_fetch` definition, activation, rendering |
369
411
  | `fetch/config.ts` | Fetch config type, defaults, clamping |
370
412
  | `fetch/schema.ts` | Params/output schema, `resolveRequest()` |
371
- | `fetch/page.ts` | Fetch + redirect-following + content-type routing |
413
+ | `fetch/page.ts` | Fetch + redirect-following + content-type routing; PDF extraction is injected via `FetchDeps` from the shared `lib/pdf.ts` |
414
+ | `fetch/structured.ts` | JSON/CSV/XML body → readable text, or `undefined` to keep the raw body |
372
415
  | `fetch/ssrf.ts` | URL/host validation and private-address blocking |
373
416
  | `fetch/extract.ts` | HTML → title + readable text |
374
- | `fetch/page.ts` | Fetch + redirect-following + content-type routing; PDF extraction is injected via `FetchDeps` from the shared `lib/pdf.ts` |
375
417
  | `fetch/find.ts` | Passage search (exact/insensitive/fuzzy) |
376
418
  | `fetch/cache.ts` | Per-registration LRU/TTL page cache (`createPageCache`) |
377
419
  | `fetch/format.ts`, `fetch/types.ts` | Result text and data types |
@@ -386,7 +428,8 @@ charset decoding (declared, `<meta>`, and BOM precedence), a streamed body over
386
428
  the byte cap, redirect re-validation (per-hop SSRF with literal IPs),
387
429
  method/header/body validation, the nested config merge and section isolation,
388
430
  extraction, formatting/paging through the tool (including a regression test that
389
- a truncated page keeps its `startIndex` hint), find and its cap/budget notices,
431
+ a truncated page keeps its `startIndex` hint), structured rendering per format and
432
+ its malformed fallbacks, find and its cap/budget notices,
390
433
  the per-registration cache, the SSRF guard, the injected PDF dependency, and both
391
434
  tools' registration/execution. Tests inject the runner, throttle, and PDF extractor
392
435
  through the tools' dependencies, so no network or PDF library is needed; the