one-code-extension 0.4.1 → 0.5.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 (419) hide show
  1. package/README.md +16 -5
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +32 -23
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +27 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +58 -31
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +123 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +515 -0
  56. package/extensions/background/index.ts +514 -236
  57. package/extensions/background/loop-fire.ts +136 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +52 -0
  63. package/extensions/background/wakeup.ts +159 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +88 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +229 -71
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/claude-compat/index.ts +7 -11
  80. package/extensions/claude-context/index.ts +446 -58
  81. package/extensions/compaction/cut.ts +157 -0
  82. package/extensions/compaction/index.ts +216 -75
  83. package/extensions/compaction/kept-thinking.ts +62 -0
  84. package/extensions/compaction/prompt.ts +15 -12
  85. package/extensions/context-budget/index.ts +24 -5
  86. package/extensions/context-management/index.ts +25 -0
  87. package/extensions/doctor/build.ts +59 -8
  88. package/extensions/doctor/builtins.ts +84 -0
  89. package/extensions/doctor/cli.ts +7 -1
  90. package/extensions/doctor/compat.ts +88 -23
  91. package/extensions/doctor/dependencies.ts +41 -5
  92. package/extensions/doctor/fix-prompt.ts +9 -2
  93. package/extensions/doctor/index.ts +102 -58
  94. package/extensions/doctor/models.ts +102 -63
  95. package/extensions/doctor/presets.ts +42 -27
  96. package/extensions/doctor/report.ts +19 -2
  97. package/extensions/doctor/viewer.ts +49 -27
  98. package/extensions/effort/index.ts +72 -19
  99. package/extensions/effort/settings.ts +21 -0
  100. package/extensions/effort/slider.ts +24 -20
  101. package/extensions/exit/index.ts +32 -1
  102. package/extensions/file-tracker/index.ts +337 -26
  103. package/extensions/file-tracker/replay.ts +96 -10
  104. package/extensions/file-tracker/restore.ts +63 -0
  105. package/extensions/file-tracker/results.ts +66 -0
  106. package/extensions/file-tracker/shell-reads.ts +211 -0
  107. package/extensions/file-tracker/tracker.ts +32 -15
  108. package/extensions/footer/footer-line.ts +45 -24
  109. package/extensions/footer/index.ts +47 -2
  110. package/extensions/footer/pr.ts +34 -5
  111. package/extensions/hooks/compaction.ts +7 -0
  112. package/extensions/hooks/executor.ts +32 -30
  113. package/extensions/hooks/index.ts +198 -31
  114. package/extensions/hooks/matcher.ts +3 -0
  115. package/extensions/hooks/protocol.ts +41 -3
  116. package/extensions/hooks/settings.ts +50 -19
  117. package/extensions/hooks/subagent-bridge.ts +2 -0
  118. package/extensions/hooks/trust.ts +13 -5
  119. package/extensions/idle-compact/index.ts +119 -0
  120. package/extensions/idle-compact/policy.ts +143 -0
  121. package/extensions/init/index.ts +11 -10
  122. package/extensions/init/prompt.ts +25 -32
  123. package/extensions/interrupted/index.ts +13 -1
  124. package/extensions/kept-thinking/index.ts +27 -0
  125. package/extensions/lib/agent-cron.ts +130 -0
  126. package/extensions/lib/agent-loader.ts +15 -1
  127. package/extensions/lib/agent-view.ts +85 -0
  128. package/extensions/lib/anthropic-payload.ts +82 -1
  129. package/extensions/lib/anthropic-server-call.ts +230 -0
  130. package/extensions/lib/anthropic-server-tools.ts +485 -0
  131. package/extensions/lib/app-launch.mjs +94 -0
  132. package/extensions/lib/argument-hints.ts +190 -0
  133. package/extensions/lib/atomic-write.ts +6 -2
  134. package/extensions/lib/auto-memory.ts +75 -0
  135. package/extensions/lib/bash-parser.ts +84 -0
  136. package/extensions/lib/btw-fork.ts +93 -0
  137. package/extensions/lib/child-extensions.ts +42 -0
  138. package/extensions/lib/child-writes.ts +45 -0
  139. package/extensions/lib/claude-context.ts +515 -100
  140. package/extensions/lib/claude-external-includes.ts +68 -0
  141. package/extensions/lib/claude-id.ts +22 -0
  142. package/extensions/lib/claude-rules.ts +323 -0
  143. package/extensions/lib/claude-settings.ts +76 -4
  144. package/extensions/lib/compaction-boundary.ts +36 -0
  145. package/extensions/lib/compaction-keep.mjs +83 -0
  146. package/extensions/lib/config-mode.ts +112 -0
  147. package/extensions/lib/consent-dialogs.ts +63 -0
  148. package/extensions/lib/consent-preview.ts +5 -1
  149. package/extensions/lib/consent-stores.ts +48 -0
  150. package/extensions/lib/context-facts.ts +78 -0
  151. package/extensions/lib/context-stack.ts +81 -0
  152. package/extensions/lib/deferred.ts +219 -25
  153. package/extensions/lib/dock-rows.ts +60 -0
  154. package/extensions/lib/empty-tool-result.ts +27 -0
  155. package/extensions/lib/environment-block.ts +76 -0
  156. package/extensions/lib/fetch-timeout.ts +24 -6
  157. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  158. package/extensions/lib/git.ts +75 -13
  159. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  160. package/extensions/lib/grammars/README.md +2 -0
  161. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  162. package/extensions/lib/guide-docs.ts +45 -0
  163. package/extensions/lib/headless-output.ts +46 -0
  164. package/extensions/lib/hook-gate.ts +7 -7
  165. package/extensions/lib/interrupt.ts +20 -4
  166. package/extensions/lib/key-input.ts +455 -0
  167. package/extensions/lib/keys.ts +14 -1
  168. package/extensions/lib/local-command.ts +47 -13
  169. package/extensions/lib/mcp-overrides.ts +1 -1
  170. package/extensions/lib/mcp-share.ts +46 -0
  171. package/extensions/lib/memory-content.ts +146 -0
  172. package/extensions/lib/memory-index.ts +18 -0
  173. package/extensions/lib/memory.ts +134 -123
  174. package/extensions/lib/model-catalog/huggingface.json +174 -0
  175. package/extensions/lib/model-catalog/models-dev.json +18297 -0
  176. package/extensions/lib/model-catalog/openrouter.json +5252 -0
  177. package/extensions/lib/model-catalog-data.ts +513 -0
  178. package/extensions/lib/model-catalog.ts +462 -0
  179. package/extensions/lib/model-choice-warnings.ts +137 -0
  180. package/extensions/lib/model-pairings.ts +152 -0
  181. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  182. package/extensions/lib/model-policy.ts +112 -15
  183. package/extensions/lib/model-tier.ts +283 -355
  184. package/extensions/lib/model-unusable.ts +1 -1
  185. package/extensions/lib/newer-model.ts +100 -0
  186. package/extensions/lib/notices.ts +87 -0
  187. package/extensions/lib/notifications.ts +308 -64
  188. package/extensions/lib/one-code-settings.ts +74 -8
  189. package/extensions/lib/open-browser.ts +63 -0
  190. package/extensions/lib/openrouter-generation.ts +94 -0
  191. package/extensions/lib/operation-deadline.ts +30 -0
  192. package/extensions/lib/original-command.ts +4 -4
  193. package/extensions/lib/package-version.ts +1 -1
  194. package/extensions/lib/panel-focus.ts +134 -0
  195. package/extensions/lib/paths.ts +17 -2
  196. package/extensions/lib/permission-gate.ts +72 -6
  197. package/extensions/lib/persisted-output.ts +60 -8
  198. package/extensions/lib/pi-ai-estimate.ts +19 -3
  199. package/extensions/lib/pi-version.ts +21 -2
  200. package/extensions/lib/plan-mode-channels.ts +20 -5
  201. package/extensions/lib/plugins.ts +26 -17
  202. package/extensions/lib/powershell-parse-script.ts +208 -0
  203. package/extensions/lib/powershell-parser.ts +344 -0
  204. package/extensions/lib/process-tree.ts +131 -8
  205. package/extensions/lib/prompt-options.ts +82 -0
  206. package/extensions/lib/queued-delivery.ts +44 -0
  207. package/extensions/lib/reminders.ts +364 -58
  208. package/extensions/lib/replaced-builtins.mjs +199 -0
  209. package/extensions/lib/replay-call.ts +70 -0
  210. package/extensions/lib/request-replay.ts +439 -0
  211. package/extensions/lib/scratchpad.ts +81 -14
  212. package/extensions/lib/send-now-channel.ts +7 -0
  213. package/extensions/lib/session-model-tier.ts +51 -0
  214. package/extensions/lib/session-work.ts +64 -0
  215. package/extensions/lib/settings-channels.ts +4 -4
  216. package/extensions/lib/shell-exit-semantics.ts +170 -0
  217. package/extensions/lib/shell-quote.ts +11 -0
  218. package/extensions/lib/shell-result.ts +70 -0
  219. package/extensions/lib/shell-spawn.ts +70 -6
  220. package/extensions/lib/shell-tool.ts +75 -25
  221. package/extensions/lib/side-call-cache.ts +124 -0
  222. package/extensions/lib/side-call-run.ts +96 -0
  223. package/extensions/lib/side-call-usage.ts +38 -0
  224. package/extensions/lib/side-call.ts +13 -1
  225. package/extensions/lib/single-flight.ts +18 -0
  226. package/extensions/lib/skill-body.ts +42 -0
  227. package/extensions/lib/skill-scan.ts +63 -24
  228. package/extensions/lib/spill-file.ts +140 -0
  229. package/extensions/lib/subagent-handback.ts +113 -0
  230. package/extensions/lib/system-role.ts +156 -0
  231. package/extensions/lib/terminal-text.ts +114 -0
  232. package/extensions/lib/text-width.ts +47 -16
  233. package/extensions/lib/timer-ops.ts +20 -0
  234. package/extensions/lib/tool-additions.ts +149 -0
  235. package/extensions/lib/tool-call-corruption.ts +134 -0
  236. package/extensions/lib/tool-path.ts +66 -0
  237. package/extensions/lib/tool-variants.ts +69 -0
  238. package/extensions/lib/tui-render.ts +255 -27
  239. package/extensions/lib/turn-budget-layout.ts +143 -0
  240. package/extensions/lib/update-check.mjs +15 -2
  241. package/extensions/lib/usage-bus.ts +1 -1
  242. package/extensions/lib/user-denial.ts +38 -0
  243. package/extensions/lib/workspace-channel.ts +16 -0
  244. package/extensions/lib/worktree-channel.ts +31 -3
  245. package/extensions/lib/worktree-isolation.ts +40 -10
  246. package/extensions/lsp/client.ts +8 -9
  247. package/extensions/lsp/index.ts +70 -4
  248. package/extensions/lsp/keep-alive.ts +1 -1
  249. package/extensions/lsp/trust.ts +119 -0
  250. package/extensions/lsp/watcher.ts +1 -1
  251. package/extensions/mcp/announce.ts +87 -5
  252. package/extensions/mcp/client.ts +161 -48
  253. package/extensions/mcp/config.ts +132 -8
  254. package/extensions/mcp/discovery.ts +19 -0
  255. package/extensions/mcp/index.ts +209 -71
  256. package/extensions/mcp/oauth/browser.ts +38 -17
  257. package/extensions/mcp/oauth/flow.ts +25 -17
  258. package/extensions/mcp/oauth/provider.ts +14 -8
  259. package/extensions/mcp/oauth/store.ts +34 -16
  260. package/extensions/mcp/panel/keys.ts +12 -11
  261. package/extensions/mcp/panel/render.ts +9 -6
  262. package/extensions/mcp/schema.ts +104 -5
  263. package/extensions/mcp/trust.ts +151 -67
  264. package/extensions/memory/entries.ts +23 -14
  265. package/extensions/memory/index.ts +134 -28
  266. package/extensions/memory/open-external.ts +33 -18
  267. package/extensions/memory/panel.ts +135 -26
  268. package/extensions/newer-model/index.ts +31 -0
  269. package/extensions/notebook/description.ts +18 -0
  270. package/extensions/notebook/index.ts +6 -5
  271. package/extensions/notebook/notebook.ts +1 -1
  272. package/extensions/permissions/index.ts +1300 -381
  273. package/extensions/permissions/matcher.ts +425 -74
  274. package/extensions/permissions/modes.ts +6 -4
  275. package/extensions/permissions/panel/host.ts +130 -0
  276. package/extensions/permissions/panel/keys.ts +48 -0
  277. package/extensions/permissions/panel/render.ts +450 -0
  278. package/extensions/permissions/panel/state.ts +550 -0
  279. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  280. package/extensions/permissions/powershell-paths.ts +197 -0
  281. package/extensions/permissions/powershell-rules.ts +207 -280
  282. package/extensions/permissions/powershell-tree.ts +557 -0
  283. package/extensions/permissions/project-trust.ts +27 -11
  284. package/extensions/permissions/protected-paths.ts +14 -2
  285. package/extensions/permissions/session-grant.ts +6 -0
  286. package/extensions/permissions/settings.ts +166 -31
  287. package/extensions/permissions/subagent-gate.ts +25 -0
  288. package/extensions/permissions/workspace.ts +78 -0
  289. package/extensions/plan-mode/index.ts +156 -69
  290. package/extensions/plan-mode/reminder.ts +89 -21
  291. package/extensions/plan-mode/texts.ts +177 -0
  292. package/extensions/plan-mode/viewer.ts +92 -27
  293. package/extensions/plugins/counts.ts +3 -3
  294. package/extensions/plugins/index.ts +61 -46
  295. package/extensions/plugins/install/install.ts +16 -0
  296. package/extensions/plugins/marketplace/sync.ts +9 -3
  297. package/extensions/plugins/marketplace/types.ts +3 -1
  298. package/extensions/plugins/panel/keys.ts +21 -24
  299. package/extensions/plugins/panel/render.ts +12 -6
  300. package/extensions/plugins/shell-expand.ts +107 -0
  301. package/extensions/plugins/template.ts +32 -15
  302. package/extensions/powershell/child.ts +7 -0
  303. package/extensions/powershell/description.ts +38 -6
  304. package/extensions/powershell/guards.ts +1 -1
  305. package/extensions/powershell/index.ts +12 -7
  306. package/extensions/recap/index.ts +65 -60
  307. package/extensions/recap/prompt.ts +2 -2
  308. package/extensions/recap/scheduler.ts +3 -5
  309. package/extensions/search-tools/grep-merge.ts +39 -0
  310. package/extensions/search-tools/index.ts +0 -0
  311. package/extensions/send-now/chord.ts +62 -0
  312. package/extensions/send-now/index.ts +133 -0
  313. package/extensions/session-title/index.ts +3 -0
  314. package/extensions/session-title/title.ts +4 -1
  315. package/extensions/skill/code-review.ts +97 -0
  316. package/extensions/skill/index.ts +421 -101
  317. package/extensions/skill/invoke.ts +128 -18
  318. package/extensions/skill/listing.ts +133 -9
  319. package/extensions/skill/panel/keys.ts +15 -16
  320. package/extensions/skill/panel/render.ts +5 -2
  321. package/extensions/skill/panel/state.ts +12 -1
  322. package/extensions/subagents/agent-args.ts +39 -0
  323. package/extensions/subagents/agent-description.ts +132 -0
  324. package/extensions/subagents/agents.ts +116 -23
  325. package/extensions/subagents/announce.ts +69 -0
  326. package/extensions/subagents/default-model.ts +2 -3
  327. package/extensions/subagents/delegation-steer.ts +4 -4
  328. package/extensions/subagents/fork-cache.ts +76 -0
  329. package/extensions/subagents/guide-agent.ts +189 -0
  330. package/extensions/subagents/hand-back-review.ts +3 -0
  331. package/extensions/subagents/index.ts +1493 -824
  332. package/extensions/subagents/live-runs.ts +26 -6
  333. package/extensions/subagents/model-select.ts +38 -25
  334. package/extensions/subagents/outcome.ts +2 -2
  335. package/extensions/subagents/panel-keys.ts +73 -31
  336. package/extensions/subagents/panel-render.ts +120 -96
  337. package/extensions/subagents/panel-widget.ts +60 -89
  338. package/extensions/subagents/runner.ts +108 -80
  339. package/extensions/subagents/runs.ts +39 -5
  340. package/extensions/subagents/send-to-main-tool.ts +1 -1
  341. package/extensions/subagents/session-turns.ts +32 -8
  342. package/extensions/subagents/shell-panel.ts +36 -178
  343. package/extensions/subagents/tasks-dialog.ts +170 -0
  344. package/extensions/subagents/viewport-mirror.ts +95 -0
  345. package/extensions/subagents/worktree.ts +57 -13
  346. package/extensions/system-prompt/environment.ts +19 -19
  347. package/extensions/system-prompt/idle-turn.ts +65 -0
  348. package/extensions/system-prompt/index.ts +131 -47
  349. package/extensions/system-prompt/template.ts +24 -50
  350. package/extensions/system-prompt/tiers/common.ts +23 -64
  351. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  352. package/extensions/system-prompt/tiers/low.ts +67 -45
  353. package/extensions/system-prompt/tiers/mid.ts +96 -38
  354. package/extensions/system-reminder/index.ts +175 -13
  355. package/extensions/tasks/descriptions.ts +190 -0
  356. package/extensions/tasks/index.ts +120 -41
  357. package/extensions/tasks/store.ts +83 -16
  358. package/extensions/tool-call-corruption/child.ts +7 -0
  359. package/extensions/tool-call-corruption/index.ts +237 -0
  360. package/extensions/tool-search/announce.ts +16 -0
  361. package/extensions/tool-search/index.ts +189 -41
  362. package/extensions/tool-style/index.ts +68 -10
  363. package/extensions/tool-style/write-description.ts +40 -0
  364. package/extensions/tool-style/write-render.ts +237 -0
  365. package/extensions/turn-duration/index.ts +7 -3
  366. package/extensions/turn-duration/line.ts +47 -4
  367. package/extensions/web/backends.ts +6 -9
  368. package/extensions/web/description.ts +50 -0
  369. package/extensions/web/index.ts +118 -53
  370. package/extensions/web/native-call.ts +15 -0
  371. package/extensions/web/native-results.ts +41 -0
  372. package/extensions/web-fetch/description.ts +42 -0
  373. package/extensions/web-fetch/extract.ts +53 -6
  374. package/extensions/web-fetch/index.ts +125 -76
  375. package/extensions/web-fetch/page-content.ts +19 -0
  376. package/extensions/web-fetch/response.ts +49 -0
  377. package/extensions/web-fetch/summarize.ts +9 -10
  378. package/extensions/workflow/agent-session.ts +105 -18
  379. package/extensions/workflow/args.ts +24 -0
  380. package/extensions/workflow/description.ts +67 -0
  381. package/extensions/workflow/globals.ts +17 -3
  382. package/extensions/workflow/index.ts +145 -162
  383. package/extensions/workflow/records.ts +1 -0
  384. package/extensions/workflow/run-manager.ts +29 -6
  385. package/extensions/workflow/saved-workflows.ts +3 -2
  386. package/extensions/workflow/types.ts +22 -1
  387. package/extensions/workflow/viewer.ts +54 -24
  388. package/extensions/workflow/widget.ts +20 -0
  389. package/extensions/worktree/descriptions.ts +91 -0
  390. package/extensions/worktree/guards.ts +316 -54
  391. package/extensions/worktree/index.ts +121 -39
  392. package/extensions/worktree/powershell-guards.ts +167 -0
  393. package/extensions/worktree/rewrite.ts +14 -16
  394. package/package.json +18 -7
  395. package/skills/artifact-design/SKILL.md +85 -0
  396. package/skills/code-review/SKILL.md +20 -20
  397. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  398. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  399. package/skills/code-review/cells/high-8-inline.md +122 -0
  400. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  401. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  402. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  403. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  404. package/skills/code-review/cells/minimal-prompt.md +32 -0
  405. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  406. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  407. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  408. package/skills/loop/SKILL.md +11 -0
  409. package/skills/security-review/SKILL.md +2 -5
  410. package/skills/simplify/SKILL.md +7 -5
  411. package/skills/workflow-authoring/SKILL.md +143 -0
  412. package/themes/onecode-light.json +1 -1
  413. package/themes/onecode.json +1 -1
  414. package/types/app.d.ts +58 -0
  415. package/types/community-packages.d.ts +26 -1
  416. package/extensions/auto-mode/recoverability.ts +0 -229
  417. package/extensions/lib/capability-index.ts +0 -466
  418. package/extensions/lib/model-facts.generated.json +0 -2675
  419. package/extensions/lib/model-facts.ts +0 -162
