gsd-pi 2.10.12 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/dist/bundled-extension-paths.d.ts +2 -0
  2. package/dist/bundled-extension-paths.js +10 -0
  3. package/dist/loader.js +45 -28
  4. package/dist/onboarding.js +100 -0
  5. package/dist/resource-loader.js +12 -3
  6. package/dist/resources/GSD-WORKFLOW.md +661 -0
  7. package/dist/resources/agents/researcher.md +29 -0
  8. package/dist/resources/agents/scout.md +56 -0
  9. package/dist/resources/agents/worker.md +31 -0
  10. package/dist/resources/extensions/ask-user-questions.ts +289 -0
  11. package/dist/resources/extensions/async-jobs/async-bash-tool.ts +211 -0
  12. package/dist/resources/extensions/async-jobs/await-tool.ts +101 -0
  13. package/dist/resources/extensions/async-jobs/cancel-job-tool.ts +34 -0
  14. package/dist/resources/extensions/async-jobs/index.ts +133 -0
  15. package/dist/resources/extensions/async-jobs/job-manager.ts +211 -0
  16. package/dist/resources/extensions/bg-shell/index.ts +3179 -0
  17. package/dist/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  18. package/dist/resources/extensions/browser-tools/capture.ts +165 -0
  19. package/dist/resources/extensions/browser-tools/core.d.ts +205 -0
  20. package/dist/resources/extensions/browser-tools/core.js +1057 -0
  21. package/dist/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
  22. package/dist/resources/extensions/browser-tools/index.ts +51 -0
  23. package/dist/resources/extensions/browser-tools/lifecycle.ts +270 -0
  24. package/dist/resources/extensions/browser-tools/package.json +24 -0
  25. package/dist/resources/extensions/browser-tools/refs.ts +264 -0
  26. package/dist/resources/extensions/browser-tools/settle.ts +197 -0
  27. package/dist/resources/extensions/browser-tools/state.ts +408 -0
  28. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
  29. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
  30. package/dist/resources/extensions/browser-tools/tools/assertions.ts +342 -0
  31. package/dist/resources/extensions/browser-tools/tools/forms.ts +801 -0
  32. package/dist/resources/extensions/browser-tools/tools/inspection.ts +492 -0
  33. package/dist/resources/extensions/browser-tools/tools/intent.ts +614 -0
  34. package/dist/resources/extensions/browser-tools/tools/interaction.ts +865 -0
  35. package/dist/resources/extensions/browser-tools/tools/navigation.ts +232 -0
  36. package/dist/resources/extensions/browser-tools/tools/pages.ts +303 -0
  37. package/dist/resources/extensions/browser-tools/tools/refs.ts +541 -0
  38. package/dist/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
  39. package/dist/resources/extensions/browser-tools/tools/session.ts +400 -0
  40. package/dist/resources/extensions/browser-tools/tools/wait.ts +247 -0
  41. package/dist/resources/extensions/browser-tools/utils.ts +660 -0
  42. package/dist/resources/extensions/context7/index.ts +428 -0
  43. package/dist/resources/extensions/context7/package.json +11 -0
  44. package/dist/resources/extensions/get-secrets-from-user.ts +625 -0
  45. package/dist/resources/extensions/google-search/index.ts +336 -0
  46. package/dist/resources/extensions/google-search/package.json +9 -0
  47. package/dist/resources/extensions/gsd/activity-log.ts +116 -0
  48. package/dist/resources/extensions/gsd/auto.ts +3732 -0
  49. package/dist/resources/extensions/gsd/commands.ts +544 -0
  50. package/dist/resources/extensions/gsd/crash-recovery.ts +105 -0
  51. package/dist/resources/extensions/gsd/dashboard-overlay.ts +564 -0
  52. package/dist/resources/extensions/gsd/dispatch-guard.ts +78 -0
  53. package/dist/resources/extensions/gsd/docs/preferences-reference.md +278 -0
  54. package/dist/resources/extensions/gsd/doctor.ts +766 -0
  55. package/dist/resources/extensions/gsd/exit-command.ts +18 -0
  56. package/dist/resources/extensions/gsd/files.ts +857 -0
  57. package/dist/resources/extensions/gsd/git-service.ts +904 -0
  58. package/dist/resources/extensions/gsd/gitignore.ts +200 -0
  59. package/dist/resources/extensions/gsd/guided-flow.ts +1054 -0
  60. package/dist/resources/extensions/gsd/index.ts +735 -0
  61. package/dist/resources/extensions/gsd/metrics.ts +374 -0
  62. package/dist/resources/extensions/gsd/migrate/command.ts +218 -0
  63. package/dist/resources/extensions/gsd/migrate/index.ts +42 -0
  64. package/dist/resources/extensions/gsd/migrate/parser.ts +323 -0
  65. package/dist/resources/extensions/gsd/migrate/parsers.ts +624 -0
  66. package/dist/resources/extensions/gsd/migrate/preview.ts +48 -0
  67. package/dist/resources/extensions/gsd/migrate/transformer.ts +346 -0
  68. package/dist/resources/extensions/gsd/migrate/types.ts +370 -0
  69. package/dist/resources/extensions/gsd/migrate/validator.ts +55 -0
  70. package/dist/resources/extensions/gsd/migrate/writer.ts +539 -0
  71. package/dist/resources/extensions/gsd/native-git-bridge.ts +181 -0
  72. package/dist/resources/extensions/gsd/native-parser-bridge.ts +135 -0
  73. package/dist/resources/extensions/gsd/observability-validator.ts +408 -0
  74. package/dist/resources/extensions/gsd/package.json +11 -0
  75. package/dist/resources/extensions/gsd/paths.ts +339 -0
  76. package/dist/resources/extensions/gsd/post-unit-hooks.ts +449 -0
  77. package/dist/resources/extensions/gsd/preferences.ts +988 -0
  78. package/dist/resources/extensions/gsd/prompt-loader.ts +92 -0
  79. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
  80. package/dist/resources/extensions/gsd/prompts/complete-slice.md +27 -0
  81. package/dist/resources/extensions/gsd/prompts/discuss.md +237 -0
  82. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
  83. package/dist/resources/extensions/gsd/prompts/execute-task.md +63 -0
  84. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -0
  85. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -0
  86. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +61 -0
  87. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +3 -0
  88. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +29 -0
  89. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -0
  90. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +13 -0
  91. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
  92. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +78 -0
  93. package/dist/resources/extensions/gsd/prompts/plan-slice.md +55 -0
  94. package/dist/resources/extensions/gsd/prompts/queue.md +100 -0
  95. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
  96. package/dist/resources/extensions/gsd/prompts/replan-slice.md +39 -0
  97. package/dist/resources/extensions/gsd/prompts/research-milestone.md +37 -0
  98. package/dist/resources/extensions/gsd/prompts/research-slice.md +28 -0
  99. package/dist/resources/extensions/gsd/prompts/review-migration.md +66 -0
  100. package/dist/resources/extensions/gsd/prompts/run-uat.md +109 -0
  101. package/dist/resources/extensions/gsd/prompts/system.md +190 -0
  102. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
  103. package/dist/resources/extensions/gsd/roadmap-slices.ts +50 -0
  104. package/dist/resources/extensions/gsd/session-forensics.ts +500 -0
  105. package/dist/resources/extensions/gsd/skill-discovery.ts +137 -0
  106. package/dist/resources/extensions/gsd/state.ts +569 -0
  107. package/dist/resources/extensions/gsd/templates/context.md +76 -0
  108. package/dist/resources/extensions/gsd/templates/decisions.md +8 -0
  109. package/dist/resources/extensions/gsd/templates/milestone-summary.md +73 -0
  110. package/dist/resources/extensions/gsd/templates/plan.md +131 -0
  111. package/dist/resources/extensions/gsd/templates/preferences.md +23 -0
  112. package/dist/resources/extensions/gsd/templates/project.md +31 -0
  113. package/dist/resources/extensions/gsd/templates/reassessment.md +28 -0
  114. package/dist/resources/extensions/gsd/templates/requirements.md +81 -0
  115. package/dist/resources/extensions/gsd/templates/research.md +46 -0
  116. package/dist/resources/extensions/gsd/templates/roadmap.md +118 -0
  117. package/dist/resources/extensions/gsd/templates/secrets-manifest.md +22 -0
  118. package/dist/resources/extensions/gsd/templates/slice-context.md +58 -0
  119. package/dist/resources/extensions/gsd/templates/slice-summary.md +99 -0
  120. package/dist/resources/extensions/gsd/templates/state.md +19 -0
  121. package/dist/resources/extensions/gsd/templates/task-plan.md +52 -0
  122. package/dist/resources/extensions/gsd/templates/task-summary.md +57 -0
  123. package/dist/resources/extensions/gsd/templates/uat.md +54 -0
  124. package/dist/resources/extensions/gsd/tests/activity-log-prune.test.ts +297 -0
  125. package/dist/resources/extensions/gsd/tests/activity-log-save.test.ts +127 -0
  126. package/dist/resources/extensions/gsd/tests/auto-draft-pause.test.ts +109 -0
  127. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +45 -0
  128. package/dist/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +196 -0
  129. package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
  130. package/dist/resources/extensions/gsd/tests/collect-from-manifest.test.ts +469 -0
  131. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +200 -0
  132. package/dist/resources/extensions/gsd/tests/cost-projection.test.ts +134 -0
  133. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +312 -0
  134. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +299 -0
  135. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +660 -0
  136. package/dist/resources/extensions/gsd/tests/discuss-prompt.test.ts +27 -0
  137. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +91 -0
  138. package/dist/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +170 -0
  139. package/dist/resources/extensions/gsd/tests/doctor.test.ts +594 -0
  140. package/dist/resources/extensions/gsd/tests/draft-promotion.test.ts +169 -0
  141. package/dist/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
  142. package/dist/resources/extensions/gsd/tests/git-service.test.ts +1788 -0
  143. package/dist/resources/extensions/gsd/tests/idle-recovery.test.ts +577 -0
  144. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +580 -0
  145. package/dist/resources/extensions/gsd/tests/manifest-status.test.ts +283 -0
  146. package/dist/resources/extensions/gsd/tests/metrics-io.test.ts +176 -0
  147. package/dist/resources/extensions/gsd/tests/metrics.test.ts +188 -0
  148. package/dist/resources/extensions/gsd/tests/migrate-command.test.ts +369 -0
  149. package/dist/resources/extensions/gsd/tests/migrate-parser.test.ts +757 -0
  150. package/dist/resources/extensions/gsd/tests/migrate-transformer.test.ts +635 -0
  151. package/dist/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +414 -0
  152. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +297 -0
  153. package/dist/resources/extensions/gsd/tests/migrate-writer.test.ts +398 -0
  154. package/dist/resources/extensions/gsd/tests/must-have-parser.test.ts +291 -0
  155. package/dist/resources/extensions/gsd/tests/network-error-fallback.test.ts +54 -0
  156. package/dist/resources/extensions/gsd/tests/next-milestone-id.test.ts +66 -0
  157. package/dist/resources/extensions/gsd/tests/orphaned-branch.test.ts +353 -0
  158. package/dist/resources/extensions/gsd/tests/parsers.test.ts +1664 -0
  159. package/dist/resources/extensions/gsd/tests/plan-milestone.test.ts +133 -0
  160. package/dist/resources/extensions/gsd/tests/plan-quality-validator.test.ts +363 -0
  161. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
  162. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
  163. package/dist/resources/extensions/gsd/tests/queue-draft-detection.test.ts +126 -0
  164. package/dist/resources/extensions/gsd/tests/reassess-prompt.test.ts +142 -0
  165. package/dist/resources/extensions/gsd/tests/regex-hardening.test.ts +281 -0
  166. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
  167. package/dist/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
  168. package/dist/resources/extensions/gsd/tests/replan-slice.test.ts +493 -0
  169. package/dist/resources/extensions/gsd/tests/requirements.test.ts +106 -0
  170. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +35 -0
  171. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
  172. package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +41 -0
  173. package/dist/resources/extensions/gsd/tests/run-uat.test.ts +316 -0
  174. package/dist/resources/extensions/gsd/tests/secure-env-collect.test.ts +185 -0
  175. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +123 -0
  176. package/dist/resources/extensions/gsd/tests/test-helpers.ts +61 -0
  177. package/dist/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +219 -0
  178. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +234 -0
  179. package/dist/resources/extensions/gsd/tests/workspace-index.test.ts +75 -0
  180. package/dist/resources/extensions/gsd/tests/worktree-integration.test.ts +234 -0
  181. package/dist/resources/extensions/gsd/tests/worktree-manager.test.ts +141 -0
  182. package/dist/resources/extensions/gsd/tests/worktree.test.ts +597 -0
  183. package/dist/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
  184. package/dist/resources/extensions/gsd/types.ts +296 -0
  185. package/dist/resources/extensions/gsd/unit-runtime.ts +184 -0
  186. package/dist/resources/extensions/gsd/workspace-index.ts +189 -0
  187. package/dist/resources/extensions/gsd/worktree-command.ts +845 -0
  188. package/dist/resources/extensions/gsd/worktree-manager.ts +459 -0
  189. package/dist/resources/extensions/gsd/{worktree.js → worktree.ts} +92 -61
  190. package/dist/resources/extensions/mac-tools/index.ts +852 -0
  191. package/dist/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  192. package/dist/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  193. package/dist/resources/extensions/mcporter/index.ts +429 -0
  194. package/dist/resources/extensions/remote-questions/config.ts +81 -0
  195. package/dist/resources/extensions/remote-questions/discord-adapter.ts +128 -0
  196. package/dist/resources/extensions/remote-questions/format.ts +163 -0
  197. package/dist/resources/extensions/remote-questions/manager.ts +192 -0
  198. package/dist/resources/extensions/remote-questions/remote-command.ts +361 -0
  199. package/dist/resources/extensions/remote-questions/slack-adapter.ts +92 -0
  200. package/dist/resources/extensions/remote-questions/status.ts +31 -0
  201. package/dist/resources/extensions/remote-questions/store.ts +77 -0
  202. package/dist/resources/extensions/remote-questions/types.ts +75 -0
  203. package/dist/resources/extensions/search-the-web/cache.ts +78 -0
  204. package/dist/resources/extensions/search-the-web/command-search-provider.ts +99 -0
  205. package/dist/resources/extensions/search-the-web/format.ts +258 -0
  206. package/dist/resources/extensions/search-the-web/http.ts +238 -0
  207. package/dist/resources/extensions/search-the-web/index.ts +65 -0
  208. package/dist/resources/extensions/search-the-web/native-search.ts +165 -0
  209. package/dist/resources/extensions/search-the-web/provider.ts +136 -0
  210. package/dist/resources/extensions/search-the-web/tavily.ts +116 -0
  211. package/dist/resources/extensions/search-the-web/tool-fetch-page.ts +581 -0
  212. package/dist/resources/extensions/search-the-web/tool-llm-context.ts +620 -0
  213. package/dist/resources/extensions/search-the-web/tool-search.ts +635 -0
  214. package/dist/resources/extensions/search-the-web/url-utils.ts +91 -0
  215. package/dist/resources/extensions/shared/bundled-extension-paths.ts +11 -0
  216. package/dist/resources/extensions/shared/confirm-ui.ts +126 -0
  217. package/dist/resources/extensions/shared/interview-ui.ts +613 -0
  218. package/dist/resources/extensions/shared/next-action-ui.ts +197 -0
  219. package/dist/resources/extensions/shared/progress-widget.ts +282 -0
  220. package/dist/resources/extensions/shared/terminal.ts +23 -0
  221. package/dist/resources/extensions/shared/thinking-widget.ts +107 -0
  222. package/dist/resources/extensions/shared/ui.ts +400 -0
  223. package/dist/resources/extensions/shared/wizard-ui.ts +551 -0
  224. package/dist/resources/extensions/slash-commands/audit.ts +88 -0
  225. package/dist/resources/extensions/slash-commands/clear.ts +10 -0
  226. package/dist/resources/extensions/slash-commands/create-extension.ts +297 -0
  227. package/dist/resources/extensions/slash-commands/create-slash-command.ts +234 -0
  228. package/dist/resources/extensions/slash-commands/index.ts +12 -0
  229. package/dist/resources/extensions/subagent/agents.ts +126 -0
  230. package/dist/resources/extensions/subagent/index.ts +1072 -0
  231. package/dist/resources/extensions/subagent/isolation.ts +498 -0
  232. package/dist/resources/extensions/ttsr/index.ts +163 -0
  233. package/dist/resources/extensions/ttsr/rule-loader.ts +121 -0
  234. package/dist/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
  235. package/dist/resources/extensions/ttsr/ttsr-manager.ts +430 -0
  236. package/dist/resources/extensions/universal-config/discovery.ts +78 -0
  237. package/dist/resources/extensions/universal-config/format.ts +160 -0
  238. package/dist/resources/extensions/universal-config/index.ts +118 -0
  239. package/dist/resources/extensions/universal-config/package.json +11 -0
  240. package/dist/resources/extensions/universal-config/scanners.ts +579 -0
  241. package/dist/resources/extensions/universal-config/tests/discovery.test.ts +111 -0
  242. package/dist/resources/extensions/universal-config/tests/format.test.ts +111 -0
  243. package/dist/resources/extensions/universal-config/tests/scanners.test.ts +438 -0
  244. package/dist/resources/extensions/universal-config/tools.ts +60 -0
  245. package/dist/resources/extensions/universal-config/types.ts +116 -0
  246. package/dist/resources/extensions/voice/index.ts +271 -0
  247. package/dist/resources/extensions/voice/speech-recognizer.py +504 -0
  248. package/dist/resources/extensions/voice/speech-recognizer.swift +154 -0
  249. package/dist/resources/skills/debug-like-expert/SKILL.md +231 -0
  250. package/dist/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  251. package/dist/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  252. package/dist/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  253. package/dist/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  254. package/dist/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  255. package/dist/resources/skills/frontend-design/SKILL.md +45 -0
  256. package/dist/resources/skills/github-workflows/SKILL.md +92 -0
  257. package/dist/resources/skills/github-workflows/references/gh/SKILL.md +255 -0
  258. package/dist/resources/skills/github-workflows/references/gh/references/issue-stories.md +204 -0
  259. package/dist/resources/skills/github-workflows/references/gh/references/labels.md +170 -0
  260. package/dist/resources/skills/github-workflows/references/gh/references/milestones.md +158 -0
  261. package/dist/resources/skills/github-workflows/references/gh/references/projects-v2.md +177 -0
  262. package/dist/resources/skills/github-workflows/references/gh/scripts/experiment_cleanup.py +191 -0
  263. package/dist/resources/skills/github-workflows/references/gh/scripts/github_project_setup.py +799 -0
  264. package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
  265. package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +608 -0
  266. package/dist/resources/skills/swiftui/SKILL.md +208 -0
  267. package/dist/resources/skills/swiftui/references/animations.md +921 -0
  268. package/dist/resources/skills/swiftui/references/architecture.md +1561 -0
  269. package/dist/resources/skills/swiftui/references/layout-system.md +1186 -0
  270. package/dist/resources/skills/swiftui/references/navigation.md +1492 -0
  271. package/dist/resources/skills/swiftui/references/networking-async.md +214 -0
  272. package/dist/resources/skills/swiftui/references/performance.md +1706 -0
  273. package/dist/resources/skills/swiftui/references/platform-integration.md +204 -0
  274. package/dist/resources/skills/swiftui/references/state-management.md +1443 -0
  275. package/dist/resources/skills/swiftui/references/swiftdata.md +297 -0
  276. package/dist/resources/skills/swiftui/references/testing-debugging.md +247 -0
  277. package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  278. package/dist/resources/skills/swiftui/workflows/add-feature.md +191 -0
  279. package/dist/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  280. package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  281. package/dist/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  282. package/dist/resources/skills/swiftui/workflows/ship-app.md +203 -0
  283. package/dist/resources/skills/swiftui/workflows/write-tests.md +235 -0
  284. package/dist/wizard.js +1 -0
  285. package/package.json +8 -6
  286. package/packages/native/dist/fd/index.d.ts +1 -0
  287. package/packages/native/dist/fd/index.js +1 -0
  288. package/packages/native/dist/grep/index.d.ts +2 -1
  289. package/packages/native/dist/grep/index.js +1 -0
  290. package/packages/native/src/__tests__/fd.test.mjs +35 -0
  291. package/packages/native/src/__tests__/grep.test.mjs +26 -9
  292. package/packages/native/src/fd/index.ts +1 -0
  293. package/packages/native/src/grep/index.ts +3 -2
  294. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  295. package/packages/pi-agent-core/dist/agent-loop.js +169 -55
  296. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  297. package/packages/pi-agent-core/dist/agent.d.ts +13 -1
  298. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  299. package/packages/pi-agent-core/dist/agent.js +16 -0
  300. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  301. package/packages/pi-agent-core/dist/types.d.ts +91 -1
  302. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  303. package/packages/pi-agent-core/dist/types.js.map +1 -1
  304. package/packages/pi-agent-core/src/agent-loop.ts +273 -63
  305. package/packages/pi-agent-core/src/agent.ts +24 -0
  306. package/packages/pi-agent-core/src/types.ts +98 -0
  307. package/packages/pi-ai/dist/env-api-keys.js +2 -0
  308. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  309. package/packages/pi-ai/dist/models.generated.d.ts +314 -0
  310. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  311. package/packages/pi-ai/dist/models.generated.js +236 -0
  312. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  313. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  314. package/packages/pi-ai/dist/providers/google-gemini-cli.js +6 -0
  315. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  316. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  317. package/packages/pi-ai/dist/providers/openai-completions.js +2 -0
  318. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  319. package/packages/pi-ai/dist/types.d.ts +1 -1
  320. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  321. package/packages/pi-ai/dist/types.js.map +1 -1
  322. package/packages/pi-ai/src/env-api-keys.ts +2 -0
  323. package/packages/pi-ai/src/models.generated.ts +236 -0
  324. package/packages/pi-ai/src/providers/google-gemini-cli.ts +8 -0
  325. package/packages/pi-ai/src/providers/openai-completions.ts +3 -0
  326. package/packages/pi-ai/src/types.ts +2 -1
  327. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  328. package/packages/pi-coding-agent/dist/cli/args.js +1 -0
  329. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  330. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +26 -0
  331. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  332. package/packages/pi-coding-agent/dist/core/agent-session.js +137 -22
  333. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  334. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +20 -0
  335. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  336. package/packages/pi-coding-agent/dist/core/auth-storage.js +43 -0
  337. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  338. package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts +51 -0
  339. package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -0
  340. package/packages/pi-coding-agent/dist/core/fallback-resolver.js +119 -0
  341. package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -0
  342. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.d.ts +2 -0
  343. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.d.ts.map +1 -0
  344. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +183 -0
  345. package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -0
  346. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  347. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  348. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  349. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  350. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  351. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  352. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  353. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +12 -1
  354. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  355. package/packages/pi-coding-agent/dist/core/session-manager.js +37 -1
  356. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  357. package/packages/pi-coding-agent/dist/core/session-manager.test.d.ts +2 -0
  358. package/packages/pi-coding-agent/dist/core/session-manager.test.d.ts.map +1 -0
  359. package/packages/pi-coding-agent/dist/core/session-manager.test.js +61 -0
  360. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -0
  361. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  362. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  363. package/packages/pi-coding-agent/dist/core/settings-manager.js +47 -0
  364. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  365. package/packages/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
  366. package/packages/pi-coding-agent/dist/core/tools/find.js +1 -0
  367. package/packages/pi-coding-agent/dist/core/tools/find.js.map +1 -1
  368. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  369. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +6 -15
  370. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  371. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +18 -1
  372. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  373. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +46 -4
  374. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  375. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  376. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +25 -2
  377. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  378. package/packages/pi-coding-agent/src/cli/args.ts +1 -0
  379. package/packages/pi-coding-agent/src/core/agent-session.ts +167 -22
  380. package/packages/pi-coding-agent/src/core/auth-storage.ts +45 -0
  381. package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +229 -0
  382. package/packages/pi-coding-agent/src/core/fallback-resolver.ts +165 -0
  383. package/packages/pi-coding-agent/src/core/index.ts +1 -0
  384. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  385. package/packages/pi-coding-agent/src/core/session-manager.test.ts +65 -0
  386. package/packages/pi-coding-agent/src/core/session-manager.ts +51 -1
  387. package/packages/pi-coding-agent/src/core/settings-manager.ts +66 -0
  388. package/packages/pi-coding-agent/src/core/tools/find.ts +1 -0
  389. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +6 -16
  390. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +49 -4
  391. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +28 -2
  392. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  393. package/packages/pi-tui/dist/autocomplete.js +3 -4
  394. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  395. package/packages/pi-tui/dist/components/editor.d.ts +7 -0
  396. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  397. package/packages/pi-tui/dist/components/editor.js +57 -50
  398. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  399. package/packages/pi-tui/src/autocomplete.ts +3 -5
  400. package/packages/pi-tui/src/components/editor.ts +70 -49
  401. package/pkg/dist/core/export-html/ansi-to-html.d.ts +22 -0
  402. package/pkg/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  403. package/pkg/dist/core/export-html/ansi-to-html.js +249 -0
  404. package/pkg/dist/core/export-html/ansi-to-html.js.map +1 -0
  405. package/pkg/dist/core/export-html/index.d.ts +37 -0
  406. package/pkg/dist/core/export-html/index.d.ts.map +1 -0
  407. package/pkg/dist/core/export-html/index.js +223 -0
  408. package/pkg/dist/core/export-html/index.js.map +1 -0
  409. package/pkg/dist/core/export-html/template.css +971 -0
  410. package/pkg/dist/core/export-html/template.html +54 -0
  411. package/pkg/dist/core/export-html/template.js +1583 -0
  412. package/pkg/dist/core/export-html/tool-renderer.d.ts +38 -0
  413. package/pkg/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  414. package/pkg/dist/core/export-html/tool-renderer.js +70 -0
  415. package/pkg/dist/core/export-html/tool-renderer.js.map +1 -0
  416. package/pkg/dist/core/export-html/vendor/highlight.min.js +1213 -0
  417. package/pkg/dist/core/export-html/vendor/marked.min.js +6 -0
  418. package/scripts/postinstall.js +30 -17
  419. package/src/resources/extensions/bg-shell/index.ts +51 -7
  420. package/src/resources/extensions/gsd/activity-log.ts +61 -14
  421. package/src/resources/extensions/gsd/auto.ts +288 -19
  422. package/src/resources/extensions/gsd/commands.ts +9 -3
  423. package/src/resources/extensions/gsd/dashboard-overlay.ts +55 -12
  424. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
  425. package/src/resources/extensions/gsd/doctor.ts +60 -3
  426. package/src/resources/extensions/gsd/files.ts +45 -1
  427. package/src/resources/extensions/gsd/git-service.ts +46 -41
  428. package/src/resources/extensions/gsd/guided-flow.ts +81 -9
  429. package/src/resources/extensions/gsd/index.ts +62 -1
  430. package/src/resources/extensions/gsd/native-git-bridge.ts +181 -0
  431. package/src/resources/extensions/gsd/paths.ts +35 -4
  432. package/src/resources/extensions/gsd/post-unit-hooks.ts +449 -0
  433. package/src/resources/extensions/gsd/preferences.ts +232 -1
  434. package/src/resources/extensions/gsd/prompt-loader.ts +45 -3
  435. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  436. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -3
  437. package/src/resources/extensions/gsd/prompts/discuss.md +10 -8
  438. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  439. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -1
  440. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -1
  441. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  442. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +3 -1
  443. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +4 -2
  444. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -1
  445. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +3 -1
  446. package/src/resources/extensions/gsd/prompts/plan-milestone.md +9 -12
  447. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -3
  448. package/src/resources/extensions/gsd/prompts/queue.md +3 -1
  449. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  450. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  451. package/src/resources/extensions/gsd/state.ts +130 -18
  452. package/src/resources/extensions/gsd/templates/context.md +1 -1
  453. package/src/resources/extensions/gsd/templates/state.md +3 -3
  454. package/src/resources/extensions/gsd/tests/activity-log-save.test.ts +127 -0
  455. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +5 -1
  456. package/src/resources/extensions/gsd/tests/doctor.test.ts +115 -1
  457. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +6 -2
  458. package/src/resources/extensions/gsd/tests/git-service.test.ts +35 -35
  459. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +8 -8
  460. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +4 -1
  461. package/src/resources/extensions/gsd/tests/network-error-fallback.test.ts +54 -0
  462. package/src/resources/extensions/gsd/tests/orphaned-branch.test.ts +5 -5
  463. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
  464. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
  465. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +12 -0
  466. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +6 -0
  467. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +4 -4
  468. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +4 -4
  469. package/src/resources/extensions/gsd/tests/worktree.test.ts +19 -19
  470. package/src/resources/extensions/gsd/types.ts +109 -0
  471. package/src/resources/extensions/gsd/worktree-manager.ts +72 -13
  472. package/src/resources/extensions/gsd/worktree.ts +5 -4
  473. package/src/resources/extensions/search-the-web/command-search-provider.ts +8 -4
  474. package/src/resources/extensions/search-the-web/native-search.ts +32 -23
  475. package/src/resources/extensions/search-the-web/provider.ts +19 -2
  476. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +62 -0
  477. package/src/resources/extensions/search-the-web/tool-llm-context.ts +62 -3
  478. package/src/resources/extensions/search-the-web/tool-search.ts +62 -3
  479. package/src/resources/extensions/shared/bundled-extension-paths.ts +11 -0
  480. package/src/resources/extensions/subagent/index.ts +2 -1
  481. package/src/resources/skills/github-workflows/SKILL.md +5 -0
  482. package/dist/resources/extensions/gsd/git-service.d.ts +0 -225
  483. package/dist/resources/extensions/gsd/git-service.js +0 -756
  484. package/dist/resources/extensions/gsd/preferences.d.ts +0 -122
  485. package/dist/resources/extensions/gsd/preferences.js +0 -616
  486. package/dist/resources/extensions/gsd/worktree.d.ts +0 -106
  487. package/dist/resources/extensions/remote-questions/config.d.ts +0 -14
  488. package/dist/resources/extensions/remote-questions/config.js +0 -68
  489. package/dist/resources/extensions/remote-questions/discord-adapter.d.ts +0 -17
  490. package/dist/resources/extensions/remote-questions/discord-adapter.js +0 -118
  491. package/dist/resources/extensions/remote-questions/format.d.ts +0 -38
  492. package/dist/resources/extensions/remote-questions/format.js +0 -119
  493. package/dist/resources/extensions/remote-questions/manager.d.ts +0 -23
  494. package/dist/resources/extensions/remote-questions/manager.js +0 -161
  495. package/dist/resources/extensions/remote-questions/remote-command.d.ts +0 -6
  496. package/dist/resources/extensions/remote-questions/remote-command.js +0 -358
  497. package/dist/resources/extensions/remote-questions/slack-adapter.d.ts +0 -15
  498. package/dist/resources/extensions/remote-questions/slack-adapter.js +0 -82
  499. package/dist/resources/extensions/remote-questions/status.d.ts +0 -9
  500. package/dist/resources/extensions/remote-questions/status.js +0 -25
  501. package/dist/resources/extensions/remote-questions/store.d.ts +0 -11
  502. package/dist/resources/extensions/remote-questions/store.js +0 -66
  503. package/dist/resources/extensions/remote-questions/types.d.ts +0 -68
  504. package/dist/resources/extensions/remote-questions/types.js +0 -4
