@gavin-hu/my-pi-agent 0.18.0 → 0.19.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 (249) hide show
  1. package/README.md +52 -44
  2. package/README.zh-CN.md +46 -41
  3. package/docs/extensions/README.md +34 -0
  4. package/docs/extensions/board.md +344 -0
  5. package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +446 -404
  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 +554 -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} +61 -50
  13. package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
  14. package/{extensions/plan/README.md → docs/extensions/plan.md} +390 -317
  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 +285 -0
  18. package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
  19. package/docs/extensions/team.md +226 -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} +258 -162
  24. package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
  25. package/extensions/board/commands.ts +87 -0
  26. package/extensions/board/config.ts +36 -0
  27. package/extensions/board/format.ts +72 -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 +312 -0
  32. package/extensions/board/result.ts +99 -0
  33. package/extensions/board/rows.ts +48 -0
  34. package/extensions/board/schema.ts +75 -0
  35. package/extensions/board/seat.ts +29 -0
  36. package/extensions/board/snapshot.ts +41 -0
  37. package/extensions/board/tools.ts +279 -0
  38. package/extensions/board/transport/router.ts +172 -0
  39. package/extensions/board/transport/server.ts +149 -0
  40. package/extensions/board/types.ts +54 -0
  41. package/extensions/board/view/actions.ts +306 -0
  42. package/extensions/board/view/client.ts +41 -0
  43. package/extensions/board/view/keys.ts +121 -0
  44. package/extensions/board/view/live.ts +397 -0
  45. package/extensions/board/view/page.ts +158 -0
  46. package/extensions/board/view/render.ts +496 -0
  47. package/extensions/board/view/styles.ts +211 -0
  48. package/extensions/board/view/team.ts +305 -0
  49. package/extensions/board/writes.ts +99 -0
  50. package/extensions/bytefly/panel/commands.ts +40 -7
  51. package/extensions/bytefly/panel/settings-runtime.ts +0 -6
  52. package/extensions/bytefly/panel/settings.ts +3 -20
  53. package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
  54. package/extensions/bytefly/picker/discovery.ts +0 -2
  55. package/extensions/bytefly/picker/types.ts +0 -2
  56. package/extensions/bytefly/update/check.ts +39 -0
  57. package/extensions/bytefly/update/format.ts +15 -0
  58. package/extensions/bytefly/update/register.ts +73 -34
  59. package/extensions/bytefly/update/state.ts +6 -3
  60. package/extensions/context/tui.ts +1 -1
  61. package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
  62. package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
  63. package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
  64. package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
  65. package/extensions/file-browse/index.ts +4 -4
  66. package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
  67. package/extensions/file-browse/{router.ts → transport/router.ts} +21 -15
  68. package/extensions/file-browse/transport/server.ts +110 -0
  69. package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
  70. package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
  71. package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
  72. package/extensions/file-browse/{html.ts → view/html.ts} +14 -12
  73. package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
  74. package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
  75. package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
  76. package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
  77. package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
  78. package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
  79. package/extensions/file-read/schema.ts +2 -1
  80. package/extensions/memory/store.ts +12 -5
  81. package/extensions/plan/blocks.ts +23 -2
  82. package/extensions/plan/commands.ts +21 -16
  83. package/extensions/plan/feedback.ts +147 -38
  84. package/extensions/plan/index.ts +6 -4
  85. package/extensions/plan/plan-index.ts +43 -9
  86. package/extensions/plan/plan-text.ts +33 -23
  87. package/extensions/plan/plans.ts +8 -11
  88. package/extensions/plan/policy.ts +20 -12
  89. package/extensions/plan/review.ts +3 -27
  90. package/extensions/plan/runtime.ts +21 -9
  91. package/extensions/plan/sections.ts +112 -0
  92. package/extensions/plan/steps.ts +3 -22
  93. package/extensions/plan/tools/exit.ts +11 -95
  94. package/extensions/plan/tui.ts +5 -34
  95. package/extensions/plan/types.ts +0 -8
  96. package/extensions/plan/web-client.ts +1 -1
  97. package/extensions/plan/web-page.ts +0 -7
  98. package/extensions/plan/web-router.ts +3 -57
  99. package/extensions/plan/web-share-session.ts +24 -30
  100. package/extensions/plan/web-streams.ts +14 -14
  101. package/extensions/plan/web-tokens.ts +2 -2
  102. package/extensions/plan/web.ts +66 -120
  103. package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
  104. package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
  105. package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
  106. package/extensions/rewind/{config.ts → config/config.ts} +1 -1
  107. package/extensions/rewind/index.ts +3 -3
  108. package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
  109. package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
  110. package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
  111. package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
  112. package/extensions/rewind/{format.ts → text/format.ts} +3 -3
  113. package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
  114. package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
  115. package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
  116. package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
  117. package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
  118. package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
  119. package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
  120. package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
  121. package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
  122. package/extensions/rewind/{status.ts → view/status.ts} +4 -4
  123. package/extensions/session-bus/commands.ts +17 -69
  124. package/extensions/session-bus/format.ts +22 -45
  125. package/extensions/session-bus/index.ts +13 -7
  126. package/extensions/session-bus/join-request.ts +40 -0
  127. package/extensions/session-bus/mail-watch.ts +134 -0
  128. package/extensions/session-bus/notice.ts +32 -4
  129. package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
  130. package/extensions/session-bus/protocol.ts +19 -49
  131. package/extensions/session-bus/resolve.ts +67 -0
  132. package/extensions/session-bus/result.ts +16 -51
  133. package/extensions/session-bus/rows.ts +4 -2
  134. package/extensions/session-bus/runtime.ts +129 -238
  135. package/extensions/session-bus/store.ts +42 -6
  136. package/extensions/session-bus/tools.ts +31 -66
  137. package/extensions/session-bus/tui.ts +4 -4
  138. package/extensions/session-bus/types.ts +13 -13
  139. package/extensions/subagent/agents.ts +9 -8
  140. package/extensions/subagent/invocation.ts +4 -2
  141. package/extensions/team/commands.ts +112 -0
  142. package/extensions/team/dispatch.ts +133 -0
  143. package/extensions/team/format.ts +105 -0
  144. package/extensions/team/index.ts +33 -0
  145. package/extensions/team/schema.ts +29 -0
  146. package/extensions/team/tools.ts +246 -0
  147. package/extensions/team/types.ts +48 -0
  148. package/extensions/web-access/fetch/page.ts +4 -1
  149. package/extensions/web-access/fetch/structured.ts +447 -0
  150. package/extensions/web-access/fetch/tool.ts +2 -1
  151. package/extensions/web-access/search/tool.ts +2 -1
  152. package/extensions/wechat/bridge.ts +76 -92
  153. package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
  154. package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
  155. package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
  156. package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
  157. package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
  158. package/extensions/wechat/dialog/channels.ts +136 -0
  159. package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
  160. package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
  161. package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
  162. package/extensions/wechat/index.ts +10 -10
  163. package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
  164. package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
  165. package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
  166. package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
  167. package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
  168. package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
  169. package/extensions/wechat/{state.ts → state/state.ts} +14 -2
  170. package/extensions/wechat/{format.ts → text/format.ts} +3 -3
  171. package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
  172. package/extensions/wechat/tools.ts +29 -12
  173. package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
  174. package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
  175. package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
  176. package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
  177. package/extensions/wechat/transport/outbound.ts +142 -0
  178. package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
  179. package/extensions/wechat/types.ts +10 -1
  180. package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
  181. package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
  182. package/extensions/worktree/guard.ts +3 -5
  183. package/lib/README.md +8 -6
  184. package/lib/board/card.ts +290 -0
  185. package/lib/board/links.ts +117 -0
  186. package/lib/board/page-pointer.ts +256 -0
  187. package/lib/board/round-text.ts +72 -0
  188. package/lib/board/round.ts +89 -0
  189. package/lib/board/rules.ts +304 -0
  190. package/lib/board/session.ts +105 -0
  191. package/lib/board/store.ts +310 -0
  192. package/lib/board/view.ts +150 -0
  193. package/lib/errors.ts +12 -4
  194. package/lib/format.ts +13 -0
  195. package/lib/http/guard.ts +112 -0
  196. package/lib/http/index.ts +16 -5
  197. package/lib/http/serve.ts +234 -0
  198. package/lib/http/server.ts +12 -10
  199. package/lib/http/sink.ts +110 -0
  200. package/lib/http/token.ts +38 -0
  201. package/lib/policy.ts +12 -2
  202. package/lib/requirements.ts +6 -7
  203. package/lib/session-label.ts +64 -0
  204. package/lib/session-roster.ts +130 -0
  205. package/lib/session-tool.ts +184 -0
  206. package/lib/tool-names.ts +25 -2
  207. package/lib/tui/glyphs.ts +2 -0
  208. package/lib/web/README.md +5 -2
  209. package/lib/web/components.ts +5 -0
  210. package/lib/web/runtime.ts +7 -1
  211. package/lib/web/tokens.ts +25 -2
  212. package/package.json +26 -14
  213. package/skills/next/SKILL.md +4 -3
  214. package/skills/project-docs/SKILL.md +133 -0
  215. package/skills/project-docs/references/audit.md +131 -0
  216. package/skills/project-docs/references/changelog.md +105 -0
  217. package/skills/project-docs/references/discovery.md +127 -0
  218. package/skills/project-docs/references/readme.md +80 -0
  219. package/skills/project-docs/references/release.md +84 -0
  220. package/skills/project-docs/references/roadmap.md +93 -0
  221. package/skills/project-docs/scripts/state.ts +316 -0
  222. package/skills/refactor/SKILL.md +100 -125
  223. package/skills/refactor/references/handoff.md +35 -0
  224. package/skills/refactor/references/signals.md +54 -0
  225. package/skills/refactor/scripts/inventory.ts +587 -0
  226. package/extensions/file-browse/README.md +0 -506
  227. package/extensions/file-browse/server.ts +0 -141
  228. package/extensions/plan/split.ts +0 -87
  229. package/extensions/session-bus/README.md +0 -247
  230. package/extensions/session-bus/team.ts +0 -102
  231. package/extensions/wechat/outbound.ts +0 -100
  232. package/lib/plans/sections.ts +0 -172
  233. package/lib/plans/setting.ts +0 -52
  234. package/lib/plans/split.ts +0 -215
  235. package/lib/plans/store.ts +0 -19
  236. package/lib/plans/team.ts +0 -151
  237. package/skills/changelog/SKILL.md +0 -148
  238. package/skills/parallel-dev/SKILL.md +0 -150
  239. /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
  240. /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
  241. /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
  242. /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
  243. /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
  244. /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
  245. /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
  246. /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
  247. /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
  248. /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
  249. /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
@@ -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