@@ -4,6 +4,11 @@
4
4
  * `web_search` is registered here with Claude Code's schema (`query`,
5
5
  * `allowed_domains`, `blocked_domains`) and routes by what the session can do:
6
6
  *
7
+ * 0. Anthropic's server-side `web_search` with dynamic filtering, in our own
8
+ * nested call, on a first-party Anthropic API-key session with a Claude
9
+ * 4.6+ model available (lib/anthropic-server-tools.ts). pi-web-search's
10
+ * Anthropic call uses the older `web_search_20250305`, so it is the next
11
+ * step down, not this one.
7
12
  * 1. Provider-native search through the community `pi-web-search` package
8
13
  * when the current model's provider has one (Anthropic, OpenAI/Codex,
9
14
  * Gemini, xAI) — no third-party key, and as close to Claude Code's
@@ -23,18 +28,24 @@
23
28
  * WebFetch lives in `extensions/web-fetch` (our own); this file only owns search.
24
29
  */
25
30
 
26
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
31
+ import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
27
32
  import { homedir } from "node:os";
28
33
  import { Type } from "typebox";
29
34
  import webSearchPackage from "pi-web-search/src/index.ts";
30
- import { getProviderKind } from "pi-web-search/src/api.ts";
31
- import { webSearch as nativeWebSearch } from "pi-web-search/src/web_search.ts";
35
+ import { callApiStream, getConfig, getProviderKind } from "pi-web-search/src/api.ts";
32
36
  import { getWebSearchModel } from "pi-web-search/src/utils.ts";
