synabun 2.0.0 → 2.0.7

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 (507) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/NOTICE +5 -0
  3. package/README.md +2 -1
  4. package/THIRD-PARTY-LICENSES.md +59 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +153 -18
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +26 -3
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/child-path.js +303 -0
  14. package/lib/claude-hooks.js +2 -1
  15. package/lib/data-home-migration.js +2 -0
  16. package/lib/desktop-app.js +265 -0
  17. package/lib/external-tools.js +7 -3
  18. package/lib/log-redact.js +102 -0
  19. package/lib/packaged-runtime.js +138 -11
  20. package/lib/start-launcher.js +88 -1
  21. package/mcp-server/dist/http.d.ts +3 -1
  22. package/mcp-server/dist/http.d.ts.map +1 -1
  23. package/mcp-server/dist/http.js +15 -4
  24. package/mcp-server/dist/http.js.map +1 -1
  25. package/mcp-server/dist/index.d.ts.map +1 -1
  26. package/mcp-server/dist/index.js +9 -6
  27. package/mcp-server/dist/index.js.map +1 -1
  28. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  29. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  30. package/mcp-server/dist/services/budget-judgments.js +153 -0
  31. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  32. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  33. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  34. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  35. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  36. package/mcp-server/dist/services/identity.d.ts +11 -0
  37. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  38. package/mcp-server/dist/services/identity.js +24 -0
  39. package/mcp-server/dist/services/identity.js.map +1 -1
  40. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  41. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  42. package/mcp-server/dist/services/neural-interface.js +34 -2
  43. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  44. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  45. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  46. package/mcp-server/dist/services/typesafe-config.js +4 -1
  47. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  48. package/mcp-server/dist/tools/agents.d.ts +10 -7
  49. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  50. package/mcp-server/dist/tools/agents.js +34 -13
  51. package/mcp-server/dist/tools/agents.js.map +1 -1
  52. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  53. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  54. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  55. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  56. package/mcp-server/dist/tui.js +2 -1
  57. package/mcp-server/dist/tui.js.map +1 -1
  58. package/mcp-server/dist/version.d.ts +5 -0
  59. package/mcp-server/dist/version.d.ts.map +1 -0
  60. package/mcp-server/dist/version.js +21 -0
  61. package/mcp-server/dist/version.js.map +1 -0
  62. package/mcp-server/package-lock.json +2 -2
  63. package/mcp-server/package.json +1 -3
  64. package/mcp-server/run.mjs +15 -9
  65. package/mcp-server/src/http.ts +18 -3
  66. package/mcp-server/src/index.ts +9 -6
  67. package/mcp-server/src/services/budget-judgments.ts +183 -0
  68. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  69. package/mcp-server/src/services/identity.ts +27 -0
  70. package/mcp-server/src/services/neural-interface.ts +34 -2
  71. package/mcp-server/src/services/typesafe-config.ts +4 -1
  72. package/mcp-server/src/tools/agents.ts +33 -14
  73. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  74. package/mcp-server/src/tui.ts +2 -1
  75. package/mcp-server/src/version.ts +22 -0
  76. package/neural-interface/i18n/en.json +2126 -209
  77. package/neural-interface/i18n/pt-BR.json +2123 -207
  78. package/neural-interface/lib/account-usage.js +163 -0
  79. package/neural-interface/lib/assistant-api.js +316 -57
  80. package/neural-interface/lib/assistant-brains/claude.js +19 -2
  81. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  82. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  83. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  84. package/neural-interface/lib/assistant-budget-split.js +408 -0
  85. package/neural-interface/lib/assistant-budget.js +94 -95
  86. package/neural-interface/lib/assistant-catalog.js +179 -27
  87. package/neural-interface/lib/assistant-config.js +91 -14
  88. package/neural-interface/lib/assistant-dispatch.js +494 -138
  89. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  90. package/neural-interface/lib/assistant-jev.js +26 -3
  91. package/neural-interface/lib/assistant-memory.js +24 -10
  92. package/neural-interface/lib/assistant-persona.js +73 -12
  93. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  94. package/neural-interface/lib/assistant-pricing.js +21 -6
  95. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  96. package/neural-interface/lib/assistant-route-gate.js +19 -3
  97. package/neural-interface/lib/assistant-router.js +373 -42
  98. package/neural-interface/lib/assistant-runtime.js +488 -59
  99. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  100. package/neural-interface/lib/assistant-test-density.js +142 -0
  101. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  102. package/neural-interface/lib/assistant-user-context.js +908 -0
  103. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  104. package/neural-interface/lib/assistant-user-learn.js +404 -0
  105. package/neural-interface/lib/assistant-user-threads.js +226 -0
  106. package/neural-interface/lib/assistant-user-writer.js +183 -0
  107. package/neural-interface/lib/augmented-path.js +317 -53
  108. package/neural-interface/lib/browser-background-tab.js +134 -0
  109. package/neural-interface/lib/browser-screenshot.js +678 -61
  110. package/neural-interface/lib/claude-accounts.js +342 -31
  111. package/neural-interface/lib/claude-agent-bridge.js +123 -9
  112. package/neural-interface/lib/claude-executable.js +6 -1
  113. package/neural-interface/lib/claude-model-catalog.js +33 -5
  114. package/neural-interface/lib/claude-panel-auth.js +221 -0
  115. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  116. package/neural-interface/lib/claude-temporary.js +46 -7
  117. package/neural-interface/lib/cli-install-api.js +137 -0
  118. package/neural-interface/lib/cli-install.js +797 -0
  119. package/neural-interface/lib/cli-path.js +130 -0
  120. package/neural-interface/lib/cli-resolver.js +744 -0
  121. package/neural-interface/lib/cli-update-planner.js +143 -7
  122. package/neural-interface/lib/cloudflared/install.js +533 -0
  123. package/neural-interface/lib/cloudflared/manager.js +545 -0
  124. package/neural-interface/lib/cloudflared/pins.js +115 -0
  125. package/neural-interface/lib/cloudflared/pins.json +63 -0
  126. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  127. package/neural-interface/lib/codex-account-auth.js +178 -0
  128. package/neural-interface/lib/codex-accounts.js +489 -0
  129. package/neural-interface/lib/codex-capabilities.js +2 -1
  130. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  131. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  132. package/neural-interface/lib/codex-panel-auth.js +62 -0
  133. package/neural-interface/lib/codex-session-settings.js +1 -1
  134. package/neural-interface/lib/codex-settings-schema.js +120 -0
  135. package/neural-interface/lib/desktop/config.js +3 -0
  136. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  137. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  138. package/neural-interface/lib/editor-completion.js +714 -0
  139. package/neural-interface/lib/editor-config.js +149 -0
  140. package/neural-interface/lib/editor-files-api.js +268 -0
  141. package/neural-interface/lib/editor-git-api.js +134 -0
  142. package/neural-interface/lib/file-explorer-api.js +1039 -0
  143. package/neural-interface/lib/file-explorer-system.js +355 -0
  144. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  145. package/neural-interface/lib/file-explorer-watch.js +239 -0
  146. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  147. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  148. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  149. package/neural-interface/lib/git-bash/install.js +529 -0
  150. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  151. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  152. package/neural-interface/lib/git-bash/manager.js +268 -0
  153. package/neural-interface/lib/git-bash/pins.js +206 -0
  154. package/neural-interface/lib/git-bash/pins.json +34 -0
  155. package/neural-interface/lib/git-bash/resolver.js +194 -0
  156. package/neural-interface/lib/git-diff-api.js +103 -0
  157. package/neural-interface/lib/git-read.js +234 -0
  158. package/neural-interface/lib/greeting/api.js +89 -0
  159. package/neural-interface/lib/greeting/budgets.js +90 -0
  160. package/neural-interface/lib/greeting/engine.js +461 -0
  161. package/neural-interface/lib/greeting/facts.js +320 -0
  162. package/neural-interface/lib/greeting/sources.js +252 -0
  163. package/neural-interface/lib/greeting/writer.js +139 -0
  164. package/neural-interface/lib/http-guards.js +189 -13
  165. package/neural-interface/lib/install-details.js +145 -0
  166. package/neural-interface/lib/machine-names.js +88 -0
  167. package/neural-interface/lib/mcp-installer.js +14 -2
  168. package/neural-interface/lib/memory-api.js +31 -7
  169. package/neural-interface/lib/native-loop-providers.js +26 -8
  170. package/neural-interface/lib/native-loop-runtime.js +9 -4
  171. package/neural-interface/lib/os-path.js +319 -0
  172. package/neural-interface/lib/plugin-installer.js +4 -2
  173. package/neural-interface/lib/project-storage.js +5 -7
  174. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  175. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  176. package/neural-interface/lib/pty-host/core.js +2 -1
  177. package/neural-interface/lib/pty-host/manager.js +27 -2
  178. package/neural-interface/lib/remote-policy.js +20 -11
  179. package/neural-interface/lib/request-gate.js +589 -0
  180. package/neural-interface/lib/session-title-generator.js +4 -3
  181. package/neural-interface/lib/system-about.js +340 -0
  182. package/neural-interface/lib/system-activity.js +66 -0
  183. package/neural-interface/lib/terminal-env.js +11 -3
  184. package/neural-interface/lib/terminal-path.js +55 -0
  185. package/neural-interface/lib/ui-blob-api.js +108 -0
  186. package/neural-interface/lib/ui-blob-store.js +68 -0
  187. package/neural-interface/lib/virtual-socket.js +12 -1
  188. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  189. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  190. package/neural-interface/lib/whatsapp/commands.js +13 -14
  191. package/neural-interface/lib/whatsapp/config.js +134 -20
  192. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  193. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  194. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  195. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  196. package/neural-interface/lib/whatsapp/manager.js +92 -32
  197. package/neural-interface/lib/whatsapp/paths.js +11 -1
  198. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  199. package/neural-interface/lib/whatsapp/redact.js +63 -0
  200. package/neural-interface/lib/whatsapp/service.js +343 -91
  201. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  202. package/neural-interface/package-lock.json +701 -2
  203. package/neural-interface/package.json +13 -2
  204. package/neural-interface/public/index.html +2 -0
  205. package/neural-interface/public/index2d.html +2 -0
  206. package/neural-interface/public/install-app.html +9 -6
  207. package/neural-interface/public/offline.html +19 -3
  208. package/neural-interface/public/onboarding.html +107 -2
  209. package/neural-interface/public/shared/account-i18n.js +7 -0
  210. package/neural-interface/public/shared/account-menu.js +539 -0
  211. package/neural-interface/public/shared/account-usage.js +19 -0
  212. package/neural-interface/public/shared/account-view.js +335 -0
  213. package/neural-interface/public/shared/agent-runtime-options.js +50 -14
  214. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  215. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  216. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  217. package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
  218. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  219. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  220. package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
  221. package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
  222. package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
  223. package/neural-interface/public/shared/assistant/asst-render.js +197 -23
  224. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  225. package/neural-interface/public/shared/assistant/asst-route.js +95 -343
  226. package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
  227. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  228. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  229. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  230. package/neural-interface/public/shared/assistant/asst-state.js +186 -2
  231. package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
  232. package/neural-interface/public/shared/blob-store.js +111 -22
  233. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  234. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  235. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  236. package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
  237. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  238. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  239. package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
  240. package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
  241. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  242. package/neural-interface/public/shared/claude-account-view.js +3 -0
  243. package/neural-interface/public/shared/cli-install-view.js +392 -0
  244. package/neural-interface/public/shared/cli-status.js +282 -22
  245. package/neural-interface/public/shared/constants.js +6 -1
  246. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  247. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  248. package/neural-interface/public/shared/cp/cp-styles.js +61 -0
  249. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  250. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  251. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  252. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  253. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  254. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  255. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  256. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  257. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  258. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  259. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  260. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  261. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  262. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  263. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  264. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  265. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  266. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  267. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  268. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  269. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  270. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  271. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  272. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  273. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  274. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  275. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  276. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  277. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  278. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  279. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  280. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  281. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  282. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  283. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  284. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  285. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  286. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  287. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  288. package/neural-interface/public/shared/file-links.js +339 -0
  289. package/neural-interface/public/shared/git-bash-view.js +145 -0
  290. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  291. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  292. package/neural-interface/public/shared/html-shell.js +161 -165
  293. package/neural-interface/public/shared/i18n.js +19 -1
  294. package/neural-interface/public/shared/install-details-view.js +86 -0
  295. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  296. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  297. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
  298. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  299. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
  300. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
  301. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  302. package/neural-interface/public/shared/pwa-install-page.js +32 -2
  303. package/neural-interface/public/shared/pwa-install.css +3 -3
  304. package/neural-interface/public/shared/pwa-install.js +100 -1
  305. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  306. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  307. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  308. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  309. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  310. package/neural-interface/public/shared/settings/settings-ia.js +33 -4
  311. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  312. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  313. package/neural-interface/public/shared/start-bridge.js +238 -19
  314. package/neural-interface/public/shared/storage.js +30 -3
  315. package/neural-interface/public/shared/styles.css +1710 -770
  316. package/neural-interface/public/shared/synabun-mascot.js +68 -58
  317. package/neural-interface/public/shared/tunnel-view.js +167 -0
  318. package/neural-interface/public/shared/ui-claude-panel.js +369 -229
  319. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  320. package/neural-interface/public/shared/ui-detail.js +15 -0
  321. package/neural-interface/public/shared/ui-explorer.js +22 -0
  322. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  323. package/neural-interface/public/shared/ui-file-links.js +103 -0
  324. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  325. package/neural-interface/public/shared/ui-help.js +26 -0
  326. package/neural-interface/public/shared/ui-invite.js +126 -116
  327. package/neural-interface/public/shared/ui-judgments.js +10 -1
  328. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  329. package/neural-interface/public/shared/ui-loading.js +187 -19
  330. package/neural-interface/public/shared/ui-menubar.js +25 -33
  331. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  332. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  333. package/neural-interface/public/shared/ui-navbar.js +48 -22
  334. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  335. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  336. package/neural-interface/public/shared/ui-sessions.js +9 -3
  337. package/neural-interface/public/shared/ui-settings.js +509 -490
  338. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  339. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  340. package/neural-interface/public/shared/ui-sync.js +22 -0
  341. package/neural-interface/public/shared/ui-terminal.js +40 -5
  342. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  343. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  344. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  345. package/neural-interface/public/shared/ui-update.js +594 -5
  346. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  347. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  348. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  349. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  350. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  351. package/neural-interface/public/shared/worker-end.js +253 -0
  352. package/neural-interface/public/sw.js +2 -2
  353. package/neural-interface/public/variant/2d/main.js +10 -1
  354. package/neural-interface/public/variant/3d/main.js +10 -1
  355. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  356. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  357. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  358. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  359. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  360. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  361. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  362. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  363. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  364. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  365. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  366. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  367. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  368. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  369. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  370. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  371. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  372. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  373. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  374. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  375. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  376. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  377. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  378. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  379. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  380. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  381. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  382. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  383. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  384. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  385. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  386. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  387. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  388. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  389. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  390. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  391. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  392. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  393. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  394. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  395. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  396. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  397. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  398. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  399. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  400. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  401. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  402. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  403. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  404. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  405. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  406. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  407. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  408. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  409. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  410. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  411. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  412. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  413. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  414. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  415. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  416. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  417. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  418. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  419. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  420. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  421. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  422. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  423. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  424. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  425. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  426. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  427. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  428. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  429. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  430. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  431. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  432. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  433. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  434. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  435. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  436. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  437. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  438. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  439. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  440. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  441. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  442. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  443. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  444. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  445. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  446. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  447. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  448. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  449. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  450. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  451. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  452. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  453. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  454. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  455. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  456. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  457. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  458. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  459. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  460. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  461. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  462. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  463. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  464. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  465. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  466. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  467. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  468. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  469. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  470. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  471. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  472. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  473. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  474. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  475. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  476. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  477. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  478. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  479. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  480. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  481. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  482. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  483. package/neural-interface/scripts/build-vendor.mjs +78 -2
  484. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  485. package/neural-interface/server.js +2085 -1629
  486. package/neural-interface/templates/rulesets/README.md +1 -1
  487. package/package.json +5 -8
  488. package/setup.js +53 -15
  489. package/benchmarks/browser-live-results.json +0 -79
  490. package/benchmarks/browser-v2-results.json +0 -258
  491. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  492. package/neural-interface/public/category-logos/claude.svg +0 -1
  493. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  494. package/neural-interface/public/category-logos/openai.svg +0 -1
  495. package/neural-interface/public/category-logos/synabun.png +0 -0
  496. package/neural-interface/public/claude.svg +0 -1
  497. package/neural-interface/public/gallery.svg +0 -6
  498. package/neural-interface/public/leonardoai.svg +0 -1
  499. package/neural-interface/public/mcp.svg +0 -4
  500. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  501. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  502. package/neural-interface/public/sessionicon.svg +0 -8
  503. package/neural-interface/public/toolbar-album.svg +0 -8
  504. package/neural-interface/public/toolbar-question.svg +0 -6
  505. package/neural-interface/public/toolbar-sessions.svg +0 -6
  506. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  507. package/skills/synabun/synabunicon.png +0 -0
