total-recall-brain 3.44.0 → 3.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (502) hide show
  1. package/bin/total-recall.mjs +10 -6
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +13 -8
  4. package/frontend/dist/assets/index-BRYE1jmv.css +1 -0
  5. package/frontend/dist/assets/index-D1goIPjC.js +5598 -0
  6. package/frontend/dist/index.html +2 -2
  7. package/frontend/dist/sw.js +5 -4
  8. package/frontend/src/App.tsx +185 -238
  9. package/frontend/src/api/auth-inventory.ts +62 -0
  10. package/frontend/src/api/memory.ts +13 -8
  11. package/frontend/src/api/plugin-credentials.ts +23 -0
  12. package/frontend/src/api/plugins.ts +37 -53
  13. package/frontend/src/api/workspace-drawers.ts +97 -0
  14. package/frontend/src/components/BrainSelector.tsx +9 -3
  15. package/frontend/src/components/plugins/PluginPanel.tsx +83 -42
  16. package/frontend/src/components/plugins/PluginSettings.tsx +10 -20
  17. package/frontend/src/components/plugins/PluginWorkspace.tsx +367 -0
  18. package/frontend/src/components/plugins/ScopedPluginCommands.tsx +142 -0
  19. package/frontend/src/components/plugins/plugin-frame.ts +44 -33
  20. package/frontend/src/components/plugins/scoped-plugin-operations.ts +28 -0
  21. package/frontend/src/components/workspace/DrawerChat.tsx +110 -0
  22. package/frontend/src/components/workspace/DrawerLayout.tsx +196 -0
  23. package/frontend/src/components/workspace/DrawerMarkup.tsx +49 -0
  24. package/frontend/src/components/workspace/PluginChunkLibrary.tsx +92 -0
  25. package/frontend/src/components/workspace/PluginHome.tsx +76 -0
  26. package/frontend/src/components/workspace/WorkspaceDrawers.tsx +96 -0
  27. package/frontend/src/components/workspace/WorkspacePage.tsx +95 -0
  28. package/frontend/src/components/workspace/usePluginDesign.ts +38 -0
  29. package/frontend/src/index.css +409 -136
  30. package/frontend/src/pages/HelpPage.tsx +15 -46
  31. package/frontend/src/pages/MemoryPage.tsx +19 -36
  32. package/frontend/src/pages/SettingsPage.tsx +1 -1
  33. package/metadata.plugin.schema.json +53 -227
  34. package/package.json +1 -1
  35. package/scaffold/.agent/skills/plugin/SKILL.md +13 -11
  36. package/scaffold/.agent/skills/plugin/evals/README.md +14 -21
  37. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +15 -18
  38. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +19 -24
  39. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +12 -20
  40. package/scaffold/.agent/skills/plugin/evals/cases/04-home-collects-credential.mjs +35 -0
  41. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +8 -28
  42. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +85 -47
  43. package/scaffold/.agent/skills/plugin/evals/run.mjs +17 -18
  44. package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +4 -4
  45. package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +2 -2
  46. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +4 -4
  47. package/scaffold/.agent/skills/plugin/references/anti-patterns.md +2 -2
  48. package/scaffold/.agent/skills/plugin/references/generative-home.md +38 -0
  49. package/scaffold/.agent/skills/plugin/references/scaffold.md +5 -5
  50. package/scaffold/.agent/skills/plugin/references/surfaces.md +13 -4
  51. package/scaffold/.agent/skills/plugin/scripts/record.mjs +75 -153
  52. package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +4 -4
  53. package/scaffold/.agent/skills/total-recall/plugins/auth/DESIGN.md +76 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +3 -1
  55. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +20 -15
  56. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
  57. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +2 -1
  58. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +13 -51
  59. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
  60. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  61. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/access-overview.md +17 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/agent-login.md +20 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/baseline-audit.md +22 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-repository-binding.md +17 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-store-maintenance.md +17 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-password.md +18 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-sessions.md +18 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/endpoint-probe.md +23 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/google-sign-in.md +21 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/mesh-machines.md +16 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/passkey-enrollment.md +21 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/registered-brains.md +16 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/scoped-access-keys.md +17 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/secret-inventory.md +25 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/signing-key-rotation.md +22 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +375 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/automations/DESIGN.md +53 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +247 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +130 -0
  84. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  85. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  87. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +36 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +151 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  90. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  91. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/control-ledger.md +42 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/local-inventory-triage.md +17 -0
  93. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/pending-control.md +39 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/run-evidence.md +41 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/schedule-vs-run-evidence.md +17 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/source-coverage.md +41 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/home.js +278 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/code-quality/DESIGN.md +53 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +23 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +5 -3
  101. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +61 -7
  102. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  103. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  104. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
  105. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +2 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +40 -40
  107. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/config.md +15 -0
  108. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/detect.md +15 -0
  109. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/gate.md +15 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/init.md +15 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/report.md +17 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/run.md +17 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/snapshot.md +15 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +55 -23
  115. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/home.mjs +214 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/DESIGN.md +146 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
  118. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +2 -1
  119. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +51 -34
  120. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +1 -1
  121. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/create.md +35 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/history.md +35 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/library.md +49 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/list.md +42 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/promote.md +40 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/read.md +25 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/remove.md +26 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/rollback.md +26 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/show.md +53 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/status.md +40 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test-path.md +38 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test.md +46 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/update.md +32 -0
  134. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/home.js +533 -0
  135. package/scaffold/.agent/skills/total-recall/plugins/embeddings/DESIGN.md +110 -0
  136. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
  137. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +2 -1
  138. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +13 -10
  139. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/benchmark.md +49 -0
  140. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/coverage.md +53 -0
  141. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/models.md +43 -0
  142. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/pull.md +41 -0
  143. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/status.md +59 -0
  144. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/home.js +353 -0
  145. package/scaffold/.agent/skills/total-recall/plugins/git/DESIGN.md +31 -0
  146. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +136 -0
  147. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +140 -24
  148. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +9 -9
  149. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +4 -2
  150. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +151 -0
  151. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  152. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  153. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  154. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  155. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  156. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  157. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  158. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  159. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +54 -0
  160. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  161. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +174 -0
  162. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  163. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  164. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  165. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  166. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  167. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  168. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +92 -0
  169. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +156 -0
  170. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/unmerged.mjs +182 -0
  171. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  172. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/change-comparison.md +27 -0
  173. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/commit-identity.md +22 -0
  174. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/create-worktree.md +25 -0
  175. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/registered-worktrees.md +22 -0
  176. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/remove-worktree.md +23 -0
  177. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/repository-ledger.md +25 -0
  178. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/unmerged-work.md +22 -0
  179. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/upstream-divergence.md +17 -0
  180. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-conflict-triage.md +17 -0
  181. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-terminal.md +22 -0
  182. package/scaffold/.agent/skills/total-recall/plugins/git/ui/home.js +273 -0
  183. package/scaffold/.agent/skills/total-recall/plugins/mesh/DESIGN.md +35 -0
  184. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +4 -5
  185. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -3
  186. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
  187. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +2 -1
  188. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +37 -39
  189. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +5 -3
  190. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/active-device-dispatch.md +22 -0
  191. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-probe.md +25 -0
  192. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-server.md +22 -0
  193. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/credential-readiness.md +23 -0
  194. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/elected-leader.md +22 -0
  195. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/enrollment-key.md +22 -0
  196. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/file-receipt.md +25 -0
  197. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-execution.md +23 -0
  198. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-presence.md +23 -0
  199. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/interactive-ssh.md +22 -0
  200. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/local-enrollment.md +23 -0
  201. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/mesh-policy.md +23 -0
  202. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/network-atlas.md +25 -0
  203. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-roster.md +26 -0
  204. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-selection-proof.md +17 -0
  205. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/owner-observations.md +23 -0
  206. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/peer-path.md +23 -0
  207. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/pinned-leader.md +24 -0
  208. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/reachability-evidence-ladder.md +17 -0
  209. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/recorded-login.md +23 -0
  210. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/remote-execution.md +22 -0
  211. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/secret-grants.md +26 -0
  212. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/{card.js → home.js} +93 -22
  213. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/DESIGN.md +86 -0
  214. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +44 -9
  215. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  216. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  217. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +128 -24
  218. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
  219. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +3 -2
  220. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +188 -34
  221. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
  222. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
  223. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  224. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
  225. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/chat.mjs +80 -0
  226. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  227. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
  228. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
  229. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
  230. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  231. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
  232. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  233. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  234. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
  235. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  236. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
  237. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  238. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  239. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
  240. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
  241. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
  242. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  243. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  244. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
  245. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  246. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  247. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  248. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  249. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  250. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  251. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  252. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  253. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  254. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
  255. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  256. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
  257. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  258. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/adapter-docs-freshness.md +16 -0
  259. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/chat-handoff.md +26 -0
  260. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/fleet-maintenance.md +21 -0
  261. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/harness-inventory.md +21 -0
  262. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-access.md +18 -0
  263. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-usage-verification.md +15 -0
  264. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/independent-review.md +17 -0
  265. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/managed-execution.md +20 -0
  266. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/official-documentation.md +18 -0
  267. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/quota-recovery.md +21 -0
  268. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/review-routing-decision.md +16 -0
  269. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/route-work.md +18 -0
  270. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/routing-evidence.md +18 -0
  271. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/run-ledger.md +18 -0
  272. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/home.js +198 -0
  273. package/scaffold/.agent/skills/total-recall/plugins/openwiki/DESIGN.md +75 -0
  274. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +2 -0
  275. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +64 -32
  276. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
  277. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +2 -1
  278. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +10 -44
  279. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +5 -4
  280. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +3 -4
  281. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +23 -18
  282. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +8 -3
  283. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  284. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +123 -113
  285. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +3 -5
  286. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +21 -47
  287. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +17 -6
  288. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/auto-refresh.md +18 -0
  289. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/bootstrap-pages.md +18 -0
  290. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generate-wiki.md +21 -0
  291. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generator-settings.md +18 -0
  292. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/host-probe.md +18 -0
  293. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/ingest-preview.md +18 -0
  294. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-hygiene.md +21 -0
  295. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-topology.md +19 -0
  296. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/passage-search.md +19 -0
  297. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/propagation-plan.md +18 -0
  298. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/provider-credentials.md +18 -0
  299. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/refresh-report.md +18 -0
  300. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/task-evidence.md +22 -0
  301. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/upstream-record.md +18 -0
  302. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/watch-upstream.md +18 -0
  303. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/wiki-atlas.md +19 -0
  304. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +261 -513
  305. package/scaffold/.agent/skills/total-recall/plugins/security/DESIGN.md +84 -0
  306. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +4 -2
  307. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -19
  308. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
  309. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +2 -3
  310. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +19 -52
  311. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/device-evidence.md +21 -0
  312. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/evidence-desk.md +18 -0
  313. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/guided-device-review.md +17 -0
  314. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/host-and-network.md +21 -0
  315. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/https-headers.md +17 -0
  316. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/mesh-coverage.md +18 -0
  317. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/private-security-tools.md +16 -0
  318. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/single-origin-header-check.md +17 -0
  319. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/source-review.md +21 -0
  320. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/ssh-permission-plan.md +17 -0
  321. package/scaffold/.agent/skills/total-recall/plugins/security/ui/home.js +283 -0
  322. package/scaffold/.agent/skills/total-recall/plugins/ssss/DESIGN.md +93 -0
  323. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +2 -2
  324. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -1
  325. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +5 -3
  326. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/conformance.md +26 -0
  327. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/registry.md +37 -0
  328. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/repair.md +42 -0
  329. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/scaffold.md +33 -0
  330. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/status.md +43 -0
  331. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validate.md +34 -0
  332. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validation-diagnostics.md +17 -0
  333. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/workspace-registry-preflight.md +17 -0
  334. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/home.js +348 -0
  335. package/scaffold/.agent/skills/total-recall/references/operations.md +3 -1
  336. package/src/cli/agent.mjs +1 -1
  337. package/src/cli/api.mjs +3 -3
  338. package/src/cli/brief.mjs +3 -0
  339. package/src/cli/command.mjs +41 -2
  340. package/src/cli/config.mjs +16 -0
  341. package/src/cli/connect.mjs +2 -7
  342. package/src/cli/dream.mjs +93 -69
  343. package/src/cli/harness.mjs +4 -4
  344. package/src/cli/help.mjs +1 -1
  345. package/src/cli/init.mjs +0 -25
  346. package/src/cli/lessons.mjs +51 -0
  347. package/src/cli/memory-plugin.mjs +1 -1
  348. package/src/cli/plugin/chunks.mjs +145 -0
  349. package/src/cli/plugin/copies.mjs +64 -14
  350. package/src/cli/plugin/create.mjs +30 -59
  351. package/src/cli/plugin/index.mjs +31 -16
  352. package/src/cli/plugin/install.mjs +10 -15
  353. package/src/cli/plugin/remove.mjs +1 -1
  354. package/src/cli/plugin/search.mjs +2 -2
  355. package/src/cli/preflight.mjs +19 -6
  356. package/src/cli/proposals.mjs +1 -1
  357. package/src/cli/recall.mjs +66 -23
  358. package/src/cli/remember.mjs +3 -14
  359. package/src/cli/server.mjs +18 -0
  360. package/src/cli/setup.mjs +2 -9
  361. package/src/cli/start.mjs +1 -0
  362. package/src/cli/startup.mjs +19 -0
  363. package/src/cli/task.mjs +9 -2
  364. package/src/cli/update.mjs +6 -2
  365. package/src/core/access-ledger.mjs +78 -0
  366. package/src/core/agent-manager.mjs +2 -2
  367. package/src/core/app-deploy/design-tokens.mjs +14 -9
  368. package/src/core/app-deploy/ui-elements.mjs +1 -1
  369. package/src/core/brain-locate.mjs +15 -0
  370. package/src/core/brain-registry.mjs +5 -0
  371. package/src/core/brain-updates.mjs +35 -11
  372. package/src/core/claude-hooks.mjs +3 -1
  373. package/src/core/cleanup-task.mjs +15 -0
  374. package/src/core/cli-commands.mjs +2 -0
  375. package/src/core/cli-maintenance.mjs +4 -1
  376. package/src/core/cli-semantic-index.mjs +23 -3
  377. package/src/core/context-compiler.mjs +20 -0
  378. package/src/core/daemon-control.mjs +5 -1
  379. package/src/core/daemon-loop.mjs +39 -62
  380. package/src/core/dashboard-bundle.mjs +261 -0
  381. package/src/core/default-plugins.mjs +1 -1
  382. package/src/core/dream.mjs +33 -105
  383. package/src/core/ensure-dependencies.mjs +152 -0
  384. package/src/core/fast-recall.mjs +2 -2
  385. package/src/core/field-evaluation.mjs +23 -0
  386. package/src/core/gate-mode.mjs +1 -1
  387. package/src/core/harness-hooks.mjs +14 -2
  388. package/src/core/lesson-hooks.mjs +74 -0
  389. package/src/core/lesson-memory.mjs +24 -0
  390. package/src/core/lesson-sessions.mjs +52 -0
  391. package/src/core/lessons.mjs +96 -0
  392. package/src/core/memory-brain.mjs +30 -8
  393. package/src/core/meta-harness.mjs +2 -2
  394. package/src/core/optimizer.mjs +66 -165
  395. package/src/core/plugin-activation.mjs +63 -20
  396. package/src/core/plugin-contracts/{card.mjs → manifest.mjs} +26 -57
  397. package/src/core/plugin-contracts/startup-check.mjs +49 -0
  398. package/src/core/plugin-contracts/ui.mjs +9 -3
  399. package/src/core/plugin-distribution.mjs +6 -6
  400. package/src/core/plugin-lifecycle.mjs +58 -0
  401. package/src/core/plugin-loader.mjs +21 -8
  402. package/src/core/plugin-runner.mjs +21 -10
  403. package/src/core/plugin-runtime.mjs +40 -1
  404. package/src/core/plugin-startup-checks.mjs +44 -0
  405. package/src/core/plugin-state.mjs +19 -5
  406. package/src/core/plugin-store.mjs +64 -33
  407. package/src/core/post-mortem.mjs +52 -36
  408. package/src/core/preflight-gate.mjs +23 -2
  409. package/src/core/project-id.mjs +15 -5
  410. package/src/core/proposal-applier.mjs +31 -2
  411. package/src/core/repo-sync.mjs +4 -6
  412. package/src/core/required-plugins.mjs +57 -10
  413. package/src/core/rule-length.mjs +23 -0
  414. package/src/core/runtime.mjs +1 -1
  415. package/src/core/scheduler.mjs +35 -43
  416. package/src/core/schema.mjs +3 -2
  417. package/src/core/search.mjs +4 -3
  418. package/src/core/server-service.mjs +148 -0
  419. package/src/core/session-watcher.mjs +19 -1
  420. package/src/core/skill-gap.mjs +16 -0
  421. package/src/core/ssss-kernel-bridge.mjs +32 -4
  422. package/src/core/ssss-operation-service.mjs +1 -0
  423. package/src/core/startup-health.mjs +34 -4
  424. package/src/core/surface.mjs +65 -54
  425. package/src/core/task-executors.mjs +54 -106
  426. package/src/core/testing/plugin-fixture.mjs +22 -21
  427. package/src/core/throttled-fetch.mjs +25 -18
  428. package/src/core/ui-chunk-choice.mjs +77 -0
  429. package/src/core/ui-chunk.registry.json +23 -0
  430. package/src/core/ui-chunks.mjs +434 -0
  431. package/src/core/validated-write.mjs +1 -0
  432. package/src/core/vault-cache.mjs +58 -59
  433. package/src/core/vector-field.mjs +134 -192
  434. package/src/core/workspace-compositions.mjs +151 -0
  435. package/src/core/workspace-drawers.mjs +79 -0
  436. package/src/server/api.mjs +4 -27
  437. package/src/server/index.mjs +28 -21
  438. package/src/server/invoke-core-child.mjs +24 -0
  439. package/src/server/invoke-core-operations.mjs +102 -0
  440. package/src/server/memory-events.mjs +31 -0
  441. package/src/server/plugin-brain-targets.mjs +89 -0
  442. package/src/server/rest.mjs +2 -0
  443. package/src/server/route-inventory.mjs +3 -2
  444. package/src/server/route-manifest.json +47 -55
  445. package/src/server/routes/_shared.mjs +7 -14
  446. package/src/server/routes/brains.mjs +29 -62
  447. package/src/server/routes/context.mjs +0 -41
  448. package/src/server/routes/dashboard.mjs +26 -0
  449. package/src/server/routes/invoke.mjs +26 -1
  450. package/src/server/routes/plugins.mjs +154 -45
  451. package/src/server/routes/ssss.mjs +0 -169
  452. package/src/server/routes/system.mjs +0 -15
  453. package/src/server/routes/tasks.mjs +0 -26
  454. package/src/server/routes/ui-chunks.mjs +192 -0
  455. package/src/server/routes/vault.mjs +2 -23
  456. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  457. package/frontend/dist/assets/index-DtwsDyFM.css +0 -1
  458. package/frontend/src/components/plugins/card/CardEngine.tsx +0 -108
  459. package/frontend/src/components/plugins/card/CardScenes.tsx +0 -103
  460. package/frontend/src/components/plugins/card/ExecutionPicker.tsx +0 -64
  461. package/frontend/src/components/plugins/card/args.ts +0 -28
  462. package/frontend/src/components/plugins/card/card.css +0 -32
  463. package/frontend/src/components/plugins/card/cli-line.ts +0 -13
  464. package/frontend/src/components/plugins/card/contract.ts +0 -91
  465. package/frontend/src/components/plugins/card/format.ts +0 -49
  466. package/frontend/src/components/plugins/card/runner-context.ts +0 -11
  467. package/frontend/src/components/plugins/card/runner.tsx +0 -43
  468. package/frontend/src/components/plugins/card/theme.ts +0 -35
  469. package/frontend/src/components/plugins/card/use-source.ts +0 -22
  470. package/frontend/src/components/plugins/card/widgets.tsx +0 -94
  471. package/frontend/src/components/plugins/workspace/WorkspaceHost.tsx +0 -23
  472. package/frontend/src/pages/PluginsPage.tsx +0 -1719
  473. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +0 -47
  474. package/scaffold/.agent/skills/plugin/prompts/06-card.md +0 -46
  475. package/scaffold/.agent/skills/plugin/references/card.md +0 -90
  476. package/scaffold/.agent/skills/plugin/subagents/card-designer.md +0 -60
  477. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +0 -67
  478. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +0 -1179
  479. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +0 -32
  480. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +0 -588
  481. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +0 -575
  482. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +0 -32
  483. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +0 -482
  484. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  485. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  486. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +0 -100
  487. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +0 -637
  488. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +0 -217
  489. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +0 -32
  490. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +0 -1276
  491. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +0 -45
  492. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +0 -1401
  493. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +0 -4
  494. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +0 -41
  495. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +0 -396
  496. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +0 -98
  497. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/DESIGN.md +0 -15
  498. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/card.js +0 -182
  499. package/src/core/append-log.mjs +0 -131
  500. package/src/core/plugin-tasks.mjs +0 -173
  501. package/templates/onboarding-interview.md +0 -82
  502. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
