@herbertgao/pi-extensions 2026.8.2 → 2026.8.4

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 (392) hide show
  1. package/README.md +50 -2
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +6 -8
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +5 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +101 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +1 -17
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +5 -3
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/{utils/agent-summary.ts → feature/agent-summary/core.ts} +37 -35
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{agent-summary.ts → agent-summary/index.ts} +2 -6
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{session-reference.ts → reference/index.ts} +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{agent-autocomplete.ts → reference/subagent.ts} +19 -9
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{pi-startup-header.ts → shell/startup-header.ts} +1 -1
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{working-message.ts → shell/working-message.ts} +5 -2
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +32 -28
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +379 -85
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +727 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +65 -775
  18. package/node_modules/@herbertgao/pi-cc-extensions/extensions/{feature → renderer}/markdown-enhance.ts +35 -31
  19. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-hover.ts → mouse/hover.ts} +5 -5
  20. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-interaction.ts → mouse/interaction.ts} +34 -35
  21. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-layout.ts → mouse/layout.ts} +5 -21
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-packets.ts → mouse/packets.ts} +1 -1
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{mouse-scroll.ts → mouse/scroll.ts} +48 -37
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/ansi-utils.ts +56 -1
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/diff-renderer.ts +2 -2
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/line-width-safety.ts +1 -1
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +12 -0
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/write-execution.ts +1 -2
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-grouping.ts → tool/grouping.ts} +18 -8
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-result.ts → tool/result.ts} +4 -13
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +0 -27
  32. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  33. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  34. package/node_modules/@herbertgao/pi-subagents/package.json +2 -2
  35. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +1 -0
  36. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +3 -1
  37. package/node_modules/@herbertgao/pi-subagents/src/index.ts +33 -35
  38. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +26 -35
  39. package/node_modules/@herbertgao/pi-subagents/src/prompts.ts +15 -1
  40. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  41. package/node_modules/pi-footer/LICENSE +21 -0
  42. package/node_modules/pi-footer/README.md +354 -0
  43. package/node_modules/pi-footer/package.json +68 -0
  44. package/node_modules/pi-footer/src/cache.ts +106 -0
  45. package/node_modules/pi-footer/src/colors.ts +237 -0
  46. package/node_modules/pi-footer/src/config.ts +202 -0
  47. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  48. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  49. package/node_modules/pi-footer/src/git.ts +135 -0
  50. package/node_modules/pi-footer/src/index.ts +268 -0
  51. package/node_modules/pi-footer/src/metrics.ts +141 -0
  52. package/node_modules/pi-footer/src/presets.ts +383 -0
  53. package/node_modules/pi-footer/src/render.ts +114 -0
  54. package/node_modules/pi-footer/src/separators.ts +52 -0
  55. package/node_modules/pi-footer/src/types.ts +122 -0
  56. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  57. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  58. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  59. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  60. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  61. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  62. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  63. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  64. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  65. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  66. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  67. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  68. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  69. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  70. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  71. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  72. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  73. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  74. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  75. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  76. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  77. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  78. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  79. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  80. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  81. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  82. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  83. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  84. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  85. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  86. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  87. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  88. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  89. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  90. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  91. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  92. package/node_modules/pi-footer/src/ui.ts +66 -0
  93. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  94. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  95. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  96. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  97. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  98. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  99. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  100. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  101. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  102. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  103. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  104. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  105. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  106. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  107. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  108. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  109. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  110. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  111. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  112. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  113. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  114. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  115. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  116. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  117. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  118. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  119. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  120. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  121. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  122. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  123. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  124. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  125. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  126. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  127. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  128. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  129. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  130. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  131. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  132. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  133. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  134. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  135. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  136. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  137. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  138. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  139. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  140. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  141. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  142. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  143. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  144. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  145. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  146. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  147. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  148. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  149. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  150. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  151. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  152. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  153. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  154. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  155. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  156. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  157. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  158. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  159. package/node_modules/pi-mcp-adapter/CHANGELOG.md +19 -0
  160. package/node_modules/pi-mcp-adapter/README.md +19 -4
  161. package/node_modules/pi-mcp-adapter/agent-dir.ts +40 -0
  162. package/node_modules/pi-mcp-adapter/commands.ts +12 -2
  163. package/node_modules/pi-mcp-adapter/direct-tools.ts +6 -8
  164. package/node_modules/pi-mcp-adapter/index.ts +15 -3
  165. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +87 -4
  166. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +138 -61
  167. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +43 -4
  168. package/node_modules/pi-mcp-adapter/oauth.ts +48 -0
  169. package/node_modules/pi-mcp-adapter/package.json +8 -2
  170. package/node_modules/pi-mcp-adapter/proxy-modes.ts +8 -10
  171. package/node_modules/pi-mcp-adapter/tool-registrar.ts +187 -3
  172. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  173. package/node_modules/pi-web-access/CHANGELOG.md +45 -0
  174. package/node_modules/pi-web-access/README.md +104 -29
  175. package/node_modules/pi-web-access/bocha.ts +221 -0
  176. package/node_modules/pi-web-access/curator-page.ts +113 -12
  177. package/node_modules/pi-web-access/curator-server.ts +56 -20
  178. package/node_modules/pi-web-access/datalab-pdf-extract.ts +568 -0
  179. package/node_modules/pi-web-access/duckduckgo.ts +123 -0
  180. package/node_modules/pi-web-access/extract.ts +288 -143
  181. package/node_modules/pi-web-access/feature-config.ts +29 -0
  182. package/node_modules/pi-web-access/gemini-api.ts +4 -0
  183. package/node_modules/pi-web-access/gemini-search.ts +45 -12
  184. package/node_modules/pi-web-access/gemini-web.ts +68 -3
  185. package/node_modules/pi-web-access/index.ts +160 -71
  186. package/node_modules/pi-web-access/jina-search.ts +284 -0
  187. package/node_modules/pi-web-access/kagi.ts +12 -12
  188. package/node_modules/pi-web-access/openai-search.ts +12 -2
  189. package/node_modules/pi-web-access/package.json +4 -3
  190. package/node_modules/pi-web-access/pdf-extract.ts +327 -228
  191. package/node_modules/pi-web-access/storage.ts +440 -2
  192. package/node_modules/pi-web-access/summary-model-scope.ts +1 -1
  193. package/node_modules/pi-web-access/summary-review.ts +4 -2
  194. package/node_modules/pi-web-access/video-extract.ts +10 -8
  195. package/node_modules/pi-web-access/xai-search.ts +12 -2
  196. package/node_modules/pi-web-access/youtube-extract.ts +2 -1
  197. package/node_modules/remote-pi/LICENSE +21 -0
  198. package/node_modules/remote-pi/README.md +675 -0
  199. package/node_modules/remote-pi/dist/actions/handlers.d.ts +122 -0
  200. package/node_modules/remote-pi/dist/actions/handlers.js +160 -0
  201. package/node_modules/remote-pi/dist/actions/handlers.js.map +1 -0
  202. package/node_modules/remote-pi/dist/actions/registry.d.ts +39 -0
  203. package/node_modules/remote-pi/dist/actions/registry.js +58 -0
  204. package/node_modules/remote-pi/dist/actions/registry.js.map +1 -0
  205. package/node_modules/remote-pi/dist/bin/supervisord.d.ts +2 -0
  206. package/node_modules/remote-pi/dist/bin/supervisord.js +86 -0
  207. package/node_modules/remote-pi/dist/bin/supervisord.js.map +1 -0
  208. package/node_modules/remote-pi/dist/commands/builtin_mirror.d.ts +58 -0
  209. package/node_modules/remote-pi/dist/commands/builtin_mirror.js +71 -0
  210. package/node_modules/remote-pi/dist/commands/builtin_mirror.js.map +1 -0
  211. package/node_modules/remote-pi/dist/commands/list_commands.d.ts +60 -0
  212. package/node_modules/remote-pi/dist/commands/list_commands.js +73 -0
  213. package/node_modules/remote-pi/dist/commands/list_commands.js.map +1 -0
  214. package/node_modules/remote-pi/dist/config.d.ts +62 -0
  215. package/node_modules/remote-pi/dist/config.js +115 -0
  216. package/node_modules/remote-pi/dist/config.js.map +1 -0
  217. package/node_modules/remote-pi/dist/daemon/client.d.ts +20 -0
  218. package/node_modules/remote-pi/dist/daemon/client.js +131 -0
  219. package/node_modules/remote-pi/dist/daemon/client.js.map +1 -0
  220. package/node_modules/remote-pi/dist/daemon/control_protocol.d.ts +176 -0
  221. package/node_modules/remote-pi/dist/daemon/control_protocol.js +63 -0
  222. package/node_modules/remote-pi/dist/daemon/control_protocol.js.map +1 -0
  223. package/node_modules/remote-pi/dist/daemon/cron_log.d.ts +45 -0
  224. package/node_modules/remote-pi/dist/daemon/cron_log.js +72 -0
  225. package/node_modules/remote-pi/dist/daemon/cron_log.js.map +1 -0
  226. package/node_modules/remote-pi/dist/daemon/cron_registry.d.ts +80 -0
  227. package/node_modules/remote-pi/dist/daemon/cron_registry.js +194 -0
  228. package/node_modules/remote-pi/dist/daemon/cron_registry.js.map +1 -0
  229. package/node_modules/remote-pi/dist/daemon/id.d.ts +24 -0
  230. package/node_modules/remote-pi/dist/daemon/id.js +36 -0
  231. package/node_modules/remote-pi/dist/daemon/id.js.map +1 -0
  232. package/node_modules/remote-pi/dist/daemon/install.d.ts +170 -0
  233. package/node_modules/remote-pi/dist/daemon/install.js +547 -0
  234. package/node_modules/remote-pi/dist/daemon/install.js.map +1 -0
  235. package/node_modules/remote-pi/dist/daemon/registry.d.ts +63 -0
  236. package/node_modules/remote-pi/dist/daemon/registry.js +153 -0
  237. package/node_modules/remote-pi/dist/daemon/registry.js.map +1 -0
  238. package/node_modules/remote-pi/dist/daemon/rpc_child.d.ts +194 -0
  239. package/node_modules/remote-pi/dist/daemon/rpc_child.js +389 -0
  240. package/node_modules/remote-pi/dist/daemon/rpc_child.js.map +1 -0
  241. package/node_modules/remote-pi/dist/daemon/supervisor.d.ts +93 -0
  242. package/node_modules/remote-pi/dist/daemon/supervisor.js +596 -0
  243. package/node_modules/remote-pi/dist/daemon/supervisor.js.map +1 -0
  244. package/node_modules/remote-pi/dist/extension_ui_bridge.d.ts +26 -0
  245. package/node_modules/remote-pi/dist/extension_ui_bridge.js +341 -0
  246. package/node_modules/remote-pi/dist/extension_ui_bridge.js.map +1 -0
  247. package/node_modules/remote-pi/dist/index.d.ts +213 -0
  248. package/node_modules/remote-pi/dist/index.js +4996 -0
  249. package/node_modules/remote-pi/dist/index.js.map +1 -0
  250. package/node_modules/remote-pi/dist/mcp/mesh_result.d.ts +10 -0
  251. package/node_modules/remote-pi/dist/mcp/mesh_result.js +42 -0
  252. package/node_modules/remote-pi/dist/mcp/mesh_result.js.map +1 -0
  253. package/node_modules/remote-pi/dist/mcp/mesh_server.d.ts +16 -0
  254. package/node_modules/remote-pi/dist/mcp/mesh_server.js +327 -0
  255. package/node_modules/remote-pi/dist/mcp/mesh_server.js.map +1 -0
  256. package/node_modules/remote-pi/dist/mesh/canonical.d.ts +30 -0
  257. package/node_modules/remote-pi/dist/mesh/canonical.js +61 -0
  258. package/node_modules/remote-pi/dist/mesh/canonical.js.map +1 -0
  259. package/node_modules/remote-pi/dist/mesh/client.d.ts +18 -0
  260. package/node_modules/remote-pi/dist/mesh/client.js +125 -0
  261. package/node_modules/remote-pi/dist/mesh/client.js.map +1 -0
  262. package/node_modules/remote-pi/dist/mesh/encoding.d.ts +38 -0
  263. package/node_modules/remote-pi/dist/mesh/encoding.js +216 -0
  264. package/node_modules/remote-pi/dist/mesh/encoding.js.map +1 -0
  265. package/node_modules/remote-pi/dist/mesh/self_revoke.d.ts +73 -0
  266. package/node_modules/remote-pi/dist/mesh/self_revoke.js +401 -0
  267. package/node_modules/remote-pi/dist/mesh/self_revoke.js.map +1 -0
  268. package/node_modules/remote-pi/dist/mesh/siblings.d.ts +49 -0
  269. package/node_modules/remote-pi/dist/mesh/siblings.js +159 -0
  270. package/node_modules/remote-pi/dist/mesh/siblings.js.map +1 -0
  271. package/node_modules/remote-pi/dist/mesh/types.d.ts +34 -0
  272. package/node_modules/remote-pi/dist/mesh/types.js +11 -0
  273. package/node_modules/remote-pi/dist/mesh/types.js.map +1 -0
  274. package/node_modules/remote-pi/dist/mesh/verify.d.ts +17 -0
  275. package/node_modules/remote-pi/dist/mesh/verify.js +86 -0
  276. package/node_modules/remote-pi/dist/mesh/verify.js.map +1 -0
  277. package/node_modules/remote-pi/dist/pairing/crypto.d.ts +8 -0
  278. package/node_modules/remote-pi/dist/pairing/crypto.js +22 -0
  279. package/node_modules/remote-pi/dist/pairing/crypto.js.map +1 -0
  280. package/node_modules/remote-pi/dist/pairing/handshake.d.ts +28 -0
  281. package/node_modules/remote-pi/dist/pairing/handshake.js +113 -0
  282. package/node_modules/remote-pi/dist/pairing/handshake.js.map +1 -0
  283. package/node_modules/remote-pi/dist/pairing/noise-sha256.d.ts +16 -0
  284. package/node_modules/remote-pi/dist/pairing/noise-sha256.js +103 -0
  285. package/node_modules/remote-pi/dist/pairing/noise-sha256.js.map +1 -0
  286. package/node_modules/remote-pi/dist/pairing/qr.d.ts +59 -0
  287. package/node_modules/remote-pi/dist/pairing/qr.js +125 -0
  288. package/node_modules/remote-pi/dist/pairing/qr.js.map +1 -0
  289. package/node_modules/remote-pi/dist/pairing/storage.d.ts +120 -0
  290. package/node_modules/remote-pi/dist/pairing/storage.js +604 -0
  291. package/node_modules/remote-pi/dist/pairing/storage.js.map +1 -0
  292. package/node_modules/remote-pi/dist/protocol/codec.d.ts +7 -0
  293. package/node_modules/remote-pi/dist/protocol/codec.js +49 -0
  294. package/node_modules/remote-pi/dist/protocol/codec.js.map +1 -0
  295. package/node_modules/remote-pi/dist/protocol/types.d.ts +393 -0
  296. package/node_modules/remote-pi/dist/protocol/types.js +2 -0
  297. package/node_modules/remote-pi/dist/protocol/types.js.map +1 -0
  298. package/node_modules/remote-pi/dist/rooms.d.ts +29 -0
  299. package/node_modules/remote-pi/dist/rooms.js +57 -0
  300. package/node_modules/remote-pi/dist/rooms.js.map +1 -0
  301. package/node_modules/remote-pi/dist/session/address.d.ts +49 -0
  302. package/node_modules/remote-pi/dist/session/address.js +58 -0
  303. package/node_modules/remote-pi/dist/session/address.js.map +1 -0
  304. package/node_modules/remote-pi/dist/session/agent_bridge.d.ts +55 -0
  305. package/node_modules/remote-pi/dist/session/agent_bridge.js +146 -0
  306. package/node_modules/remote-pi/dist/session/agent_bridge.js.map +1 -0
  307. package/node_modules/remote-pi/dist/session/bridge.d.ts +37 -0
  308. package/node_modules/remote-pi/dist/session/bridge.js +130 -0
  309. package/node_modules/remote-pi/dist/session/bridge.js.map +1 -0
  310. package/node_modules/remote-pi/dist/session/broker.d.ts +189 -0
  311. package/node_modules/remote-pi/dist/session/broker.js +507 -0
  312. package/node_modules/remote-pi/dist/session/broker.js.map +1 -0
  313. package/node_modules/remote-pi/dist/session/broker_remote.d.ts +74 -0
  314. package/node_modules/remote-pi/dist/session/broker_remote.js +583 -0
  315. package/node_modules/remote-pi/dist/session/broker_remote.js.map +1 -0
  316. package/node_modules/remote-pi/dist/session/cwd_lock.d.ts +33 -0
  317. package/node_modules/remote-pi/dist/session/cwd_lock.js +126 -0
  318. package/node_modules/remote-pi/dist/session/cwd_lock.js.map +1 -0
  319. package/node_modules/remote-pi/dist/session/envelope.d.ts +33 -0
  320. package/node_modules/remote-pi/dist/session/envelope.js +117 -0
  321. package/node_modules/remote-pi/dist/session/envelope.js.map +1 -0
  322. package/node_modules/remote-pi/dist/session/global_config.d.ts +33 -0
  323. package/node_modules/remote-pi/dist/session/global_config.js +73 -0
  324. package/node_modules/remote-pi/dist/session/global_config.js.map +1 -0
  325. package/node_modules/remote-pi/dist/session/ipc.d.ts +27 -0
  326. package/node_modules/remote-pi/dist/session/ipc.js +22 -0
  327. package/node_modules/remote-pi/dist/session/ipc.js.map +1 -0
  328. package/node_modules/remote-pi/dist/session/leader_election.d.ts +32 -0
  329. package/node_modules/remote-pi/dist/session/leader_election.js +106 -0
  330. package/node_modules/remote-pi/dist/session/leader_election.js.map +1 -0
  331. package/node_modules/remote-pi/dist/session/local_config.d.ts +55 -0
  332. package/node_modules/remote-pi/dist/session/local_config.js +157 -0
  333. package/node_modules/remote-pi/dist/session/local_config.js.map +1 -0
  334. package/node_modules/remote-pi/dist/session/mesh_message.d.ts +20 -0
  335. package/node_modules/remote-pi/dist/session/mesh_message.js +28 -0
  336. package/node_modules/remote-pi/dist/session/mesh_message.js.map +1 -0
  337. package/node_modules/remote-pi/dist/session/mesh_node.d.ts +116 -0
  338. package/node_modules/remote-pi/dist/session/mesh_node.js +573 -0
  339. package/node_modules/remote-pi/dist/session/mesh_node.js.map +1 -0
  340. package/node_modules/remote-pi/dist/session/peer.d.ts +139 -0
  341. package/node_modules/remote-pi/dist/session/peer.js +451 -0
  342. package/node_modules/remote-pi/dist/session/peer.js.map +1 -0
  343. package/node_modules/remote-pi/dist/session/peer_inventory.d.ts +13 -0
  344. package/node_modules/remote-pi/dist/session/peer_inventory.js +48 -0
  345. package/node_modules/remote-pi/dist/session/peer_inventory.js.map +1 -0
  346. package/node_modules/remote-pi/dist/session/peer_limits.d.ts +9 -0
  347. package/node_modules/remote-pi/dist/session/peer_limits.js +21 -0
  348. package/node_modules/remote-pi/dist/session/peer_limits.js.map +1 -0
  349. package/node_modules/remote-pi/dist/session/setup_wizard.d.ts +39 -0
  350. package/node_modules/remote-pi/dist/session/setup_wizard.js +63 -0
  351. package/node_modules/remote-pi/dist/session/setup_wizard.js.map +1 -0
  352. package/node_modules/remote-pi/dist/session/tool_gate.d.ts +5 -0
  353. package/node_modules/remote-pi/dist/session/tool_gate.js +11 -0
  354. package/node_modules/remote-pi/dist/session/tool_gate.js.map +1 -0
  355. package/node_modules/remote-pi/dist/session/tools.d.ts +24 -0
  356. package/node_modules/remote-pi/dist/session/tools.js +252 -0
  357. package/node_modules/remote-pi/dist/session/tools.js.map +1 -0
  358. package/node_modules/remote-pi/dist/session/wizard.d.ts +13 -0
  359. package/node_modules/remote-pi/dist/session/wizard.js +20 -0
  360. package/node_modules/remote-pi/dist/session/wizard.js.map +1 -0
  361. package/node_modules/remote-pi/dist/settings.d.ts +15 -0
  362. package/node_modules/remote-pi/dist/settings.js +52 -0
  363. package/node_modules/remote-pi/dist/settings.js.map +1 -0
  364. package/node_modules/remote-pi/dist/transport/peer_channel.d.ts +37 -0
  365. package/node_modules/remote-pi/dist/transport/peer_channel.js +98 -0
  366. package/node_modules/remote-pi/dist/transport/peer_channel.js.map +1 -0
  367. package/node_modules/remote-pi/dist/transport/pi_forward_client.d.ts +29 -0
  368. package/node_modules/remote-pi/dist/transport/pi_forward_client.js +62 -0
  369. package/node_modules/remote-pi/dist/transport/pi_forward_client.js.map +1 -0
  370. package/node_modules/remote-pi/dist/transport/relay_client.d.ts +91 -0
  371. package/node_modules/remote-pi/dist/transport/relay_client.js +206 -0
  372. package/node_modules/remote-pi/dist/transport/relay_client.js.map +1 -0
  373. package/node_modules/remote-pi/dist/ui/footer.d.ts +32 -0
  374. package/node_modules/remote-pi/dist/ui/footer.js +34 -0
  375. package/node_modules/remote-pi/dist/ui/footer.js.map +1 -0
  376. package/node_modules/remote-pi/docs/daemon.md +289 -0
  377. package/node_modules/remote-pi/package.json +88 -0
  378. package/node_modules/remote-pi/service-templates/launchd.plist.template +35 -0
  379. package/node_modules/remote-pi/service-templates/systemd.service.template +19 -0
  380. package/node_modules/remote-pi/service-templates/task-launcher.vbs.template +10 -0
  381. package/node_modules/remote-pi/service-templates/task-scheduler.xml.template +38 -0
  382. package/node_modules/remote-pi/skills/agent-network/SKILL.md +310 -0
  383. package/package.json +25 -10
  384. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/write-display-utils.ts +0 -29
  385. /package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{session-reference-core.ts → reference/session.ts} +0 -0
  386. /package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/{pi-aliases.ts → shell/aliases.ts} +0 -0
  387. /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/ATTRIBUTION.md +0 -0
  388. /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/diff-presentation.ts +0 -0
  389. /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/index.ts +0 -0
  390. /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/render-utils.ts +0 -0
  391. /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/shiki-highlight.ts +0 -0
  392. /package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{tool-diff → tool/diff}/types.ts +0 -0