@@ -1,6 +1,7 @@
1
1
  import { spawn, spawnSync } from 'child_process';
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, chmodSync } from 'fs';
3
3
  import { join, resolve, isAbsolute } from 'path';
4
+ import { withPackagedPathLast } from './augmented-path.js';
4
5
 
5
6
  const GITHUB_URL_RE = /^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/i;
6
7
 
@@ -87,11 +88,22 @@ export function maskEnv(env) {
87
88
  return out;
88
89
  }
89
90
 
91
+ /**
92
+ * The environment of a git / package-manager run: this process's, with a
93
+ * packaged application's runtime and npm's global bin after the user's own
94
+ * PATH (augmented-path.js withPackagedPathLast): a repository is installed
95
+ * with the user's npm, pnpm, yarn, uv or pip when they have one, SynaBun's
96
+ * bundled npm only when they have none. Unchanged for an npm or Git install.
97
+ */
98
+ export function installEnv(extra = {}, base = process.env) {
99
+ return withPackagedPathLast({ ...base, ...extra });
100
+ }
101
+
90
102
  async function runCommand(cmd, args, opts = {}) {
91
103
  return new Promise((resolvePromise) => {
92
104
  const child = spawn(cmd, args, {
93
105
  cwd: opts.cwd,
94
- env: { ...process.env, ...(opts.env || {}) },
106
+ env: installEnv(opts.env || {}),
95
107
  stdio: ['ignore', 'pipe', 'pipe'],
96
108
  shell: false,
97
109
  });
@@ -466,7 +478,7 @@ export async function installDependencies({ dataHome, name, repoPath, onLine })
466
478
 
467
479
  if (hasPyproject || hasRequirements) {
468
480
  if (hasUvLock || hasPyproject) {
469
- const uvExists = spawnSync('which', ['uv']).status === 0;
481
+ const uvExists = spawnSync('which', ['uv'], { env: installEnv() }).status === 0;
470
482
  if (uvExists) {
471
483
  logInstall(dataHome, name, 'running uv sync');
472
484
  const res = await runCommand('uv', ['sync'], { cwd: repoPath, onLine: (line) => { logInstall(dataHome, name, line); onLine?.(line); } });
@@ -99,6 +99,15 @@ export function filterInjected(results, { caller, session, generation } = {}, co
99
99
  if (seen.size > 500) seen.delete(seen.values().next().value);
100
100
  return fresh;
101
101
  }
102
+ /**
103
+ * Record memories as already in an Assistant conversation's context (its user-context digest, a
104
+ * nudge or a cue: lib/assistant-user-context.js), in the same ledger automatic recall reads: they
105
+ * are not injected again in that context generation.
106
+ */
107
+ export function markInjected(rows, { session, generation } = {}) {
108
+ const list = (Array.isArray(rows) ? rows : []).filter(r => r && r.id).map(r => ({ id: r.id, revision: r.revision }));
109
+ if (list.length) filterInjected(list, { caller: 'assistant', session, generation });
110
+ }
102
111
  function input(body) {
103
112
  if (typeof body?.query !== 'string' || !body.query.trim() || body.query.length > 16000) throw new Error('query must be non-empty text of at most 16000 characters.');
104
113
  const out = {query:body.query};
@@ -316,9 +325,14 @@ async function planConflict(text, body, ctx, retrieve) {
316
325
  // The assistant passes { origin:'assistant', defer:true }: retrieval and ranking
317
326
  // run now, but nothing is rendered or committed to the injection ledger until
318
327
  // it calls finish(tier) with the size its prompt-urgency judgment chose;
319
- // finish(null) withholds the memories. The HTTP route never passes options.
320
- export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin = null, defer = false } = {}) {
328
+ // finish(null) withholds the memories. `divert` (category names; the assistant's
329
+ // user-context engine) keeps those categories out of the rendered block: they
330
+ // are searched and ranked with the rest, then returned apart as `diverted`
331
+ // (never rendered, never committed here). The HTTP route never passes options.
332
+ export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin = null, defer = false, divert = null } = {}) {
321
333
  const opts=input(body);
334
+ const divertSet=Array.isArray(divert) && divert.length ? new Set(divert.map(String)) : null;
335
+ const isDiverted=r=>!!divertSet && divertSet.has(String(r.category));
322
336
  const ctx=origin ? {...hookContext(body),origin} : hookContext(body);
323
337
  const judgeOff=body.judge === false;
324
338
  // `surface` implies ranking; `rank:true` alone keeps meaning brief-rank.
@@ -332,7 +346,7 @@ export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin
332
346
  return out;
333
347
  };
334
348
  const skipFollowup=()=>{
335
- const out=withStale({results:[],context:'',already_present:false,skipped:'referential-followup',estimated_tokens:0,ranked:false,rank_surface:null,judged:0,dropped:[],floor:null});
349
+ const out=withStale({results:[],context:'',already_present:false,skipped:'referential-followup',estimated_tokens:0,ranked:false,rank_surface:null,judged:0,dropped:[],floor:null,diverted:[]});
336
350
  return defer ? {...out,deferred:true,candidates:0,finish:()=>out} : out;
337
351
  };
338
352
  // A judged ranking decides relevance for follow-ups too; the regex is the
@@ -347,7 +361,8 @@ export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin
347
361
  const budget=Math.max(100,Math.min(2000,Number(body.token_budget)||600));
348
362
  const identity={caller:body.caller,session:body.claudeSessionId || body.session,generation:body.context_generation};
349
363
  let candidates=filterInjected(result.results,identity,false);
350
- const alreadyPresent=result.results.length>0 && candidates.length===0;
364
+ // "Already supplied" is about the block: a diverted category never renders in it.
365
+ const alreadyPresent=result.results.some(r=>!isDiverted(r)) && !candidates.some(r=>!isDiverted(r));
351
366
  let ranked=false, scores=null, dropped=[], floor=null, logId=null;
352
367
  if (canRank && (candidates.length > 1 || (floorWanted && candidates.length === 1))) {
353
368
  const settings=surfaceConfig(surface);
@@ -370,6 +385,14 @@ export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin
370
385
  }
371
386
  if (!ranked && isReferentialFollowup(opts.query)) return skipFollowup();
372
387
  }
388
+ // Diverted categories leave the candidates here, after the ranking and its floor judged them.
389
+ let diverted=[];
390
+ if (divertSet) {
391
+ diverted=candidates.filter(isDiverted).map(r=>({id:r.id,revision:r.revision,category:r.category,project:r.project,content:r.content,
392
+ subcategory:r.payload?.subcategory ?? null,importance:r.payload?.importance ?? null,created_at:r.created_at,updated_at:r.payload?.updated_at ?? r.created_at,
393
+ related_memory_ids:r.payload?.related_memory_ids ?? null,score:r.score,ranked:ranked && scores.has(r.id),relevance:ranked && scores.has(r.id) ? scores.get(r.id) : null}));
394
+ candidates=candidates.filter(r=>!isDiverted(r));
395
+ }
373
396
  // The first `lim` candidates that fit `tok` tokens (the classic path: limit / budget).
374
397
  const render=(lim,tok)=>{
375
398
  const pool=surface ? candidates.slice(0,lim) : candidates;
@@ -394,7 +417,7 @@ export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin
394
417
  // `scores` null: the row is still annotated, with no candidates.
395
418
  const outcome=(extra)=>({surface,floor,
396
419
  candidates:scores ? [...scores].map(([id,relevance])=>({id,relevance,level:relevanceLevel(relevance)})) : [],
397
- ...extra,dropped:dropped.map(d=>d.id)});
420
+ ...extra,dropped:dropped.map(d=>d.id),...(diverted.length ? {diverted:diverted.map(d=>d.id)} : {})});
398
421
  // Commit what was injected to the ledger, record it on the ranking's log row, shape the answer.
399
422
  const finalize=({results,context,delivered},extra={})=>{
400
423
  filterInjected(delivered,identity);
@@ -402,6 +425,7 @@ export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin
402
425
  const out={results:results.filter(r=>delivered.some(h=>h.id===r.id)),context,already_present:alreadyPresent,
403
426
  estimated_tokens:estimateTokens(context),degraded:result.degraded,ranked,rank_surface:ranked ? surface : null,
404
427
  judged:scores?.size ?? 0,dropped,floor};
428
+ if (divertSet) out.diverted=diverted;
405
429
  if (ranked && !delivered.length && dropped.length) out.skipped='judged-irrelevant';
406
430
  return out;
407
431
  };
@@ -415,7 +439,7 @@ export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin
415
439
  if (!tier) {
416
440
  if (logId) annotateTypeSafeLog(logId,outcome({injected:[],withheld:'prompt-urgency'}));
417
441
  settled={results:[],context:'',already_present:false,estimated_tokens:0,degraded:result.degraded,ranked,
418
- rank_surface:ranked ? surface : null,judged:scores?.size ?? 0,dropped,floor,withheld:'prompt-urgency'};
442
+ rank_surface:ranked ? surface : null,judged:scores?.size ?? 0,dropped,floor,withheld:'prompt-urgency',...(divertSet ? {diverted:[]} : {})};
419
443
  return settled;
420
444
  }
421
445
  const lim=Math.max(1,Math.min(50,Math.floor(Number(tier.limit))||limit));
@@ -424,7 +448,7 @@ export async function hookRecall(body = {}, retrieve = retrieveMemory, { origin
424
448
  return settled;
425
449
  };
426
450
  return {deferred:true,ranked,rank_surface:ranked ? surface : null,judged:scores?.size ?? 0,dropped,floor,
427
- already_present:alreadyPresent,degraded:result.degraded,candidates:candidates.length,finish};
451
+ already_present:alreadyPresent,degraded:result.degraded,candidates:candidates.length,...(divertSet ? {diverted} : {}),finish};
428
452
  }
429
453
 
430
454
  /**
@@ -248,6 +248,9 @@ export async function createCodexNativeLoopAdapter(options = {}) {
248
248
  collectMedia = null,
249
249
  // Assistant task runs: the browser policy's hook and the run's notes (a function: never in the state file).
250
250
  runMode = 'loop', onNote = null, browserPolicyTrust = null,
251
+ // Inherited variables to remove before extraEnv applies (the Codex account's launch:
252
+ // lib/codex-accounts.js launchFor() — inherited OpenAI/Codex credentials never reach the run).
253
+ unsetEnv = [],
251
254
  } = options;
252
255
  const permissionPolicy = normalizePermissionPolicy(rawPolicy);
253
256
  const capability = normalizeCapability(rawCapability);
@@ -281,7 +284,9 @@ export async function createCodexNativeLoopAdapter(options = {}) {
281
284
  // Computer-use workers: the grant unlocks the `computer` group in the MCP child.
282
285
  SYNABUN_DESKTOP_GRANT: typeof desktopGrantProvider === 'function' ? desktopGrantProvider() || undefined : undefined,
283
286
  });
284
- const env = stringEnv({ ...process.env, ...pins, CODEX_HOME: codexHome, ...(browserPolicy ? { SYNABUN_BROWSER_POLICY_CWD: cwd || process.cwd() } : {}), ...stringEnv(extraEnv) });
287
+ const inherited = { ...process.env };
288
+ for (const key of Array.isArray(unsetEnv) ? unsetEnv : []) delete inherited[key];
289
+ const env = stringEnv({ ...inherited, ...pins, CODEX_HOME: codexHome, ...(browserPolicy ? { SYNABUN_BROWSER_POLICY_CWD: cwd || process.cwd() } : {}), ...stringEnv(extraEnv) });
285
290
  const config = {};
286
291
  if (Object.keys(pins).length) config.mcp_servers = { SynaBun: { env: pins } };
287
292
  if (extended) config.model_context_window = Math.floor(Number(modelContextWindow));
@@ -423,6 +428,11 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
423
428
  extraEnv = {}, permissionPolicy: rawPolicy = 'auto', permissionBroker = null,
424
429
  capability: rawCapability = 'full', maxBudgetUsd = null, outputSchema = null, runMode = 'loop',
425
430
  desktopGrantProvider = null,
431
+ // Inherited variables to remove before extraEnv applies (the Claude account's launch:
432
+ // credentials the server was started with never reach a worker).
433
+ unsetEnv = [],
434
+ // The host's last word on the environment: on Windows the Git Bash it names (lib/git-bash/).
435
+ childEnv = null,
426
436
  } = options;
427
437
  const permissionPolicy = normalizePermissionPolicy(rawPolicy);
428
438
  const capability = normalizeCapability(rawCapability);
@@ -432,8 +442,10 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
432
442
  let alive = true;
433
443
  let sessionId = null;
434
444
  let activeTurn = null;
435
- const env = stringEnv({
436
- ...cleanClaudeEnvironment(),
445
+ const inherited = cleanClaudeEnvironment();
446
+ for (const key of Array.isArray(unsetEnv) ? unsetEnv : []) delete inherited[key];
447
+ const ownEnv = stringEnv({
448
+ ...inherited,
437
449
  SYNABUN_TERMINAL_SESSION: runId,
438
450
  SYNABUN_PROJECT: cwd ? memoryProject(cwd) : 'global',
439
451
  SYNABUN_PROFILE: 'full',
@@ -442,6 +454,7 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
442
454
  SYNABUN_RUN_MODE: runMode === 'task' ? 'task' : undefined,
443
455
  ...stringEnv(extraEnv),
444
456
  });
457
+ const env = typeof childEnv === 'function' ? stringEnv(childEnv(ownEnv)) : ownEnv;
445
458
  const brokerRequest = async (request) => {
446
459
  try {
447
460
  const reply = await permissionBroker.request({
@@ -649,11 +662,16 @@ export async function createClaudeNativeLoopAdapter(options = {}) {
649
662
  }
650
663
  if (pumpError) throw pumpError;
651
664
  })();
652
- pump.catch((error) => onEvent({
653
- provider: 'claude-code',
654
- runId,
655
- event: { type: 'synabun.error', message: error?.message || String(error) },
656
- }));
665
+ pump.catch((error) => {
666
+ // dispose() aborts the query when the run ends, however it ended, and the
667
+ // SDK throws "Claude Code process aborted by user" for it: not a failure.
668
+ if (abortController.signal.aborted) return;
669
+ onEvent({
670
+ provider: 'claude-code',
671
+ runId,
672
+ event: { type: 'synabun.error', message: error?.message || String(error) },
673
+ });
674
+ });
657
675
 
658
676
  return {
659
677
  identity: () => ({ providerSessionId: sessionId }),
@@ -373,9 +373,14 @@ export class NativeLoopRuntime {
373
373
  record.startupCancelPromise = new Promise((resolveCancel) => { record._cancelStartup = resolveCancel; });
374
374
  this.records.set(runId, record);
375
375
  this._emit('sidepanel:run-created', record);
376
- const maxMs = Math.max(1, Number(nativeState.maxMinutes) || 30) * 60_000;
377
- record.timer = setTimeout(() => { this.stop(runId, 'time_cap').catch(() => {}); }, maxMs);
378
- record.timer.unref?.();
376
+ // A task run's (assistant dispatch) maxMinutes is work time, which its dispatcher counts and
377
+ // enforces (assistant-dispatch.js, the work clock): idle time between its turns does not count,
378
+ // so it gets no wall-clock cap here. A loop's maxMinutes stays the wall clock.
379
+ if (record.runMode !== 'task') {
380
+ const maxMs = Math.max(1, Number(nativeState.maxMinutes) || 30) * 60_000;
381
+ record.timer = setTimeout(() => { this.stop(runId, 'time_cap').catch(() => {}); }, maxMs);
382
+ record.timer.unref?.();
383
+ }
379
384
 
380
385
  const startupSignal = record.startupAbortController.signal;
381
386
  const factoryPromise = Promise.resolve().then(() => factory({
@@ -510,7 +515,7 @@ export class NativeLoopRuntime {
510
515
  return;
511
516
  }
512
517
  const elapsed = this.now() - new Date(state.startedAt || record.startedAt).getTime();
513
- if (elapsed >= Math.max(1, Number(state.maxMinutes) || 30) * 60_000) {
518
+ if (record.runMode !== 'task' && elapsed >= Math.max(1, Number(state.maxMinutes) || 30) * 60_000) {
514
519
  await this.stop(record.runId, 'time_cap');
515
520
  return;
516
521
  }
@@ -0,0 +1,319 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun — the OS PATH a new terminal would get
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // A process keeps the PATH it was started with. SynaBun started from Finder,
6
+ // the Dock, the Start menu or a login item got the desktop's PATH, often
7
+ // without ~/.local/bin or Homebrew; started before a CLI was installed, it
8
+ // never sees the folder the installer added. This reads the PATH the
9
+ // operating system would give a terminal opened now:
10
+ //
11
+ // Windows the machine PATH, then the user PATH, from the registry
12
+ // (`reg query`; REG_EXPAND_SZ expanded with this process's
13
+ // environment), which is how Windows builds a new process's
14
+ // PATH. No PowerShell.
15
+ // macOS, Linux the PATH the user's login shell ends up with
16
+ // (`$SHELL -i -l -c`, which runs .zprofile/.zshrc,
17
+ // .bash_profile/.bashrc: nvm, asdf, mise, fnm, Homebrew's
18
+ // shellenv), started from the launchd default PATH, in a
19
+ // session of its own with no terminal, under a short timeout;
20
+ // on macOS also /etc/paths and /etc/paths.d
21
+ //
22
+ // The answer is cached briefly (`ttlMs`), read again in the background when it
23
+ // goes stale, and handed to augmented-path.js (rememberOsPath), whose PATH
24
+ // every lookup and spawn of a user's CLI uses. Re-check reads it again at once
25
+ // (refresh({ force: true })). A failed read keeps the previous answer and is
26
+ // retried sooner. Nothing here throws.
27
+
28
+ import { execFile, spawn } from 'node:child_process';
29
+ import { randomBytes } from 'node:crypto';
30
+ import { readFileSync, readdirSync } from 'node:fs';
31
+ import os from 'node:os';
32
+ import path from 'node:path';
33
+ import { rememberOsPath } from './augmented-path.js';
34
+
35
+ /** Where Windows keeps PATH: the machine's, then the user's (the order Windows joins them in). */
36
+ export const WINDOWS_PATH_KEYS = Object.freeze([
37
+ 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',
38
+ 'HKCU\\Environment',
39
+ ]);
40
+
41
+ /** The PATH launchd gives an app on macOS (and a sane start on Linux): what a new terminal's shell begins from. */
42
+ export const LAUNCHD_PATH = '/usr/bin:/bin:/usr/sbin:/sbin';
43
+
44
+ /** Shells that take `-i -l -c`. Others (csh, nu, xonsh, pwsh …) are read through /bin/sh -l. */
45
+ const INTERACTIVE_LOGIN_SHELLS = new Set(['bash', 'zsh', 'ksh', 'ksh93', 'mksh', 'fish', 'yash']);
46
+ const LOGIN_ONLY_SHELLS = new Set(['sh', 'dash']);
47
+ const MAX_SHELL_OUTPUT = 1024 * 1024;
48
+
49
+ export function splitPathValue(value, platform = process.platform) {
50
+ return String(value || '').split(platform === 'win32' ? ';' : ':')
51
+ .map(entry => platform === 'win32' ? entry.trim().replace(/^"(.*)"$/, '$1').trim() : entry.trim())
52
+ .filter(Boolean);
53
+ }
54
+
55
+ export function dedupePath(entries, platform = process.platform) {
56
+ const seen = new Set();
57
+ return entries.filter((entry) => {
58
+ const text = String(entry || '').trim();
59
+ if (!text) return false;
60
+ const key = platform === 'win32' ? text.toLowerCase().replace(/[\\/]+$/, '') : text.replace(/\/+$/, '') || '/';
61
+ if (seen.has(key)) return false;
62
+ seen.add(key);
63
+ return true;
64
+ });
65
+ }
66
+
67
+ /**
68
+ * The Path value in what `reg query <key> /v Path` prints:
69
+ *
70
+ * HKEY_CURRENT_USER\Environment
71
+ * Path REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Microsoft\WindowsApps;C:\Users\you\.local\bin
72
+ *
73
+ * → { type: 'REG_EXPAND_SZ', value: '…' }, or null when there is none.
74
+ */
75
+ export function parseRegQuery(text) {
76
+ for (const line of String(text || '').split(/\r?\n/)) {
77
+ const match = line.match(/^\s+(\S+)\s+(REG_EXPAND_SZ|REG_SZ)(?:\s+(.*))?$/i);
78
+ if (match && match[1].toLowerCase() === 'path') return { type: match[2].toUpperCase(), value: (match[3] || '').trim() };
79
+ }
80
+ return null;
81
+ }
82
+
83
+ /** %NAME% replaced from `env` (names compared without case, as Windows does); unknown names stay as written. */
84
+ export function expandWindowsVars(value, env = process.env) {
85
+ const lookup = new Map(Object.entries(env || {}).map(([name, val]) => [name.toUpperCase(), val]));
86
+ return String(value || '').replace(/%([^%;]+)%/g, (whole, name) => {
87
+ const found = lookup.get(name.toUpperCase());
88
+ return found == null || found === '' ? whole : String(found);
89
+ });
90
+ }
91
+
92
+ function decodeUtf8(buffer) {
93
+ try { return new TextDecoder('utf-8', { fatal: true }).decode(buffer); } catch { return null; }
94
+ }
95
+
96
+ /**
97
+ * `reg query <key> /v Path`, its output as text. reg.exe writes in the
98
+ * console's code page; a folder name outside ASCII (C:\Users\João) is then not
99
+ * UTF-8, and the query is asked again through a console switched to UTF-8
100
+ * (chcp 65001). Fixed strings only: nothing of a request reaches either line.
101
+ */
102
+ export async function runRegQuery(key, { env = process.env, timeoutMs = 3000, execFileImpl = execFile } = {}) {
103
+ const systemRoot = env.SystemRoot || env.SYSTEMROOT || 'C:\\Windows';
104
+ const reg = path.win32.join(systemRoot, 'System32', 'reg.exe');
105
+ const comspec = env.ComSpec || env.COMSPEC || path.win32.join(systemRoot, 'System32', 'cmd.exe');
106
+ const run = (file, args, extra = {}) => new Promise((resolve) => {
107
+ try {
108
+ execFileImpl(file, args, { encoding: 'buffer', timeout: timeoutMs, windowsHide: true, env, maxBuffer: 1024 * 1024, ...extra }, (error, stdout) => {
109
+ resolve({ error, stdout: Buffer.isBuffer(stdout) ? stdout : Buffer.from(String(stdout || '')) });
110
+ });
111
+ } catch (error) {
112
+ resolve({ error, stdout: Buffer.alloc(0) });
113
+ }
114
+ });
115
+ const first = await run(reg, ['query', key, '/v', 'Path']);
116
+ if (!first.stdout.length) throw first.error || new Error('reg.exe printed nothing');
117
+ const text = decodeUtf8(first.stdout);
118
+ if (text !== null) return text;
119
+ const second = await run(comspec, ['/d', '/s', '/c', `"chcp 65001 >nul & "${reg}" query "${key}" /v Path"`], { windowsVerbatimArguments: true });
120
+ return (second.stdout.length && decodeUtf8(second.stdout)) || first.stdout.toString('latin1');
121
+ }
122
+
123
+ /** The registry's PATH: machine entries, then user entries, each once. */
124
+ export async function readWindowsPath({ env = process.env, query = runRegQuery, timeoutMs = 3000 } = {}) {
125
+ const entries = [];
126
+ const sources = [];
127
+ const errors = [];
128
+ for (const key of WINDOWS_PATH_KEYS) {
129
+ try {
130
+ const found = parseRegQuery(await query(key, { env, timeoutMs }));
131
+ if (found) {
132
+ const part = splitPathValue(expandWindowsVars(found.value, env), 'win32');
133
+ entries.push(...part);
134
+ sources.push(...part.map(dir => ({ dir, source: key.startsWith('HKLM') ? 'system' : 'user' })));
135
+ }
136
+ } catch (error) {
137
+ errors.push(`${key}: ${String(error?.message || error).split(/\r?\n/)[0]}`);
138
+ }
139
+ }
140
+ return { entries: dedupePath(entries, 'win32'), sources, ok: entries.length > 0, errors };
141
+ }
142
+
143
+ /** The user's login shell: $SHELL, else the account's (a launchd- or desktop-started app may have no SHELL). */
144
+ export function loginShellOf({ env = process.env, userInfo = () => os.userInfo() } = {}) {
145
+ if (env.SHELL) return env.SHELL;
146
+ try { return userInfo()?.shell || null; } catch { return null; }
147
+ }
148
+
149
+ /**
150
+ * How to ask `shell` for the PATH a login session ends with. The PATH is
151
+ * printed on a line of its own between two marker lines, so whatever the
152
+ * startup files print around it (banners, warnings) is ignored.
153
+ */
154
+ export function loginShellCommand(shell, marker) {
155
+ const name = path.posix.basename(String(shell || '')).toLowerCase();
156
+ const script = `printf '%s\\n' '${marker}' "$PATH" '${marker}'`;
157
+ if (INTERACTIVE_LOGIN_SHELLS.has(name)) return { file: shell, args: ['-i', '-l', '-c', script] };
158
+ if (LOGIN_ONLY_SHELLS.has(name)) return { file: shell, args: ['-l', '-c', script] };
159
+ return { file: '/bin/sh', args: ['-l', '-c', script] };
160
+ }
161
+
162
+ /** The PATH line between the two markers, or null. */
163
+ export function parseLoginShellOutput(text, marker) {
164
+ const lines = String(text || '').split(/\r?\n/);
165
+ for (let i = 0; i + 2 < lines.length; i++) {
166
+ if (lines[i] === marker && lines[i + 2] === marker) return lines[i + 1];
167
+ }
168
+ return null;
169
+ }
170
+
171
+ /**
172
+ * The PATH the user's login shell builds. Started in a session of its own
173
+ * (detached: no controlling terminal, so a startup file cannot wait on
174
+ * /dev/tty), from the launchd default PATH, in the home folder; killed with
175
+ * its whole group after `timeoutMs`.
176
+ */
177
+ export function readLoginShellPath({
178
+ env = process.env,
179
+ platform = process.platform,
180
+ shell = loginShellOf({ env }),
181
+ spawnImpl = spawn,
182
+ timeoutMs = 4000,
183
+ home = env.HOME || os.homedir(),
184
+ } = {}) {
185
+ return new Promise((resolve) => {
186
+ const marker = `SYNABUN_PATH_${randomBytes(6).toString('hex').toUpperCase()}`;
187
+ const command = loginShellCommand(shell || '/bin/sh', marker);
188
+ const childEnv = { ...env, PATH: LAUNCHD_PATH, TERM: 'dumb' };
189
+ let child = null;
190
+ let out = '';
191
+ let settled = false;
192
+ let timer = null;
193
+ const finish = (result) => {
194
+ if (settled) return;
195
+ settled = true;
196
+ clearTimeout(timer);
197
+ resolve({ shell: command.file, ...result });
198
+ };
199
+ const killGroup = () => {
200
+ try { process.kill(-child.pid, 'SIGKILL'); } catch { try { child?.kill('SIGKILL'); } catch { /* gone */ } }
201
+ };
202
+ try {
203
+ child = spawnImpl(command.file, command.args, { env: childEnv, cwd: home, stdio: ['ignore', 'pipe', 'ignore'], detached: platform !== 'win32' });
204
+ } catch (error) {
205
+ return finish({ entries: [], ok: false, error: `${command.file} could not be started: ${error?.message || error}` });
206
+ }
207
+ timer = setTimeout(() => {
208
+ killGroup();
209
+ finish({ entries: [], ok: false, error: `${command.file} did not answer within ${Math.round(timeoutMs / 100) / 10} s` });
210
+ }, timeoutMs);
211
+ timer.unref?.();
212
+ child.on('error', (error) => finish({ entries: [], ok: false, error: `${command.file} could not be started: ${error?.message || error}` }));
213
+ child.stdout?.setEncoding?.('utf8');
214
+ child.stdout?.on('data', (chunk) => {
215
+ out += chunk;
216
+ if (out.length > MAX_SHELL_OUTPUT) {
217
+ killGroup();
218
+ finish({ entries: [], ok: false, error: `${command.file} printed too much` });
219
+ }
220
+ });
221
+ child.on('close', () => {
222
+ const line = parseLoginShellOutput(out, marker);
223
+ if (line == null) return finish({ entries: [], ok: false, error: `${command.file} did not print a PATH` });
224
+ finish({ entries: dedupePath(splitPathValue(line, platform), platform), ok: true, error: null });
225
+ });
226
+ });
227
+ }
228
+
229
+ /** macOS's /etc/paths, then each file of /etc/paths.d in name order (what path_helper builds a login PATH from). */
230
+ export function readEtcPaths({ readFile = readFileSync, readdir = readdirSync } = {}) {
231
+ const out = [];
232
+ const take = (file) => {
233
+ try {
234
+ for (const line of String(readFile(file, 'utf8')).split(/\r?\n/)) {
235
+ const entry = line.trim();
236
+ if (entry && !entry.startsWith('#')) out.push(entry);
237
+ }
238
+ } catch { /* not there */ }
239
+ };
240
+ take('/etc/paths');
241
+ let names = [];
242
+ try { names = readdir('/etc/paths.d').map(String).sort(); } catch { /* not there */ }
243
+ for (const name of names) take(`/etc/paths.d/${name}`);
244
+ return out;
245
+ }
246
+
247
+ /**
248
+ * A cached reader of the OS PATH. `cached()` answers at once (and starts a
249
+ * read in the background when the answer is stale); `refresh()` waits for one.
250
+ */
251
+ export function createOsPathReader({
252
+ platform = process.platform,
253
+ env = process.env,
254
+ ttlMs = 60_000,
255
+ failureTtlMs = 15_000,
256
+ readWindows = readWindowsPath,
257
+ readLoginShell = readLoginShellPath,
258
+ readEtc = readEtcPaths,
259
+ remember = rememberOsPath,
260
+ now = Date.now,
261
+ } = {}) {
262
+ let state = { entries: [], at: 0, ok: false, source: null, error: null, shell: null };
263
+ let inflight = null;
264
+
265
+ async function read() {
266
+ if (env.SYNABUN_TOOL_DISCOVERY === 'path') return { entries: [], ok: true, source: 'off', error: null, shell: null };
267
+ if (platform === 'win32') {
268
+ const found = await readWindows({ env });
269
+ return { entries: found.entries, sources: found.sources || [], ok: found.ok, source: 'registry', error: found.errors?.length ? found.errors.join('; ') : null, shell: null };
270
+ }
271
+ const found = await readLoginShell({ env, platform });
272
+ const etc = platform === 'darwin' ? readEtc() : [];
273
+ const entries = dedupePath([...(found.entries || []), ...etc], platform);
274
+ return {
275
+ entries,
276
+ ok: !!found.ok,
277
+ source: found.ok ? 'login-shell' : etc.length ? 'etc-paths' : 'none',
278
+ error: found.error || null,
279
+ shell: found.shell || null,
280
+ };
281
+ }
282
+
283
+ function snapshot() {
284
+ return { entries: state.entries.slice(), sources: (state.sources || []).map(entry => ({ ...entry })), at: state.at, ok: state.ok, source: state.source, error: state.error, shell: state.shell };
285
+ }
286
+
287
+ function stale() {
288
+ return !state.at || now() - state.at >= (state.ok ? ttlMs : failureTtlMs);
289
+ }
290
+
291
+ function refresh({ force = false } = {}) {
292
+ if (inflight) return inflight;
293
+ if (!force && !stale()) return Promise.resolve(snapshot());
294
+ inflight = (async () => {
295
+ let result;
296
+ try { result = await read(); } catch (error) { result = { entries: [], ok: false, source: 'none', error: String(error?.message || error), shell: null }; }
297
+ // A read that failed keeps what an earlier one found.
298
+ const entries = result.ok || !state.entries.length ? result.entries : state.entries;
299
+ state = { ...result, entries, at: now() };
300
+ try { remember(entries); } catch { /* nothing to tell */ }
301
+ return snapshot();
302
+ })().finally(() => { inflight = null; });
303
+ return inflight;
304
+ }
305
+
306
+ function cached() {
307
+ if (stale() && !inflight) refresh().catch(() => {});
308
+ return state.entries.slice();
309
+ }
310
+
311
+ function invalidate() {
312
+ state = { ...state, at: 0 };
313
+ }
314
+
315
+ return { refresh, cached, invalidate, snapshot, stale };
316
+ }
317
+
318
+ /** This process's reader (server.js starts it at boot; Re-check forces it). */
319
+ export const osPath = createOsPathReader();
@@ -5,6 +5,7 @@ import {
5
5
  } from 'fs';
6
6
  import { join, resolve, dirname } from 'path';
7
7
  import os from 'node:os';
8
+ import { runGitReadSync } from './git-read.js';
8
9
 
9
10
  const PLUGINS_ROOT = () => join(os.homedir(), '.claude', 'plugins');
10
11
  const INSTALLED_PLUGINS_FILE = () => join(PLUGINS_ROOT(), 'installed_plugins.json');
@@ -48,9 +49,10 @@ function claudeNotFoundErr() {
48
49
  return { ok: false, error: 'Claude Code CLI not found on PATH. Install `claude` to manage plugins.' };
49
50
  }
50
51
 
52
+ // (git is asked as lib/git-read.js runs a read: the folder comes from a request, or from Claude Code's registry)
51
53
  function gitHeadSha(repoPath) {
52
54
  if (!repoPath || !existsSync(repoPath)) return 'unknown';
53
- const r = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: repoPath, encoding: 'utf-8' });
55
+ const r = runGitReadSync(repoPath, ['rev-parse', 'HEAD'], { encoding: 'utf-8' });
54
56
  const out = (r.stdout || '').trim();