@@ -0,0 +1,620 @@
1
+ /**
2
+ * search_and_read tool — web search + content extraction for AI agents.
3
+ *
4
+ * Single-call web search + page content extraction optimized for AI agents.
5
+ * Unlike search-the-web → fetch_page (two steps), this returns pre-extracted,
6
+ * relevance-scored page content in one API call.
7
+ *
8
+ * Supports two backends:
9
+ * - Tavily: POST-based, client-side token budgeting via budgetContent()
10
+ * - Brave: GET-based LLM Context API with server-side budgeting
11
+ *
12
+ * Provider is selected by resolveSearchProvider() — same as tool-search.ts.
13
+ *
14
+ * Best for: "I need to know about X" — when you want content, not just links.
15
+ * Use search-the-web when you want links/URLs to browse selectively.
16
+ */
17
+
18
+ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
19
+ import { truncateHead, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "@gsd/pi-coding-agent";
20
+ import { Text } from "@gsd/pi-tui";
21
+ import { Type } from "@sinclair/typebox";
22
+ import { StringEnum } from "@gsd/pi-ai";
23
+
24
+ import { LRUTTLCache } from "./cache.js";
25
+ import { fetchWithRetryTimed, HttpError, classifyError, type RateLimitInfo } from "./http.js";
26
+ import { normalizeQuery, extractDomain } from "./url-utils.js";
27
+ import { formatLLMContext, type LLMContextSnippet, type LLMContextSource } from "./format.js";
28
+ import type { TavilyResult, TavilySearchResponse } from "./tavily.js";
29
+ import { publishedDateToAge } from "./tavily.js";
30
+ import { getTavilyApiKey, getOllamaApiKey, resolveSearchProvider } from "./provider.js";
31
+
32
+ // =============================================================================
33
+ // Types
34
+ // =============================================================================
35
+
36
+ interface BraveLLMContextResponse {
37
+ grounding?: {
38
+ generic?: Array<{
39
+ url: string;
40
+ title: string;
41
+ snippets: string[];
42
+ }>;
43
+ poi?: {
44
+ name: string;
45
+ url: string;
46
+ title: string;
47
+ snippets: string[];
48
+ } | null;
49
+ map?: Array<{
50
+ name: string;
51
+ url: string;
52
+ title: string;
53
+ snippets: string[];
54
+ }>;
55
+ };
56
+ sources?: Record<string, {
57
+ title: string;
58
+ hostname: string;
59
+ age: string[] | null;
60
+ }>;
61
+ }
62
+
63
+ interface CachedLLMContext {
64
+ grounding: LLMContextSnippet[];
65
+ sources: Record<string, LLMContextSource>;
66
+ estimatedTokens: number;
67
+ }
68
+
69
+ interface LLMContextDetails {
70
+ query: string;
71
+ sourceCount: number;
72
+ snippetCount: number;
73
+ estimatedTokens: number;
74
+ cached: boolean;
75
+ latencyMs?: number;
76
+ rateLimit?: RateLimitInfo;
77
+ threshold?: string;
78
+ maxTokens?: number;
79
+ errorKind?: string;
80
+ error?: string;
81
+ retryAfterMs?: number;
82
+ provider?: 'tavily' | 'brave' | 'ollama';
83
+ }
84
+
85
+ // =============================================================================
86
+ // Cache
87
+ // =============================================================================
88
+
89
+ // LLM Context cache: max 50 entries, 10-minute TTL
90
+ const contextCache = new LRUTTLCache<CachedLLMContext>({ max: 50, ttlMs: 600_000 });
91
+ contextCache.startPurgeInterval(60_000);
92
+
93
+ // =============================================================================
94
+ // Helpers
95
+ // =============================================================================
96
+
97
+ function getBraveApiKey(): string {
98
+ return process.env.BRAVE_API_KEY || "";
99
+ }
100
+
101
+ function braveHeaders(): Record<string, string> {
102
+ return {
103
+ "Accept": "application/json",
104
+ "Accept-Encoding": "gzip",
105
+ "X-Subscription-Token": getBraveApiKey(),
106
+ };
107
+ }
108
+
109
+ /** Rough token estimate: ~4 chars per token for English text. */
110
+ function estimateTokens(text: string): number {
111
+ return Math.ceil(text.length / 4);
112
+ }
113
+
114
+ /**
115
+ * Distribute a token budget across Tavily results to build LLM context.
116
+ *
117
+ * Client-side equivalent of Brave's server-side LLM Context API budgeting.
118
+ * Filters by score threshold, sorts by relevance, and truncates content to fit
119
+ * within the token budget. Uses `raw_content` when available (richer text from
120
+ * Tavily's "advanced" search depth), falling back to `content`.
121
+ *
122
+ * @param results — Raw Tavily search results
123
+ * @param maxTokens — Caller-requested token limit
124
+ * @param threshold — Minimum score (0–1) for inclusion
125
+ * @returns Grounding snippets, source metadata, and estimated token usage
126
+ */
127
+ export function budgetContent(
128
+ results: TavilyResult[],
129
+ maxTokens: number,
130
+ threshold: number,
131
+ ): { grounding: LLMContextSnippet[]; sources: Record<string, LLMContextSource>; estimatedTokens: number } {
132
+ // Filter by score threshold and sort by score descending (highest relevance first)
133
+ const filtered = results
134
+ .filter(r => r.score >= threshold)
135
+ .sort((a, b) => b.score - a.score);
136
+
137
+ if (filtered.length === 0) {
138
+ return { grounding: [], sources: {}, estimatedTokens: 0 };
139
+ }
140
+
141
+ // Use 80% of maxTokens as effective budget (conservative to avoid overshoot)
142
+ const effectiveBudget = Math.floor(maxTokens * 0.8);
143
+ const perResultBudget = Math.max(1, Math.floor(effectiveBudget / filtered.length));
144
+
145
+ const grounding: LLMContextSnippet[] = [];
146
+ const sources: Record<string, LLMContextSource> = {};
147
+ let totalTokens = 0;
148
+
149
+ for (const result of filtered) {
150
+ if (totalTokens >= effectiveBudget) break;
151
+
152
+ const remainingBudget = effectiveBudget - totalTokens;
153
+ const budget = Math.min(perResultBudget, remainingBudget);
154
+
155
+ // Use raw_content if available, fall back to content
156
+ let text = result.raw_content ?? result.content;
157
+
158
+ // Truncate to per-result budget (tokens → chars at ~4 chars/token)
159
+ const maxChars = budget * 4;
160
+ if (text.length > maxChars) {
161
+ text = text.slice(0, maxChars);
162
+ }
163
+
164
+ const tokens = estimateTokens(text);
165
+ totalTokens += tokens;
166
+
167
+ grounding.push({
168
+ url: result.url,
169
+ title: result.title || "(untitled)",
170
+ snippets: [text],
171
+ });
172
+
173
+ // Build source with age in [null, null, ageString] format for formatLLMContext compatibility.
174
+ // formatLLMContext reads source.age?.[2] for the human-readable age display.
175
+ const ageString = result.published_date ? publishedDateToAge(result.published_date) : undefined;
176
+ sources[result.url] = {
177
+ title: result.title || "(untitled)",
178
+ hostname: extractDomain(result.url),
179
+ age: ageString ? [null as unknown as string, null as unknown as string, ageString] : null,
180
+ };
181
+ }
182
+
183
+ return { grounding, sources, estimatedTokens: totalTokens };
184
+ }
185
+
186
+ // =============================================================================
187
+ // Tavily LLM Context Execution
188
+ // =============================================================================
189
+
190
+ /** Map threshold names to Tavily score cutoffs. */
191
+ const THRESHOLD_TO_SCORE: Record<string, number> = {
192
+ strict: 0.7,
193
+ balanced: 0.5,
194
+ lenient: 0.3,
195
+ };
196
+
197
+ /**
198
+ * Execute a search_and_read query against the Tavily API.
199
+ *
200
+ * Uses POST with advanced search depth + raw_content to get full page text,
201
+ * then feeds results through budgetContent() for client-side token budgeting.
202
+ */
203
+ async function executeTavilyLLMContext(
204
+ params: { query: string; maxTokens: number; maxUrls: number; threshold: string; count: number },
205
+ signal?: AbortSignal,
206
+ ): Promise<{ cached: CachedLLMContext; latencyMs: number; rateLimit?: RateLimitInfo }> {
207
+ const scoreThreshold = THRESHOLD_TO_SCORE[params.threshold] ?? 0.5;
208
+
209
+ const requestBody: Record<string, unknown> = {
210
+ query: params.query,
211
+ max_results: params.count,
212
+ search_depth: "advanced",
213
+ include_raw_content: true,
214
+ include_answer: true,
215
+ };
216
+
217
+ const timed = await fetchWithRetryTimed("https://api.tavily.com/search", {
218
+ method: "POST",
219
+ headers: {
220
+ "Content-Type": "application/json",
221
+ "Authorization": `Bearer ${getTavilyApiKey()}`,
222
+ },
223
+ body: JSON.stringify(requestBody),
224
+ signal,
225
+ }, 2);
226
+
227
+ const data: TavilySearchResponse = await timed.response.json();
228
+ const cached = budgetContent(data.results, params.maxTokens, scoreThreshold);
229
+
230
+ return { cached, latencyMs: timed.latencyMs, rateLimit: timed.rateLimit };
231
+ }
232
+
233
+ // =============================================================================
234
+ // Ollama LLM Context Execution
235
+ // =============================================================================
236
+
237
+ interface OllamaWebSearchResult {
238
+ title: string;
239
+ url: string;
240
+ content: string;
241
+ }
242
+
243
+ interface OllamaWebSearchResponse {
244
+ results: OllamaWebSearchResult[];
245
+ }
246
+
247
+ /**
248
+ * Execute a search_and_read query against the Ollama web_search API.
249
+ *
250
+ * Uses the same web_search endpoint as tool-search, then applies
251
+ * budgetContent() for client-side token budgeting (similar to Tavily path).
252
+ */
253
+ async function executeOllamaLLMContext(
254
+ params: { query: string; maxTokens: number; count: number; threshold: string },
255
+ signal?: AbortSignal,
256
+ ): Promise<{ cached: CachedLLMContext; latencyMs: number; rateLimit?: RateLimitInfo }> {
257
+ const scoreThreshold = THRESHOLD_TO_SCORE[params.threshold] ?? 0.5;
258
+
259
+ const timed = await fetchWithRetryTimed("https://ollama.com/api/web_search", {
260
+ method: "POST",
261
+ headers: {
262
+ "Content-Type": "application/json",
263
+ "Authorization": `Bearer ${getOllamaApiKey()}`,
264
+ },
265
+ body: JSON.stringify({ query: params.query, max_results: params.count }),
266
+ signal,
267
+ }, 2);
268
+
269
+ const data: OllamaWebSearchResponse = await timed.response.json();
270
+
271
+ // Convert Ollama results to TavilyResult-compatible format for budgetContent
272
+ const tavilyLikeResults: TavilyResult[] = (data.results || []).map(r => ({
273
+ title: r.title || "(untitled)",
274
+ url: r.url,
275
+ content: r.content || "",
276
+ score: 1.0, // Ollama doesn't provide scores, assume all are relevant
277
+ }));
278
+
279
+ const cached = budgetContent(tavilyLikeResults, params.maxTokens, scoreThreshold);
280
+
281
+ return { cached, latencyMs: timed.latencyMs, rateLimit: timed.rateLimit };
282
+ }
283
+
284
+ // =============================================================================
285
+ // Tool Registration
286
+ // =============================================================================
287
+
288
+ export function registerLLMContextTool(pi: ExtensionAPI) {
289
+ pi.registerTool({
290
+ name: "search_and_read",
291
+ label: "Search & Read",
292
+ description:
293
+ "Search the web AND read page content in a single call. Returns pre-extracted, " +
294
+ "relevance-scored text from multiple pages — no separate fetch_page needed. " +
295
+ "Best when you need content, not just links. " +
296
+ "For selective URL browsing, use search-the-web + fetch_page instead.",
297
+ promptSnippet: "Search and read web page content in one step",
298
+ promptGuidelines: [
299
+ "Use search_and_read when you need actual page content about a topic — it searches and extracts in one call.",
300
+ "Prefer search_and_read over search-the-web + fetch_page when you just need to learn about something.",
301
+ "Use search-the-web when you need to browse specific URLs, control which pages to read, or want just links.",
302
+ "Start with the default maxTokens (8192). Use smaller values (2048-4096) for simple factual queries.",
303
+ "Use threshold='strict' for focused, high-relevance results. Use 'lenient' for broad coverage.",
304
+ ],
305
+ parameters: Type.Object({
306
+ query: Type.String({ description: "Search query — what you want to learn about" }),
307
+ maxTokens: Type.Optional(
308
+ Type.Number({
309
+ minimum: 1024,
310
+ maximum: 32768,
311
+ default: 8192,
312
+ description: "Approximate maximum tokens of content to return (default: 8192). Lower = faster + cheaper inference.",
313
+ })
314
+ ),
315
+ maxUrls: Type.Optional(
316
+ Type.Number({
317
+ minimum: 1,
318
+ maximum: 20,
319
+ default: 10,
320
+ description: "Maximum number of source URLs to include (default: 10).",
321
+ })
322
+ ),
323
+ threshold: Type.Optional(
324
+ StringEnum(["strict", "balanced", "lenient"] as const, {
325
+ description: "Relevance threshold. 'strict' = fewer but more relevant. 'balanced' (default). 'lenient' = broader coverage.",
326
+ })
327
+ ),
328
+ count: Type.Optional(
329
+ Type.Number({
330
+ minimum: 1,
331
+ maximum: 50,
332
+ default: 20,
333
+ description: "Maximum search results to consider (default: 20). More = broader but slower.",
334
+ })
335
+ ),
336
+ }),
337
+
338
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
339
+ if (signal?.aborted) {
340
+ return { content: [{ type: "text", text: "Search cancelled." }], details: undefined as unknown };
341
+ }
342
+
343
+ // ------------------------------------------------------------------
344
+ // Resolve search provider
345
+ // ------------------------------------------------------------------
346
+ const provider = resolveSearchProvider();
347
+ if (!provider) {
348
+ return {
349
+ content: [{ type: "text", text: "search_and_read unavailable: No search API key is set. Use secure_env_collect to set TAVILY_API_KEY, BRAVE_API_KEY, or OLLAMA_API_KEY." }],
350
+ isError: true,
351
+ details: { errorKind: "auth_error", error: "No search API key set" } satisfies Partial<LLMContextDetails>,
352
+ };
353
+ }
354
+
355
+ const maxTokens = params.maxTokens ?? 8192;
356
+ const maxUrls = params.maxUrls ?? 10;
357
+ const threshold = params.threshold ?? "balanced";
358
+ const count = params.count ?? 20;
359
+
360
+ // ------------------------------------------------------------------
361
+ // Cache lookup (provider-prefixed key)
362
+ // ------------------------------------------------------------------
363
+ const cacheKey = normalizeQuery(params.query) + `|t:${maxTokens}|u:${maxUrls}|th:${threshold}|c:${count}|p:${provider}`;
364
+ const cached = contextCache.get(cacheKey);
365
+
366
+ if (cached) {
367
+ const output = formatLLMContext(params.query, cached.grounding, cached.sources, {
368
+ cached: true,
369
+ tokenCount: cached.estimatedTokens,
370
+ });
371
+
372
+ const truncation = truncateHead(output, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
373
+ let content = truncation.content;
374
+ if (truncation.truncated) {
375
+ const tempFile = await (pi as any).writeTempFile(output, { prefix: "llm-context-" });
376
+ content += `\n\n[Truncated. Full content: ${tempFile}]`;
377
+ }
378
+
379
+ const totalSnippets = cached.grounding.reduce((sum, g) => sum + g.snippets.length, 0);
380
+ const details: LLMContextDetails = {
381
+ query: params.query,
382
+ sourceCount: cached.grounding.length,
383
+ snippetCount: totalSnippets,
384
+ estimatedTokens: cached.estimatedTokens,
385
+ cached: true,
386
+ threshold,
387
+ maxTokens,
388
+ provider,
389
+ };
390
+
391
+ return { content: [{ type: "text", text: content }], details };
392
+ }
393
+
394
+ onUpdate?.({ content: [{ type: "text", text: `Searching & reading about "${params.query}"...` }], details: undefined as unknown });
395
+
396
+ try {
397
+ // ------------------------------------------------------------------
398
+ // Provider-specific fetch
399
+ // ------------------------------------------------------------------
400
+ let result: CachedLLMContext;
401
+ let latencyMs: number | undefined;
402
+ let rateLimit: RateLimitInfo | undefined;
403
+
404
+ if (provider === "tavily") {
405
+ const tavilyResult = await executeTavilyLLMContext(
406
+ { query: params.query, maxTokens, maxUrls, threshold, count },
407
+ signal,
408
+ );
409
+ result = tavilyResult.cached;
410
+ latencyMs = tavilyResult.latencyMs;
411
+ rateLimit = tavilyResult.rateLimit;
412
+ } else if (provider === "ollama") {
413
+ const ollamaResult = await executeOllamaLLMContext(
414
+ { query: params.query, maxTokens, count, threshold },
415
+ signal,
416
+ );
417
+ result = ollamaResult.cached;
418
+ latencyMs = ollamaResult.latencyMs;
419
+ rateLimit = ollamaResult.rateLimit;
420
+ } else {
421
+ // ================================================================
422
+ // BRAVE PATH (unchanged API logic)
423
+ // ================================================================
424
+ const url = new URL("https://api.search.brave.com/res/v1/llm/context");
425
+ url.searchParams.append("q", params.query);
426
+ url.searchParams.append("count", String(count));
427
+ url.searchParams.append("maximum_number_of_tokens", String(maxTokens));
428
+ url.searchParams.append("maximum_number_of_urls", String(maxUrls));
429
+ url.searchParams.append("context_threshold_mode", threshold);
430
+
431
+ // Use a custom fetch flow to read error bodies from the Brave API
432
+ let timed;
433
+ try {
434
+ timed = await fetchWithRetryTimed(url.toString(), {
435
+ method: "GET",
436
+ headers: braveHeaders(),
437
+ signal,
438
+ }, 2);
439
+ } catch (fetchErr) {
440
+ // Try to extract Brave's structured error detail from the response body.
441
+ // This is especially useful for plan/subscription errors (OPTION_NOT_IN_PLAN).
442
+ let errorMessage: string | undefined;
443
+ let errorKindOverride: string | undefined;
444
+ if (fetchErr instanceof HttpError && fetchErr.response) {
445
+ try {
446
+ const body = await fetchErr.response.clone().json().catch(() => null);
447
+ if (body?.error?.detail) {
448
+ errorMessage = body.error.detail;
449
+ if (body.error.code === "OPTION_NOT_IN_PLAN") {
450
+ errorKindOverride = "plan_error";
451
+ errorMessage = `LLM Context API not available on your current Brave plan. ${body.error.detail} Upgrade at https://api-dashboard.search.brave.com/app/subscriptions — or use search-the-web + fetch_page as an alternative.`;
452
+ }
453
+ }
454
+ } catch { /* body already consumed or parse error — use generic message */ }
455
+ }
456
+ const classified = classifyError(fetchErr);
457
+ const message = errorMessage || classified.message;
458
+ return {
459
+ content: [{ type: "text", text: `search_and_read unavailable: ${message}` }],
460
+ details: {
461
+ errorKind: errorKindOverride || classified.kind,
462
+ error: message,
463
+ retryAfterMs: classified.retryAfterMs,
464
+ query: params.query,
465
+ provider,
466
+ } satisfies Partial<LLMContextDetails>,
467
+ isError: true,
468
+ };
469
+ }
470
+
471
+ const data: BraveLLMContextResponse = await timed.response.json();
472
+
473
+ // ------------------------------------------------------------------
474
+ // Normalize Brave response
475
+ // ------------------------------------------------------------------
476
+ const grounding: LLMContextSnippet[] = [];
477
+
478
+ if (data.grounding?.generic) {
479
+ for (const item of data.grounding.generic) {
480
+ if (item.snippets && item.snippets.length > 0) {
481
+ grounding.push({
482
+ url: item.url,
483
+ title: item.title,
484
+ snippets: item.snippets,
485
+ });
486
+ }
487
+ }
488
+ }
489
+
490
+ // Include POI data if present
491
+ if (data.grounding?.poi && data.grounding.poi.snippets?.length) {
492
+ grounding.push({
493
+ url: data.grounding.poi.url,
494
+ title: data.grounding.poi.title || data.grounding.poi.name,
495
+ snippets: data.grounding.poi.snippets,
496
+ });
497
+ }
498
+
499
+ // Include map data if present
500
+ if (data.grounding?.map) {
501
+ for (const item of data.grounding.map) {
502
+ if (item.snippets?.length) {
503
+ grounding.push({
504
+ url: item.url,
505
+ title: item.title || item.name,
506
+ snippets: item.snippets,
507
+ });
508
+ }
509
+ }
510
+ }
511
+
512
+ const sources: Record<string, LLMContextSource> = {};
513
+ if (data.sources) {
514
+ for (const [sourceUrl, sourceInfo] of Object.entries(data.sources)) {
515
+ sources[sourceUrl] = {
516
+ title: sourceInfo.title,
517
+ hostname: sourceInfo.hostname,
518
+ age: sourceInfo.age,
519
+ };
520
+ }
521
+ }
522
+
523
+ // Estimate total token count from all snippets
524
+ const allText = grounding.map(g => g.snippets.join(" ")).join(" ");
525
+ const estimatedTokens = estimateTokens(allText);
526
+
527
+ result = { grounding, sources, estimatedTokens };
528
+ latencyMs = timed.latencyMs;
529
+ rateLimit = timed.rateLimit;
530
+ }
531
+
532
+ // ------------------------------------------------------------------
533
+ // Shared post-fetch: cache, format, truncate, return
534
+ // ------------------------------------------------------------------
535
+ contextCache.set(cacheKey, result);
536
+
537
+ const output = formatLLMContext(params.query, result.grounding, result.sources, {
538
+ tokenCount: result.estimatedTokens,
539
+ });
540
+
541
+ const truncation = truncateHead(output, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
542
+ let content = truncation.content;
543
+
544
+ if (truncation.truncated) {
545
+ const tempFile = await (pi as any).writeTempFile(output, { prefix: "llm-context-" });
546
+ content += `\n\n[Truncated. Full content: ${tempFile}]`;
547
+ }
548
+
549
+ const totalSnippets = result.grounding.reduce((sum, g) => sum + g.snippets.length, 0);
550
+ const details: LLMContextDetails = {
551
+ query: params.query,
552
+ sourceCount: result.grounding.length,
553
+ snippetCount: totalSnippets,
554
+ estimatedTokens: result.estimatedTokens,
555
+ cached: false,
556
+ latencyMs,
557
+ rateLimit,
558
+ threshold,
559
+ maxTokens,
560
+ provider,
561
+ };
562
+
563
+ return { content: [{ type: "text", text: content }], details };
564
+ } catch (error) {
565
+ const classified = classifyError(error);
566
+ return {
567
+ content: [{ type: "text", text: `Search failed: ${classified.message}` }],
568
+ details: {
569
+ errorKind: classified.kind,
570
+ error: classified.message,
571
+ query: params.query,
572
+ provider,
573
+ } satisfies Partial<LLMContextDetails>,
574
+ isError: true,
575
+ };
576
+ }
577
+ },
578
+
579
+ renderCall(args, theme) {
580
+ let text = theme.fg("toolTitle", theme.bold("search_and_read "));
581
+ text += theme.fg("muted", `"${args.query}"`);
582
+
583
+ const meta: string[] = [];
584
+ if (args.maxTokens && args.maxTokens !== 8192) meta.push(`${(args.maxTokens / 1000).toFixed(0)}k tokens`);
585
+ if (args.threshold && args.threshold !== "balanced") meta.push(`threshold:${args.threshold}`);
586
+ if (args.maxUrls && args.maxUrls !== 10) meta.push(`${args.maxUrls} urls`);
587
+ if (meta.length > 0) {
588
+ text += " " + theme.fg("dim", `(${meta.join(", ")})`);
589
+ }
590
+
591
+ return new Text(text, 0, 0);
592
+ },
593
+
594
+ renderResult(result, { expanded }, theme) {
595
+ const details = result.details as LLMContextDetails | undefined;
596
+ if (details?.errorKind || details?.error) {
597
+ const kindTag = details.errorKind ? theme.fg("dim", ` [${details.errorKind}]`) : "";
598
+ return new Text(theme.fg("error", `✗ ${details.error ?? "Search failed"}`) + kindTag, 0, 0);
599
+ }
600
+
601
+ const providerTag = details?.provider ? theme.fg("dim", ` [${details.provider}]`) : "";
602
+ const cacheTag = details?.cached ? theme.fg("dim", " [cached]") : "";
603
+ const latencyTag = details?.latencyMs ? theme.fg("dim", ` ${details.latencyMs}ms`) : "";
604
+ const tokenTag = details?.estimatedTokens
605
+ ? theme.fg("dim", ` ~${(details.estimatedTokens / 1000).toFixed(1)}k tokens`)
606
+ : "";
607
+
608
+ let text = theme.fg("success",
609
+ `✓ ${details?.sourceCount ?? 0} sources, ${details?.snippetCount ?? 0} snippets for "${details?.query}"`) +
610
+ providerTag + tokenTag + cacheTag + latencyTag;
611
+
612
+ if (expanded && result.content[0]?.type === "text") {
613
+ const preview = result.content[0].text.split("\n").slice(0, 10).join("\n");
614
+ text += "\n\n" + theme.fg("dim", preview);
615
+ }
616
+
617
+ return new Text(text, 0, 0);
618
+ },
619
+ });
620
+ }