@@ -1,8 +1,34 @@
1
+ import { closeSync, constants, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, type Stats, unlinkSync, writeFileSync } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
3
+ import { join } from "node:path";
1
4
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
5
  import type { ExtractedContent } from "./extract.ts";
3
6
  import type { SearchResult } from "./perplexity.ts";
7
+ import { getWebSearchConfigDir } from "./utils.ts";
4
8
 
5
9
  const CACHE_TTL_MS = 60 * 60 * 1000;
10
+ const FETCH_CACHE_DIR = "web-search-cache";
11
+ const FETCH_CACHE_VERSION = 1;
12
+ const CACHE_KEY_PATTERN = /^[A-Za-z0-9_-]+\.json$/;
13
+ const CACHE_TMP_PATTERN = /^[A-Za-z0-9_-]+\.json\.\d+\.\d+(?:\.[a-f0-9]{32})?\.tmp$/;
14
+ const CACHE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
15
+ const MAX_METADATA_TEXT = 8192;
16
+ const DEFAULT_CACHE_LIMITS = { maxEntries: 128, maxBytes: 128 * 1024 * 1024 };
17
+ const O_DIRECTORY = process.platform === "win32" ? 0 : (constants.O_DIRECTORY ?? 0);
18
+ const O_NOFOLLOW = process.platform === "win32" ? 0 : (constants.O_NOFOLLOW ?? 0);
19
+
20
+ interface FetchCacheLimits {
21
+ maxEntries: number;
22
+ maxBytes: number;
23
+ }
24
+
25
+ interface CacheFile {
26
+ name: string;
27
+ size: number;
28
+ mtimeMs: number;
29
+ dev: number;
30
+ ino: number;
31
+ }
6
32
 