55
57
  return out || 'unknown';
56
58
  }
@@ -252,7 +254,7 @@ export function repairSynabunPlugins() {
252
254
  if (!meta.ok) { failed.push({ marketplaceName: name, error: meta.error }); continue; }
253
255
  let githubUrl = null;
254
256
  try {
255
- const r = spawnSync('git', ['remote', 'get-url', 'origin'], { cwd: path, encoding: 'utf-8' });
257
+ const r = runGitReadSync(path, ['remote', 'get-url', 'origin'], { encoding: 'utf-8' });
256
258
  const u = (r.stdout || '').trim();
257
259
  if (/^https?:\/\//.test(u)) githubUrl = u.replace(/\.git$/, '');
258
260
  } catch {}
@@ -18,8 +18,8 @@ import {
18
18
  resolve,
19
19
  sep,
20
20
  } from 'node:path';
21
- import { spawnSync } from 'node:child_process';
22
21
  import { isMainThread, parentPort, Worker, workerData } from 'node:worker_threads';
22
+ import { runGitReadSync } from './git-read.js';
23
23
 
24
24
  export const PROJECT_STORAGE_SAFE_CONFIRMATION = 'CLEAR SELECTED CACHES';
25
25
  export const PROJECT_STORAGE_DEPENDENCY_CONFIRMATION = 'DELETE DEPENDENCIES';
@@ -216,11 +216,11 @@ function measurePath(rootPath, { entryLimit = MEASURE_ENTRY_LIMIT } = {}) {
216
216
  return { bytes, fileCount, incomplete, containsRepository };
217
217
  }
218
218
 
219
+ // Both questions are reads, run as lib/git-read.js runs one: a registered project is a folder like any other, and
220
+ // listing its files starts nothing its own git configuration names (`git ls-files` asks the file system monitor).
219
221
  function gitInfoForProject(projectRoot) {
220
222
  const canonicalProjectRoot = canonicalExistingPath(projectRoot);
221
- const probe = spawnSync('git', ['-C', projectRoot, 'rev-parse', '--show-toplevel'], {
222
- encoding: 'utf8', timeout: 5_000, windowsHide: true,
223
- });
223
+ const probe = runGitReadSync(projectRoot, ['rev-parse', '--show-toplevel'], { encoding: 'utf8', timeout: 5_000 });
224
224
  if (probe.status !== 0) {
225
225
  let current = canonicalProjectRoot;
226
226
  while (true) {
@@ -237,9 +237,7 @@ function gitInfoForProject(projectRoot) {
237
237
  if (!pathIsWithin(gitRoot, canonicalProjectRoot, { allowEqual: true })) {
238
238
  return { isRepository: false, verified: false, root: gitRoot, tracked: [] };
239
239
  }
240
- const listed = spawnSync('git', ['-C', gitRoot, 'ls-files', '-z'], {
241
- encoding: 'buffer', timeout: 20_000, maxBuffer: 64 * 1024 * 1024, windowsHide: true,
242
- });
240
+ const listed = runGitReadSync(gitRoot, ['ls-files', '-z'], { encoding: 'buffer', timeout: 20_000, maxBuffer: 64 * 1024 * 1024 });
243
241
  if (listed.status !== 0 || listed.error) {
244
242
  return { isRepository: true, verified: false, root: gitRoot, tracked: [] };
245
243
  }