@@ -0,0 +1,434 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { createValidator } from '@ssss/cli/validator';
5
+ import { discoverPlugins, resolvePluginFile, validatePluginManifest } from './plugin-loader.mjs';
6
+ import { pluginActivation } from './plugin-state.mjs';
7
+ import { executePluginState, listPluginStates, PluginStateStore } from './plugin-state-store.mjs';
8
+ import { collectCssVariables, extractFrontmatter, resolveTokenReferences } from './app-deploy/design-tokens.mjs';
9
+
10
+ const type = 'ui-chunk:chunk';
11
+ const idPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
12
+ const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
13
+ const surfaces = new Set(['page', 'left', 'right', 'bottom']);
14
+ const maxFileBytes = 64 * 1024;
15
+ const maxQueryLength = 1024;
16
+ const POOL = 'workspace-chunk-pool';
17
+ export const uiChunkRegistryPath = path.join(import.meta.dirname, 'ui-chunk.registry.json');
18
+ const registry = JSON.parse(fs.readFileSync(uiChunkRegistryPath, 'utf8'));
19
+ const validator = createValidator({ extensions: [registry] });
20
+
21
+ export class UiChunkInputError extends Error {}
22
+ class UiChunkFileError extends Error {}
23
+
24
+ function isId(value) {
25
+ return typeof value === 'string' && value.length <= 64 && idPattern.test(value);
26
+ }
27
+
28
+ function isText(value, max) {
29
+ return typeof value === 'string' && value.trim().length > 0 &&
30
+ value.length <= max && !/[\u0000-\u001f\u007f]/u.test(value);
31
+ }
32
+
33
+ function validTimestamp(value) {
34
+ return typeof value === 'string' && timestampPattern.test(value) && Number.isFinite(Date.parse(value));
35
+ }
36
+
37
+ function validList(list, max, test) {
38
+ return Array.isArray(list) && list.length > 0 && list.length <= max &&
39
+ new Set(list).size === list.length && list.every(test);
40
+ }
41
+
42
+ // An installed plugin itself may be a deliberate owner link. Files and directories
43
+ // inside its resolved root must be regular, and never traverse another link.
44
+ function ownerFile(root, relative) {
45
+ const filename = path.join(root, relative);
46
+ if (!filename.startsWith(root + path.sep) || fs.realpathSync(filename) !== filename || !fs.lstatSync(filename).isFile()) {
47
+ throw new UiChunkFileError('Chunk files and DESIGN.md must be regular files inside the plugin.');
48
+ }
49
+ return filename;
50
+ }
51
+
52
+ function readOwnerFile(root, relative) {
53
+ const filename = ownerFile(root, relative);
54
+ const fd = fs.openSync(filename, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
55
+ try {
56
+ const stat = fs.fstatSync(fd);
57
+ if (!stat.isFile() || fs.realpathSync(filename) !== filename) {
58
+ throw new UiChunkFileError('Chunk files must be regular files inside the plugin.');
59
+ }
60
+ if (stat.size > maxFileBytes) throw new UiChunkFileError('Chunk file exceeds 64 KiB.');
61
+ const buffer = Buffer.alloc(Math.min(stat.size + 1, maxFileBytes + 1));
62
+ let used = 0;
63
+ while (used < buffer.length) {
64
+ const count = fs.readSync(fd, buffer, used, buffer.length - used, null);
65
+ if (count === 0) break;
66
+ used += count;
67
+ }
68
+ if (used !== stat.size) throw new UiChunkFileError('Chunk file changed while being read. Retry.');
69
+ return buffer.subarray(0, used);
70
+ } finally {
71
+ fs.closeSync(fd);
72
+ }
73
+ }
74
+
75
+ const componentProperties = {
76
+ backgroundColor: 'background-color', textColor: 'color', color: 'color',
77
+ borderColor: 'border-color', borderWidth: 'border-width', borderStyle: 'border-style',
78
+ rounded: 'border-radius', padding: 'padding', gap: 'gap', size: 'inline-size',
79
+ width: 'width', height: 'height', fontFamily: 'font-family', fontSize: 'font-size',
80
+ fontWeight: 'font-weight', lineHeight: 'line-height', letterSpacing: 'letter-spacing',
81
+ fontFeature: 'font-feature-settings', fontVariation: 'font-variation-settings',
82
+ };
83
+
84
+ function designValue(value) {
85
+ const css = String(value);
86
+ if (/[;{}<>\\\r\n]|\/\*|\burl\s*\(/i.test(css)) {
87
+ throw new UiChunkFileError('Unsafe design token value.');
88
+ }
89
+ return css;
90
+ }
91
+
92
+ export function compilePluginDesign(markdown) {
93
+ // Older owners published the token map in a fenced YAML section.
94
+ const legacy = !/^\uFEFF?---(?:\r?\n|$)/.test(markdown) &&
95
+ markdown.match(/^```yaml\r?\n(tokens:\r?\n[\s\S]*?)^```[ \t]*$/m);
96
+ if (/^\uFEFF?---(?:\r?\n|$)/.test(markdown) && !/^\uFEFF?---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/.test(markdown)) {
97
+ throw new UiChunkFileError('Incomplete design frontmatter.');
98
+ }
99
+ const tokens = extractFrontmatter(legacy ? '---\n' + legacy[1] + '---\n' : markdown);
100
+ const flat = Object.entries(tokens.tokens || {}).filter(([name, value]) =>
101
+ /^(?:color|font|spacing|radius|border-radius)(?:-[a-z0-9-]+)?$/.test(name) &&
102
+ (typeof value === 'string' || typeof value === 'number'));
103
+ const entries = new Map([...flat, ...collectCssVariables(tokens)]);
104
+ const properties = Object.fromEntries([...entries].map(([name, value]) => ['--' + name, designValue(value)]));
105
+ const resolved = resolveTokenReferences(tokens);
106
+ const components = [];
107
+ const rules = Object.entries(resolved.components || {}).map(([component, values]) => {
108
+ if (!/^[A-Za-z0-9_-]{1,80}$/.test(component) || !values || typeof values !== 'object') return '';
109
+ const definitions = { ...values.typography, ...values };
110
+ const declarations = Object.entries(componentProperties).flatMap(([key, property]) => {
111
+ const value = definitions[key];
112
+ return typeof value === 'string' || typeof value === 'number' ? [property + ':' + designValue(value) + ';'] : [];
113
+ });
114
+ if (!declarations.length) return '';
115
+ components.push(component);
116
+ return '[data-design-component="' + component + '"]{' + declarations.join('') + '}';
117
+ });
118
+ const css = ':root{' + Object.entries(properties).map(([name, value]) => name + ':' + value + ';').join('') + '}\n' +
119
+ rules.join('\n');
120
+ return { css, components };
121
+ }
122
+
123
+ export function readPluginDesign(plugin, { projectRoot = process.cwd(), revision } = {}) {
124
+ if (!isId(plugin)) throw new UiChunkInputError('Invalid plugin ID.');
125
+ const owner = discoverPlugins(projectRoot).find(entry => entry.id === plugin && entry.valid);
126
+ if (!owner || !pluginActivation(owner, { projectRoot }).enabled) return null;
127
+ const root = fs.realpathSync(owner.dir);
128
+ const declared = owner.manifest.ui?.design_tokens;
129
+ for (const file of declared ? [declared] : ['DESIGN.md', 'ui/DESIGN.md']) {
130
+ let content;
131
+ try {
132
+ content = readOwnerFile(root, file);
133
+ } catch (error) {
134
+ if (!declared && error.code === 'ENOENT') continue;
135
+ throw new UiChunkInputError('The plugin design file could not be read.');
136
+ }
137
+ const version = createHash('sha256').update(content).digest('hex');
138
+ if (version === revision) return { unchanged: true };
139
+ try {
140
+ return { revision: version, ...compilePluginDesign(new TextDecoder('utf-8', { fatal: true }).decode(content)) };
141
+ } catch {
142
+ throw new UiChunkInputError('The plugin design contains invalid tokens. Showing the last valid design.');
143
+ }
144
+ }
145
+ return { revision: null, css: '', components: [] };
146
+ }
147
+
148
+ function chunkDirectory(root) {
149
+ for (const relative of ['ui', 'ui/chunks']) {
150
+ const directory = path.join(root, relative);
151
+ let stat;
152
+ try {
153
+ stat = fs.lstatSync(directory);
154
+ } catch (error) {
155
+ if (error.code === 'ENOENT') return null;
156
+ throw error;
157
+ }
158
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
159
+ throw new UiChunkFileError('ui/chunks must be a regular directory inside the plugin.');
160
+ }
161
+ }
162
+ return path.join(root, 'ui', 'chunks');
163
+ }
164
+
165
+ function nativePanel(manifest, id, root) {
166
+ const element = manifest.ui?.elements?.find(entry => entry.id === id);
167
+ if (!element || !['panel', 'form', 'list', 'detail', 'widget'].includes(element.kind)) {
168
+ throw new UiChunkFileError('panel must identify a declared native content element.');
169
+ }
170
+ try {
171
+ resolvePluginFile(root, element.module);
172
+ } catch {
173
+ throw new UiChunkFileError('The declared panel module is unavailable.');
174
+ }
175
+ }
176
+
177
+ function readChunk(root, manifest, id) {
178
+ const contents = readOwnerFile(root, 'ui/chunks/' + id + '.md');
179
+ return parseChunk(contents, root, manifest, id);
180
+ }
181
+
182
+ function parseChunk(contents, root, manifest, id) {
183
+ let source;
184
+ try {
185
+ source = new TextDecoder('utf-8', { fatal: true }).decode(contents);
186
+ } catch {
187
+ throw new UiChunkFileError('Chunk file must be UTF-8.');
188
+ }
189
+ const result = validator.validateDocument(source);
190
+ if (!result.valid || result.data?.type !== type) {
191
+ throw new UiChunkFileError('Invalid SSSS UI chunk frontmatter. Run total-recall ssss validate for field errors.');
192
+ }
193
+ const data = result.data;
194
+ if (data.plugin !== manifest.id || data.id !== id) {
195
+ throw new UiChunkFileError('Chunk identity must match the owning manifest and filename.');
196
+ }
197
+ if (!isText(data.title, 120) || !isText(data.description, 600) || !validTimestamp(data.timestamp)) {
198
+ throw new UiChunkFileError('Chunk needs a short title, description and ISO timestamp.');
199
+ }
200
+ if (!validList(data.intents, 12, item => isText(item, 100)) ||
201
+ !validList(data.surfaces, 4, surface => surfaces.has(surface))) {
202
+ throw new UiChunkFileError('Chunk needs distinct intent strings and allowed surfaces.');
203
+ }
204
+ if (data.panel !== undefined) {
205
+ if (!isId(data.panel)) throw new UiChunkFileError('Invalid panel ID.');
206
+ nativePanel(manifest, data.panel, root);
207
+ }
208
+ const operations = new Map(manifest.cli?.subcommands?.map(command => [command.name, command]));
209
+ if (data.operations !== undefined && !validList(data.operations, 24, operation => operations.has(operation))) {
210
+ throw new UiChunkFileError('operations must name distinct CLI operations declared by this plugin.');
211
+ }
212
+ if (data.data !== undefined && !validList(data.data, 24, ref => {
213
+ if (!isText(ref, 200)) return false;
214
+ const separator = ref.indexOf(':');
215
+ const operation = separator > 0 && operations.get(ref.slice(0, separator));
216
+ return operation && operation.sensitive_output !== true &&
217
+ /^(?:\/(?:[^~/]|~[01])*)+$/u.test(ref.slice(separator + 1));
218
+ })) {
219
+ throw new UiChunkFileError('data must name non-sensitive declared operations and JSON pointers in the form operation:/path.');
220
+ }
221
+ const html = result.body.trim();
222
+ if (!html.startsWith('<') || html.includes('\0')) {
223
+ throw new UiChunkFileError('Chunk body must contain raw HTML.');
224
+ }
225
+ return {
226
+ key: data.plugin + ':' + id,
227
+ plugin: data.plugin,
228
+ id,
229
+ title: data.title.trim(),
230
+ description: data.description.trim(),
231
+ intents: data.intents.map(intent => intent.trim()),
232
+ surfaces: data.surfaces,
233
+ ...(data.panel === undefined ? {} : { panel: data.panel }),
234
+ ...(data.operations === undefined ? {} : { operations: data.operations }),
235
+ ...(data.data === undefined ? {} : { data: data.data }),
236
+ sha256: createHash('sha256').update(contents).digest('hex'),
237
+ html,
238
+ };
239
+ }
240
+
241
+ function metadata({ html: _html, ...chunk }) {
242
+ return chunk;
243
+ }
244
+
245
+ function poolDirectory(brainDir) {
246
+ return path.join(brainDir, 'config', POOL);
247
+ }
248
+
249
+ function poolFile(brainDir, plugin, id) {
250
+ return path.join(poolDirectory(brainDir), plugin + ':' + id + '.json');
251
+ }
252
+
253
+ function poolChunk(value, root, manifest, id) {
254
+ if (!value || value.plugin !== manifest.id || value.id !== id ||
255
+ typeof value.markdown !== 'string' || Buffer.byteLength(value.markdown) > maxFileBytes) {
256
+ throw new UiChunkFileError('Invalid workspace block record.');
257
+ }
258
+ return { ...parseChunk(Buffer.from(value.markdown), root, manifest, id), origin: 'workspace' };
259
+ }
260
+
261
+ export async function addWorkspaceUiChunk(brainDir, { plugin, id, markdown }, projectRoot = process.cwd()) {
262
+ if (!isId(plugin) || !isId(id)) throw new UiChunkInputError('Invalid block identity.');
263
+ const owner = discoverPlugins(projectRoot).find(entry => entry.id === plugin && entry.valid);
264
+ if (!owner || !pluginActivation(owner, { projectRoot }).enabled) throw new UiChunkInputError('The block owner must be installed and enabled.');
265
+ const root = fs.realpathSync(owner.dir);
266
+ const directory = chunkDirectory(root);
267
+ if (directory && fs.lstatSync(path.join(directory, id + '.md'), { throwIfNoEntry: false })) {
268
+ throw new UiChunkInputError('That block ID belongs to the installed plugin.');
269
+ }
270
+ const value = { plugin, id, markdown };
271
+ let chunk;
272
+ try { chunk = poolChunk(value, root, owner.manifest, id); }
273
+ catch (error) {
274
+ if (error instanceof UiChunkFileError) throw new UiChunkInputError(error.message);
275
+ throw error;
276
+ }
277
+ const file = poolFile(brainDir, plugin, id);
278
+ const result = await executePluginState({ file, plugin: POOL, action: 'write', value, expectedVersion: null });
279
+ return { chunk: metadata(chunk), version: result.version };
280
+ }
281
+
282
+ function diagnostic(error, plugin, id) {
283
+ const message = error instanceof UiChunkFileError
284
+ ? error.message
285
+ : 'Could not read the owner chunk files.';
286
+ return { plugin, ...(id ? { id } : {}), message };
287
+ }
288
+
289
+ export function validateUiChunkFile(filename, { pluginDir } = {}) {
290
+ if (typeof filename !== 'string' || !filename) throw new UiChunkInputError('Specify a chunk file.');
291
+ const file = path.resolve(filename);
292
+ const root = fs.realpathSync(pluginDir || path.resolve(file, '../../..'));
293
+ const id = path.basename(file, '.md');
294
+ if (!isId(id) || fs.realpathSync(path.dirname(file)) !== chunkDirectory(root) ||
295
+ path.basename(file) !== id + '.md') {
296
+ throw new UiChunkInputError('Place the file at ui/chunks/<id>.md inside its plugin.');
297
+ }
298
+ const manifest = JSON.parse(readOwnerFile(root, 'plugin.json').toString('utf8'));
299
+ if (!validatePluginManifest(manifest).valid) throw new UiChunkInputError('The owner plugin manifest is invalid.');
300
+ ownerFile(root, 'DESIGN.md');
301
+ return metadata(readChunk(root, manifest, id));
302
+ }
303
+
304
+ function tokens(value) {
305
+ return new Set(value.normalize('NFKC').toLowerCase().match(/[\p{L}\p{N}]+/gu) || []);
306
+ }
307
+
308
+ function scoreChunk(chunk, query, terms) {
309
+ if (!query) return 0;
310
+ if (query === chunk.key || query === chunk.id) return Number.MAX_SAFE_INTEGER;
311
+ const identity = tokens(chunk.key + ' ' + chunk.title);
312
+ const intents = tokens([...chunk.intents, ...(chunk.operations || []), ...(chunk.data || [])].join(' '));
313
+ const description = tokens(chunk.description);
314
+ let score = 0;
315
+ for (const term of terms) {
316
+ score += (identity.has(term) ? 5 : 0) + (intents.has(term) ? 3 : 0) + (description.has(term) ? 1 : 0);
317
+ }
318
+ return score;
319
+ }
320
+
321
+ export function searchUiChunks({ projectRoot = process.cwd(), brainDir, plugin, surface, query = '', limit = 40, offset = 0 } = {}) {
322
+ if (plugin !== undefined && !isId(plugin)) throw new UiChunkInputError('Invalid plugin ID.');
323
+ if (surface !== undefined && !surfaces.has(surface)) throw new UiChunkInputError('Invalid UI surface.');
324
+ if (typeof query !== 'string' || query.length > maxQueryLength) throw new UiChunkInputError('Chunk query exceeds 1024 characters or is invalid.');
325
+ if (!Number.isInteger(limit) || limit < 1 || limit > 200) throw new UiChunkInputError('Limit must be between 1 and 200.');
326
+ if (!Number.isSafeInteger(offset) || offset < 0) throw new UiChunkInputError('Offset must be a nonnegative integer.');
327
+ const chunks = [];
328
+ const diagnostics = [];
329
+ const normalized = query.trim().toLowerCase();
330
+ const terms = tokens(normalized);
331
+ const installed = discoverPlugins(projectRoot).filter(owner => owner.valid && (!plugin || owner.id === plugin));
332
+ const visible = new Map();
333
+ const keys = new Set();
334
+ const add = (record) => {
335
+ if (keys.has(record.key)) return;
336
+ keys.add(record.key);
337
+ if (surface && !record.surfaces.includes(surface)) return;
338
+ const score = scoreChunk(record, normalized, terms);
339
+ if (!normalized || score > 0) chunks.push({ chunk: metadata(record), score });
340
+ };
341
+
342
+ for (const owner of installed) {
343
+ try {
344
+ if (!pluginActivation(owner, { projectRoot }).enabled) continue;
345
+ const root = fs.realpathSync(owner.dir);
346
+ const directory = chunkDirectory(root);
347
+ visible.set(owner.id, { root, manifest: owner.manifest, directory });
348
+ if (!directory) continue;
349
+ const files = fs.readdirSync(directory).filter(name => name.endsWith('.md')).sort();
350
+ if (!files.length) continue;
351
+ ownerFile(root, 'DESIGN.md');
352
+ for (const file of files) {
353
+ const id = path.basename(file, '.md');
354
+ if (!isId(id)) {
355
+ diagnostics.push({ plugin: owner.id, message: 'Chunk filename must be <kebab-case-id>.md.' });
356
+ continue;
357
+ }
358
+ try {
359
+ add(readChunk(root, owner.manifest, id));
360
+ } catch (error) {
361
+ diagnostics.push(diagnostic(error, owner.id, id));
362
+ }
363
+ }
364
+ } catch (error) {
365
+ diagnostics.push(diagnostic(error, owner.id));
366
+ }
367
+ }
368
+ if (brainDir) {
369
+ for (const { value } of listPluginStates(poolDirectory(brainDir), POOL)) {
370
+ const owner = visible.get(value?.plugin);
371
+ if (!owner || !isId(value.id)) continue;
372
+ try {
373
+ if (owner.directory && fs.lstatSync(path.join(owner.directory, value.id + '.md'), { throwIfNoEntry: false })) continue;
374
+ add(poolChunk(value, owner.root, owner.manifest, value.id));
375
+ } catch (error) { diagnostics.push(diagnostic(error, value.plugin, value.id)); }
376
+ }
377
+ }
378
+ chunks.sort((a, b) => b.score - a.score || a.chunk.key.localeCompare(b.chunk.key, 'en'));
379
+ return { chunks: chunks.slice(offset, offset + limit).map(entry => entry.chunk), total: chunks.length, diagnostics, retrieval: 'lexical' };
380
+ }
381
+
382
+ export function getUiChunk(plugin, id, { projectRoot = process.cwd(), surface, brainDir } = {}) {
383
+ if (!isId(plugin) || !isId(id)) throw new UiChunkInputError('Invalid chunk identity.');
384
+ if (surface !== undefined && !surfaces.has(surface)) throw new UiChunkInputError('Invalid UI surface.');
385
+ const owner = discoverPlugins(projectRoot).find(entry => entry.id === plugin && entry.valid);
386
+ if (!owner) return null;
387
+ try {
388
+ if (!pluginActivation(owner, { projectRoot }).enabled) return null;
389
+ const root = fs.realpathSync(owner.dir);
390
+ const directory = chunkDirectory(root);
391
+ let chunk;
392
+ if (directory && fs.lstatSync(path.join(directory, id + '.md'), { throwIfNoEntry: false })) {
393
+ ownerFile(root, 'DESIGN.md');
394
+ chunk = readChunk(root, owner.manifest, id);
395
+ } else if (brainDir) {
396
+ const record = new PluginStateStore(poolFile(brainDir, plugin, id), POOL).read(null);
397
+ if (record) chunk = poolChunk(record, root, owner.manifest, id);
398
+ }
399
+ if (!chunk) return null;
400
+ return !surface || chunk.surfaces.includes(surface) ? chunk : null;
401
+ } catch {
402
+ return null;
403
+ }
404
+ }
405
+
406
+ export function auditUiChunkCoverage({ projectRoot = process.cwd(), plugin } = {}) {
407
+ if (plugin !== undefined && !isId(plugin)) throw new UiChunkInputError('Invalid plugin ID.');
408
+ return discoverPlugins(projectRoot).filter(owner => !plugin || owner.id === plugin).map(owner => {
409
+ let design = null;
410
+ for (const file of ['DESIGN.md', 'ui/DESIGN.md']) {
411
+ try {
412
+ ownerFile(fs.realpathSync(owner.dir), file);
413
+ design = file;
414
+ break;
415
+ } catch { /* Check the other supported owner path. */ }
416
+ }
417
+ const { chunks, total, diagnostics } = searchUiChunks({ projectRoot, plugin: owner.id, surface: 'page', limit: 200 });
418
+ const covered = new Set(chunks.flatMap(chunk => chunk.operations || []));
419
+ const operations = owner.manifest?.cli?.subcommands?.map(command => command.name) || [];
420
+ const missing = operations.filter(operation => !covered.has(operation));
421
+ return {
422
+ plugin: owner.id,
423
+ active: owner.valid && pluginActivation(owner, { projectRoot }).enabled,
424
+ design,
425
+ panels: (owner.manifest?.ui?.elements || []).filter(element =>
426
+ ['panel', 'form', 'list', 'detail', 'widget'].includes(element.kind)).map(element => element.id),
427
+ pageBlocks: total,
428
+ dataBindings: [...new Set(chunks.flatMap(chunk => chunk.data || []))].sort(),
429
+ operations: { total: operations.length, missing },
430
+ partial: chunks.length < total,
431
+ diagnostics,
432
+ };
433
+ });
434
+ }
@@ -156,6 +156,7 @@ export async function writeNodeValidatedAsync(node, vaultDir, options = {}) {
156
156
  agentRole,
157
157
  leaseStore,
158
158
  eventLogDir,
159
+ allowLong: options.allowLong === true,
159
160
  });
