@asterxsk/kiln 0.3.0 → 0.4.2

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 (522) hide show
  1. package/README.md +155 -155
  2. package/agent/AGENTS.md +67 -67
  3. package/agent/README.md +5 -5
  4. package/agent/extensions/ask-user/index.ts +418 -418
  5. package/agent/extensions/ask-user/install.ps1 +27 -27
  6. package/agent/extensions/ask-user/install.sh +24 -24
  7. package/agent/extensions/ask-user/package-lock.json +769 -769
  8. package/agent/extensions/ask-user/package.json +19 -19
  9. package/agent/extensions/ask-user/prompt.ts +45 -45
  10. package/agent/extensions/ask-user/tsconfig.json +7 -7
  11. package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
  12. package/agent/extensions/background-terminals/index.ts +627 -627
  13. package/agent/extensions/background-terminals/install.ps1 +27 -27
  14. package/agent/extensions/background-terminals/install.sh +24 -24
  15. package/agent/extensions/background-terminals/manager.test.ts +735 -735
  16. package/agent/extensions/background-terminals/output.test.ts +109 -109
  17. package/agent/extensions/background-terminals/package-lock.json +769 -769
  18. package/agent/extensions/background-terminals/package.json +17 -17
  19. package/agent/extensions/background-terminals/prompt.test.ts +125 -125
  20. package/agent/extensions/background-terminals/ps.test.ts +82 -82
  21. package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
  22. package/agent/extensions/background-terminals/src/domain.ts +87 -87
  23. package/agent/extensions/background-terminals/src/manager.ts +907 -907
  24. package/agent/extensions/background-terminals/src/output.ts +84 -84
  25. package/agent/extensions/background-terminals/src/prompt.ts +142 -142
  26. package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
  27. package/agent/extensions/background-terminals/src/runtime.ts +36 -36
  28. package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
  29. package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
  30. package/agent/extensions/background-terminals/tsconfig.json +7 -7
  31. package/agent/extensions/destructive/README.md +31 -31
  32. package/agent/extensions/destructive/index.ts +88 -88
  33. package/agent/extensions/destructive/install.ps1 +27 -27
  34. package/agent/extensions/destructive/install.sh +24 -24
  35. package/agent/extensions/file-search/index.spec.ts +443 -443
  36. package/agent/extensions/file-search/index.ts +459 -459
  37. package/agent/extensions/file-search/install.ps1 +27 -27
  38. package/agent/extensions/file-search/install.sh +24 -24
  39. package/agent/extensions/file-search/package-lock.json +2253 -2253
  40. package/agent/extensions/file-search/package.json +23 -23
  41. package/agent/extensions/file-search/src/args.ts +122 -122
  42. package/agent/extensions/file-search/src/binaries.ts +422 -422
  43. package/agent/extensions/file-search/src/output.ts +126 -126
  44. package/agent/extensions/file-search/src/process.ts +146 -146
  45. package/agent/extensions/file-search/src/prompt.ts +52 -52
  46. package/agent/extensions/file-search/tsconfig.json +7 -7
  47. package/agent/extensions/goal/README.md +50 -50
  48. package/agent/extensions/goal/index.ts +155 -155
  49. package/agent/extensions/goal/install.ps1 +27 -27
  50. package/agent/extensions/goal/install.sh +24 -24
  51. package/agent/extensions/kiln-update/README.md +38 -25
  52. package/agent/extensions/kiln-update/index.ts +177 -99
  53. package/agent/extensions/modelconf/PLAN.md +915 -915
  54. package/agent/extensions/modelconf/README.md +66 -66
  55. package/agent/extensions/modelconf/index.ts +296 -296
  56. package/agent/extensions/modelconf/install.ps1 +27 -27
  57. package/agent/extensions/modelconf/install.sh +24 -24
  58. package/agent/extensions/modelconf/package-lock.json +1809 -1809
  59. package/agent/extensions/modelconf/package.json +13 -13
  60. package/agent/extensions/modelconf/src/fuzzy.ts +26 -26
  61. package/agent/extensions/modelconf/src/glob.ts +13 -13
  62. package/agent/extensions/modelconf/src/persistence.test.ts +46 -46
  63. package/agent/extensions/modelconf/src/persistence.ts +164 -164
  64. package/agent/extensions/modelconf/src/ui/ModelConfView.test.ts +75 -75
  65. package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
  66. package/agent/extensions/modelconf/tsconfig.json +15 -15
  67. package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
  68. package/agent/extensions/pi-web-access/LICENSE +21 -21
  69. package/agent/extensions/pi-web-access/README.md +470 -470
  70. package/agent/extensions/pi-web-access/SECURITY.md +5 -5
  71. package/agent/extensions/pi-web-access/activity.ts +101 -101
  72. package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
  73. package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
  74. package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
  75. package/agent/extensions/pi-web-access/content-find.ts +139 -139
  76. package/agent/extensions/pi-web-access/credential-source.ts +191 -191
  77. package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
  78. package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
  79. package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
  80. package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
  81. package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
  82. package/agent/extensions/pi-web-access/exa.ts +526 -526
  83. package/agent/extensions/pi-web-access/extract.ts +1196 -1196
  84. package/agent/extensions/pi-web-access/feature-config.ts +29 -29
  85. package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
  86. package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
  87. package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
  88. package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
  89. package/agent/extensions/pi-web-access/gemini-search.ts +21 -21
  90. package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
  91. package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
  92. package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
  93. package/agent/extensions/pi-web-access/github-api.ts +197 -197
  94. package/agent/extensions/pi-web-access/github-extract.ts +746 -746
  95. package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
  96. package/agent/extensions/pi-web-access/index.ts +1737 -1737
  97. package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
  98. package/agent/extensions/pi-web-access/package.json +64 -64
  99. package/agent/extensions/pi-web-access/page-query.ts +96 -96
  100. package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
  101. package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
  102. package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
  103. package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
  104. package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
  105. package/agent/extensions/pi-web-access/search-types.ts +20 -20
  106. package/agent/extensions/pi-web-access/source-check.ts +282 -282
  107. package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
  108. package/agent/extensions/pi-web-access/storage.ts +521 -521
  109. package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
  110. package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
  111. package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
  112. package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
  113. package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
  114. package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
  115. package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
  116. package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
  117. package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
  118. package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
  119. package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
  120. package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
  121. package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
  122. package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
  123. package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
  124. package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
  125. package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
  126. package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
  127. package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
  128. package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
  129. package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
  130. package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
  131. package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
  132. package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
  133. package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
  134. package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
  135. package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
  136. package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
  137. package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
  138. package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
  139. package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
  140. package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
  141. package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
  142. package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
  143. package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
  144. package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
  145. package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
  146. package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
  147. package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
  148. package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
  149. package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
  150. package/agent/extensions/pi-web-access/test/summary-model-scope.test.mjs +106 -106
  151. package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
  152. package/agent/extensions/pi-web-access/test/web-search-answer-render.test.mjs +66 -66
  153. package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
  154. package/agent/extensions/pi-web-access/tsconfig.json +11 -11
  155. package/agent/extensions/pi-web-access/utils.ts +451 -451
  156. package/agent/extensions/pi-web-access/video-extract.ts +392 -392
  157. package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
  158. package/agent/extensions/shared/activity-status.ts +31 -31
  159. package/agent/extensions/shared/child-session.test.ts +270 -270
  160. package/agent/extensions/shared/child-session.ts +148 -148
  161. package/agent/extensions/shared/context-utilization.test.ts +48 -48
  162. package/agent/extensions/shared/context-utilization.ts +47 -47
  163. package/agent/extensions/shared/dashboard-state.ts +99 -99
  164. package/agent/extensions/shared/install.ps1 +27 -27
  165. package/agent/extensions/shared/install.sh +24 -24
  166. package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
  167. package/agent/extensions/shared/tool-call-timeout.ts +104 -104
  168. package/agent/extensions/skillsconf/README.md +74 -74
  169. package/agent/extensions/skillsconf/index.ts +92 -92
  170. package/agent/extensions/skillsconf/install.ps1 +27 -27
  171. package/agent/extensions/skillsconf/install.sh +24 -24
  172. package/agent/extensions/skillsconf/package-lock.json +1809 -1809
  173. package/agent/extensions/skillsconf/package.json +18 -18
  174. package/agent/extensions/skillsconf/src/delete-skill.test.ts +64 -64
  175. package/agent/extensions/skillsconf/src/delete-skill.ts +45 -45
  176. package/agent/extensions/skillsconf/src/filter.test.ts +80 -80
  177. package/agent/extensions/skillsconf/src/filter.ts +74 -74
  178. package/agent/extensions/skillsconf/src/fuzzy.ts +26 -26
  179. package/agent/extensions/skillsconf/src/persistence.test.ts +72 -72
  180. package/agent/extensions/skillsconf/src/persistence.ts +169 -169
  181. package/agent/extensions/skillsconf/src/ui/SkillConfView.test.ts +337 -337
  182. package/agent/extensions/skillsconf/src/ui/SkillConfView.ts +758 -758
  183. package/agent/extensions/skillsconf/src/ui/text-input.ts +91 -91
  184. package/agent/extensions/skillsconf/src/ui/tui-helpers.ts +144 -144
  185. package/agent/extensions/skillsconf/tsconfig.json +15 -15
  186. package/agent/extensions/statusline/index.ts +273 -282
  187. package/agent/extensions/statusline/install.ps1 +27 -27
  188. package/agent/extensions/statusline/install.sh +24 -24
  189. package/agent/extensions/subagents/by-the-way.test.ts +29 -29
  190. package/agent/extensions/subagents/claude.test.ts +119 -119
  191. package/agent/extensions/subagents/codex.test.ts +102 -102
  192. package/agent/extensions/subagents/context-usage.test.ts +107 -107
  193. package/agent/extensions/subagents/docs/design-plan.md +568 -568
  194. package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
  195. package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
  196. package/agent/extensions/subagents/index.ts +779 -779
  197. package/agent/extensions/subagents/install.ps1 +27 -27
  198. package/agent/extensions/subagents/install.sh +24 -24
  199. package/agent/extensions/subagents/manager.test.ts +276 -276
  200. package/agent/extensions/subagents/package-lock.json +2244 -2244
  201. package/agent/extensions/subagents/package.json +19 -19
  202. package/agent/extensions/subagents/result-delivery.test.ts +27 -27
  203. package/agent/extensions/subagents/src/backend.ts +73 -73
  204. package/agent/extensions/subagents/src/backends/claude.ts +701 -701
  205. package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
  206. package/agent/extensions/subagents/src/backends/pi.ts +575 -575
  207. package/agent/extensions/subagents/src/backends/stub.ts +300 -300
  208. package/agent/extensions/subagents/src/by-the-way.ts +21 -21
  209. package/agent/extensions/subagents/src/domain.ts +253 -253
  210. package/agent/extensions/subagents/src/format.ts +74 -74
  211. package/agent/extensions/subagents/src/manager.ts +736 -736
  212. package/agent/extensions/subagents/src/prompt.ts +92 -92
  213. package/agent/extensions/subagents/src/result-delivery.ts +20 -20
  214. package/agent/extensions/subagents/src/runtime.ts +53 -53
  215. package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
  216. package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
  217. package/agent/extensions/subagents/takeover.test.ts +29 -29
  218. package/agent/extensions/subagents/tsconfig.json +7 -7
  219. package/agent/extensions/todo/AGENTS.md +38 -38
  220. package/agent/extensions/todo/LICENSE +21 -21
  221. package/agent/extensions/todo/config.ts +55 -55
  222. package/agent/extensions/todo/index.ts +151 -151
  223. package/agent/extensions/todo/install.ps1 +27 -27
  224. package/agent/extensions/todo/install.sh +24 -24
  225. package/agent/extensions/todo/locales/de.json +17 -17
  226. package/agent/extensions/todo/locales/en.json +15 -15
  227. package/agent/extensions/todo/locales/es.json +17 -17
  228. package/agent/extensions/todo/locales/fr.json +17 -17
  229. package/agent/extensions/todo/locales/pt-BR.json +17 -17
  230. package/agent/extensions/todo/locales/pt.json +17 -17
  231. package/agent/extensions/todo/locales/ru.json +17 -17
  232. package/agent/extensions/todo/locales/uk.json +17 -17
  233. package/agent/extensions/todo/locales/zh.json +17 -17
  234. package/agent/extensions/todo/package-lock.json +3358 -3358
  235. package/agent/extensions/todo/package.json +67 -67
  236. package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
  237. package/agent/extensions/todo/state/invariants.ts +20 -20
  238. package/agent/extensions/todo/state/replay.ts +38 -38
  239. package/agent/extensions/todo/state/selectors.ts +107 -107
  240. package/agent/extensions/todo/state/state-reducer.ts +326 -326
  241. package/agent/extensions/todo/state/state.ts +18 -18
  242. package/agent/extensions/todo/state/store.ts +82 -82
  243. package/agent/extensions/todo/state/task-graph.ts +57 -57
  244. package/agent/extensions/todo/todo-overlay.ts +200 -200
  245. package/agent/extensions/todo/todo.ts +155 -155
  246. package/agent/extensions/todo/tool/response-envelope.ts +109 -109
  247. package/agent/extensions/todo/tool/types.ts +206 -206
  248. package/agent/extensions/todo/view/format.ts +177 -177
  249. package/agent/extensions/trim-context/README.md +54 -54
  250. package/agent/extensions/trim-context/index.ts +487 -487
  251. package/agent/extensions/trim-context/install.ps1 +27 -27
  252. package/agent/extensions/trim-context/install.sh +24 -24
  253. package/agent/keybindings.json +7 -7
  254. package/agent/version.txt +1 -1
  255. package/bin/kiln.js +30 -21
  256. package/package.json +1 -1
  257. package/agent/extensions/taste/index.ts +0 -443
  258. package/agent/extensions/taste/install.ps1 +0 -27
  259. package/agent/extensions/taste/install.sh +0 -24
  260. package/agent.bak.20260905-190020/AGENTS.md +0 -67
  261. package/agent.bak.20260905-190020/README.md +0 -5
  262. package/agent.bak.20260905-190020/extensions/ask-user/index.ts +0 -418
  263. package/agent.bak.20260905-190020/extensions/ask-user/install.ps1 +0 -23
  264. package/agent.bak.20260905-190020/extensions/ask-user/install.sh +0 -21
  265. package/agent.bak.20260905-190020/extensions/ask-user/package-lock.json +0 -769
  266. package/agent.bak.20260905-190020/extensions/ask-user/package.json +0 -19
  267. package/agent.bak.20260905-190020/extensions/ask-user/prompt.ts +0 -45
  268. package/agent.bak.20260905-190020/extensions/ask-user/tsconfig.json +0 -7
  269. package/agent.bak.20260905-190020/extensions/background-terminals/docs/implementation-guide.md +0 -942
  270. package/agent.bak.20260905-190020/extensions/background-terminals/index.ts +0 -629
  271. package/agent.bak.20260905-190020/extensions/background-terminals/install.ps1 +0 -23
  272. package/agent.bak.20260905-190020/extensions/background-terminals/install.sh +0 -21
  273. package/agent.bak.20260905-190020/extensions/background-terminals/package-lock.json +0 -769
  274. package/agent.bak.20260905-190020/extensions/background-terminals/package.json +0 -17
  275. package/agent.bak.20260905-190020/extensions/background-terminals/src/domain.ts +0 -87
  276. package/agent.bak.20260905-190020/extensions/background-terminals/src/manager.ts +0 -907
  277. package/agent.bak.20260905-190020/extensions/background-terminals/src/output.ts +0 -84
  278. package/agent.bak.20260905-190020/extensions/background-terminals/src/prompt.ts +0 -142
  279. package/agent.bak.20260905-190020/extensions/background-terminals/src/result-delivery.ts +0 -27
  280. package/agent.bak.20260905-190020/extensions/background-terminals/src/runtime.ts +0 -36
  281. package/agent.bak.20260905-190020/extensions/background-terminals/src/ui/output-view.ts +0 -79
  282. package/agent.bak.20260905-190020/extensions/background-terminals/src/ui/ps.ts +0 -621
  283. package/agent.bak.20260905-190020/extensions/background-terminals/src/widget-order.ts +0 -121
  284. package/agent.bak.20260905-190020/extensions/background-terminals/test/manager.test.ts +0 -735
  285. package/agent.bak.20260905-190020/extensions/background-terminals/test/output.test.ts +0 -109
  286. package/agent.bak.20260905-190020/extensions/background-terminals/test/prompt.test.ts +0 -125
  287. package/agent.bak.20260905-190020/extensions/background-terminals/test/ps.test.ts +0 -82
  288. package/agent.bak.20260905-190020/extensions/background-terminals/test/result-delivery.test.ts +0 -44
  289. package/agent.bak.20260905-190020/extensions/background-terminals/tsconfig.json +0 -7
  290. package/agent.bak.20260905-190020/extensions/destructive/README.md +0 -34
  291. package/agent.bak.20260905-190020/extensions/destructive/index.ts +0 -124
  292. package/agent.bak.20260905-190020/extensions/destructive/install.ps1 +0 -23
  293. package/agent.bak.20260905-190020/extensions/destructive/install.sh +0 -21
  294. package/agent.bak.20260905-190020/extensions/file-search/index.ts +0 -459
  295. package/agent.bak.20260905-190020/extensions/file-search/install.ps1 +0 -23
  296. package/agent.bak.20260905-190020/extensions/file-search/install.sh +0 -21
  297. package/agent.bak.20260905-190020/extensions/file-search/package-lock.json +0 -2253
  298. package/agent.bak.20260905-190020/extensions/file-search/package.json +0 -23
  299. package/agent.bak.20260905-190020/extensions/file-search/src/args.ts +0 -122
  300. package/agent.bak.20260905-190020/extensions/file-search/src/binaries.ts +0 -422
  301. package/agent.bak.20260905-190020/extensions/file-search/src/output.ts +0 -126
  302. package/agent.bak.20260905-190020/extensions/file-search/src/process.ts +0 -146
  303. package/agent.bak.20260905-190020/extensions/file-search/src/prompt.ts +0 -52
  304. package/agent.bak.20260905-190020/extensions/file-search/test/index.spec.ts +0 -443
  305. package/agent.bak.20260905-190020/extensions/file-search/tsconfig.json +0 -7
  306. package/agent.bak.20260905-190020/extensions/goal/README.md +0 -50
  307. package/agent.bak.20260905-190020/extensions/goal/index.ts +0 -155
  308. package/agent.bak.20260905-190020/extensions/goal/install.ps1 +0 -23
  309. package/agent.bak.20260905-190020/extensions/goal/install.sh +0 -21
  310. package/agent.bak.20260905-190020/extensions/kiln-update/README.md +0 -25
  311. package/agent.bak.20260905-190020/extensions/kiln-update/index.ts +0 -99
  312. package/agent.bak.20260905-190020/extensions/kiln-update/install.ps1 +0 -23
  313. package/agent.bak.20260905-190020/extensions/kiln-update/install.sh +0 -21
  314. package/agent.bak.20260905-190020/extensions/modelconf/PLAN.md +0 -915
  315. package/agent.bak.20260905-190020/extensions/modelconf/README.md +0 -66
  316. package/agent.bak.20260905-190020/extensions/modelconf/index.ts +0 -296
  317. package/agent.bak.20260905-190020/extensions/modelconf/install.ps1 +0 -23
  318. package/agent.bak.20260905-190020/extensions/modelconf/install.sh +0 -21
  319. package/agent.bak.20260905-190020/extensions/modelconf/package-lock.json +0 -1809
  320. package/agent.bak.20260905-190020/extensions/modelconf/package.json +0 -13
  321. package/agent.bak.20260905-190020/extensions/modelconf/src/fuzzy.ts +0 -26
  322. package/agent.bak.20260905-190020/extensions/modelconf/src/glob.ts +0 -13
  323. package/agent.bak.20260905-190020/extensions/modelconf/src/persistence.test.ts +0 -46
  324. package/agent.bak.20260905-190020/extensions/modelconf/src/persistence.ts +0 -164
  325. package/agent.bak.20260905-190020/extensions/modelconf/src/ui/ModelConfView.test.ts +0 -75
  326. package/agent.bak.20260905-190020/extensions/modelconf/src/ui/ModelConfView.ts +0 -1101
  327. package/agent.bak.20260905-190020/extensions/modelconf/tsconfig.json +0 -15
  328. package/agent.bak.20260905-190020/extensions/pi-web-access/CHANGELOG.md +0 -690
  329. package/agent.bak.20260905-190020/extensions/pi-web-access/LICENSE +0 -21
  330. package/agent.bak.20260905-190020/extensions/pi-web-access/README.md +0 -470
  331. package/agent.bak.20260905-190020/extensions/pi-web-access/SECURITY.md +0 -5
  332. package/agent.bak.20260905-190020/extensions/pi-web-access/activity.ts +0 -101
  333. package/agent.bak.20260905-190020/extensions/pi-web-access/auth-fetch.ts +0 -148
  334. package/agent.bak.20260905-190020/extensions/pi-web-access/banner.png +0 -0
  335. package/agent.bak.20260905-190020/extensions/pi-web-access/brightdata-unlocker.ts +0 -272
  336. package/agent.bak.20260905-190020/extensions/pi-web-access/chrome-cookies.ts +0 -669
  337. package/agent.bak.20260905-190020/extensions/pi-web-access/content-find.ts +0 -139
  338. package/agent.bak.20260905-190020/extensions/pi-web-access/credential-source.ts +0 -191
  339. package/agent.bak.20260905-190020/extensions/pi-web-access/data-uri-sanitize.ts +0 -406
  340. package/agent.bak.20260905-190020/extensions/pi-web-access/datalab-pdf-extract.ts +0 -568
  341. package/agent.bak.20260905-190020/extensions/pi-web-access/declared-web-links.ts +0 -173
  342. package/agent.bak.20260905-190020/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +0 -496
  343. package/agent.bak.20260905-190020/extensions/pi-web-access/evidence/contract-probe.mjs +0 -140
  344. package/agent.bak.20260905-190020/extensions/pi-web-access/exa.ts +0 -526
  345. package/agent.bak.20260905-190020/extensions/pi-web-access/extract.ts +0 -1196
  346. package/agent.bak.20260905-190020/extensions/pi-web-access/feature-config.ts +0 -29
  347. package/agent.bak.20260905-190020/extensions/pi-web-access/fetch-params.ts +0 -111
  348. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-adc.ts +0 -298
  349. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-api.ts +0 -353
  350. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-pdf-extract.ts +0 -108
  351. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-search.ts +0 -21
  352. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-url-context.ts +0 -128
  353. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-web-config.ts +0 -101
  354. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-web.ts +0 -487
  355. package/agent.bak.20260905-190020/extensions/pi-web-access/github-api.ts +0 -197
  356. package/agent.bak.20260905-190020/extensions/pi-web-access/github-extract.ts +0 -746
  357. package/agent.bak.20260905-190020/extensions/pi-web-access/github-issue-pr.ts +0 -700
  358. package/agent.bak.20260905-190020/extensions/pi-web-access/index.ts +0 -1737
  359. package/agent.bak.20260905-190020/extensions/pi-web-access/package-lock.json +0 -5808
  360. package/agent.bak.20260905-190020/extensions/pi-web-access/package.json +0 -64
  361. package/agent.bak.20260905-190020/extensions/pi-web-access/page-query.ts +0 -96
  362. package/agent.bak.20260905-190020/extensions/pi-web-access/pdf-extract.ts +0 -409
  363. package/agent.bak.20260905-190020/extensions/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
  364. package/agent.bak.20260905-190020/extensions/pi-web-access/promise-try.d.ts +0 -7
  365. package/agent.bak.20260905-190020/extensions/pi-web-access/query-rewrite.ts +0 -51
  366. package/agent.bak.20260905-190020/extensions/pi-web-access/render-search-error.ts +0 -170
  367. package/agent.bak.20260905-190020/extensions/pi-web-access/rsc-extract.ts +0 -338
  368. package/agent.bak.20260905-190020/extensions/pi-web-access/search-types.ts +0 -20
  369. package/agent.bak.20260905-190020/extensions/pi-web-access/source-check.ts +0 -282
  370. package/agent.bak.20260905-190020/extensions/pi-web-access/ssrf-protection.ts +0 -526
  371. package/agent.bak.20260905-190020/extensions/pi-web-access/storage.ts +0 -521
  372. package/agent.bak.20260905-190020/extensions/pi-web-access/summary-model-scope.ts +0 -125
  373. package/agent.bak.20260905-190020/extensions/pi-web-access/test/auth-fetch.test.mjs +0 -208
  374. package/agent.bak.20260905-190020/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +0 -840
  375. package/agent.bak.20260905-190020/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +0 -441
  376. package/agent.bak.20260905-190020/extensions/pi-web-access/test/config-path.test.mjs +0 -283
  377. package/agent.bak.20260905-190020/extensions/pi-web-access/test/content-find.test.mjs +0 -25
  378. package/agent.bak.20260905-190020/extensions/pi-web-access/test/credential-source.test.mjs +0 -118
  379. package/agent.bak.20260905-190020/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +0 -210
  380. package/agent.bak.20260905-190020/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +0 -552
  381. package/agent.bak.20260905-190020/extensions/pi-web-access/test/declared-web-links.test.mjs +0 -212
  382. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +0 -40
  383. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +0 -334
  384. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +0 -95
  385. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-modes.test.mjs +0 -53
  386. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +0 -92
  387. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-params.test.mjs +0 -86
  388. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-render-call.test.mjs +0 -34
  389. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-routing.test.mjs +0 -173
  390. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +0 -257
  391. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-api-transport.test.mjs +0 -170
  392. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +0 -133
  393. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +0 -178
  394. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +0 -148
  395. package/agent.bak.20260905-190020/extensions/pi-web-access/test/get-search-content.test.mjs +0 -223
  396. package/agent.bak.20260905-190020/extensions/pi-web-access/test/github-extract.test.mjs +0 -378
  397. package/agent.bak.20260905-190020/extensions/pi-web-access/test/github-issue-pr.test.mjs +0 -565
  398. package/agent.bak.20260905-190020/extensions/pi-web-access/test/inline-content-config.test.mjs +0 -99
  399. package/agent.bak.20260905-190020/extensions/pi-web-access/test/lazy-extract-load.test.mjs +0 -118
  400. package/agent.bak.20260905-190020/extensions/pi-web-access/test/local-video-oversize.test.mjs +0 -52
  401. package/agent.bak.20260905-190020/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +0 -50
  402. package/agent.bak.20260905-190020/extensions/pi-web-access/test/page-query.test.mjs +0 -51
  403. package/agent.bak.20260905-190020/extensions/pi-web-access/test/pdf-config.test.mjs +0 -140
  404. package/agent.bak.20260905-190020/extensions/pi-web-access/test/pdf-extract.test.mjs +0 -500
  405. package/agent.bak.20260905-190020/extensions/pi-web-access/test/proxy-transport.test.mjs +0 -286
  406. package/agent.bak.20260905-190020/extensions/pi-web-access/test/query-rewrite.test.mjs +0 -52
  407. package/agent.bak.20260905-190020/extensions/pi-web-access/test/rsc-fallback.test.mjs +0 -102
  408. package/agent.bak.20260905-190020/extensions/pi-web-access/test/search-error-render.test.mjs +0 -152
  409. package/agent.bak.20260905-190020/extensions/pi-web-access/test/search-providers.test.mjs +0 -274
  410. package/agent.bak.20260905-190020/extensions/pi-web-access/test/source-check.test.mjs +0 -179
  411. package/agent.bak.20260905-190020/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +0 -205
  412. package/agent.bak.20260905-190020/extensions/pi-web-access/test/ssrf-protection.test.mjs +0 -456
  413. package/agent.bak.20260905-190020/extensions/pi-web-access/test/summary-model-scope.test.mjs +0 -106
  414. package/agent.bak.20260905-190020/extensions/pi-web-access/test/tool-registration-config.test.mjs +0 -182
  415. package/agent.bak.20260905-190020/extensions/pi-web-access/test/web-search-answer-render.test.mjs +0 -66
  416. package/agent.bak.20260905-190020/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +0 -64
  417. package/agent.bak.20260905-190020/extensions/pi-web-access/tsconfig.json +0 -11
  418. package/agent.bak.20260905-190020/extensions/pi-web-access/utils.ts +0 -451
  419. package/agent.bak.20260905-190020/extensions/pi-web-access/video-extract.ts +0 -392
  420. package/agent.bak.20260905-190020/extensions/pi-web-access/youtube-extract.ts +0 -328
  421. package/agent.bak.20260905-190020/extensions/shared/activity-status.ts +0 -31
  422. package/agent.bak.20260905-190020/extensions/shared/child-session.ts +0 -148
  423. package/agent.bak.20260905-190020/extensions/shared/context-utilization.ts +0 -47
  424. package/agent.bak.20260905-190020/extensions/shared/dashboard-state.ts +0 -99
  425. package/agent.bak.20260905-190020/extensions/shared/install.ps1 +0 -23
  426. package/agent.bak.20260905-190020/extensions/shared/install.sh +0 -21
  427. package/agent.bak.20260905-190020/extensions/shared/test/child-session.test.ts +0 -270
  428. package/agent.bak.20260905-190020/extensions/shared/test/context-utilization.test.ts +0 -48
  429. package/agent.bak.20260905-190020/extensions/shared/test/tool-call-timeout.test.ts +0 -117
  430. package/agent.bak.20260905-190020/extensions/shared/test/widget-order.test.ts +0 -167
  431. package/agent.bak.20260905-190020/extensions/shared/tool-call-timeout.ts +0 -104
  432. package/agent.bak.20260905-190020/extensions/skillsconf/README.md +0 -74
  433. package/agent.bak.20260905-190020/extensions/skillsconf/index.ts +0 -92
  434. package/agent.bak.20260905-190020/extensions/skillsconf/install.ps1 +0 -23
  435. package/agent.bak.20260905-190020/extensions/skillsconf/install.sh +0 -21
  436. package/agent.bak.20260905-190020/extensions/skillsconf/package-lock.json +0 -1809
  437. package/agent.bak.20260905-190020/extensions/skillsconf/package.json +0 -18
  438. package/agent.bak.20260905-190020/extensions/skillsconf/src/delete-skill.test.ts +0 -64
  439. package/agent.bak.20260905-190020/extensions/skillsconf/src/delete-skill.ts +0 -45
  440. package/agent.bak.20260905-190020/extensions/skillsconf/src/filter.test.ts +0 -80
  441. package/agent.bak.20260905-190020/extensions/skillsconf/src/filter.ts +0 -74
  442. package/agent.bak.20260905-190020/extensions/skillsconf/src/fuzzy.ts +0 -26
  443. package/agent.bak.20260905-190020/extensions/skillsconf/src/persistence.test.ts +0 -72
  444. package/agent.bak.20260905-190020/extensions/skillsconf/src/persistence.ts +0 -169
  445. package/agent.bak.20260905-190020/extensions/skillsconf/src/ui/SkillConfView.test.ts +0 -337
  446. package/agent.bak.20260905-190020/extensions/skillsconf/src/ui/SkillConfView.ts +0 -758
  447. package/agent.bak.20260905-190020/extensions/skillsconf/src/ui/text-input.ts +0 -91
  448. package/agent.bak.20260905-190020/extensions/skillsconf/src/ui/tui-helpers.ts +0 -144
  449. package/agent.bak.20260905-190020/extensions/skillsconf/tsconfig.json +0 -15
  450. package/agent.bak.20260905-190020/extensions/statusline/index.ts +0 -282
  451. package/agent.bak.20260905-190020/extensions/statusline/install.ps1 +0 -23
  452. package/agent.bak.20260905-190020/extensions/statusline/install.sh +0 -21
  453. package/agent.bak.20260905-190020/extensions/subagents/docs/design-plan.md +0 -568
  454. package/agent.bak.20260905-190020/extensions/subagents/docs/effect-v4-extension-guide.md +0 -354
  455. package/agent.bak.20260905-190020/extensions/subagents/docs/effect-v4-notes.md +0 -571
  456. package/agent.bak.20260905-190020/extensions/subagents/index.ts +0 -826
  457. package/agent.bak.20260905-190020/extensions/subagents/install.ps1 +0 -23
  458. package/agent.bak.20260905-190020/extensions/subagents/install.sh +0 -21
  459. package/agent.bak.20260905-190020/extensions/subagents/package-lock.json +0 -2244
  460. package/agent.bak.20260905-190020/extensions/subagents/package.json +0 -19
  461. package/agent.bak.20260905-190020/extensions/subagents/src/backend.ts +0 -73
  462. package/agent.bak.20260905-190020/extensions/subagents/src/backends/claude.ts +0 -701
  463. package/agent.bak.20260905-190020/extensions/subagents/src/backends/codex.ts +0 -1060
  464. package/agent.bak.20260905-190020/extensions/subagents/src/backends/pi.ts +0 -575
  465. package/agent.bak.20260905-190020/extensions/subagents/src/backends/stub.ts +0 -300
  466. package/agent.bak.20260905-190020/extensions/subagents/src/by-the-way.ts +0 -21
  467. package/agent.bak.20260905-190020/extensions/subagents/src/domain.ts +0 -253
  468. package/agent.bak.20260905-190020/extensions/subagents/src/format.ts +0 -74
  469. package/agent.bak.20260905-190020/extensions/subagents/src/manager.ts +0 -736
  470. package/agent.bak.20260905-190020/extensions/subagents/src/prompt.ts +0 -92
  471. package/agent.bak.20260905-190020/extensions/subagents/src/result-delivery.ts +0 -20
  472. package/agent.bak.20260905-190020/extensions/subagents/src/runtime.ts +0 -53
  473. package/agent.bak.20260905-190020/extensions/subagents/src/ui/takeover.ts +0 -583
  474. package/agent.bak.20260905-190020/extensions/subagents/src/ui/transcript.ts +0 -201
  475. package/agent.bak.20260905-190020/extensions/subagents/src/widget-order.ts +0 -121
  476. package/agent.bak.20260905-190020/extensions/subagents/test/by-the-way.test.ts +0 -29
  477. package/agent.bak.20260905-190020/extensions/subagents/test/claude.test.ts +0 -119
  478. package/agent.bak.20260905-190020/extensions/subagents/test/codex.test.ts +0 -102
  479. package/agent.bak.20260905-190020/extensions/subagents/test/context-usage.test.ts +0 -107
  480. package/agent.bak.20260905-190020/extensions/subagents/test/manager.test.ts +0 -276
  481. package/agent.bak.20260905-190020/extensions/subagents/test/result-delivery.test.ts +0 -27
  482. package/agent.bak.20260905-190020/extensions/subagents/test/takeover.test.ts +0 -29
  483. package/agent.bak.20260905-190020/extensions/subagents/tsconfig.json +0 -7
  484. package/agent.bak.20260905-190020/extensions/taste/index.ts +0 -443
  485. package/agent.bak.20260905-190020/extensions/taste/install.ps1 +0 -23
  486. package/agent.bak.20260905-190020/extensions/taste/install.sh +0 -21
  487. package/agent.bak.20260905-190020/extensions/todo/AGENTS.md +0 -38
  488. package/agent.bak.20260905-190020/extensions/todo/LICENSE +0 -21
  489. package/agent.bak.20260905-190020/extensions/todo/config.ts +0 -55
  490. package/agent.bak.20260905-190020/extensions/todo/index.ts +0 -151
  491. package/agent.bak.20260905-190020/extensions/todo/install.ps1 +0 -23
  492. package/agent.bak.20260905-190020/extensions/todo/install.sh +0 -21
  493. package/agent.bak.20260905-190020/extensions/todo/locales/de.json +0 -17
  494. package/agent.bak.20260905-190020/extensions/todo/locales/en.json +0 -15
  495. package/agent.bak.20260905-190020/extensions/todo/locales/es.json +0 -17
  496. package/agent.bak.20260905-190020/extensions/todo/locales/fr.json +0 -17
  497. package/agent.bak.20260905-190020/extensions/todo/locales/pt-BR.json +0 -17
  498. package/agent.bak.20260905-190020/extensions/todo/locales/pt.json +0 -17
  499. package/agent.bak.20260905-190020/extensions/todo/locales/ru.json +0 -17
  500. package/agent.bak.20260905-190020/extensions/todo/locales/uk.json +0 -17
  501. package/agent.bak.20260905-190020/extensions/todo/locales/zh.json +0 -17
  502. package/agent.bak.20260905-190020/extensions/todo/package-lock.json +0 -3358
  503. package/agent.bak.20260905-190020/extensions/todo/package.json +0 -67
  504. package/agent.bak.20260905-190020/extensions/todo/state/i18n-bridge.ts +0 -64
  505. package/agent.bak.20260905-190020/extensions/todo/state/invariants.ts +0 -20
  506. package/agent.bak.20260905-190020/extensions/todo/state/replay.ts +0 -38
  507. package/agent.bak.20260905-190020/extensions/todo/state/selectors.ts +0 -107
  508. package/agent.bak.20260905-190020/extensions/todo/state/state-reducer.ts +0 -326
  509. package/agent.bak.20260905-190020/extensions/todo/state/state.ts +0 -18
  510. package/agent.bak.20260905-190020/extensions/todo/state/store.ts +0 -82
  511. package/agent.bak.20260905-190020/extensions/todo/state/task-graph.ts +0 -57
  512. package/agent.bak.20260905-190020/extensions/todo/todo-overlay.ts +0 -204
  513. package/agent.bak.20260905-190020/extensions/todo/todo.ts +0 -155
  514. package/agent.bak.20260905-190020/extensions/todo/tool/response-envelope.ts +0 -109
  515. package/agent.bak.20260905-190020/extensions/todo/tool/types.ts +0 -206
  516. package/agent.bak.20260905-190020/extensions/todo/view/format.ts +0 -177
  517. package/agent.bak.20260905-190020/extensions/todo/widget-order.ts +0 -121
  518. package/agent.bak.20260905-190020/extensions/trim-context/README.md +0 -54
  519. package/agent.bak.20260905-190020/extensions/trim-context/index.ts +0 -487
  520. package/agent.bak.20260905-190020/extensions/trim-context/install.ps1 +0 -23
  521. package/agent.bak.20260905-190020/extensions/trim-context/install.sh +0 -21
  522. package/agent.bak.20260905-190020/keybindings.json +0 -7