7
33
  export interface QueryResultData {
8
34
  query: string;
@@ -12,6 +38,22 @@ export interface QueryResultData {
12
38
  provider?: string;
13
39
  }
14
40
 
41
+ interface FetchCacheRef {
42
+ version: typeof FETCH_CACHE_VERSION;
43
+ key: string;
44
+ storedAt: number;
45
+ }
46
+
47
+ interface StoredFetchUrlMetadata {
48
+ url: string;
49
+ title: string;
50
+ error: string | null;
51
+ contentLength: number;
52
+ mimeType?: string;
53
+ status?: number;
54
+ duration?: number;
55
+ }
56
+
15
57
  export interface StoredSearchData {
16
58
  id: string;
17
59
  type: "search" | "fetch" | "research";
@@ -19,6 +61,9 @@ export interface StoredSearchData {
19
61
  queries?: QueryResultData[];
20
62
  urls?: ExtractedContent[];
21
63
  artifact?: unknown;
64
+ fetchCache?: FetchCacheRef;
65
+ urlMetadata?: StoredFetchUrlMetadata[];
66
+ fetchCacheError?: string;
22
67
  }
23
68
 
24
69
  const storedResults = new Map<string, StoredSearchData>();
@@ -27,12 +72,387 @@ export function generateId(): string {
27
72
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
28
73
  }
29
74
 
75
+ export function getFetchCacheDir(): string {
76
+ return join(getWebSearchConfigDir(), FETCH_CACHE_DIR);
77
+ }
78
+
79
+ function fetchCachePath(key: string): string | null {
80
+ if (!CACHE_KEY_PATTERN.test(key)) return null;
81
+ return join(getFetchCacheDir(), key);
82
+ }
83
+
84
+ function cacheKeyForId(id: string): string {
85
+ if (!CACHE_ID_PATTERN.test(id)) {
86
+ throw new Error(`Invalid fetched content cache id: ${id}`);
87
+ }
88
+ return `${id}.json`;
89
+ }
90
+
91
+ function truncateMetadataText(value: string | undefined): string {
92
+ if (!value) return "";
93
+ return value.length > MAX_METADATA_TEXT ? `${value.slice(0, MAX_METADATA_TEXT)}...` : value;
94
+ }
95
+
96
+ function metadataForUrls(urls: ExtractedContent[]): StoredFetchUrlMetadata[] {
97
+ return urls.map((url) => ({
98
+ url: truncateMetadataText(url.url),
99
+ title: truncateMetadataText(url.title),
100
+ error: url.error ? truncateMetadataText(url.error) : null,
101
+ contentLength: url.content.length,
102
+ ...(url.mimeType ? { mimeType: truncateMetadataText(url.mimeType) } : {}),
103
+ ...(typeof url.status === "number" ? { status: url.status } : {}),
104
+ ...(typeof url.duration === "number" ? { duration: url.duration } : {}),
105
+ }));
106
+ }
107
+
108
+ function isFetchCacheRef(value: unknown): value is FetchCacheRef {
109
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
110
+ const ref = value as Record<string, unknown>;
111
+ return ref.version === FETCH_CACHE_VERSION &&
112
+ typeof ref.key === "string" && CACHE_KEY_PATTERN.test(ref.key) &&
113
+ typeof ref.storedAt === "number" && Number.isFinite(ref.storedAt);
114
+ }
115
+
116
+ function isStoredFetchUrlMetadata(value: unknown): value is StoredFetchUrlMetadata {
117
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
118
+ const meta = value as Record<string, unknown>;
119
+ return typeof meta.url === "string" &&
120
+ typeof meta.title === "string" &&
121
+ (meta.error === null || typeof meta.error === "string") &&
122
+ typeof meta.contentLength === "number" && Number.isFinite(meta.contentLength) && meta.contentLength >= 0 &&
123
+ (meta.mimeType === undefined || typeof meta.mimeType === "string") &&
124
+ (meta.status === undefined || typeof meta.status === "number") &&
125
+ (meta.duration === undefined || typeof meta.duration === "number");
126
+ }
127
+
128
+ function isInlineFetchedUrl(value: unknown): value is ExtractedContent {
129
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
130
+ const url = value as Record<string, unknown>;
131
+ return typeof url.url === "string" &&
132
+ typeof url.title === "string" &&
133
+ typeof url.content === "string" &&
134
+ (url.error === null || typeof url.error === "string");
135
+ }
136
+
137
+ function isInlineFetchData(data: StoredSearchData): data is StoredSearchData & { urls: ExtractedContent[] } {
138
+ return data.type === "fetch" && Array.isArray(data.urls) && data.urls.every(isInlineFetchedUrl);
139
+ }
140
+
141
+ function cacheLimits(limits?: Partial<FetchCacheLimits>): FetchCacheLimits {
142
+ const resolved = {
143
+ maxEntries: limits?.maxEntries ?? DEFAULT_CACHE_LIMITS.maxEntries,
144
+ maxBytes: limits?.maxBytes ?? DEFAULT_CACHE_LIMITS.maxBytes,
145
+ };
146
+ if (!Number.isFinite(resolved.maxEntries) || !Number.isInteger(resolved.maxEntries) || resolved.maxEntries <= 0 ||
147
+ !Number.isFinite(resolved.maxBytes) || !Number.isInteger(resolved.maxBytes) || resolved.maxBytes <= 0) {
148
+ throw new Error("Fetched content cache limits must be finite positive integers");
149
+ }
150
+ return resolved;
151
+ }
152
+
153
+ function enforceDirectoryMode(fd: number): void {
154
+ try {
155
+ fchmodSync(fd, 0o700);
156
+ } catch (err) {
157
+ if (process.platform !== "win32") throw err;
158
+ }
159
+ }
160
+
161
+ function enforceFileMode(fd: number): void {
162
+ try {
163
+ fchmodSync(fd, 0o600);
164
+ } catch (err) {
165
+ if (process.platform !== "win32") throw err;
166
+ }
167
+ }
168
+
169
+ function safeFetchCacheDir(create: true): string;
170
+ function safeFetchCacheDir(create: false): string | null;
171
+ function safeFetchCacheDir(create: boolean): string | null {
172
+ const dir = getFetchCacheDir();
173
+ if (create) mkdirSync(dir, { recursive: true, mode: 0o700 });
174
+ let before: Stats;
175
+ try {
176
+ before = lstatSync(dir);
177
+ } catch (err) {
178
+ if (!create && (err as NodeJS.ErrnoException).code === "ENOENT") return null;
179
+ throw err;
180
+ }
181
+ if (before.isSymbolicLink() || !before.isDirectory()) {
182
+ throw new Error("Fetched content cache path is not a safe directory");
183
+ }
184
+ if (process.platform === "win32") {
185
+ const after = lstatSync(dir);
186
+ if (after.isSymbolicLink() || !after.isDirectory() || after.dev !== before.dev || after.ino !== before.ino) {
187
+ throw new Error("Fetched content cache directory changed while securing it");
188
+ }
189
+ return dir;
190
+ }
191
+ let fd: number | null = null;
192
+ try {
193
+ fd = openSync(dir, constants.O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
194
+ const opened = fstatSync(fd);
195
+ if (!opened.isDirectory() || opened.dev !== before.dev || opened.ino !== before.ino) {
196
+ throw new Error("Fetched content cache directory changed while opening");
197
+ }
198
+ enforceDirectoryMode(fd);
199
+ closeSync(fd);
200
+ fd = null;
201
+ const after = lstatSync(dir);
202
+ if (after.isSymbolicLink() || !after.isDirectory() || after.dev !== before.dev || after.ino !== before.ino) {
203
+ throw new Error("Fetched content cache directory changed while securing it");
204
+ }
205
+ return dir;
206
+ } finally {
207
+ if (fd !== null) try { closeSync(fd); } catch {}
208
+ }
209
+ }
210
+
211
+ function openRegularFile(path: string): { fd: number; info: Stats } {
212
+ const before = lstatSync(path);
213
+ if (before.isSymbolicLink() || !before.isFile()) throw new Error("Fetched content cache entry is not a regular file");
214
+ const fd = openSync(path, constants.O_RDONLY | O_NOFOLLOW);
215
+ try {
216
+ const info = fstatSync(fd);
217
+ if (!info.isFile() || info.dev !== before.dev || info.ino !== before.ino) {
218
+ throw new Error("Fetched content cache entry changed while opening");
219
+ }
220
+ return { fd, info };
221
+ } catch (err) {
222
+ closeSync(fd);
223
+ throw err;
224
+ }
225
+ }
226
+
227
+ type CacheUnlinkResult = "removed" | "missing" | "changed" | "error";
228
+
229
+ function unlinkCacheFile(dir: string, file: CacheFile): CacheUnlinkResult {
230
+ try {
231
+ const root = lstatSync(dir);
232
+ if (root.isSymbolicLink() || !root.isDirectory()) return "changed";
233
+ const path = join(dir, file.name);
234
+ let current: Stats;
235
+ try {
236
+ current = lstatSync(path);
237
+ } catch (err) {
238
+ return (err as NodeJS.ErrnoException).code === "ENOENT" ? "missing" : "error";
239
+ }
240
+ if (current.isSymbolicLink() || !current.isFile() || current.dev !== file.dev || current.ino !== file.ino) return "changed";
241
+ try {
242
+ unlinkSync(path);
243
+ return "removed";
244
+ } catch (err) {
245
+ return (err as NodeJS.ErrnoException).code === "ENOENT" ? "missing" : "error";
246
+ }
247
+ } catch {
248
+ return "error";
249
+ }
250
+ }
251
+
252
+ function pruneFetchCache(now: number, limits: FetchCacheLimits, preferredKey?: string, reservation?: { key: string; bytes: number }): boolean {
253
+ let dir: string | null;
254
+ try {
255
+ dir = safeFetchCacheDir(false);
256
+ } catch {
257
+ return false;
258
+ }
259
+ if (!dir) return true;
260
+
261
+ let entries: string[];
262
+ try {
263
+ entries = readdirSync(dir);
264
+ } catch {
265
+ return false;
266
+ }
267
+
268
+ const files: CacheFile[] = [];
269
+ for (const entry of entries) {
270
+ if (!CACHE_KEY_PATTERN.test(entry) && !CACHE_TMP_PATTERN.test(entry)) continue;
271
+ const path = join(dir, entry);
272
+ let opened: ReturnType<typeof openRegularFile>;
273
+ try {
274
+ opened = openRegularFile(path);
275
+ } catch {
276
+ continue;
277
+ }
278
+ try {
279
+ enforceFileMode(opened.fd);
280
+ } catch {
281
+ closeSync(opened.fd);
282
+ continue;
283
+ }
284
+ closeSync(opened.fd);
285
+ const file = { name: entry, size: opened.info.size, mtimeMs: opened.info.mtimeMs, dev: opened.info.dev, ino: opened.info.ino };
286
+ if (now - file.mtimeMs >= CACHE_TTL_MS) {
287
+ const removed = unlinkCacheFile(dir, file);
288
+ if (removed !== "removed" && removed !== "missing") return false;
289
+ continue;
290
+ }
291
+ if (CACHE_KEY_PATTERN.test(entry)) files.push(file);
292
+ }
293
+
294
+ files.sort((a, b) => a.mtimeMs - b.mtimeMs || a.name.localeCompare(b.name));
295
+ const projectedUsage = () => {
296
+ const replaced = reservation ? files.find((file) => file.name === reservation.key) : undefined;
297
+ return {
298
+ entries: files.length + (reservation && !replaced ? 1 : 0),
299
+ bytes: files.reduce((total, file) => total + file.size, 0) + (reservation ? reservation.bytes - (replaced?.size ?? 0) : 0),
300
+ };
301
+ };
302
+ const attempted = new Set<string>();
303
+ let usage = projectedUsage();
304
+ while (usage.entries > limits.maxEntries || usage.bytes > limits.maxBytes) {
305
+ const index = files.findIndex((file) => file.name !== preferredKey && !attempted.has(file.name));
306
+ if (index < 0) break;
307
+ const file = files[index];
308
+ attempted.add(file.name);
309
+ const removed = unlinkCacheFile(dir, file);
310
+ if (removed === "removed" || removed === "missing") files.splice(index, 1);
311
+ usage = projectedUsage();
312
+ }
313
+ return usage.entries <= limits.maxEntries && usage.bytes <= limits.maxBytes;
314
+ }
315
+
316
+ function writeFetchCache(data: StoredSearchData & { urls: ExtractedContent[] }): FetchCacheRef {
317
+ const limits = DEFAULT_CACHE_LIMITS;
318
+ const serialized = JSON.stringify(data);
319
+ const size = Buffer.byteLength(serialized);
320
+ if (size > limits.maxBytes) throw new Error(`Fetched content cache entry exceeds ${limits.maxBytes} bytes`);
321
+
322
+ const dir = safeFetchCacheDir(true);
323
+ const key = cacheKeyForId(data.id);
324
+ if (!pruneFetchCache(Date.now(), limits, key, { key, bytes: size })) {
325
+ throw new Error("Fetched content cache could not reserve space for a new entry");
326
+ }
327
+ const finalPath = join(dir, key);
328
+ const tmpName = `${key}.${process.pid}.${Date.now()}.${randomBytes(16).toString("hex")}.tmp`;
329
+ const tmpPath = join(dir, tmpName);
330
+ let fd: number | null = null;
331
+ let tmpFile: CacheFile | null = null;
332
+ let renamed = false;
333
+ try {
334
+ fd = openSync(tmpPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | O_NOFOLLOW, 0o600);
335
+ const tmpInfo = fstatSync(fd);
336
+ tmpFile = { name: tmpName, size: tmpInfo.size, mtimeMs: tmpInfo.mtimeMs, dev: tmpInfo.dev, ino: tmpInfo.ino };
337
+ enforceFileMode(fd);
338
+ writeFileSync(fd, serialized, "utf8");
339
+ fsyncSync(fd);
340
+ closeSync(fd);
341
+ fd = null;
342
+ safeFetchCacheDir(false);
343
+ renameSync(tmpPath, finalPath);
344
+ renamed = true;
345
+ const written = lstatSync(finalPath);
346
+ if (!written.isFile() || written.dev !== tmpFile.dev || written.ino !== tmpFile.ino) {
347
+ throw new Error("Fetched content cache entry changed after writing");
348
+ }
349
+ if (!pruneFetchCache(Date.now(), limits, key)) {
350
+ throw new Error("Fetched content cache could not meet its limits after writing");
351
+ }
352
+ } catch (err) {
353
+ if (fd !== null) try { closeSync(fd); } catch {}
354
+ if (tmpFile) unlinkCacheFile(dir, { ...tmpFile, name: renamed ? key : tmpName });
355
+ throw err;
356
+ }
357
+ return { version: FETCH_CACHE_VERSION, key, storedAt: Date.now() };
358
+ }
359
+
360
+ function cacheWriteError(err: unknown): string {
361
+ const message = err instanceof Error ? err.message : String(err);
362
+ return `Failed to write fetched content cache: ${message}`;
363
+ }
364
+
365
+ function createFetchSessionData(data: StoredSearchData & { urls: ExtractedContent[] }, ref: FetchCacheRef | null, cacheError?: string): StoredSearchData {
366
+ return {
367
+ id: data.id,
368
+ type: "fetch",
369
+ timestamp: data.timestamp,
370
+ urlMetadata: metadataForUrls(data.urls),
371
+ ...(ref ? { fetchCache: ref } : {}),
372
+ ...(cacheError ? { fetchCacheError: truncateMetadataText(cacheError) } : {}),
373
+ };
374
+ }
375
+
376
+ function fetchUrlMetadata(data: StoredSearchData): StoredFetchUrlMetadata[] {
377
+ if (data.urlMetadata) return data.urlMetadata;
378
+ return isInlineFetchData(data) ? metadataForUrls(data.urls) : [];
379
+ }
380
+
381
+ function unavailableFetchData(data: StoredSearchData, reason: string): StoredSearchData {
382
+ return {
383
+ ...data,
384
+ urls: fetchUrlMetadata(data).map((meta) => ({
385
+ url: meta.url,
386
+ title: meta.title,
387
+ content: "",
388
+ error: reason,
389
+ ...(meta.mimeType ? { mimeType: meta.mimeType } : {}),
390
+ ...(typeof meta.status === "number" ? { status: meta.status } : {}),
391
+ ...(typeof meta.duration === "number" ? { duration: meta.duration } : {}),
392
+ })),
393
+ };
394
+ }
395
+
396
+ function readCachedFetchData(data: StoredSearchData, now = Date.now()): StoredSearchData {
397
+ if (data.type !== "fetch") return data;
398
+ if (now - data.timestamp >= CACHE_TTL_MS) {
399
+ return unavailableFetchData(data, "Cached fetched content is missing or expired");
400
+ }
401
+ if (isInlineFetchData(data)) return data;
402
+ if (!data.fetchCache) {
403
+ return unavailableFetchData(data, data.fetchCacheError ?? "Cached fetched content is unavailable");
404
+ }
405
+ const path = fetchCachePath(data.fetchCache.key);
406
+ if (!path) return unavailableFetchData(data, "Cached fetched content is missing or expired");
407
+ let fd: number | null = null;
408
+ try {
409
+ if (!safeFetchCacheDir(false)) return unavailableFetchData(data, "Cached fetched content is missing or expired");
410
+ const opened = openRegularFile(path);
411
+ fd = opened.fd;
412
+ enforceFileMode(fd);
413
+ const parsed: unknown = JSON.parse(readFileSync(fd, "utf8"));
414
+ if (!isValidStoredData(parsed) || parsed.type !== "fetch" || parsed.id !== data.id || !isInlineFetchData(parsed)) {
415
+ return unavailableFetchData(data, "Cached fetched content is invalid");
416
+ }
417
+ return { ...parsed, fetchCache: data.fetchCache, urlMetadata: data.urlMetadata };
418
+ } catch (err) {
419
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
420
+ return unavailableFetchData(data, "Cached fetched content is missing or expired");
421
+ }
422
+ const message = err instanceof Error ? err.message : String(err);
423
+ return unavailableFetchData(data, `Cached fetched content could not be read: ${message}`);
424
+ } finally {
425
+ if (fd !== null) try { closeSync(fd); } catch {}
426
+ }
427
+ }
428
+
429
+ export function pruneExpiredFetchCache(now = Date.now(), requestedLimits?: Partial<FetchCacheLimits>): void {
430
+ const limits = cacheLimits(requestedLimits);
431
+ try { pruneFetchCache(now, limits); } catch {}
432
+ }
433
+
30
434
  export function storeResult(id: string, data: StoredSearchData): void {
31
435
  storedResults.set(id, data);
32
436
  }
33
437
 
438
+ export function storeFetchedContentResult(id: string, data: StoredSearchData & { type: "fetch"; urls: ExtractedContent[] }): StoredSearchData {
439
+ let ref: FetchCacheRef | null = null;
440
+ let cacheError: string | undefined;
441
+ try {
442
+ ref = writeFetchCache(data);
443
+ } catch (err) {
444
+ cacheError = cacheWriteError(err);
445
+ }
446
+ storedResults.set(id, ref ? { ...data, fetchCache: ref, urlMetadata: metadataForUrls(data.urls) } : { ...data, fetchCacheError: cacheError });
447
+ return createFetchSessionData(data, ref, cacheError);
448
+ }
449
+
34
450
  export function getResult(id: string): StoredSearchData | null {
35
- return storedResults.get(id) ?? null;
451
+ const data = storedResults.get(id);
452
+ if (!data) return null;
453
+ const loaded = readCachedFetchData(data);
454
+ if (loaded !== data) storedResults.set(id, loaded);
455
+ return loaded;
36
456
  }
37
457
 
38
458
  export function getAllResults(): StoredSearchData[] {
@@ -40,6 +460,19 @@ export function getAllResults(): StoredSearchData[] {
40
460
  }
41
461
 
42
462
  export function deleteResult(id: string): boolean {
463
+ const data = storedResults.get(id);
464
+ if (data?.fetchCache) {
465
+ try {
466
+ const dir = safeFetchCacheDir(false);
467
+ const path = fetchCachePath(data.fetchCache.key);
468
+ if (dir && path) {
469
+ const info = lstatSync(path);
470
+ if (!info.isSymbolicLink() && info.isFile()) {
471
+ unlinkCacheFile(dir, { name: data.fetchCache.key, size: info.size, mtimeMs: info.mtimeMs, dev: info.dev, ino: info.ino });
472
+ }
473
+ }
474
+ } catch {}
475
+ }
43
476
  return storedResults.delete(id);
44
477
  }
45
478
 
@@ -54,7 +487,11 @@ function isValidStoredData(data: unknown): data is StoredSearchData {
54
487
  if (d.type !== "search" && d.type !== "fetch" && d.type !== "research") return false;
55
488
  if (typeof d.timestamp !== "number") return false;
56
489
  if (d.type === "search" && !Array.isArray(d.queries)) return false;
57
- if (d.type === "fetch" && !Array.isArray(d.urls)) return false;
490
+ if (d.type === "fetch") {
491
+ if (Array.isArray(d.urls)) return d.urls.every(isInlineFetchedUrl);
492
+ if (!Array.isArray(d.urlMetadata) || !d.urlMetadata.every(isStoredFetchUrlMetadata)) return false;
493
+ return d.fetchCache === undefined ? d.fetchCacheError === undefined || typeof d.fetchCacheError === "string" : isFetchCacheRef(d.fetchCache);
494
+ }
58
495
  if (d.type === "research" && (!d.artifact || typeof d.artifact !== "object")) return false;
59
496
  return true;
60
497
  }
@@ -62,6 +499,7 @@ function isValidStoredData(data: unknown): data is StoredSearchData {
62
499
  export function restoreFromSession(ctx: ExtensionContext): void {
63
500
  storedResults.clear();
64
501
  const now = Date.now();
502
+ pruneExpiredFetchCache(now);
65
503
 
66
504
  for (const entry of ctx.sessionManager.getBranch()) {
67
505
  if (entry.type === "custom" && entry.customType === "web-search-results") {
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
- const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
5
+ const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
6
6
 
7
7
  interface SummaryModelScopeContext {
8
8
  cwd: string;
@@ -3,6 +3,8 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
4
4
  import type { QueryResultData } from "./storage.ts";
5
5
 
6
+ type ProviderHeaders = Record<string, string | null>;
7
+
6
8
  const PREFERRED_SUMMARY_MODELS = [
7
9
  { provider: "anthropic", id: "claude-haiku-4-5" },
8
10
  { provider: "openai-codex", id: "gpt-5.3-codex-spark" },
@@ -196,14 +198,14 @@ function parseModelSelector(value: string): { provider: string; id: string } {
196
198
  async function resolveSummaryModelCandidates(
197
199
  ctx: SummaryGenerationContext,
198
200
  modelOverride?: string,
199
- ): Promise<{ candidates: Array<{ model: Model<Api>; apiKey: string; headers?: Record<string, string> }>; errors: string[] }> {
201
+ ): Promise<{ candidates: Array<{ model: Model<Api>; apiKey: string; headers?: ProviderHeaders }>; errors: string[] }> {
200
202
  const enabledModelPatterns = loadEnabledModelPatterns(ctx);
201
203
  const specs: Array<{ provider: string; id: string }> = [];
202
204
  const normalizedOverride = typeof modelOverride === "string" ? modelOverride.trim() : "";
203
205
  if (normalizedOverride.length > 0) specs.push(parseModelSelector(normalizedOverride));
204
206
  specs.push(...PREFERRED_SUMMARY_MODELS);
205
207
 
206
- const candidates: Array<{ model: Model<Api>; apiKey: string; headers?: Record<string, string> }> = [];
208
+ const candidates: Array<{ model: Model<Api>; apiKey: string; headers?: ProviderHeaders }> = [];
207
209
  const errors: string[] = [];
208
210
  const seen = new Set<string>();
209
211
  for (const spec of specs) {
@@ -3,13 +3,13 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { resolve, extname, basename, join, dirname } from "node:path";
5
5
  import { activityMonitor } from "./activity.ts";
6
+ import { canAttachImages } from "./feature-config.ts";
6
7
  import { isGeminiWebAvailable, queryWithCookies } from "./gemini-web.ts";
7
- import { queryGeminiApiWithVideo, getApiKey, fetchGeminiApi, API_BASE, redactGeminiApiResponse } from "./gemini-api.ts";
8
+ import { queryGeminiApiWithVideo, getApiKey, fetchGeminiApi, getVersionedApiBase, getUploadBase, redactGeminiApiResponse } from "./gemini-api.ts";
8
9
  import { extractHeadingTitle, type ExtractedContent, type ExtractOptions, type FrameResult } from "./extract.ts";
9
10
  import { readExecError, trimErrorText, mapFfmpegError, getWebSearchConfigPath } from "./utils.ts";
10
11
 
11
12
  const CONFIG_PATH = getWebSearchConfigPath();
12
- const UPLOAD_BASE = "https://generativelanguage.googleapis.com/upload/v1beta";
13
13
 
14
14
  const DEFAULT_VIDEO_PROMPT = `Extract the complete content of this video. Include:
15
15
  1. Video title (infer from content if not explicit), duration
@@ -182,9 +182,11 @@ export async function extractVideo(
182
182
  ?? await tryVideoGeminiWeb(info, effectivePrompt, effectiveModel, signal);
183
183
 
184
184
  if (result) {
185
- const thumbnail = await extractVideoFrame(info.absolutePath);
186
- if (!("error" in thumbnail)) {
187
- result.thumbnail = thumbnail;
185
+ if (canAttachImages()) {
186
+ const thumbnail = await extractVideoFrame(info.absolutePath);
187
+ if (!("error" in thumbnail)) {
188
+ result.thumbnail = thumbnail;
189
+ }
188
190
  }
189
191
  activityMonitor.logComplete(activityId, 200);
190
192
  return result;
@@ -311,7 +313,7 @@ async function uploadToFilesApi(
311
313
  ): Promise<{ name: string; uri: string }> {
312
314
  const displayName = basename(info.absolutePath);
313
315
 
314
- const initRes = await fetchGeminiApi(`${UPLOAD_BASE}/files`, {
316
+ const initRes = await fetchGeminiApi(`${getUploadBase()}/files`, {
315
317
  method: "POST",
316
318
  headers: {
317
319
  "X-Goog-Upload-Protocol": "resumable",
@@ -364,7 +366,7 @@ async function pollFileState(
364
366
  while (Date.now() < deadline) {
365
367
  if (signal?.aborted) throw new Error("Aborted");
366
368
 
367
- const res = await fetchGeminiApi(`${API_BASE}/${fileName}`, { signal }, apiKey);
369
+ const res = await fetchGeminiApi(`${getVersionedApiBase()}/${fileName}`, { signal }, apiKey);
368
370
  if (!res.ok) throw new Error(`File state check failed: ${res.status}`);
369
371
 
370
372
  const data = await res.json() as { state: string };
@@ -378,7 +380,7 @@ async function pollFileState(
378
380
  }
379
381
 
380
382
  function deleteGeminiFile(fileName: string, apiKey: string): void {
381
- void fetchGeminiApi(`${API_BASE}/${fileName}`, { method: "DELETE" }, apiKey).catch((err) => {
383
+ void fetchGeminiApi(`${getVersionedApiBase()}/${fileName}`, { method: "DELETE" }, apiKey).catch((err) => {
382
384
  const message = err instanceof Error ? err.message : String(err);
383
385
  console.error(`Failed to delete Gemini file ${fileName}: ${message}`);
384
386
  });
@@ -36,10 +36,12 @@ interface WebSearchConfig {
36
36
  xaiSearchModel?: unknown;
37
37
  }
38
38
 
39
+ type ProviderHeaders = Record<string, string | null>;
40
+
39
41
  interface XaiAuth {
40
42
  apiKey: string;
41
43
  model: string;
42
- headers: Record<string, string>;
44
+ headers: ProviderHeaders;
43
45
  }
44
46
 
45
47
  let cachedConfig: WebSearchConfig | null = null;
@@ -69,6 +71,14 @@ function resolveConfiguredSearchModel(value: unknown): string | undefined {
69
71
  return value.trim();
70
72
  }
71
73
 
74
+ function toRequestHeaders(headers: ProviderHeaders): Record<string, string> {
75
+ const requestHeaders: Record<string, string> = {};
76
+ for (const [name, value] of Object.entries(headers)) {
77
+ if (value !== null) requestHeaders[name] = value;
78
+ }
79
+ return requestHeaders;
80
+ }
81
+
72
82
  /**
73
83
  * Resolve auth from pi's model registry first, so a Grok subscription pays for
74
84
  * its own searches and no api key has to be configured at all. Mirrors how the
@@ -287,7 +297,7 @@ export async function searchWithXai(
287
297
  const response = await fetch(XAI_RESPONSES_URL, {
288
298
  method: "POST",
289
299
  headers: {
290
- ...auth.headers,
300
+ ...toRequestHeaders(auth.headers),
291
301
  Authorization: `Bearer ${auth.apiKey}`,
292
302
  "Content-Type": "application/json",
293
303
  },
@@ -1,6 +1,7 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { activityMonitor } from "./activity.ts";
4
+ import { canAttachImages } from "./feature-config.ts";
4
5
  import { isGeminiWebAvailable, queryWithCookies } from "./gemini-web.ts";
5
6
  import { isGeminiApiAvailable, queryGeminiApiWithVideo } from "./gemini-api.ts";
6
7
  import { isPerplexityAvailable, searchWithPerplexity } from "./perplexity.ts";
@@ -116,7 +117,7 @@ export async function extractYouTube(
116
117
 
117
118
  if (result) {
118
119
  result.url = url;
119
- if (!result.error && videoId) {
120
+ if (!result.error && videoId && canAttachImages()) {
120
121
  const thumb = await fetchYouTubeThumbnail(videoId);
121
122
  if (thumb) result.thumbnail = thumb;
122
123
  }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacob Moura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.