37
+ import { tryNativeWeb } from "../lib/anthropic-server-call.ts";
38
+ import { CUT_OFF_NOTE, nativeSearchBody, searchOutcome, searchSources, sourceLine, type ThinkingFields } from "../lib/anthropic-server-tools.ts";
33
39
  import { readJsonFile } from "../lib/atomic-write.ts";
34
- import { DEFER_CHANNEL } from "../lib/deferred.ts";
40
+ import { recordUsage } from "../lib/usage-bus.ts";
41
+ import { DEFER_CHANNEL, WITHHOLD_CHANNEL } from "../lib/deferred.ts";
35
42
  import { oneCodeSettingsPath } from "../lib/one-code-settings.ts";
36
43
  import { persistIfLarge, sessionResultsDir } from "../lib/persisted-output.ts";
37
44
  import { ccToolRenderers } from "../lib/tui-render.ts";
45
+ import { registerFormTool } from "../lib/tool-variants.ts";
46
+ import { webSearchDescription } from "./description.ts";
47
+ import { formatNativeSearchResult } from "./native-results.ts";
48
+ import { runNativeSearch } from "./native-call.ts";
38
49
  import {
39
50
  DEFAULT_MAX_RESULTS,
40
51
  formatSearchResults,
@@ -68,16 +79,15 @@ export default function webExtension(pi: ExtensionAPI) {
68
79
 
69
80
  // Once per session: the user is told the first time a search goes keyless.
70
81
  let keylessNoticeShown = false;
82
+ // Per-session: the thinking-off fields a native-call model turned out to need.
83
+ const learnedNativeThinking = new Map<string, ThinkingFields>();
71
84
 
72
- pi.registerTool({
85
+ // Short or long text by the model's tier (description.ts, lib/tool-variants.ts).
86
+ const tool = defineTool({
73
87
  name: "web_search",
74
88
  label: "Web Search",
75
89
  ...ccToolRenderers<{ query?: string }>("Web Search", { title: (args) => args?.query }),
76
- description:
77
- "Search the web. Returns result blocks with titles and URLs.\n\n" +
78
- "- `allowed_domains` / `blocked_domains` filter results (enforced on Brave/Tavily/Exa; with provider-native search they become `site:` operators in the query, best-effort).\n" +
79
- '- After answering from results, end with a "Sources:" list of the URLs you used as markdown links.\n' +
80
- "- Uses the model provider's own search when it has one; otherwise a configured search API (Brave, Tavily) or, with no key, a free rate-limited endpoint — the result names which.",
90
+ description: webSearchDescription("short"),
81
91
  parameters: Type.Object({
82
92
  query: Type.String({ minLength: 2, description: "The search query to use" }),
83
93
  allowed_domains: Type.Optional(Type.Array(Type.String(), { description: "Only include search results from these domains" })),
@@ -86,37 +96,90 @@ export default function webExtension(pi: ExtensionAPI) {
86
96
  async execute(toolCallId, params, signal, onUpdate, ctx) {
87
97
  const filters = { allowed: params.allowed_domains, blocked: params.blocked_domains };
88
98
 
89
- // 1. Provider-native search (pi-web-search) when the current model — or a
90
- // web-search.json model — supports it. Its result text is passed through;
91
- // it reports failures as "Failed: …" text with details.error and no
92
- // isError, which a weak model can read as a successful search with odd
93
- // content, so isError is stamped here.
99
+ // 0. Anthropic's server-side search with dynamic filtering, on a
100
+ // first-party Anthropic API-key session. The domain filters are enforced
101
+ // by the search itself; it takes one list or the other, so a call with
102
+ // both goes down the paths below. Anything short of an answer from real
103
+ // results falls through, with a note saying why.
104
+ let nativeNote: string | undefined;
105
+ const bothFilters = Boolean(params.allowed_domains?.length && params.blocked_domains?.length);
106
+ if (!bothFilters) {
107
+ const native = await tryNativeWeb(ctx, {
108
+ body: (model) =>
109
+ nativeSearchBody({ model, query: params.query, allowedDomains: params.allowed_domains, blockedDomains: params.blocked_domains }),
110
+ outcome: searchOutcome,
111
+ onUsage: (usage) => recordUsage(pi, "web-search", usage),
112
+ learned: learnedNativeThinking,
113
+ onStart: () =>
114
+ onUpdate?.({
115
+ content: [{ type: "text", text: `Searching with Anthropic's web search for "${params.query}"...` }],
116
+ details: { query: params.query } as SearchDetails,
117
+ }),
118
+ signal,
119
+ });
120
+ if (native.kind === "cancelled") {
121
+ return {
122
+ content: [{ type: "text", text: `Search for "${params.query}" was cancelled.` }],
123
+ details: { query: params.query } as SearchDetails,
124
+ isError: true,
125
+ };
126
+ }
127
+ if (native.kind === "answered") {
128
+ const sources = searchSources(native.result);
129
+ const filtered = Boolean(params.allowed_domains?.length || params.blocked_domains?.length);
130
+ const text = [
131
+ native.text,
132
+ ...(native.cutOff ? ["", CUT_OFF_NOTE] : []),
133
+ "",
134
+ "Sources:",
135
+ ...sources.map(sourceLine),
136
+ "",
137
+ `(Searched with Anthropic's server-side web search with dynamic filtering, on ${native.spec}${filtered ? "; the domain filters were enforced by the search" : ""}.)`,
138
+ ].join("\n");
139
+ return {
140
+ content: [{ type: "text", text: persistIfLarge(text, { dir: sessionResultsDir(ctx), id: toolCallId }) }],
141
+ details: { query: params.query, native: true, backend: `anthropic web_search via ${native.spec}`, resultCount: sources.length } as SearchDetails,
142
+ };
143
+ }
144
+ if (native.kind === "fell-back") {
145
+ nativeNote = `(Anthropic's server-side web search did not answer (${native.reason}); the results below come from the next search route.)`;
146
+ }
147
+ }
148
+ const withNativeNote = (text: string) => nativeNote ? `${text}\n\n${nativeNote}` : text;
149
+
150
+ // 1. Reuse pi-web-search's transport, not its tool wrapper: that wrapper
151
+ // truncates before we can persist the full answer and its sources.
94
152
  const nativeModel = await getWebSearchModel(ctx);
95
153
  if (nativeModel) {
96
- const result = await nativeWebSearch(
97
- toolCallId,
98
- { query: withSiteOperators(params.query, filters) },
99
- signal ?? new AbortController().signal,
100
- onUpdate,
101
- ctx,
102
- );
103
- // pi-web-search exposes no domain parameters and returns a synthesized
104
- // answer (nothing to post-filter), so the filters ride as `site:`
105
- // operators only — say so, rather than let the model assume enforcement.
106
- const hasFilters = Boolean(params.allowed_domains?.length || params.blocked_domains?.length);
107
- const content =
108
- hasFilters && !result.details?.error
109
- ? [
110
- ...result.content,
111
- { type: "text" as const, text: "(Domain filters were applied as `site:` operators in the query — best-effort with provider-native search; verify the sources' hosts.)" },
112
- ]
113
- : result.content;
114
- return {
115
- ...result,
116
- content,
117
- details: { ...result.details, query: params.query, native: true, backend: `${nativeModel.provider}/${nativeModel.id}` } as SearchDetails,
118
- isError: Boolean(result.isError) || Boolean(result.details?.error),
119
- };
154
+ const backend = `${nativeModel.provider}/${nativeModel.id}`;
155
+ try {
156
+ const config = getConfig(nativeModel);
157
+ const response = await runNativeSearch((nativeSignal) => callApiStream(ctx, nativeModel, {
158
+ contents: [{ role: "user", parts: [{ text: withSiteOperators(params.query, filters) }] }],
159
+ ...(config.kind === "google" ? { tools: [{ [config.searchTool!]: {} }] } : {}),
160
+ }, (update) => { if (!nativeSignal.aborted) onUpdate?.(update); }, nativeSignal), signal);
161
+ const result = formatNativeSearchResult(response);
162
+ // This route returns a synthesized answer, not rows we can filter.
163
+ const hasFilters = Boolean(params.allowed_domains?.length || params.blocked_domains?.length);
164
+ const text = withNativeNote([
165
+ result.text,
166
+ `(Searched with ${backend}.)`,
167
+ ...(hasFilters ? ["(Domain filters were applied as `site:` operators in the query — best-effort with provider-native search; verify the sources' hosts.)"] : []),
168
+ ].join("\n\n"));
169
+ return {
170
+ content: [{ type: "text", text: persistIfLarge(text, { dir: sessionResultsDir(ctx), id: toolCallId }) }],
171
+ details: { ...result.details, query: params.query, native: true, backend } as SearchDetails,
172
+ };
173
+ } catch (error) {
174
+ if (signal?.aborted) {
175
+ return {
176
+ content: [{ type: "text", text: `Search for "${params.query}" was cancelled.` }],
177
+ details: { query: params.query } as SearchDetails,
178
+ isError: true,
179
+ };
180
+ }
181
+ nativeNote = withNativeNote(`(Provider-native search on ${backend} failed (${error instanceof Error ? error.message : String(error)}); the next search route was tried.)`);
182
+ }
120
183
  }
121
184
 
122
185
  // 2. Third-party chain.
@@ -133,7 +196,7 @@ export default function webExtension(pi: ExtensionAPI) {
133
196
  }
134
197
  // Bounded by result count, but a provider's error body (quoted in
135
198
  // "Fell back after") or long snippets can still be large: persist, never slice.
136
- const text = persistIfLarge(formatSearchResults(params.query, outcome), { dir: sessionResultsDir(ctx), id: toolCallId });
199
+ const text = persistIfLarge(withNativeNote(formatSearchResults(params.query, outcome)), { dir: sessionResultsDir(ctx), id: toolCallId });
137
200
  return {
138
201
  content: [{ type: "text", text }],
139
202
  details: {
@@ -147,7 +210,7 @@ export default function webExtension(pi: ExtensionAPI) {
147
210
  } catch (error) {
148
211
  const message = (error as Error).message;
149
212
  const text = persistIfLarge(
150
- `web_search failed on ${ctx.model?.provider}/${ctx.model?.id} (no native web search on this provider).\n${message}`,
213
+ withNativeNote(`web_search failed on ${ctx.model?.provider}/${ctx.model?.id}.\n${message}`),
151
214
  { dir: sessionResultsDir(ctx), id: toolCallId },
152
215
  );
153
216
  return {
@@ -158,6 +221,7 @@ export default function webExtension(pi: ExtensionAPI) {
158
221
  }
159
222
  },
160
223
  });
224
+ registerFormTool(pi, tool, webSearchDescription);
161
225
 
162
226
  // url_context reports failures ("Failed: …" text, details.error set) without
163
227
  // isError. Stamp it here rather than patching the vendor package.
@@ -179,20 +243,21 @@ export default function webExtension(pi: ExtensionAPI) {
179
243
  // still advertised it in the names-only reminder (and as a defer_loading
180
244
  // definition) on every provider, so a non-Gemini model would load it and burn
181
245
  // two rounds discovering it cannot run (TOOL-FIDELITY-REVIEW-2026-09-07 M5).
182
- // Mirror pi-web-search's gate: register it once the first Gemini model is
183
- // seen. The one-shot flag keeps later session_tree/model_select events from
184
- // re-emitting (each emit past session_start takes the DEFER handler's
185
- // late-arrival path — deactivate + reschedule the listing rewrite — which is a
186
- // no-op once url_context is already registered).
246
+ // Mirror that gate in the deferred registry too: withdraw on leaving Gemini
247
+ // and re-defer on returning. Emit only when availability changes, since a
248
+ // repeated defer would deactivate a tool already loaded through tool_search.
249
+ // The registry owns announcements and keeps the cached listing frozen.
187
250
  let urlContextDeferred = false;
188
251
  const deferUrlContextIfGemini = (model: { provider?: string; api?: string } | undefined) => {
189
- // ctx.model is undefined until a model resolves (session_start can fire
190
- // first), and getProviderKind dereferences model.provider without a guard —
191
- // so an undefined model must short-circuit here, as pi-web-search's own
192
- // `!!model && getProviderKind(...)` does.
193
- if (urlContextDeferred || !model || getProviderKind(model) !== "google") return;
194
- urlContextDeferred = true;
195
- pi.events.emit(DEFER_CHANNEL, { name: "url_context", keywords: ["url", "analyze page", "gemini"] });
252
+ // session_start can run before a model resolves; getProviderKind requires one.
253
+ const available = !!model && getProviderKind(model) === "google";
254
+ if (available === urlContextDeferred) return;
255
+ urlContextDeferred = available;
256
+ if (available) {
257
+ pi.events.emit(DEFER_CHANNEL, { name: "url_context", keywords: ["url", "analyze page", "gemini"] });
258
+ } else {
259
+ pi.events.emit(WITHHOLD_CHANNEL, { name: "url_context" });
260
+ }
196
261
  };
197
262
  pi.on("session_start", (_event, ctx) => deferUrlContextIfGemini(ctx.model));
198
263
  pi.on("session_tree", (_event, ctx) => deferUrlContextIfGemini(ctx.model));
@@ -0,0 +1,15 @@
1
+ import { runWithDeadline } from "../lib/operation-deadline.ts";
2
+
3
+ /** Bound the provider-native transport, including auth, streamed bodies and source resolution. */
4
+ export const NATIVE_SEARCH_TIMEOUT_MS = 120_000;
5
+
6
+ export async function runNativeSearch<T>(
7
+ call: (signal: AbortSignal) => Promise<T>,
8
+ signal: AbortSignal | undefined,
9
+ ): Promise<T> {
10
+ return runWithDeadline(call, {
11
+ signal,
12
+ timeoutMs: NATIVE_SEARCH_TIMEOUT_MS,
13
+ message: "Provider-native search did not answer within 120 seconds.",
14
+ });
15
+ }
@@ -0,0 +1,41 @@
1
+ /** Provider-native search formatting without pi-web-search's lossy truncateHead wrapper. */
2
+ import { applyCitations, type StreamResult } from "pi-web-search/src/api.ts";
3
+
4
+ export function formatNativeSearchResult(result: StreamResult) {
5
+ if (result.nativeSearchUsed === false) throw new Error("The provider returned a response without performing a web search.");
6
+ // The vendor substitutes this placeholder when the stream contains no answer text.
7
+ if (!result.text.trim() || result.text.trim() === "No answer available.") throw new Error("The provider returned an empty web-search answer.");
8
+ const cited = applyCitations(result.text, result.groundingMetadata);
9
+ const sources = result.sources?.length ? result.sources : cited.sources;
10
+ const failedSearch = result.searchResults?.find((item) => item.type === "web_search_tool_result_error");
11
+ if (failedSearch) throw new Error(`The provider's search returned ${failedSearch.status ?? "an unspecified tool error"}.`);
12
+ if (!sources.length && !result.searchResults?.some((item) => item.url)) throw new Error("The provider returned no search results.");
13
+ const seen = new Set<string>();
14
+ const extraResults = (result.searchResults ?? []).filter((item) => {
15
+ if (!item.url || sources.some((source) => source.url === item.url)) return false;
16
+ const key = `${item.title ?? ""}\t${item.url}`;
17
+ if (seen.has(key)) return false;
18
+ seen.add(key);
19
+ return true;
20
+ });
21
+ const parts = [cited.text];
22
+ if (sources.length) parts.push(`## Sources\n${sources.map((source, i) => `${i + 1}. [${source.title}](${source.url})`).join("\n")}`);
23
+ if (extraResults.length) {
24
+ parts.push(`## Additional Search Results\n${extraResults.map((item, i) => `${i + 1}. ${item.title || item.url} - ${item.url}`).join("\n")}`);
25
+ }
26
+ return {
27
+ text: parts.join("\n\n"),
28
+ details: {
29
+ sources,
30
+ providerKind: result.providerKind,
31
+ nativeSearchUsed: result.nativeSearchUsed,
32
+ nativeSearchEvents: result.nativeSearchEvents,
33
+ nativeSearchCalls: result.nativeSearchCalls,
34
+ searchQueries: result.searchQueries ?? result.groundingMetadata?.webSearchQueries,
35
+ searchResults: result.searchResults,
36
+ citations: result.citations,
37
+ grounded: sources.length > 0 || (result.searchResults?.length ?? 0) > 0,
38
+ resultCount: result.searchResults?.length || sources.length,
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * web_fetch's model-facing description (pure).
3
+ *
4
+ * The short form, on frontier and workhorse models, is One Code's own: it
5
+ * describes what this fetch does that Claude Code's does not (an optional
6
+ * `prompt`, windowed pages, Anthropic's server-side fetch). The long form, on
7
+ * cheap and tiny models (lib/tool-variants.ts), is the text Claude Code writes
8
+ * for its weakest models, with One Code's tool names, without the claude.ai
9
+ * artifact line, with `prompt` optional and with the paging note in place of
10
+ * "Results may be summarized".
11
+ */
12
+
13
+ import type { DescriptionForm } from "../lib/tool-variants.ts";
14
+
15
+ export const WEB_FETCH_SHORT_DESCRIPTION =
16
+ "Fetch a URL and return its readable content as markdown. Navigation and boilerplate are stripped. Pass `prompt` to have a small fast model answer it from the full page instead of returning the page itself — prefer that for long pages (on an Anthropic API-key session, Anthropic's server-side fetch answers it). Without `prompt`, long pages are windowed; pass `offset` to continue reading. Responses are cached for 15 minutes. Cross-host redirects are reported instead of followed; call again with the new URL to follow one.";
17
+
18
+ export const WEB_FETCH_LONG_DESCRIPTION = `IMPORTANT: web_fetch WILL FAIL for authenticated or private URLs. Before using this tool, check if the URL points to an authenticated service (e.g. Google Docs, Confluence, Jira, GitHub). If so, look for a specialized MCP tool that provides authenticated access.
19
+
20
+ - Fetches content from a specified URL and processes it using an AI model
21
+ - Takes a URL and an optional prompt as input
22
+ - Fetches the URL content, converts HTML to markdown
23
+ - Processes the content with the prompt using a small, fast model
24
+ - Returns the model's response about the content
25
+ - Use this tool when you need to retrieve and analyze web content
26
+
27
+ Usage notes:
28
+ - IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions.
29
+ - The URL must be a fully-formed valid URL
30
+ - HTTP URLs will be automatically upgraded to HTTPS
31
+ - localhost and other hostnames without a dot are not supported; for a local server, use curl via bash
32
+ - The prompt should describe what information you want to extract from the page
33
+ - This tool is read-only and does not modify any files
34
+ - Without a prompt, a long page is returned in windows; pass \`offset\` to continue reading
35
+ - Includes a self-cleaning cache (entries expire after 15 minutes) for faster responses when repeatedly accessing the same URL
36
+ - When a URL redirects to a different host, the tool will inform you and provide the redirect URL in a special format. You should then make a new web_fetch request with the redirect URL to fetch the content.
37
+ - For GitHub URLs, prefer using the gh CLI via bash instead (e.g., gh pr view, gh issue view, gh api).
38
+ `;
39
+
40
+ export function webFetchDescription(form: DescriptionForm): string {
41
+ return form === "long" ? WEB_FETCH_LONG_DESCRIPTION : WEB_FETCH_SHORT_DESCRIPTION;
42
+ }
@@ -47,19 +47,25 @@ export function normalizeUrl(input: string): NormalizedUrl {
47
47
  throw new Error(`Not a valid URL: ${input}`);
48
48
  }
49
49
 
50
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
51
+ throw new Error(`Unsupported URL scheme "${parsed.protocol}" — only http and https are fetched.`);
52
+ }
53
+ if (parsed.username || parsed.password) throw new Error("URLs with embedded credentials are not supported.");
54
+ if (!parsed.hostname.replace(/\.+$/, "").includes(".")) {
55
+ throw new Error("web_fetch cannot fetch localhost or other hostnames without a dot. To reach a local server, use bash with curl instead.");
56
+ }
50
57
  if (parsed.protocol === "http:") {
51
58
  parsed.protocol = "https:";
52
59
  return { url: parsed.toString(), note: "Upgraded http to https." };
53
60
  }
54
- if (parsed.protocol !== "https:") {
55
- throw new Error(`Unsupported URL scheme "${parsed.protocol}" — only http and https are fetched.`);
56
- }
57
61
  return { url: parsed.toString() };
58
62
  }
59
63
 
60
64
  export function isSameHost(a: string, b: string): boolean {
61
65
  try {
62
- return new URL(a).host === new URL(b).host;
66
+ const target = new URL(a);
67
+ const source = new URL(b);
68
+ return target.host === source.host && target.protocol === source.protocol && !target.username && !target.password;
63
69
  } catch {
64
70
  return false;
65
71
  }
@@ -88,11 +94,22 @@ export interface ExtractResult {
88
94
  * first (it strips navigation and boilerplate); if it finds no article — common
89
95
  * for API references and landing pages — the whole body is converted instead.
90
96
  */
91
- export async function htmlToMarkdown(html: string, _url: string): Promise<ExtractResult> {
97
+ export async function htmlToMarkdown(html: string, url: string): Promise<ExtractResult> {
92
98
  const { Readability, parseHTML, Turndown } = await loadLibs();
93
99
  const { document } = parseHTML(html);
94
100
  const turndown = createTurndown(Turndown);
101
+ // linkedom does not resolve relative attributes against the fetched URL.
102
+ let base = url;
103
+ try { base = new URL(document.querySelector("base[href]")?.getAttribute("href") ?? url, url).href; } catch { /* use the fetched URL */ }
104
+ for (const [selector, attribute] of [["a[href]", "href"], ["img[src]", "src"]]) {
105
+ for (const element of document.querySelectorAll(selector)) {
106
+ try { element.setAttribute(attribute, new URL(element.getAttribute(attribute)!, base).href); } catch { /* keep malformed links as written */ }
107
+ }
108
+ }
95
109
 
110
+ // Readability mutates the document, and linkedom can create an empty body
111
+ // for a fragment. Keep the original content for the whole-page fallback.
112
+ const body = document.querySelector("body")?.innerHTML ?? document.toString();
96
113
  // linkedom's Document is structurally compatible with what Readability needs
97
114
  // but is not the DOM lib's Document type, hence the cast.
98
115
  let article: { title?: string | null; content?: string | null } | null = null;
@@ -110,7 +127,6 @@ export async function htmlToMarkdown(html: string, _url: string): Promise<Extrac
110
127
  };
111
128
  }
112
129
 
113
- const body = document.body?.innerHTML ?? html;
114
130
  return {
115
131
  title: document.title || undefined,
116
132
  markdown: turndown.turndown(body).trim(),
@@ -137,3 +153,34 @@ export function paginate(text: string, offset: number, maxChars: number): Page {
137
153
  totalChars: text.length,
138
154
  };
139
155
  }
156
+
157
+ /** A redirect to another host: reported, not followed (Claude Code's rule). Not a failure. */
158
+ export class CrossHostRedirect extends Error {
159
+ constructor(
160
+ readonly redirectUrl: string,
161
+ readonly status: number,
162
+ ) {
163
+ super(`Redirects to a different host: ${redirectUrl}`);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Claude Code's WebFetch redirect result, with our tool name, and the prompt
169
+ * line only when the call had a prompt (ours is optional). Before it, the
170
+ * redirect read as a fetch failure ("Verify the URL and host, retry, or use
171
+ * web_search instead"), advice that does not apply.
172
+ */
173
+ export function redirectMessage(originalUrl: string, redirect: CrossHostRedirect, prompt: string | undefined): string {
174
+ const statusText = redirect.status === 301 ? "Moved Permanently" : redirect.status === 308 ? "Permanent Redirect" : redirect.status === 307 ? "Temporary Redirect" : "Found";
175
+ return [
176
+ "REDIRECT DETECTED: The URL redirects to a different host.",
177
+ "",
178
+ `Original URL: ${originalUrl}`,
179
+ `Redirect URL: ${redirect.redirectUrl}`,
180
+ `Status: ${redirect.status} ${statusText}`,
181
+ "",
182
+ "To complete your request, I need to fetch content from the redirected URL. Please use web_fetch again with these parameters:",
183
+ `- url: "${redirect.redirectUrl}"`,
184
+ ...(prompt ? [`- prompt: "${prompt}"`] : []),
185
+ ].join("\n");
186
+ }