@@ -1,1737 +0,0 @@
1
- import type { AgentToolResult, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { Box, Text, truncateToWidth, type KeyId } from "@earendil-works/pi-tui";
3
- import { Type } from "typebox";
4
- import { StringEnum, type ImageContent, type TextContent } from "@earendil-works/pi-ai/compat";
5
- import type { ExtractedContent, ExtractOptions } from "./extract.ts";
6
- import { normalizeFetchContentParams } from "./fetch-params.ts";
7
- import { resolveAuthFetchProfile, type AuthFetchProfile } from "./auth-fetch.ts";
8
- import { findContent, type FindMode } from "./content-find.ts";
9
- import { answerFromPage } from "./page-query.ts";
10
- import { clearCloneCache } from "./github-extract.ts";
11
- import { search, type ProviderAvailability } from "./gemini-search.ts";
12
- export type { ProviderAvailability } from "./gemini-search.ts";
13
- import type { SearchResult } from "./search-types.ts";
14
- import { formatSeconds, getWebSearchConfigPath, installGlobalProxyFetch, runWithProxy } from "./utils.ts";
15
- import {
16
- clearResults,
17
- deleteResult,
18
- generateId,
19
- getAllResults,
20
- getResult,
21
- restoreFromSession,
22
- storeFetchedContentResult,
23
- storeResult,
24
- type QueryResultData,
25
- type StoredSearchData,
26
- } from "./storage.ts";
27
- import { activityMonitor, type ActivityEntry } from "./activity.ts";
28
- import { existsSync, readFileSync } from "node:fs";
29
- import { isExaAvailable } from "./exa.ts";
30
- import { getActiveGoogleEmail, getGeminiWebAvailabilityDiagnostic, getGeminiWebAvailabilityDiagnosticDetails, isGeminiWebAvailable } from "./gemini-web.ts";
31
- import { isBrowserCookieAccessAllowed } from "./gemini-web-config.ts";
32
- import { buildSearchErrorPlan, type SearchErrorDetails, type SearchErrorPlan } from "./render-search-error.ts";
33
- import {
34
- buildResearchArtifact,
35
- withClaimAssessment,
36
- storeResearchArtifact,
37
- getResearchArtifact,
38
- type RecencyFilter,
39
- type ResearchArtifact,
40
- } from "./source-check.ts";
41
-
42
- type ExtensionTheme = ExtensionContext["ui"]["theme"];
43
-
44
- const WEB_SEARCH_CONFIG_PATH = getWebSearchConfigPath();
45
-
46
- let extractModulePromise: Promise<typeof import("./extract.ts")> | undefined;
47
- async function fetchAllContent(
48
- urls: string[],
49
- signal?: AbortSignal,
50
- options?: ExtractOptions,
51
- ): Promise<ExtractedContent[]> {
52
- const extractModule = await (extractModulePromise ??= import("./extract.ts"));
53
- return extractModule.fetchAllContent(urls, signal, options);
54
- }
55
-
56
- function withRegisteredFetchOptions(
57
- options: ExtractOptions | undefined,
58
- toolNames: ExtractOptions["toolNames"],
59
- proxy?: string,
60
- ): ExtractOptions {
61
- return {
62
- ...(options ?? {}),
63
- toolNames,
64
- ...(proxy !== undefined ? { proxy } : {}),
65
- };
66
- }
67
-
68
- function isAbortError(err: unknown): boolean {
69
- return (err instanceof Error ? err.message : String(err)).toLowerCase().includes("abort");
70
- }
71
-
72
- /** Shared collapsed/expanded renderer for an error/cancel plan produced by
73
- * buildSearchErrorPlan(). Used by every tool renderResult's error branch so
74
- * Ctrl+O (app.tools.expand) reveals diagnostics instead of a dead-end single line. */
75
- function renderSearchErrorPlan(plan: SearchErrorPlan, expanded: boolean, theme: ExtensionTheme) {
76
- if (expanded) {
77
- return new Text(plan.expanded.map((l, i) => i === 0 ? theme.fg("error", l) : theme.fg("toolOutput", l)).join("\n"), 0, 0);
78
- }
79
- const box = new Box(1, 0, (t) => theme.bg("toolErrorBg", t));
80
- box.addChild(new Text(theme.fg("error", plan.expanded[0]), 0, 0));
81
- for (const line of plan.collapsed) {
82
- box.addChild(new Text(theme.fg("dim", line), 0, 0));
83
- }
84
- if (plan.expandHint) {
85
- box.addChild(new Text(theme.fg("muted", plan.expandHint), 0, 0));
86
- }
87
- return box;
88
- }
89
-
90
- interface WebSearchConfig {
91
- anysearchApiKey?: unknown;
92
- xcrawlApiKey?: unknown;
93
- brightdataApiKey?: unknown;
94
- brightdataSerpZone?: unknown;
95
- kagiApiKey?: unknown;
96
- ollamaApiKey?: unknown;
97
- serpbaseApiKey?: unknown;
98
- serperApiKey?: unknown;
99
- tinyfishApiKey?: unknown;
100
- valyuApiKey?: unknown;
101
- xaiApiKey?: unknown;
102
- maxInlineContentChars?: unknown;
103
- webSearch?: {
104
- enabled?: boolean;
105
- };
106
- tools?: Partial<Record<keyof ToolNames, { enabled?: boolean }>>;
107
- commands?: Partial<Record<"websearch" | "search" | "google-account", { enabled?: boolean }>>;
108
- toolNames?: Partial<ToolNames>;
109
- shortcuts?: {
110
- activity?: KeyId;
111
- };
112
- ssrf?: {
113
- /** CIDR ranges exempted from the SSRF guard (e.g. fake-IP proxy ranges). */
114
- allowRanges?: string[];
115
- /** Skip local hostname DNS preflight when an HTTP(S)_PROXY env var applies. */
116
- trustEnvProxy?: boolean;
117
- };
118
- }
119
-
120
- function parseConfigRoot(raw: string): Record<string, unknown> {
121
- let parsed: unknown;
122
- try {
123
- parsed = JSON.parse(raw);
124
- } catch (err) {
125
- const message = err instanceof Error ? err.message : String(err);
126
- throw new Error(`Failed to parse ${WEB_SEARCH_CONFIG_PATH}: ${message}`);
127
- }
128
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
129
- throw new Error(`Invalid config in ${WEB_SEARCH_CONFIG_PATH}: expected a JSON object`);
130
- }
131
- return parsed as Record<string, unknown>;
132
- }
133
-
134
- function loadConfig(): WebSearchConfig {
135
- if (!existsSync(WEB_SEARCH_CONFIG_PATH)) return {};
136
- return parseConfigRoot(readFileSync(WEB_SEARCH_CONFIG_PATH, "utf-8")) as WebSearchConfig;
137
- }
138
-
139
- type ToolNames = {
140
- webSearch: string;
141
- sourceCheck: string;
142
- fetchContent: string;
143
- getSearchContent: string;
144
- };
145
-
146
- const DEFAULT_TOOL_NAMES: ToolNames = {
147
- webSearch: "web_search",
148
- sourceCheck: "source_check",
149
- fetchContent: "fetch_content",
150
- getSearchContent: "get_search_content",
151
- };
152
- const TOOL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
153
- const DEFAULT_SHORTCUTS = { activity: "ctrl+shift+w" } satisfies Record<string, KeyId>;
154
-
155
- function isToolEnabled(config: WebSearchConfig, key: keyof ToolNames): boolean {
156
- const override = config.tools?.[key]?.enabled;
157
- if (typeof override === "boolean") return override;
158
- return key !== "webSearch" && key !== "sourceCheck" || config.webSearch?.enabled !== false;
159
- }
160
-
161
- function isCommandEnabled(config: WebSearchConfig, name: "websearch" | "search" | "google-account"): boolean {
162
- return config.commands?.[name]?.enabled !== false;
163
- }
164
-
165
- function joinToolNames(names: string[]): string {
166
- if (names.length === 0) return "stored content";
167
- if (names.length === 1) return names[0];
168
- if (names.length === 2) return `${names[0]} or ${names[1]}`;
169
- return `${names.slice(0, -1).join(", ")}, or ${names[names.length - 1]}`;
170
- }
171
-
172
- function resolveToolNames(config: WebSearchConfig): ToolNames {
173
- if (config.toolNames !== undefined && (!config.toolNames || typeof config.toolNames !== "object" || Array.isArray(config.toolNames))) {
174
- throw new Error(`toolNames in ${WEB_SEARCH_CONFIG_PATH} must be an object`);
175
- }
176
- const names = { ...DEFAULT_TOOL_NAMES };
177
- for (const key of Object.keys(DEFAULT_TOOL_NAMES) as Array<keyof ToolNames>) {
178
- const value = config.toolNames?.[key];
179
- if (value === undefined) continue;
180
- if (typeof value !== "string") throw new Error(`toolNames.${key} in ${WEB_SEARCH_CONFIG_PATH} must be a string`);
181
- const trimmed = value.trim();
182
- if (!TOOL_NAME_PATTERN.test(trimmed)) {
183
- throw new Error(`toolNames.${key} in ${WEB_SEARCH_CONFIG_PATH} must start with a letter and contain only letters, numbers, underscores, or hyphens`);
184
- }
185
- names[key] = trimmed;
186
- }
187
- const registeredKeys = (Object.keys(DEFAULT_TOOL_NAMES) as Array<keyof ToolNames>)
188
- .filter(key => isToolEnabled(config, key));
189
- const seen = new Map<string, keyof ToolNames>();
190
- for (const key of registeredKeys) {
191
- const name = names[key];
192
- const previous = seen.get(name);
193
- if (previous) throw new Error(`toolNames.${key} duplicates toolNames.${previous} in ${WEB_SEARCH_CONFIG_PATH}`);
194
- seen.set(name, key);
195
- }
196
- return names;
197
- }
198
-
199
- function loadConfigForExtensionInit(): WebSearchConfig {
200
- try {
201
- return loadConfig();
202
- } catch (err) {
203
- const message = err instanceof Error ? err.message : String(err);
204
- console.error(`[pi-web-access] ${message}`);
205
- return {};
206
- }
207
- }
208
-
209
- function normalizeRecencyFilter(value: unknown): RecencyFilter | undefined {
210
- return value === "day" || value === "week" || value === "month" || value === "year"
211
- ? value
212
- : undefined;
213
- }
214
-
215
- function normalizeQueryList(queryList: unknown[]): string[] {
216
- const normalized: string[] = [];
217
- for (const query of queryList) {
218
- if (typeof query !== "string") continue;
219
- const trimmed = query.trim();
220
- if (trimmed.length > 0) normalized.push(trimmed);
221
- }
222
- return normalized;
223
- }
224
-
225
- async function getProviderAvailability(): Promise<ProviderAvailability> {
226
- const exa = isExaAvailable();
227
- return { all: exa, exa };
228
- }
229
-
230
- const pendingFetches = new Map<string, AbortController>();
231
- let sessionActive = false;
232
- let widgetVisible = false;
233
- let widgetUnsubscribe: (() => void) | null = null;
234
-
235
-
236
- const DEFAULT_MAX_INLINE_CONTENT_CHARS = 30_000;
237
- const MAX_INLINE_CONTENT_CHARS = 200_000;
238
-
239
- function getMaxInlineContentChars(config = loadConfig()): number {
240
- const value = config.maxInlineContentChars;
241
- if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
242
- return DEFAULT_MAX_INLINE_CONTENT_CHARS;
243
- }
244
- return Math.min(value, MAX_INLINE_CONTENT_CHARS);
245
- }
246
-
247
- function stripThumbnails(results: ExtractedContent[]): ExtractedContent[] {
248
- return results.map(({ thumbnail, frames, ...rest }) => rest);
249
- }
250
-
251
- function storeFetchResult(pi: { appendEntry(type: string, data: unknown): void }, responseId: string, data: StoredSearchData & { type: "fetch"; urls: ExtractedContent[] }, authProfile?: AuthFetchProfile): boolean {
252
- if (authProfile?.cache === "off") return false;
253
- pi.appendEntry("web-search-results", storeFetchedContentResult(responseId, data));
254
- return true;
255
- }
256
-
257
- function initialContentSlice(content: string, maxChars: number): {
258
- text: string;
259
- endOffset: number;
260
- totalBytes: number;
261
- totalLines: number;
262
- shownBytes: number;
263
- shownLines: number;
264
- } {
265
- let endOffset = Math.min(content.length, maxChars);
266
- if (endOffset < content.length) {
267
- const lineBreak = content.lastIndexOf("\n", endOffset);
268
- if (lineBreak >= Math.floor(maxChars * 0.8)) endOffset = lineBreak + 1;
269
- }
270
- const text = content.slice(0, endOffset);
271
- return {
272
- text,
273
- endOffset,
274
- totalBytes: Buffer.byteLength(content),
275
- totalLines: content.length === 0 ? 0 : content.split("\n").length,
276
- shownBytes: Buffer.byteLength(text),
277
- shownLines: text.length === 0 ? 0 : text.split("\n").length,
278
- };
279
- }
280
-
281
- function normalizeFindQueries(value: string | string[]): string[] {
282
- const queries = (Array.isArray(value) ? value : [value]).map(query => query.trim()).filter(Boolean);
283
- if (queries.length === 0) throw new Error("findText must contain at least one non-empty string");
284
- return queries;
285
- }
286
-
287
- interface GetSearchContentParams {
288
- responseId: string;
289
- query?: string;
290
- queryIndex?: number;
291
- url?: string;
292
- urlIndex?: number;
293
- offset?: number;
294
- limit?: number;
295
- findText?: string | string[];
296
- findMode?: FindMode;
297
- }
298
-
299
- type RawGetSearchContentParams = Omit<GetSearchContentParams, "findMode"> & { findMode?: unknown };
300
-
301
- function normalizeFindMode(value: unknown): FindMode | undefined {
302
- if (value === undefined) return undefined;
303
- if (value === "exact" || value === "case-insensitive" || value === "fuzzy") return value;
304
- throw new Error('findMode must be "exact", "case-insensitive", or "fuzzy"');
305
- }
306
-
307
- function normalizeGetSearchContentParams(params: RawGetSearchContentParams): GetSearchContentParams {
308
- const normalized: GetSearchContentParams = { ...params, findMode: normalizeFindMode(params.findMode) };
309
-
310
- if (normalized.query?.trim() === "") delete normalized.query;
311
- if (normalized.url?.trim() === "") delete normalized.url;
312
-
313
- if (normalized.findText !== undefined) {
314
- delete normalized.offset;
315
- delete normalized.limit;
316
- }
317
-
318
- return normalized;
319
- }
320
-
321
- function formatInputValue(value: unknown): string {
322
- if (typeof value === "string") return JSON.stringify(value);
323
- if (typeof value === "number") return Number.isNaN(value) ? "NaN" : String(value);
324
- try {
325
- const serialized = JSON.stringify(value);
326
- return serialized === undefined ? String(value) : serialized;
327
- } catch {
328
- return String(value);
329
- }
330
- }
331
-
332
- function formatSearchSummary(results: SearchResult[], answer: string): string {
333
- if (results.length === 0) {
334
- return answer ? `${answer}\n\n---\n\n**Sources:**\nNo sources returned.` : "No results found.";
335
- }
336
- let output = answer ? `${answer}\n\n---\n\n**Sources:**\n` : "";
337
- output += results.map((r, i) => `${i + 1}. ${r.title}\n ${r.url}`).join("\n\n");
338
- return output;
339
- }
340
-
341
- function formatSourceCheckResult(artifact: ResearchArtifact, getSearchContentTool: string | null = DEFAULT_TOOL_NAMES.getSearchContent): string {
342
- const assessment = artifact.claims?.[0];
343
- const lines = [`# Source check: ${artifact.query}`, ""];
344
- if (assessment) {
345
- lines.push(`**Status:** ${assessment.status} (confidence ${assessment.confidence.toFixed(2)})`);
346
- lines.push(`**Rationale:** ${assessment.rationale}`);
347
- if (assessment.supporting_passages.length > 0) lines.push(`**Supporting passages:** ${assessment.supporting_passages.join(", ")}`);
348
- if (assessment.contradicting_passages.length > 0) lines.push(`**Contradicting passages:** ${assessment.contradicting_passages.join(", ")}`);
349
- lines.push("");
350
- }
351
- if (artifact.sources.length > 0) {
352
- lines.push("## Sources");
353
- for (const source of artifact.sources) lines.push(`${source.rank}. [${source.quality}] ${source.title}\n ${source.url}`);
354
- lines.push("");
355
- }
356
- if (artifact.errors?.length) lines.push(`Search errors: ${artifact.errors.map((entry) => `${entry.query}: ${entry.error}`).join("; ")}`);
357
- lines.push(getSearchContentTool
358
- ? `Artifact responseId: ${artifact.id} (retrievable via ${getSearchContentTool}).`
359
- : `Artifact responseId: ${artifact.id}. Content retrieval is not registered.`);
360
- return lines.join("\n");
361
- }
362
-
363
- function hasFullInlineCoverage(urls: string[], inlineContent: ExtractedContent[] | undefined): boolean {
364
- if (!inlineContent || inlineContent.length === 0) return false;
365
- const coveredUrls = new Set(inlineContent.map(c => c.url));
366
- return urls.every(url => coveredUrls.has(url));
367
- }
368
-
369
- function formatFullResults(queryData: QueryResultData): string {
370
- let output = `## Results for: "${queryData.query}"\n\n`;
371
- if (queryData.answer) {
372
- output += `${queryData.answer}\n\n---\n\n`;
373
- }
374
- for (const r of queryData.results) {
375
- output += `### ${r.title}\n${r.url}\n\n`;
376
- }
377
- return output;
378
- }
379
-
380
- function abortPendingFetches(): void {
381
- for (const controller of pendingFetches.values()) {
382
- controller.abort();
383
- }
384
- pendingFetches.clear();
385
- }
386
-
387
- function updateWidget(ctx: ExtensionContext): void {
388
- const theme = ctx.ui.theme;
389
- const entries = activityMonitor.getEntries();
390
- const lines: string[] = [];
391
-
392
- lines.push(theme.fg("accent", "─── Web Search Activity " + "─".repeat(36)));
393
-
394
- if (entries.length === 0) {
395
- lines.push(theme.fg("muted", " No activity yet"));
396
- } else {
397
- for (const e of entries) {
398
- lines.push(" " + formatEntryLine(e, theme));
399
- }
400
- }
401
-
402
- lines.push(theme.fg("accent", "─".repeat(60)));
403
-
404
- const rateInfo = activityMonitor.getRateLimitInfo();
405
- const resetMs = rateInfo.oldestTimestamp ? Math.max(0, rateInfo.oldestTimestamp + rateInfo.windowMs - Date.now()) : 0;
406
- const resetSec = Math.ceil(resetMs / 1000);
407
- lines.push(
408
- theme.fg("muted", `Rate: ${rateInfo.used}/${rateInfo.max}`) +
409
- (resetMs > 0 ? theme.fg("dim", ` (resets in ${resetSec}s)`) : ""),
410
- );
411
-
412
- ctx.ui.setWidget("web-activity", lines);
413
- }
414
-
415
- function formatEntryLine(
416
- entry: ActivityEntry,
417
- theme: ExtensionTheme,
418
- ): string {
419
- const typeStr = entry.type === "api" ? "API" : "GET";
420
- const target =
421
- entry.type === "api"
422
- ? `"${truncateToWidth(entry.query || "", 28, "")}"`
423
- : truncateToWidth(entry.url?.replace(/^https?:\/\//, "") || "", 30, "");
424
-
425
- const duration = entry.endTime
426
- ? `${((entry.endTime - entry.startTime) / 1000).toFixed(1)}s`
427
- : `${((Date.now() - entry.startTime) / 1000).toFixed(1)}s`;
428
-
429
- let statusStr: string;
430
- let indicator: string;
431
- if (entry.error) {
432
- statusStr = "err";
433
- indicator = theme.fg("error", "✗");
434
- } else if (entry.status === null) {
435
- statusStr = "...";
436
- indicator = theme.fg("warning", "⋯");
437
- } else if (entry.status === 0) {
438
- statusStr = "abort";
439
- indicator = theme.fg("muted", "○");
440
- } else {
441
- statusStr = String(entry.status);
442
- indicator = entry.status >= 200 && entry.status < 300 ? theme.fg("success", "✓") : theme.fg("error", "✗");
443
- }
444
-
445
- return `${typeStr.padEnd(4)} ${target.padEnd(32)} ${statusStr.padStart(5)} ${duration.padStart(5)} ${indicator}`;
446
- }
447
-
448
- function handleSessionChange(ctx: ExtensionContext): void {
449
- abortPendingFetches();
450
- clearCloneCache();
451
- sessionActive = true;
452
- restoreFromSession(ctx);
453
- // Unsubscribe before clear() to avoid callback with stale ctx
454
- widgetUnsubscribe?.();
455
- widgetUnsubscribe = null;
456
- activityMonitor.clear();
457
- if (widgetVisible) {
458
- // Re-subscribe with new ctx
459
- widgetUnsubscribe = activityMonitor.onUpdate(() => updateWidget(ctx));
460
- updateWidget(ctx);
461
- }
462
- }
463
-
464
- export default function (pi: ExtensionAPI) {
465
- const initConfig = loadConfigForExtensionInit();
466
- installGlobalProxyFetch();
467
- const toolNames = resolveToolNames(initConfig);
468
- const webSearchEnabled = isToolEnabled(initConfig, "webSearch");
469
- const sourceCheckEnabled = isToolEnabled(initConfig, "sourceCheck");
470
- const fetchContentEnabled = isToolEnabled(initConfig, "fetchContent");
471
- const getSearchContentEnabled = isToolEnabled(initConfig, "getSearchContent");
472
- // Names as registered this session, so fetch failure guidance never points
473
- // at tools that are disabled or were renamed after init.
474
- const registeredToolNames = {
475
- ...(webSearchEnabled ? { webSearch: toolNames.webSearch } : {}),
476
- ...(fetchContentEnabled ? { fetchContent: toolNames.fetchContent } : {}),
477
- };
478
- const storedContentSources = joinToolNames([
479
- ...(webSearchEnabled ? [toolNames.webSearch] : []),
480
- ...(sourceCheckEnabled ? [toolNames.sourceCheck] : []),
481
- ...(fetchContentEnabled ? [toolNames.fetchContent] : []),
482
- ]);
483
- const searchQueryDescription = webSearchEnabled
484
- ? `Get content for this query (${toolNames.webSearch})`
485
- : "Get content for a stored search query";
486
- const fetchContentStorageNote = getSearchContentEnabled
487
- ? `Full original content is stored for retrieval with ${toolNames.getSearchContent}.`
488
- : "Full original content is stored internally, but the retrieval tool is not registered.";
489
- const activityKey = initConfig.shortcuts?.activity || DEFAULT_SHORTCUTS.activity;
490
-
491
- function startBackgroundFetch(urls: string[], proxy?: string): string | null {
492
- if (urls.length === 0) return null;
493
- const fetchId = generateId();
494
- const controller = new AbortController();
495
- pendingFetches.set(fetchId, controller);
496
- runWithProxy(proxy, () => fetchAllContent(urls, controller.signal, withRegisteredFetchOptions(undefined, registeredToolNames, proxy)))
497
- .then((fetched) => {
498
- if (!sessionActive || !pendingFetches.has(fetchId)) return;
499
- const data = {
500
- id: fetchId,
501
- type: "fetch",
502
- timestamp: Date.now(),
503
- urls: stripThumbnails(fetched),
504
- } satisfies StoredSearchData & { type: "fetch"; urls: ExtractedContent[] };
505
- pi.appendEntry("web-search-results", storeFetchedContentResult(fetchId, data));
506
- const ok = fetched.filter(f => !f.error).length;
507
- const availability = ok === fetched.length
508
- ? "Full page content now available."
509
- : ok > 0
510
- ? "Partial page content now available."
511
- : "No page content was fetched. Stored fetch diagnostics are available.";
512
- pi.sendMessage(
513
- {
514
- customType: "web-search-content-ready",
515
- content: `Content fetched for ${ok}/${fetched.length} URLs [${fetchId}]. ${availability}`,
516
- display: true,
517
- },
518
- { triggerTurn: true },
519
- );
520
- })
521
- .catch((err) => {
522
- if (!sessionActive || !pendingFetches.has(fetchId)) return;
523
- const message = err instanceof Error ? err.message : String(err);
524
- const isAbort = (err instanceof Error && err.name === "AbortError") || message.toLowerCase().includes("abort");
525
- if (!isAbort) {
526
- pi.sendMessage(
527
- {
528
- customType: "web-search-error",
529
- content: `Content fetch failed [${fetchId}]: ${message}`,
530
- display: true,
531
- },
532
- { triggerTurn: false },
533
- );
534
- }
535
- })
536
- .finally(() => { pendingFetches.delete(fetchId); });
537
- return fetchId;
538
- }
539
-
540
- function storeAndPublishSearch(results: QueryResultData[]): string {
541
- const id = generateId();
542
- const data: StoredSearchData = {
543
- id, type: "search", timestamp: Date.now(), queries: results,
544
- };
545
- storeResult(id, data);
546
- pi.appendEntry("web-search-results", data);
547
- return id;
548
- }
549
-
550
- interface SearchReturnOptions {
551
- queryList: string[];
552
- results: QueryResultData[];
553
- urls: string[];
554
- includeContent: boolean;
555
- inlineContent?: ExtractedContent[];
556
- proxy?: string;
557
- }
558
-
559
-
560
- function buildSearchReturn(opts: SearchReturnOptions): AgentToolResult<Record<string, unknown>> {
561
- const sc = opts.results.filter(r => !r.error).length;
562
- const tr = opts.results.reduce((sum, r) => sum + r.results.length, 0);
563
-
564
- let output = "";
565
- for (const { query, answer, results, error } of opts.results) {
566
- if (opts.queryList.length > 1) {
567
- output += `## Query: "${query}"\n\n`;
568
- }
569
- if (error) output += `Error: ${error}\n\n`;
570
- else output += formatSearchSummary(results, answer) + "\n\n";
571
- }
572
-
573
- const hasInlineReady = hasFullInlineCoverage(opts.urls, opts.inlineContent);
574
- let fetchId: string | null = null;
575
- if (hasInlineReady && opts.inlineContent) {
576
- fetchId = generateId();
577
- const data = {
578
- id: fetchId,
579
- type: "fetch",
580
- timestamp: Date.now(),
581
- urls: opts.inlineContent,
582
- } satisfies StoredSearchData & { type: "fetch"; urls: ExtractedContent[] };
583
- pi.appendEntry("web-search-results", storeFetchedContentResult(fetchId, data));
584
- output += `---\nFull content for ${opts.inlineContent.length} sources available [${fetchId}].`;
585
- } else if (opts.includeContent) {
586
- fetchId = startBackgroundFetch(opts.urls, opts.proxy);
587
- if (fetchId) {
588
- output += `---\nContent fetching in background [${fetchId}]. Will notify when ready.`;
589
- }
590
- }
591
-
592
- const searchId = storeAndPublishSearch(opts.results);
593
- const isBackgroundFetch = fetchId !== null && !hasInlineReady;
594
-
595
- return {
596
- content: [{ type: "text", text: output.trim() }],
597
- details: {
598
- queries: opts.queryList,
599
- queryCount: opts.queryList.length,
600
- successfulQueries: sc,
601
- totalResults: tr,
602
- includeContent: opts.includeContent,
603
- fetchId,
604
- fetchUrls: isBackgroundFetch ? opts.urls : undefined,
605
- searchId,
606
- },
607
- };
608
- }
609
-
610
-
611
- pi.registerShortcut(activityKey, {
612
- description: "Toggle web search activity",
613
- handler: async (ctx) => {
614
- widgetVisible = !widgetVisible;
615
- if (widgetVisible) {
616
- widgetUnsubscribe = activityMonitor.onUpdate(() => updateWidget(ctx));
617
- updateWidget(ctx);
618
- } else {
619
- widgetUnsubscribe?.();
620
- widgetUnsubscribe = null;
621
- ctx.ui.setWidget("web-activity", undefined);
622
- }
623
- },
624
- });
625
-
626
- pi.on("session_start", async (_event, ctx) => handleSessionChange(ctx));
627
- pi.on("session_tree", async (_event, ctx) => handleSessionChange(ctx));
628
-
629
- pi.on("session_shutdown", () => {
630
- sessionActive = false;
631
- abortPendingFetches();
632
- clearCloneCache();
633
- clearResults();
634
- // Unsubscribe before clear() to avoid callback with stale ctx
635
- widgetUnsubscribe?.();
636
- widgetUnsubscribe = null;
637
- activityMonitor.clear();
638
- widgetVisible = false;
639
- });
640
-
641
- if (webSearchEnabled) pi.registerTool({
642
- name: toolNames.webSearch,
643
- label: "Web Search",
644
- description:
645
- `Search the web using Exa. Returns an AI-synthesized answer with source citations. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background.`,
646
- promptSnippet:
647
- "Use for web research questions. Prefer {queries:[...]} with 2-4 varied angles over a single query for broader coverage.",
648
- parameters: Type.Object({
649
- query: Type.Optional(Type.String({ description: "Single search query. For research tasks, prefer 'queries' with multiple varied angles instead." })),
650
- queries: Type.Optional(Type.Array(Type.String(), { description: "Multiple queries searched in sequence, each returning its own synthesized answer. Prefer this for research — vary phrasing, scope, and angle across 2-4 queries to maximize coverage. Good: ['React vs Vue performance benchmarks 2026', 'React vs Vue developer experience comparison', 'React ecosystem size vs Vue ecosystem']. Bad: ['React vs Vue', 'React vs Vue comparison', 'React vs Vue review'] (too similar, redundant results)." })),
651
- numResults: Type.Optional(Type.Integer({ minimum: 1, maximum: 20, description: "Results per query (default: 5, max: 20)" })),
652
- includeContent: Type.Optional(Type.Boolean({ description: "Fetch full page content (async)" })),
653
- recencyFilter: Type.Optional(
654
- StringEnum(["day", "week", "month", "year"], { description: "Filter by recency" }),
655
- ),
656
- domainFilter: Type.Optional(Type.Array(Type.String(), { description: "Limit to domains (prefix with - to exclude)" })),
657
- proxy: Type.Optional(Type.String({
658
- description: "http(s) proxy URL (e.g. http://host:port) used for every outbound request in this call (search APIs and content fetches). Node fetch ignores HTTP(S)_PROXY env vars, so set this (or `proxy` in web-search.json) when direct access is blocked; empty string forces direct access.",
659
- })),
660
- }),
661
-
662
- async execute(callId, params, signal, onUpdate, ctx) {
663
- return runWithProxy(typeof params.proxy === "string" ? params.proxy : undefined, async () => {
664
- const rawQueryList: unknown[] = Array.isArray(params.queries)
665
- ? params.queries
666
- : (params.query !== undefined ? [params.query] : []);
667
- const queryList = normalizeQueryList(rawQueryList);
668
- const recencyFilter = normalizeRecencyFilter(params.recencyFilter);
669
-
670
- if (queryList.length === 0) {
671
- return {
672
- content: [{ type: "text", text: "Error: No query provided. Use 'query' or 'queries' parameter." }],
673
- details: { error: "No query provided" },
674
- };
675
- }
676
-
677
- const searchResults: QueryResultData[] = [];
678
- const allUrls: string[] = [];
679
- const allInlineContent: ExtractedContent[] = [];
680
-
681
- for (let i = 0; i < queryList.length; i++) {
682
- const query = queryList[i];
683
-
684
- onUpdate?.({
685
- content: [{ type: "text", text: `Searching ${i + 1}/${queryList.length}: "${query}"...` }],
686
- details: { phase: "search", progress: i / queryList.length, currentQuery: query },
687
- });
688
-
689
- try {
690
- const { answer, results, inlineContent, provider } = await search(query, {
691
- numResults: params.numResults,
692
- recencyFilter,
693
- domainFilter: params.domainFilter,
694
- includeContent: params.includeContent,
695
- signal,
696
- });
697
-
698
- searchResults.push({ query, answer, results, error: null, provider });
699
- for (const r of results) {
700
- if (!allUrls.includes(r.url)) {
701
- allUrls.push(r.url);
702
- }
703
- }
704
- if (inlineContent) allInlineContent.push(...inlineContent);
705
- } catch (err) {
706
- if (signal?.aborted || isAbortError(err)) throw err;
707
- const message = err instanceof Error ? err.message : String(err);
708
- searchResults.push({ query, answer: "", results: [], error: message, provider: "exa" });
709
- }
710
- }
711
-
712
- return buildSearchReturn({
713
- queryList,
714
- results: searchResults,
715
- urls: allUrls,
716
- includeContent: params.includeContent ?? false,
717
- inlineContent: allInlineContent.length > 0 ? allInlineContent : undefined,
718
- proxy: typeof params.proxy === "string" ? params.proxy : undefined,
719
- });
720
- });
721
- },
722
-
723
- renderCall(args, theme) {
724
- const input = args as { query?: unknown; queries?: unknown };
725
- const rawQueryList: unknown[] = Array.isArray(input.queries)
726
- ? input.queries
727
- : (input.query !== undefined ? [input.query] : []);
728
- const queryList = normalizeQueryList(rawQueryList);
729
- if (queryList.length === 0) {
730
- return new Text(theme.fg("toolTitle", theme.bold("search ")) + theme.fg("error", "(no query)"), 0, 0);
731
- }
732
- if (queryList.length === 1) {
733
- const q = queryList[0];
734
- const display = q.length > 60 ? q.slice(0, 57) + "..." : q;
735
- return new Text(theme.fg("toolTitle", theme.bold("search ")) + theme.fg("accent", `"${display}"`), 0, 0);
736
- }
737
- const lines = [theme.fg("toolTitle", theme.bold("search ")) + theme.fg("accent", `${queryList.length} queries`)];
738
- for (const q of queryList.slice(0, 5)) {
739
- const display = q.length > 50 ? q.slice(0, 47) + "..." : q;
740
- lines.push(theme.fg("muted", ` "${display}"`));
741
- }
742
- if (queryList.length > 5) {
743
- lines.push(theme.fg("muted", ` ... and ${queryList.length - 5} more`));
744
- }
745
- return new Text(lines.join("\n"), 0, 0);
746
- },
747
-
748
- renderResult(result, { expanded, isPartial }, theme) {
749
- const details = result.details as {
750
- queryCount?: number;
751
- successfulQueries?: number;
752
- totalResults?: number;
753
- error?: string;
754
- fetchId?: string;
755
- fetchUrls?: string[];
756
- phase?: string;
757
- progress?: number;
758
- currentQuery?: string;
759
- cancelled?: boolean;
760
- cancelReason?: string;
761
- browserConnected?: boolean;
762
- lastHeartbeatAgeMs?: number | null;
763
- cancelledQueries?: import("./render-search-error.ts").CancelledQueryDetail[];
764
- };
765
-
766
- if (isPartial) {
767
- if (details?.phase === "searching") {
768
- const progress = details?.progress ?? 0;
769
- const bar = "\u2588".repeat(Math.floor(progress * 10)) + "\u2591".repeat(10 - Math.floor(progress * 10));
770
- const query = details?.currentQuery || "";
771
- const display = query.length > 40 ? query.slice(0, 37) + "..." : query;
772
- return new Text(theme.fg("accent", `[${bar}] ${display}`), 0, 0);
773
- }
774
- const progress = details?.progress ?? 0;
775
- const bar = "\u2588".repeat(Math.floor(progress * 10)) + "\u2591".repeat(10 - Math.floor(progress * 10));
776
- return new Text(theme.fg("accent", `[${bar}] ${details?.phase || "searching"}`), 0, 0);
777
- }
778
-
779
- if (details?.error) {
780
- // Expandable Ctrl+O diagnostics: which queries completed, per-query errors,
781
- // browser connection state, cancel reason. See render-search-error.ts.
782
- const plan = buildSearchErrorPlan(details as SearchErrorDetails);
783
- if (plan) return renderSearchErrorPlan(plan, expanded, theme);
784
- return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
785
- }
786
-
787
- let statusLine: string;
788
- const queryInfo = details?.queryCount === 1 ? "" : `${details?.successfulQueries}/${details?.queryCount} queries, `;
789
- statusLine = theme.fg("success", `${queryInfo}${details?.totalResults ?? 0} sources`);
790
- if (details?.fetchId && details?.fetchUrls) {
791
- statusLine += theme.fg("muted", ` (fetching ${details.fetchUrls.length} URLs)`);
792
- } else if (details?.fetchId) {
793
- statusLine += theme.fg("muted", " (content ready)");
794
- }
795
-
796
- // Build expanded lines first so collapsed view can reference total count
797
- const lines = [statusLine];
798
- {
799
- const textContent = result.content.find((c) => c.type === "text")?.text || "";
800
- const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
801
- for (const line of preview.split("\n")) {
802
- lines.push(theme.fg("dim", line));
803
- }
804
- }
805
-
806
- if (details?.fetchUrls && details.fetchUrls.length > 0) {
807
- lines.push(theme.fg("muted", "Fetching:"));
808
- for (const u of details.fetchUrls.slice(0, 5)) {
809
- const display = u.length > 60 ? u.slice(0, 57) + "..." : u;
810
- lines.push(theme.fg("dim", " " + display));
811
- }
812
- if (details.fetchUrls.length > 5) {
813
- lines.push(theme.fg("dim", ` ... and ${details.fetchUrls.length - 5} more`));
814
- }
815
- }
816
-
817
- const totalLines = lines.length;
818
-
819
- if (!expanded) {
820
- const box = new Box(1, 0);
821
- box.addChild(new Text(statusLine, 0, 0));
822
-
823
- let collapsedLines = 1; // statusLine
824
- {
825
- const textContent = result.content.find((c) => c.type === "text")?.text || "";
826
- const firstContentLine = textContent.split("\n").find(l => {
827
- const t = l.trim();
828
- return t && !t.startsWith("[") && !t.startsWith("#") && !t.startsWith("---");
829
- });
830
- const fallbackLine = (firstContentLine?.trim() || "").replace(/\*\*/g, "");
831
- if (fallbackLine) {
832
- const preview = fallbackLine.length > 120 ? fallbackLine.slice(0, 117) + "..." : fallbackLine;
833
- box.addChild(new Text(theme.fg("dim", preview), 0, 0));
834
- collapsedLines++;
835
- }
836
- }
837
- const moreLines = Math.max(0, totalLines - collapsedLines);
838
- if (moreLines > 0) {
839
- box.addChild(new Text(theme.fg("muted", `\n... (${moreLines} more lines, ${totalLines} total, ctrl+o to expand)`), 0, 0));
840
- }
841
- return box;
842
- }
843
-
844
- return new Text(lines.join("\n"), 0, 0);
845
- },
846
- });
847
-
848
- if (sourceCheckEnabled) pi.registerTool({
849
- name: toolNames.sourceCheck,
850
- label: "Source Check",
851
- description: "Check a claim against web sources and return a bounded machine-readable research artifact with exact passage citations.",
852
- promptSnippet: "Verify a claim with structured source evidence and passage-level citations.",
853
- parameters: Type.Object({
854
- claim: Type.String({ description: "The assertion to check against web sources." }),
855
- queries: Type.Optional(Type.Array(Type.String(), { description: "Search queries (default: the claim)." })),
856
- numResults: Type.Optional(Type.Integer({ minimum: 1, maximum: 20, description: "Results per query (default: 5, max: 20)." })),
857
- fetchContent: Type.Optional(Type.Boolean({ description: "Fetch up to 5 result pages for exact passage extraction." })),
858
- recencyFilter: Type.Optional(StringEnum(["day", "week", "month", "year"], { description: "Filter by recency." })),
859
- domainFilter: Type.Optional(Type.Array(Type.String(), { description: "Limit to domains; prefix with - to exclude." })),
860
- proxy: Type.Optional(Type.String({
861
- description: "http(s) proxy URL (e.g. http://host:port) used for every outbound request in this call (search APIs and result-page fetches). Empty string forces direct access.",
862
- })),
863
- }),
864
- async execute(_callId, params, signal, _onUpdate, ctx) {
865
- return runWithProxy(typeof params.proxy === "string" ? params.proxy : undefined, async () => {
866
- const claim = typeof params.claim === "string" ? params.claim.trim() : "";
867
- if (!claim) {
868
- return { content: [{ type: "text", text: "Error: 'claim' is required." }], details: { error: "Missing claim" } };
869
- }
870
-
871
- const requestedQueries = Array.isArray(params.queries)
872
- ? params.queries.filter((query): query is string => typeof query === "string").map((query) => query.trim()).filter(Boolean)
873
- : [];
874
- const queries = (requestedQueries.length > 0 ? requestedQueries : [claim]).slice(0, 8);
875
- const numResults = typeof params.numResults === "number" && Number.isFinite(params.numResults)
876
- ? Math.min(20, Math.max(1, Math.floor(params.numResults)))
877
- : 5;
878
- const domainFilter = Array.isArray(params.domainFilter)
879
- ? params.domainFilter.filter((domain): domain is string => typeof domain === "string")
880
- : undefined;
881
- const recencyFilter = normalizeRecencyFilter(params.recencyFilter);
882
- const resultsByUrl = new Map<string, SearchResult>();
883
- const summaries: string[] = [];
884
- const errors: Array<{ query: string; error: string }> = [];
885
- let provider: string | undefined;
886
-
887
- for (const query of queries) {
888
- if (signal?.aborted) break;
889
- try {
890
- const response = await search(query, {
891
- numResults,
892
- recencyFilter,
893
- domainFilter,
894
- signal,
895
- });
896
- if (signal?.aborted) break;
897
- provider ??= response.provider;
898
- if (response.answer) summaries.push(`${query}: ${response.answer}`);
899
- for (const result of response.results) {
900
- if (!resultsByUrl.has(result.url)) resultsByUrl.set(result.url, result);
901
- }
902
- } catch (err) {
903
- if (signal?.aborted || isAbortError(err)) break;
904
- errors.push({ query, error: err instanceof Error ? err.message : String(err) });
905
- }
906
- }
907
-
908
- const results = [...resultsByUrl.values()].slice(0, 20).map((result, index) => ({ ...result, rank: index + 1 }));
909
- let fetched: ExtractedContent[] = [];
910
- if (params.fetchContent && results.length > 0) {
911
- const urls = results.slice(0, 5).map((result) => result.url);
912
- try {
913
- fetched = await fetchAllContent(urls, signal, withRegisteredFetchOptions(undefined, registeredToolNames, typeof params.proxy === "string" ? params.proxy : undefined));
914
- } catch (err) {
915
- if (signal?.aborted || isAbortError(err)) throw err;
916
- fetched = urls.map((url) => ({ url, title: "", content: "", error: err instanceof Error ? err.message : String(err) }));
917
- }
918
- }
919
- const artifact = withClaimAssessment(buildResearchArtifact({
920
- query: claim,
921
- provider,
922
- summary: summaries.length > 0 ? summaries.join("\n\n") : undefined,
923
- results,
924
- fetched,
925
- recency: recencyFilter,
926
- domainFilter,
927
- }), [claim]);
928
- if (errors.length > 0) artifact.errors = errors;
929
- storeResearchArtifact(artifact);
930
- pi.appendEntry("web-search-results", {
931
- id: artifact.id,
932
- type: "research",
933
- timestamp: artifact.timestamp,
934
- artifact,
935
- });
936
- return {
937
- content: [{ type: "text", text: formatSourceCheckResult(artifact, getSearchContentEnabled ? toolNames.getSearchContent : null) }],
938
- details: { responseId: artifact.id, artifact, sourceCount: artifact.sources.length, passageCount: artifact.passages.length },
939
- };
940
- });
941
- },
942
- });
943
-
944
- if (fetchContentEnabled) pi.registerTool({
945
- name: toolNames.fetchContent,
946
- label: "Fetch Content",
947
- description: `Fetch URL(s) and extract readable content as markdown. Use mode "raw" for exact textual HTTP response bodies or mode "answer" with prompt to answer using only fetched content. Direct image URLs return resized image content. Supports YouTube transcripts, GitHub repositories, PDFs, and local videos. ${fetchContentStorageNote}`,
948
- promptSnippet:
949
- "Use to fetch readable or raw URL content, direct images, GitHub repos, and videos. Mode answer answers a prompt using only the fetched source.",
950
- parameters: Type.Object({
951
- url: Type.Optional(Type.String({ description: "Single URL to fetch" })),
952
- urls: Type.Optional(Type.Array(Type.String(), { description: "Multiple URLs (parallel)" })),
953
- forceClone: Type.Optional(Type.Boolean({
954
- description: "Force cloning large GitHub repositories that exceed the size threshold",
955
- })),
956
- prompt: Type.Optional(Type.String({
957
- description: "Question or instruction for video analysis, or the page-local question required by mode answer.",
958
- })),
959
- mode: Type.Optional(StringEnum(["readable", "raw", "answer"], {
960
- description: "Fetch mode: readable (default extraction), raw (exact textual HTTP body), or answer (answer prompt using only fetched content).",
961
- })),
962
- answerModel: Type.Optional(Type.String({
963
- description: "Optional provider/model-id override for mode answer. Defaults to the current Pi model.",
964
- })),
965
- timestamp: Type.Optional(Type.String({
966
- description: "Extract video frame(s) at a timestamp or time range. Single: '1:23:45', '23:45', or '85' (seconds). Range: '23:41-25:00' extracts evenly-spaced frames across that span (default 6). Use frames with ranges to control density; single+frames uses a fixed 5s interval. YouTube requires yt-dlp + ffmpeg; local videos require ffmpeg. Use a range when you know the approximate area but not the exact moment — you'll get a contact sheet to visually identify the right frame.",
967
- })),
968
- frames: Type.Optional(Type.Integer({
969
- minimum: 1,
970
- maximum: 12,
971
- description: "Number of frames to extract. Use with timestamp range for custom density, with single timestamp to get N frames at 5s intervals, or alone to sample across the entire video. Requires yt-dlp + ffmpeg for YouTube, ffmpeg for local video.",
972
- })),
973
- model: Type.Optional(Type.String({
974
- description: "Override the Gemini model for video/YouTube analysis (e.g. 'gemini-3.6-flash'). Defaults to config or gemini-3.6-flash.",
975
- })),
976
- auth: Type.Optional(Type.Union([Type.String(), Type.Boolean()], {
977
- description: "Opt into an authFetch profile for local browser-cookie fetching. Use a profile name, or true only when exactly one profile exists.",
978
- })),
979
- proxy: Type.Optional(Type.String({
980
- description: "http(s) proxy URL (e.g. http://host:port) used for this fetch. Needed when the target is unreachable directly; localhost and NO_PROXY hosts always bypass the proxy. Empty string forces direct access.",
981
- })),
982
- }),
983
-
984
- async execute(_toolCallId, params, signal, onUpdate, ctx): Promise<AgentToolResult<Record<string, unknown>>> {
985
- let normalized: ReturnType<typeof normalizeFetchContentParams>;
986
- try {
987
- normalized = normalizeFetchContentParams(params);
988
- } catch (err) {
989
- const error = err instanceof Error ? err.message : String(err);
990
- return { content: [{ type: "text", text: `Error: ${error}` }], details: { error } };
991
- }
992
- const { urlList, options } = normalized;
993
- return runWithProxy(options.proxy, async () => {
994
- const mode = options.mode ?? "readable";
995
- if (mode === "answer" && !options.prompt) {
996
- return { content: [{ type: "text", text: "Error: mode answer requires prompt." }], details: { error: "mode answer requires prompt" } };
997
- }
998
- if (mode === "raw" && (options.forceClone === true || options.timestamp || options.frames || options.prompt || options.model || options.answerModel)) {
999
- return { content: [{ type: "text", text: "Error: mode raw cannot be combined with forceClone, prompt, timestamp, frames, model, or answerModel." }], details: { error: "Incompatible raw mode options" } };
1000
- }
1001
- if (mode !== "answer" && options.answerModel) {
1002
- return { content: [{ type: "text", text: "Error: answerModel requires mode answer." }], details: { error: "answerModel requires mode answer" } };
1003
- }
1004
- if (mode === "answer" && options.model) {
1005
- return { content: [{ type: "text", text: "Error: use answerModel, not model, with mode answer." }], details: { error: "model is incompatible with mode answer" } };
1006
- }
1007
- if (mode === "answer" && options.auth !== undefined) {
1008
- return { content: [{ type: "text", text: "Error: auth cannot be combined with mode answer." }], details: { error: "auth cannot be combined with mode answer" } };
1009
- }
1010
- let authFetchProfile: AuthFetchProfile | undefined;
1011
- if (options.auth !== undefined) {
1012
- try {
1013
- authFetchProfile = resolveAuthFetchProfile(options.auth);
1014
- } catch (err) {
1015
- const error = err instanceof Error ? err.message : String(err);
1016
- return { content: [{ type: "text", text: `Error: ${error}` }], details: { error } };
1017
- }
1018
- }
1019
- if (urlList.length === 0) {
1020
- return {
1021
- content: [{ type: "text", text: "Error: No URL provided." }],
1022
- details: { error: "No URL provided" },
1023
- };
1024
- }
1025
-
1026
- onUpdate?.({
1027
- content: [{ type: "text", text: `Fetching ${urlList.length} URL(s)...` }],
1028
- details: { phase: "fetch", progress: 0 },
1029
- });
1030
-
1031
- const { answerModel: _answerModel, auth: _auth, ...extractionOptions } = options;
1032
- const fetchOptions = mode === "answer"
1033
- ? (() => {
1034
- const { prompt: _prompt, ...rest } = extractionOptions;
1035
- return { ...rest, ...(authFetchProfile ? { authFetchProfile } : {}) };
1036
- })()
1037
- : { ...extractionOptions, ...(authFetchProfile ? { authFetchProfile } : {}) };
1038
- const fetchResults = await fetchAllContent(urlList, signal, withRegisteredFetchOptions(fetchOptions, registeredToolNames, options.proxy));
1039
- const presentedResults = mode === "answer"
1040
- ? await Promise.all(fetchResults.map(async result => {
1041
- if (result.error) return result;
1042
- if (result.thumbnail || result.mimeType?.startsWith("image/")) {
1043
- return { ...result, error: "Page answer requires textual fetched content" };
1044
- }
1045
- try {
1046
- const answer = await answerFromPage({
1047
- question: options.prompt!,
1048
- pageText: result.content,
1049
- sourceUrl: result.url,
1050
- ...(options.answerModel ? { model: options.answerModel } : {}),
1051
- }, ctx, signal);
1052
- return { ...result, content: answer.text };
1053
- } catch (err) {
1054
- return { ...result, error: `Page answer failed: ${err instanceof Error ? err.message : String(err)}` };
1055
- }
1056
- }))
1057
- : fetchResults;
1058
- const successful = presentedResults.filter((r) => !r.error).length;
1059
- const totalChars = presentedResults.reduce((sum, r) => sum + r.content.length, 0);
1060
-
1061
- const responseId = generateId();
1062
- const data = {
1063
- id: responseId,
1064
- type: "fetch",
1065
- timestamp: Date.now(),
1066
- urls: stripThumbnails(fetchResults),
1067
- } satisfies StoredSearchData & { type: "fetch"; urls: ExtractedContent[] };
1068
- const storedContent = storeFetchResult(pi, responseId, data, authFetchProfile);
1069
-
1070
- if (urlList.length === 1) {
1071
- const result = presentedResults[0];
1072
- if (result.error) {
1073
- return {
1074
- content: [{ type: "text", text: `Error: ${result.error}` }],
1075
- details: { urls: urlList, urlCount: 1, successful: 0, error: result.error, ...(storedContent ? { responseId } : {}), prompt: params.prompt, timestamp: params.timestamp, frames: params.frames },
1076
- };
1077
- }
1078
-
1079
- const fullLength = result.content.length;
1080
- const slice = initialContentSlice(result.content, getMaxInlineContentChars());
1081
- const truncated = slice.endOffset < fullLength;
1082
- let output = slice.text;
1083
-
1084
- if (truncated) {
1085
- output += `\n\n---\nShowing ${slice.endOffset} of ${fullLength} chars, ${slice.shownBytes} of ${slice.totalBytes} bytes, and ${slice.shownLines} of ${slice.totalLines} lines. `;
1086
- output += storedContent
1087
- ? getSearchContentEnabled
1088
- ? `Use ${toolNames.getSearchContent}({ responseId: "${responseId}", urlIndex: 0, offset: ${slice.endOffset} }) for the next slice.`
1089
- : "Content retrieval is not registered."
1090
- : "Authenticated fetch cache is off; repeat the fetch to read more.";
1091
- }
1092
-
1093
- const content: Array<TextContent | ImageContent> = [];
1094
- if (result.frames?.length) {
1095
- for (const frame of result.frames) {
1096
- content.push({ type: "image", data: frame.data, mimeType: frame.mimeType });
1097
- content.push({ type: "text", text: `Frame at ${frame.timestamp}` });
1098
- }
1099
- } else if (result.thumbnail) {
1100
- content.push({ type: "image", data: result.thumbnail.data, mimeType: result.thumbnail.mimeType });
1101
- }
1102
- content.push({ type: "text", text: output });
1103
-
1104
- const imageCount = (result.frames?.length ?? 0) + (result.thumbnail ? 1 : 0);
1105
- return {
1106
- content,
1107
- details: {
1108
- urls: urlList,
1109
- urlCount: 1,
1110
- successful: 1,
1111
- totalChars: fullLength,
1112
- title: result.title,
1113
- ...(storedContent ? { responseId } : {}),
1114
- truncated,
1115
- hasImage: imageCount > 0,
1116
- imageCount,
1117
- prompt: params.prompt,
1118
- timestamp: params.timestamp,
1119
- frames: params.frames,
1120
- duration: result.duration,
1121
- mode,
1122
- mimeType: result.mimeType,
1123
- status: result.status,
1124
- totalBytes: slice.totalBytes,
1125
- totalLines: slice.totalLines,
1126
- shownBytes: slice.shownBytes,
1127
- shownLines: slice.shownLines,
1128
- },
1129
- };
1130
- }
1131
-
1132
- let output = "## Fetched URLs\n\n";
1133
- for (const { url, title, content, error } of presentedResults) {
1134
- if (error) {
1135
- output += `- ${url}: Error - ${error}\n`;
1136
- } else {
1137
- output += `- ${title || url} (${content.length} chars)\n`;
1138
- }
1139
- }
1140
- output += storedContent
1141
- ? getSearchContentEnabled
1142
- ? `\n---\nUse ${toolNames.getSearchContent}({ responseId: "${responseId}", urlIndex: 0 }) to retrieve bounded content slices.`
1143
- : "\n---\nContent retrieval is not registered."
1144
- : "\n---\nAuthenticated fetch cache is off; repeat the fetch to read content.";
1145
-
1146
- return {
1147
- content: [{ type: "text", text: output }],
1148
- details: { urls: urlList, urlCount: urlList.length, successful, totalChars, ...(storedContent ? { responseId } : {}) },
1149
- };
1150
- });
1151
- },
1152
-
1153
- renderCall(args, theme) {
1154
- const { urlList, options } = normalizeFetchContentParams(args);
1155
- const { prompt, timestamp, frames, model, mode, answerModel, auth } = options;
1156
- if (urlList.length === 0) {
1157
- return new Text(theme.fg("toolTitle", theme.bold("fetch ")) + theme.fg("error", "(no URL)"), 0, 0);
1158
- }
1159
- const lines: string[] = [];
1160
- if (urlList.length === 1) {
1161
- const display = urlList[0].length > 60 ? urlList[0].slice(0, 57) + "..." : urlList[0];
1162
- lines.push(theme.fg("toolTitle", theme.bold("fetch ")) + theme.fg("accent", display));
1163
- } else {
1164
- lines.push(theme.fg("toolTitle", theme.bold("fetch ")) + theme.fg("accent", `${urlList.length} URLs`));
1165
- for (const u of urlList.slice(0, 5)) {
1166
- const display = u.length > 60 ? u.slice(0, 57) + "..." : u;
1167
- lines.push(theme.fg("muted", " " + display));
1168
- }
1169
- if (urlList.length > 5) {
1170
- lines.push(theme.fg("muted", ` ... and ${urlList.length - 5} more`));
1171
- }
1172
- }
1173
- if (mode && mode !== "readable") {
1174
- lines.push(theme.fg("dim", " mode: ") + theme.fg("warning", mode));
1175
- }
1176
- if (timestamp) {
1177
- lines.push(theme.fg("dim", " timestamp: ") + theme.fg("warning", timestamp));
1178
- }
1179
- if (typeof frames === "number") {
1180
- lines.push(theme.fg("dim", " frames: ") + theme.fg("warning", String(frames)));
1181
- }
1182
- if (prompt) {
1183
- const display = prompt.length > 250 ? prompt.slice(0, 247) + "..." : prompt;
1184
- lines.push(theme.fg("dim", " prompt: ") + theme.fg("muted", `"${display}"`));
1185
- }
1186
- if (model) {
1187
- lines.push(theme.fg("dim", " model: ") + theme.fg("warning", model));
1188
- }
1189
- if (answerModel) {
1190
- lines.push(theme.fg("dim", " answer model: ") + theme.fg("warning", answerModel));
1191
- }
1192
- if (auth !== undefined) {
1193
- lines.push(theme.fg("dim", " auth: ") + theme.fg("warning", auth === true ? "true" : auth));
1194
- }
1195
- return new Text(lines.join("\n"), 0, 0);
1196
- },
1197
-
1198
- renderResult(result, { expanded, isPartial }, theme) {
1199
- const details = result.details as {
1200
- urlCount?: number;
1201
- successful?: number;
1202
- totalChars?: number;
1203
- error?: string;
1204
- title?: string;
1205
- truncated?: boolean;
1206
- responseId?: string;
1207
- phase?: string;
1208
- progress?: number;
1209
- hasImage?: boolean;
1210
- imageCount?: number;
1211
- prompt?: string;
1212
- timestamp?: string;
1213
- frames?: number;
1214
- duration?: number;
1215
- };
1216
-
1217
- if (isPartial) {
1218
- const progress = details?.progress ?? 0;
1219
- const bar = "\u2588".repeat(Math.floor(progress * 10)) + "\u2591".repeat(10 - Math.floor(progress * 10));
1220
- return new Text(theme.fg("accent", `[${bar}] ${details?.phase || "fetching"}`), 0, 0);
1221
- }
1222
-
1223
- if (details?.error) {
1224
- const fd = details as typeof details & { urls?: string[] };
1225
- const extras: string[] = [];
1226
- if (typeof fd.urlCount === "number" || typeof fd.successful === "number") {
1227
- extras.push(`urls: ${fd.successful ?? 0}/${fd.urlCount ?? 0} succeeded`);
1228
- }
1229
- if (fd.responseId) extras.push(`response id: ${fd.responseId}`);
1230
- if (fd.urls && fd.urls.length > 0) {
1231
- for (const u of fd.urls.slice(0, 8)) extras.push(` \u25b8 ${u}`);
1232
- if (fd.urls.length > 8) extras.push(` ... and ${fd.urls.length - 8} more`);
1233
- }
1234
- const plan = buildSearchErrorPlan({ error: details.error, extraLines: extras });
1235
- if (plan) return renderSearchErrorPlan(plan, expanded, theme);
1236
- return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
1237
- }
1238
-
1239
- if (details?.urlCount === 1) {
1240
- const title = details?.title || "Untitled";
1241
- const imgCount = details?.imageCount ?? (details?.hasImage ? 1 : 0);
1242
- const imageBadge = imgCount > 1
1243
- ? theme.fg("accent", ` [${imgCount} images]`)
1244
- : imgCount === 1
1245
- ? theme.fg("accent", " [image]")
1246
- : "";
1247
- let statusLine = theme.fg("success", title) + theme.fg("muted", ` (${details?.totalChars ?? 0} chars)`) + imageBadge;
1248
- if (details?.truncated) {
1249
- statusLine += theme.fg("warning", " [truncated]");
1250
- }
1251
- if (typeof details?.duration === "number") {
1252
- statusLine += theme.fg("muted", ` | ${formatSeconds(Math.floor(details.duration))} total`);
1253
- }
1254
- const textContent = result.content.find((c) => c.type === "text")?.text || "";
1255
- if (!expanded) {
1256
- const brief = textContent.length > 200 ? textContent.slice(0, 200) + "..." : textContent;
1257
- return new Text(statusLine + "\n" + theme.fg("dim", brief), 0, 0);
1258
- }
1259
- const lines = [statusLine];
1260
- if (details?.prompt) {
1261
- const display = details.prompt.length > 250 ? details.prompt.slice(0, 247) + "..." : details.prompt;
1262
- lines.push(theme.fg("dim", ` prompt: "${display}"`));
1263
- }
1264
- if (details?.timestamp) {
1265
- lines.push(theme.fg("dim", ` timestamp: ${details.timestamp}`));
1266
- }
1267
- if (typeof details?.frames === "number") {
1268
- lines.push(theme.fg("dim", ` frames: ${details.frames}`));
1269
- }
1270
- const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
1271
- lines.push(theme.fg("dim", preview));
1272
- return new Text(lines.join("\n"), 0, 0);
1273
- }
1274
-
1275
- const countColor = (details?.successful ?? 0) > 0 ? "success" : "error";
1276
- const statusLine = theme.fg(countColor, `${details?.successful}/${details?.urlCount} URLs`) + theme.fg("muted", getSearchContentEnabled ? " (content stored)" : " (content fetched)");
1277
- if (!expanded) {
1278
- return new Text(statusLine, 0, 0);
1279
- }
1280
- const textContent = result.content.find((c) => c.type === "text")?.text || "";
1281
- const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
1282
- return new Text(statusLine + "\n" + theme.fg("dim", preview), 0, 0);
1283
- },
1284
- });
1285
-
1286
- if (getSearchContentEnabled) {
1287
- const maxInlineContentChars = getMaxInlineContentChars(initConfig);
1288
- pi.registerTool({
1289
- name: toolNames.getSearchContent,
1290
- label: "Get Search Content",
1291
- description: `Retrieve bounded content slices or find matching passages in a previous ${storedContentSources} call.`,
1292
- promptSnippet:
1293
- `Use after ${storedContentSources} to retrieve stored content via responseId. Use findText to locate passages without paging through the full content.`,
1294
- parameters: Type.Object({
1295
- responseId: Type.String({ description: `The responseId from ${storedContentSources}` }),
1296
- query: Type.Optional(Type.String({ description: searchQueryDescription })),
1297
- queryIndex: Type.Optional(Type.Integer({ minimum: 0, description: "Get content for query at index" })),
1298
- url: Type.Optional(Type.String({ description: "Get content for this URL" })),
1299
- urlIndex: Type.Optional(Type.Integer({ minimum: 0, description: "Get content for URL at index" })),
1300
- offset: Type.Optional(Type.Integer({ minimum: 0, description: "Character offset for fetched URL content slices (default 0). Ignored when findText is supplied." })),
1301
- limit: Type.Optional(Type.Integer({ minimum: 1, maximum: maxInlineContentChars, description: "Maximum characters to return for fetched URL content slices (default and max are set by maxInlineContentChars). Ignored when findText is supplied." })),
1302
- findText: Type.Optional(Type.Union([
1303
- Type.String({ minLength: 1, maxLength: 500 }),
1304
- Type.Array(Type.String({ minLength: 1, maxLength: 500 }), { minItems: 1, maxItems: 10 }),
1305
- ], { description: "Text or texts to find in the selected stored content. When supplied, offset and limit are ignored." })),
1306
- findMode: Type.Optional(StringEnum(["exact", "case-insensitive", "fuzzy"], { description: "Matching mode for findText (default: case-insensitive). Requires findText." })),
1307
- }),
1308
-
1309
- async execute(_toolCallId, rawParams): Promise<AgentToolResult<Record<string, unknown>>> {
1310
- const params = normalizeGetSearchContentParams(rawParams);
1311
- if (params.findMode !== undefined && params.findText === undefined) {
1312
- return {
1313
- content: [{ type: "text", text: `findMode ${formatInputValue(params.findMode)} requires findText; provide findText or omit findMode.` }],
1314
- details: { error: "findMode requires findText" },
1315
- };
1316
- }
1317
- const data = getResult(params.responseId);
1318
- if (!data) {
1319
- return {
1320
- content: [{ type: "text", text: `Error: No stored results for responseId ${formatInputValue(params.responseId)}. Use a responseId returned by ${storedContentSources}.` }],
1321
- details: { error: "Not found", responseId: params.responseId },
1322
- };
1323
- }
1324
-
1325
- if (data.type === "research") {
1326
- const artifact = getResearchArtifact(params.responseId);
1327
- if (!artifact) {
1328
- return {
1329
- content: [{ type: "text", text: `Error: stored research artifact for responseId ${formatInputValue(params.responseId)} was not found. Use a responseId returned by ${storedContentSources}.` }],
1330
- details: { error: "Artifact not found", responseId: params.responseId },
1331
- };
1332
- }
1333
- const serialized = JSON.stringify(artifact, null, 2);
1334
- if (params.findText !== undefined) {
1335
- try {
1336
- const found = findContent(serialized, normalizeFindQueries(params.findText), params.findMode ?? "case-insensitive");
1337
- const { text, ...findDetails } = found;
1338
- return {
1339
- content: [{ type: "text", text }],
1340
- details: { responseId: artifact.id, type: "research", contentLength: serialized.length, findMode: params.findMode ?? "case-insensitive", ...findDetails },
1341
- };
1342
- } catch (err) {
1343
- const error = err instanceof Error ? err.message : String(err);
1344
- return {
1345
- content: [{ type: "text", text: `Unable to find ${formatInputValue(params.findText)} in research artifact for responseId ${formatInputValue(params.responseId)}: ${error}. Check findText and use a supported findMode.` }],
1346
- details: { error, responseId: params.responseId, type: "research" },
1347
- };
1348
- }
1349
- }
1350
- const offset = params.offset ?? 0;
1351
- const limit = params.limit ?? maxInlineContentChars;
1352
- if (!Number.isInteger(offset) || offset < 0) {
1353
- return {
1354
- content: [{ type: "text", text: `Invalid offset: received ${formatInputValue(offset)} for responseId ${formatInputValue(params.responseId)}; offset must be a non-negative integer. Use 0 or a larger integer.` }],
1355
- details: { error: "Invalid offset", offset },
1356
- };
1357
- }
1358
- if (!Number.isInteger(limit) || limit <= 0 || limit > maxInlineContentChars) {
1359
- return {
1360
- content: [{ type: "text", text: `Invalid limit: received ${formatInputValue(limit)} for responseId ${formatInputValue(params.responseId)}; limit must be an integer from 1 to ${maxInlineContentChars}. Use a value in that range.` }],
1361
- details: { error: "Invalid limit", limit, maxLimit: maxInlineContentChars },
1362
- };
1363
- }
1364
- if (offset > serialized.length) {
1365
- return {
1366
- content: [{ type: "text", text: `Offset ${offset} is out of range for responseId ${formatInputValue(params.responseId)}. Received offset ${offset}; valid range is 0-${serialized.length}. Use an offset within that range.` }],
1367
- details: { error: "Offset out of range", offset, contentLength: serialized.length },
1368
- };
1369
- }
1370
- const endOffset = Math.min(offset + limit, serialized.length);
1371
- const artifactSlice = serialized.slice(offset, endOffset);
1372
- const hasMore = endOffset < serialized.length;
1373
- return {
1374
- content: [{ type: "text", text: artifactSlice }],
1375
- details: { responseId: artifact.id, type: "research", contentLength: serialized.length, offset, limit, returnedChars: artifactSlice.length, nextOffset: hasMore ? endOffset : null, truncated: hasMore },
1376
- };
1377
- }
1378
-
1379
- if (data.type === "search" && data.queries) {
1380
- let queryData: QueryResultData | undefined;
1381
-
1382
- if (params.query !== undefined) {
1383
- queryData = data.queries.find((q) => q.query === params.query);
1384
- if (!queryData) {
1385
- const available = data.queries.map((q) => `"${q.query}"`).join(", ");
1386
- return {
1387
- content: [{ type: "text", text: `Query ${formatInputValue(params.query)} was not found for responseId ${formatInputValue(params.responseId)}. Received query=${formatInputValue(params.query)}. Available queries: ${available || "none"}. Use one of the available queries or queryIndex.` }],
1388
- details: { error: "Query not found" },
1389
- };
1390
- }
1391
- } else if (params.queryIndex !== undefined) {
1392
- queryData = data.queries[params.queryIndex];
1393
- if (!queryData) {
1394
- const available = data.queries.map((q, i) => `${i}: "${q.query}"`).join(", ");
1395
- return {
1396
- content: [{ type: "text", text: `Query index ${formatInputValue(params.queryIndex)} is out of range for responseId ${formatInputValue(params.responseId)}. Received queryIndex=${formatInputValue(params.queryIndex)}; valid indexes are 0-${data.queries.length - 1}. Available queries: ${available || "none"}. Use one of the available indexes.` }],
1397
- details: { error: "Index out of range" },
1398
- };
1399
- }
1400
- } else {
1401
- const available = data.queries.map((q, i) => `${i}: "${q.query}"`).join(", ");
1402
- return {
1403
- content: [{ type: "text", text: `Specify query or queryIndex for responseId ${formatInputValue(params.responseId)}. Available queries: ${available || "none"}.` }],
1404
- details: { error: "No query specified" },
1405
- };
1406
- }
1407
-
1408
- if (queryData.error) {
1409
- return {
1410
- content: [{ type: "text", text: `Error retrieving query ${formatInputValue(queryData.query)} from responseId ${formatInputValue(params.responseId)}: ${queryData.error}. Check the stored search result and retry with another query or queryIndex if needed.` }],
1411
- details: { error: queryData.error, query: queryData.query },
1412
- };
1413
- }
1414
-
1415
- const fullResults = formatFullResults(queryData);
1416
- if (params.findText !== undefined) {
1417
- try {
1418
- const found = findContent(fullResults, normalizeFindQueries(params.findText), params.findMode ?? "case-insensitive");
1419
- const { text, ...findDetails } = found;
1420
- return {
1421
- content: [{ type: "text", text }],
1422
- details: { query: queryData.query, resultCount: queryData.results.length, findMode: params.findMode ?? "case-insensitive", ...findDetails },
1423
- };
1424
- } catch (err) {
1425
- const error = err instanceof Error ? err.message : String(err);
1426
- return {
1427
- content: [{ type: "text", text: `Unable to find ${formatInputValue(params.findText)} in query ${formatInputValue(queryData.query)} for responseId ${formatInputValue(params.responseId)}: ${error}. Check findText and use a supported findMode.` }],
1428
- details: { error, query: queryData.query },
1429
- };
1430
- }
1431
- }
1432
-
1433
- return {
1434
- content: [{ type: "text", text: fullResults }],
1435
- details: { query: queryData.query, resultCount: queryData.results.length },
1436
- };
1437
- }
1438
-
1439
- if (data.type === "fetch" && data.urls) {
1440
- let urlData: ExtractedContent | undefined;
1441
- let selectedUrlIndex = -1;
1442
-
1443
- if (params.url !== undefined) {
1444
- selectedUrlIndex = data.urls.findIndex((u) => u.url === params.url);
1445
- urlData = data.urls[selectedUrlIndex];
1446
- if (!urlData) {
1447
- const available = data.urls.map((u) => u.url).join("\n ");
1448
- return {
1449
- content: [{ type: "text", text: `URL ${formatInputValue(params.url)} was not found for responseId ${formatInputValue(params.responseId)}. Received url=${formatInputValue(params.url)}. Available URLs:\n ${available || " none"}\nUse one of the available URLs or urlIndex.` }],
1450
- details: { error: "URL not found" },
1451
- };
1452
- }
1453
- } else if (params.urlIndex !== undefined) {
1454
- selectedUrlIndex = params.urlIndex;
1455
- urlData = data.urls[selectedUrlIndex];
1456
- if (!urlData) {
1457
- const available = data.urls.map((u, i) => `${i}: ${u.url}`).join("\n ");
1458
- return {
1459
- content: [{ type: "text", text: `URL index ${formatInputValue(params.urlIndex)} is out of range for responseId ${formatInputValue(params.responseId)}. Received urlIndex=${formatInputValue(params.urlIndex)}; valid indexes are 0-${data.urls.length - 1}. Available URLs:\n ${available || " none"}\nUse one of the available indexes.` }],
1460
- details: { error: "Index out of range" },
1461
- };
1462
- }
1463
- } else {
1464
- const available = data.urls.map((u, i) => `${i}: ${u.url}`).join("\n ");
1465
- return {
1466
- content: [{ type: "text", text: `Specify url or urlIndex for responseId ${formatInputValue(params.responseId)}. Available URLs:\n ${available || " none"}` }],
1467
- details: { error: "No URL specified" },
1468
- };
1469
- }
1470
-
1471
- if (urlData.error) {
1472
- return {
1473
- content: [{ type: "text", text: `Error retrieving URL ${formatInputValue(urlData.url)} from responseId ${formatInputValue(params.responseId)}: ${urlData.error}. Check the stored fetch result and retry with another URL or urlIndex if needed.` }],
1474
- details: { error: urlData.error, url: urlData.url },
1475
- };
1476
- }
1477
-
1478
- if (params.findText !== undefined) {
1479
- try {
1480
- const found = findContent(urlData.content, normalizeFindQueries(params.findText), params.findMode ?? "case-insensitive");
1481
- const { text, ...findDetails } = found;
1482
- return {
1483
- content: [{ type: "text", text: `# ${urlData.title || urlData.url}\n\n${text}` }],
1484
- details: { url: urlData.url, title: urlData.title, contentLength: urlData.content.length, findMode: params.findMode ?? "case-insensitive", ...findDetails },
1485
- };
1486
- } catch (err) {
1487
- const error = err instanceof Error ? err.message : String(err);
1488
- return {
1489
- content: [{ type: "text", text: `Unable to find ${formatInputValue(params.findText)} in URL ${formatInputValue(urlData.url)} for responseId ${formatInputValue(params.responseId)}: ${error}. Check findText and use a supported findMode.` }],
1490
- details: { error, url: urlData.url },
1491
- };
1492
- }
1493
- }
1494
-
1495
- const offset = params.offset ?? 0;
1496
- const limit = params.limit ?? maxInlineContentChars;
1497
- if (!Number.isInteger(offset) || offset < 0) {
1498
- return {
1499
- content: [{ type: "text", text: `Invalid offset: received ${formatInputValue(offset)} for URL ${formatInputValue(urlData.url)}; offset must be a non-negative integer. Use 0 or a larger integer.` }],
1500
- details: { error: "Invalid offset", offset },
1501
- };
1502
- }
1503
- if (!Number.isInteger(limit) || limit <= 0 || limit > maxInlineContentChars) {
1504
- return {
1505
- content: [{ type: "text", text: `Invalid limit: received ${formatInputValue(limit)} for URL ${formatInputValue(urlData.url)}; limit must be an integer from 1 to ${maxInlineContentChars}. Use a value in that range.` }],
1506
- details: { error: "Invalid limit", limit, maxLimit: maxInlineContentChars },
1507
- };
1508
- }
1509
- if (offset > urlData.content.length) {
1510
- return {
1511
- content: [{ type: "text", text: `Offset ${offset} is out of range for URL ${formatInputValue(urlData.url)} in responseId ${formatInputValue(params.responseId)}. Received offset ${offset}; valid range is 0-${urlData.content.length}. Use an offset within that range.` }],
1512
- details: { error: "Offset out of range", offset, contentLength: urlData.content.length },
1513
- };
1514
- }
1515
-
1516
- const endOffset = Math.min(offset + limit, urlData.content.length);
1517
- const contentSlice = urlData.content.slice(offset, endOffset);
1518
- const hasMore = endOffset < urlData.content.length;
1519
- let text = `# ${urlData.title || urlData.url}\n\n${contentSlice}`;
1520
- if (hasMore || offset > 0) {
1521
- text += `\n\n---\nShowing chars ${offset}-${endOffset} of ${urlData.content.length}.`;
1522
- if (hasMore) {
1523
- text += ` Use ${toolNames.getSearchContent}({ responseId: "${params.responseId}", urlIndex: ${selectedUrlIndex}, offset: ${endOffset}, limit: ${limit} }) for the next slice.`;
1524
- }
1525
- }
1526
-
1527
- return {
1528
- content: [{ type: "text", text }],
1529
- details: {
1530
- url: urlData.url,
1531
- title: urlData.title,
1532
- contentLength: urlData.content.length,
1533
- offset,
1534
- limit,
1535
- returnedChars: contentSlice.length,
1536
- nextOffset: hasMore ? endOffset : null,
1537
- truncated: hasMore,
1538
- },
1539
- };
1540
- }
1541
-
1542
- return {
1543
- content: [{ type: "text", text: `Invalid stored data for responseId ${formatInputValue(params.responseId)}: received type ${formatInputValue(data.type)}. Use a responseId returned by ${storedContentSources}.` }],
1544
- details: { error: "Invalid data" },
1545
- };
1546
- },
1547
-
1548
- renderCall(args, theme) {
1549
- const { responseId, query, queryIndex, url, urlIndex, offset, findText } = args as {
1550
- responseId: string;
1551
- query?: string;
1552
- queryIndex?: number;
1553
- url?: string;
1554
- urlIndex?: number;
1555
- offset?: number;
1556
- findText?: string | string[];
1557
- };
1558
- let target = "";
1559
- if (query) target = `query="${query}"`;
1560
- else if (queryIndex !== undefined) target = `queryIndex=${queryIndex}`;
1561
- else if (url) target = url.length > 30 ? url.slice(0, 27) + "..." : url;
1562
- else if (urlIndex !== undefined) target = `urlIndex=${urlIndex}`;
1563
- if (offset !== undefined) target += target ? ` @ ${offset}` : `offset=${offset}`;
1564
- if (findText !== undefined) {
1565
- const queries = Array.isArray(findText) ? findText : [findText];
1566
- target += `${target ? " · " : ""}find ${queries.length}`;
1567
- }
1568
- return new Text(theme.fg("toolTitle", theme.bold("get_content ")) + theme.fg("accent", target || responseId.slice(0, 8)), 0, 0);
1569
- },
1570
-
1571
- renderResult(result, { expanded }, theme) {
1572
- const details = result.details as {
1573
- error?: string;
1574
- query?: string;
1575
- url?: string;
1576
- title?: string;
1577
- resultCount?: number;
1578
- contentLength?: number;
1579
- offset?: number;
1580
- returnedChars?: number;
1581
- nextOffset?: number | null;
1582
- matchCount?: number;
1583
- returnedMatches?: number;
1584
- };
1585
-
1586
- if (details?.error) {
1587
- const extras: string[] = [];
1588
- if (details.query) extras.push(`query: ${details.query}`);
1589
- if (details.url) extras.push(`url: ${details.url}`);
1590
- else if (details.title) extras.push(`resource: ${details.title}`);
1591
- const plan = buildSearchErrorPlan({ error: details.error, extraLines: extras });
1592
- if (plan) return renderSearchErrorPlan(plan, expanded, theme);
1593
- return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
1594
- }
1595
-
1596
- let statusLine: string;
1597
- if (typeof details?.matchCount === "number") {
1598
- statusLine = theme.fg("success", details?.title || details?.query || "Content") + theme.fg("muted", ` (${details.matchCount} matches, ${details.returnedMatches ?? 0} shown)`);
1599
- } else if (details?.query) {
1600
- statusLine = theme.fg("success", `"${details.query}"`) + theme.fg("muted", ` (${details.resultCount} results)`);
1601
- } else {
1602
- const start = details?.offset ?? 0;
1603
- const returned = details?.returnedChars ?? details?.contentLength ?? 0;
1604
- const end = start + returned;
1605
- const slice = details?.nextOffset !== undefined || start > 0
1606
- ? `, showing ${start}-${end}`
1607
- : "";
1608
- statusLine = theme.fg("success", details?.title || "Content") + theme.fg("muted", ` (${details?.contentLength ?? 0} chars${slice})`);
1609
- }
1610
-
1611
- if (!expanded) {
1612
- return new Text(statusLine, 0, 0);
1613
- }
1614
-
1615
- const textContent = result.content.find((c) => c.type === "text")?.text || "";
1616
- const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent;
1617
- return new Text(statusLine + "\n" + theme.fg("dim", preview), 0, 0);
1618
- },
1619
- });
1620
- }
1621
-
1622
- if (isCommandEnabled(initConfig, "google-account")) pi.registerCommand("google-account", {
1623
- description: "Show the active Google account for Gemini Web",
1624
- handler: async () => {
1625
- if (!isBrowserCookieAccessAllowed()) {
1626
- pi.sendMessage({
1627
- customType: "google-account",
1628
- content: [{ type: "text", text: `Gemini Web browser cookie access is disabled. Set allowBrowserCookies: true in ${WEB_SEARCH_CONFIG_PATH} to enable it.` }],
1629
- display: true,
1630
- details: { available: false, cookieAccessAllowed: false },
1631
- }, { triggerTurn: true, deliverAs: "followUp" });
1632
- return;
1633
- }
1634
-
1635
- const cookies = await isGeminiWebAvailable();
1636
- if (!cookies) {
1637
- const diagnostic = getGeminiWebAvailabilityDiagnostic();
1638
- const diagnosticDetails = getGeminiWebAvailabilityDiagnosticDetails();
1639
- const attempted = formatCookieAttempts(diagnosticDetails?.attempts ?? []);
1640
- const text = diagnostic
1641
- ? `Gemini Web is unavailable: ${diagnostic}${attempted ? ` Attempted browser profiles: ${attempted}.` : ""}`
1642
- : "Gemini Web is unavailable. Sign into gemini.google.com in a supported Chromium-based browser.";
1643
- pi.sendMessage({
1644
- customType: "google-account",
1645
- content: [{ type: "text", text }],
1646
- display: true,
1647
- details: { available: false, cookieAccessAllowed: true, diagnostic, cookieDiagnostic: diagnosticDetails },
1648
- }, { triggerTurn: true, deliverAs: "followUp" });
1649
- return;
1650
- }
1651
-
1652
- const email = await getActiveGoogleEmail(cookies);
1653
- const text = email
1654
- ? `Active Google account: ${email}`
1655
- : "Gemini Web is available, but the active Google account could not be determined.";
1656
-
1657
- pi.sendMessage({
1658
- customType: "google-account",
1659
- content: [{ type: "text", text }],
1660
- display: true,
1661
- details: { available: true, email: email ?? null },
1662
- }, { triggerTurn: true, deliverAs: "followUp" });
1663
- },
1664
- });
1665
-
1666
- function formatCookieAttempts(attempts: { browser: string; profile: string; status: string }[]): string {
1667
- return attempts.map(({ browser, profile, status }) => `${browser}/${profile} (${status})`).join(", ");
1668
- }
1669
-
1670
- if (isCommandEnabled(initConfig, "search")) pi.registerCommand("search", {
1671
- description: "Browse stored web search results",
1672
- handler: async (_args, ctx) => {
1673
- const results = getAllResults();
1674
-
1675
- if (results.length === 0) {
1676
- ctx.ui.notify("No stored search results", "info");
1677
- return;
1678
- }
1679
-
1680
- const options = results.map((r) => {
1681
- const age = Math.floor((Date.now() - r.timestamp) / 60000);
1682
- const ageStr = age < 60 ? `${age}m ago` : `${Math.floor(age / 60)}h ago`;
1683
- if (r.type === "search" && r.queries) {
1684
- const query = r.queries[0]?.query || "unknown";
1685
- return `[${r.id.slice(0, 6)}] "${query}" (${r.queries.length} queries) - ${ageStr}`;
1686
- }
1687
- if (r.type === "fetch" && (r.urls || r.urlMetadata)) {
1688
- return `[${r.id.slice(0, 6)}] ${(r.urls ?? r.urlMetadata ?? []).length} URLs fetched - ${ageStr}`;
1689
- }
1690
- return `[${r.id.slice(0, 6)}] ${r.type} - ${ageStr}`;
1691
- });
1692
-
1693
- const choice = await ctx.ui.select("Stored Search Results", options);
1694
- if (!choice) return;
1695
-
1696
- const match = choice.match(/^\[([a-z0-9]+)\]/);
1697
- if (!match) return;
1698
-
1699
- const selected = results.find((r) => r.id.startsWith(match[1]));
1700
- if (!selected) return;
1701
-
1702
- const actions = ["View details", "Delete"];
1703
- const action = await ctx.ui.select(`Result ${selected.id.slice(0, 6)}`, actions);
1704
-
1705
- if (action === "Delete") {
1706
- deleteResult(selected.id);
1707
- ctx.ui.notify(`Deleted ${selected.id.slice(0, 6)}`, "info");
1708
- } else if (action === "View details") {
1709
- let info = `ID: ${selected.id}\nType: ${selected.type}\nAge: ${Math.floor((Date.now() - selected.timestamp) / 60000)}m\n\n`;
1710
- if (selected.type === "search" && selected.queries) {
1711
- info += "Queries:\n";
1712
- const queries = selected.queries.slice(0, 10);
1713
- for (const q of queries) {
1714
- info += `- "${q.query}" (${q.results.length} results)\n`;
1715
- }
1716
- if (selected.queries.length > 10) {
1717
- info += `... and ${selected.queries.length - 10} more\n`;
1718
- }
1719
- }
1720
- if (selected.type === "fetch" && (selected.urls || selected.urlMetadata)) {
1721
- info += "URLs:\n";
1722
- const urlItems = selected.urls ?? selected.urlMetadata ?? [];
1723
- const urls = urlItems.slice(0, 10);
1724
- for (const u of urls) {
1725
- const urlDisplay = u.url.length > 50 ? u.url.slice(0, 47) + "..." : u.url;
1726
- const contentLength = "content" in u ? u.content.length : u.contentLength;
1727
- info += `- ${urlDisplay} (${u.error || `${contentLength} chars`})\n`;
1728
- }
1729
- if (urlItems.length > 10) {
1730
- info += `... and ${urlItems.length - 10} more\n`;
1731
- }
1732
- }
1733
- ctx.ui.notify(info, "info");
1734
- }
1735
- },
1736
- });
1737
- }