160
161
 
161
162
  if (result.success && !dryRun) {
@@ -9,7 +9,8 @@ import { brainDir } from './local-config.mjs';
9
9
  * Supports multiple vault directories (global + project brains)
10
10
  * without cross-contamination.
11
11
  */
12
- const cacheMap = new Map(); // vaultDir → { nodes, watcher }
12
+ const cacheMap = new Map(); // vaultDir → { nodes, watcher, retryWatcherAt }
13
+ const WATCH_RETRY_MS = 30_000;
13
14
 
14
15
  /**
15
16
  * Files the runtime regenerates itself (OKF bundle manifest and changelog, the
@@ -35,19 +36,15 @@ export function isGeneratedVaultPath(relPath) {
35
36
  */
36
37
  export function getNodes(vaultDir) {
37
38
  const resolvedDir = vaultDir || path.join(brainDir, 'memory-vault');
39
+ start(resolvedDir);
38
40
  const entry = cacheMap.get(resolvedDir);
39
- if (entry?.nodes !== null && entry?.nodes !== undefined) {
41
+ if (entry.nodes !== null) {
40
42
  logger.debug('Vault cache hit', { vaultDir: resolvedDir });
41
43
  return entry.nodes;
42
44
  }
43
45
  logger.debug('Vault cache miss, loading nodes from disk', { vaultDir: resolvedDir });
44
46
  const nodes = loadNodes(resolvedDir);
45
- if (entry) {
46
- entry.nodes = nodes;
47
- } else {
48
- cacheMap.set(resolvedDir, { nodes, watcher: null });
49
- startWatcher(resolvedDir);
50
- }
47
+ if (entry.watcher) entry.nodes = nodes;
51
48
  return nodes;
52
49
  }
53
50
 
@@ -86,61 +83,56 @@ export function invalidate(vaultDir) {
86
83
  *
87
84
  * @param {string} root - Directory to watch
88
85
  * @param {(eventType: string, filename: string) => void} onChange - Receives paths relative to root
86
+ * @param {(error: Error) => void} onError - Stops the whole watch if coverage is lost
89
87
  * @returns {{ close: () => void }}
90
88
  */
91
- function watchTree(root, onChange) {
89
+ function watchTree(root, onChange, onError) {
92
90
  if (process.platform === 'darwin' || process.platform === 'win32') {
93
91
  const watcher = fs.watch(root, { recursive: true }, onChange);
92
+ watcher.on('error', onError);
94
93
  watcher.unref();
95
94
  return watcher;
96
95
  }
97
96
 
98
97
  const watchers = new Map();
98
+ const close = () => {
99
+ for (const watcher of watchers.values()) watcher.close();
100
+ watchers.clear();
101
+ };
99
102
  const watchDir = (dir) => {
100
103
  if (watchers.has(dir)) return;
101
- let watcher;
102
- try {
103
- watcher = fs.watch(dir, (eventType, filename) => {
104
- if (!filename) return;
105
- const full = path.join(dir, String(filename));
106
- if (eventType === 'rename') {
107
- try {
108
- if (fs.statSync(full).isDirectory()) walk(full);
109
- } catch {
110
- // Removed; its watcher closes itself on error.
104
+ const watcher = fs.watch(dir, (eventType, filename) => {
105
+ if (!filename) return;
106
+ const full = path.join(dir, String(filename));
107
+ if (eventType === 'rename') {
108
+ try {
109
+ if (fs.lstatSync(full).isDirectory()) walk(full);
110
+ } catch (error) {
111
+ if (error.code !== 'ENOENT') {
112
+ onError(error);
113
+ return;
111
114
  }
112
115
  }
113
- onChange(eventType, path.relative(root, full));
114
- });
115
- } catch {
116
- return;
117
- }
118
- watcher.on('error', () => {
119
- watcher.close();
120
- watchers.delete(dir);
116
+ }
117
+ onChange(eventType, path.relative(root, full));
121
118
  });
119
+ watcher.on('error', onError);
122
120
  watcher.unref();
123
121
  watchers.set(dir, watcher);
124
122
  };
125
123
  const walk = (dir) => {
126
124
  watchDir(dir);
127
- let entries = [];
128
- try {
129
- entries = fs.readdirSync(dir, { withFileTypes: true });
130
- } catch {
131
- return;
132
- }
133
- for (const item of entries) {
125
+ for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
134
126
  if (item.isDirectory()) walk(path.join(dir, item.name));
135
127
  }
136
128
  };
137
- walk(root);
138
- return {
139
- close() {
140
- for (const watcher of watchers.values()) watcher.close();
141
- watchers.clear();
142
- },
143
- };
129
+ try {
130
+ walk(root);
131
+ } catch (error) {
132
+ close();
133
+ throw error;
134
+ }
135
+ return { close };
144
136
  }
145
137
 
146
138
  /**
@@ -150,29 +142,36 @@ function watchTree(root, onChange) {
150
142
  * @param {string} [vaultDir] - Path to the vault directory to watch
151
143
  */
152
144
  function startWatcher(vaultDir) {
153
- const resolvedDir = vaultDir || path.join(brainDir, 'memory-vault');
154
- const entry = cacheMap.get(resolvedDir);
155
- if (!entry || entry.watcher) return;
156
-
157
- if (!fs.existsSync(resolvedDir)) {
158
- try {
159
- fs.mkdirSync(resolvedDir, { recursive: true });
160
- } catch (err) {
161
- logger.error('Failed to create vault directory for watcher', { error: err.message });
162
- return;
163
- }
164
- }
145
+ const entry = cacheMap.get(vaultDir);
146
+ if (entry.watcher || entry.retryWatcherAt > Date.now()) return;
165
147
 
166
- logger.info('Starting filesystem watcher for vault cache', { vaultDir: resolvedDir });
148
+ const onError = (error) => {
149
+ const watcher = entry.watcher;
150
+ entry.watcher = null;
151
+ entry.nodes = null;
152
+ entry.retryWatcherAt = Date.now() + WATCH_RETRY_MS;
153
+ watcher?.close();
154
+ logger.warn('Vault cache watcher unavailable. Reading from disk until the watcher recovers.', {
155
+ vaultDir,
156
+ code: error.code,
157
+ error: error.message,
158
+ });
159
+ };
167
160
  try {
168
- entry.watcher = watchTree(resolvedDir, (eventType, filename) => {
161
+ if (!fs.existsSync(vaultDir)) fs.mkdirSync(vaultDir, { recursive: true });
162
+ logger.info('Starting filesystem watcher for vault cache', { vaultDir });
163
+ const watcher = watchTree(vaultDir, (eventType, filename) => {
169
164
  if (filename && filename.endsWith('.md') && !isGeneratedVaultPath(filename)) {
170
165
  logger.debug(`Vault filesystem change detected (${eventType}: ${filename}), invalidating cache`);
171
- invalidate(resolvedDir);
166
+ invalidate(vaultDir);
172
167
  }
168
+ }, (error) => {
169
+ if (entry.watcher === watcher) onError(error);
173
170
  });
174
- } catch (err) {
175
- logger.warn('Failed to start vault fs watcher, cache will rely on explicit invalidations', { error: err.message });
171
+ entry.watcher = watcher;
172
+ entry.retryWatcherAt = 0;
173
+ } catch (error) {
174
+ onError(error);
176
175
  }
177
176
  }
178
177
 
@@ -184,7 +183,7 @@ function startWatcher(vaultDir) {
184
183
  export function start(vaultDir) {
185
184
  const resolvedDir = vaultDir || path.join(brainDir, 'memory-vault');
186
185
  if (!cacheMap.has(resolvedDir)) {
187
- cacheMap.set(resolvedDir, { nodes: null, watcher: null });
186
+ cacheMap.set(resolvedDir, { nodes: null, watcher: null, retryWatcherAt: 0 });
188
187
  }
189
188
  startWatcher(resolvedDir);
190
189
  }