total-recall-brain 3.45.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 (362) hide show
  1. package/bin/total-recall.mjs +4 -2
  2. package/frontend/dist/assets/index-BRYE1jmv.css +1 -0
  3. package/frontend/dist/assets/index-D1goIPjC.js +5598 -0
  4. package/frontend/dist/index.html +2 -2
  5. package/frontend/dist/sw.js +5 -4
  6. package/frontend/src/App.tsx +183 -236
  7. package/frontend/src/api/auth-inventory.ts +62 -0
  8. package/frontend/src/api/memory.ts +13 -8
  9. package/frontend/src/api/plugin-credentials.ts +23 -0
  10. package/frontend/src/api/plugins.ts +37 -53
  11. package/frontend/src/api/workspace-drawers.ts +97 -0
  12. package/frontend/src/components/BrainSelector.tsx +9 -3
  13. package/frontend/src/components/plugins/PluginPanel.tsx +83 -42
  14. package/frontend/src/components/plugins/PluginSettings.tsx +10 -20
  15. package/frontend/src/components/plugins/PluginWorkspace.tsx +367 -0
  16. package/frontend/src/components/plugins/ScopedPluginCommands.tsx +142 -0
  17. package/frontend/src/components/plugins/plugin-frame.ts +44 -33
  18. package/frontend/src/components/plugins/scoped-plugin-operations.ts +28 -0
  19. package/frontend/src/components/workspace/DrawerChat.tsx +110 -0
  20. package/frontend/src/components/workspace/DrawerLayout.tsx +196 -0
  21. package/frontend/src/components/workspace/DrawerMarkup.tsx +49 -0
  22. package/frontend/src/components/workspace/PluginChunkLibrary.tsx +92 -0
  23. package/frontend/src/components/workspace/PluginHome.tsx +76 -0
  24. package/frontend/src/components/workspace/WorkspaceDrawers.tsx +96 -0
  25. package/frontend/src/components/workspace/WorkspacePage.tsx +95 -0
  26. package/frontend/src/components/workspace/usePluginDesign.ts +38 -0
  27. package/frontend/src/index.css +409 -136
  28. package/frontend/src/pages/HelpPage.tsx +15 -46
  29. package/frontend/src/pages/MemoryPage.tsx +19 -36
  30. package/frontend/src/pages/SettingsPage.tsx +1 -1
  31. package/metadata.plugin.schema.json +53 -227
  32. package/package.json +1 -1
  33. package/scaffold/.agent/skills/plugin/SKILL.md +13 -11
  34. package/scaffold/.agent/skills/plugin/evals/README.md +14 -21
  35. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +15 -18
  36. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +19 -24
  37. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +12 -20
  38. package/scaffold/.agent/skills/plugin/evals/cases/04-home-collects-credential.mjs +35 -0
  39. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +8 -28
  40. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +85 -47
  41. package/scaffold/.agent/skills/plugin/evals/run.mjs +17 -18
  42. package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +4 -4
  43. package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +2 -2
  44. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +4 -4
  45. package/scaffold/.agent/skills/plugin/references/anti-patterns.md +2 -2
  46. package/scaffold/.agent/skills/plugin/references/generative-home.md +38 -0
  47. package/scaffold/.agent/skills/plugin/references/scaffold.md +5 -5
  48. package/scaffold/.agent/skills/plugin/references/surfaces.md +13 -4
  49. package/scaffold/.agent/skills/plugin/scripts/record.mjs +75 -153
  50. package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +4 -4
  51. package/scaffold/.agent/skills/total-recall/plugins/auth/DESIGN.md +76 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +3 -1
  53. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +20 -15
  54. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
  55. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +2 -1
  56. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +13 -51
  57. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/access-overview.md +17 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/agent-login.md +20 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/baseline-audit.md +22 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-repository-binding.md +17 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-store-maintenance.md +17 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-password.md +18 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-sessions.md +18 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/endpoint-probe.md +23 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/google-sign-in.md +21 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/mesh-machines.md +16 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/passkey-enrollment.md +21 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/registered-brains.md +16 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/scoped-access-keys.md +17 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/secret-inventory.md +25 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/signing-key-rotation.md +22 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +375 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/automations/{ui/DESIGN.md → DESIGN.md} +32 -21
  74. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +17 -1
  75. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +10 -1
  76. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +6 -6
  77. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +4 -3
  78. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +11 -27
  79. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -2
  80. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/control-ledger.md +42 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/local-inventory-triage.md +17 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/pending-control.md +39 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/run-evidence.md +41 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/schedule-vs-run-evidence.md +17 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/source-coverage.md +41 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/{card.js → home.js} +32 -27
  87. package/scaffold/.agent/skills/total-recall/plugins/code-quality/DESIGN.md +53 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +23 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +4 -2
  90. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +4 -2
  91. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +6 -6
  92. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +2 -1
  93. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +40 -40
  94. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/config.md +15 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/detect.md +15 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/gate.md +15 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/init.md +15 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/report.md +17 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/run.md +17 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/snapshot.md +15 -0
  101. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +55 -23
  102. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/home.mjs +214 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/DESIGN.md +146 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +6 -6
  105. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +2 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +51 -34
  107. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +1 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/create.md +35 -0
  109. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/history.md +35 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/library.md +49 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/list.md +42 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/promote.md +40 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/read.md +25 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/remove.md +26 -0
  115. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/rollback.md +26 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/show.md +53 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/status.md +40 -0
  118. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test-path.md +38 -0
  119. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test.md +46 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/update.md +32 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/home.js +533 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/embeddings/DESIGN.md +110 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +6 -6
  124. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +2 -1
  125. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +13 -10
  126. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/benchmark.md +49 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/coverage.md +53 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/models.md +43 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/pull.md +41 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/status.md +59 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/home.js +353 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/git/DESIGN.md +31 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +25 -1
  134. package/scaffold/.agent/skills/total-recall/plugins/git/cli.mjs +5 -1
  135. package/scaffold/.agent/skills/total-recall/plugins/git/package-lock.json +6 -6
  136. package/scaffold/.agent/skills/total-recall/plugins/git/package.json +2 -1
  137. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +17 -27
  138. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -6
  139. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +24 -6
  140. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +33 -7
  141. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +29 -5
  142. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/unmerged.mjs +182 -0
  143. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/change-comparison.md +27 -0
  144. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/commit-identity.md +22 -0
  145. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/create-worktree.md +25 -0
  146. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/registered-worktrees.md +22 -0
  147. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/remove-worktree.md +23 -0
  148. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/repository-ledger.md +25 -0
  149. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/unmerged-work.md +22 -0
  150. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/upstream-divergence.md +17 -0
  151. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-conflict-triage.md +17 -0
  152. package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-terminal.md +22 -0
  153. package/scaffold/.agent/skills/total-recall/plugins/git/ui/home.js +273 -0
  154. package/scaffold/.agent/skills/total-recall/plugins/mesh/DESIGN.md +35 -0
  155. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +4 -5
  156. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -3
  157. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +6 -6
  158. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +2 -1
  159. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +37 -39
  160. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +5 -3
  161. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/active-device-dispatch.md +22 -0
  162. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-probe.md +25 -0
  163. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-server.md +22 -0
  164. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/credential-readiness.md +23 -0
  165. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/elected-leader.md +22 -0
  166. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/enrollment-key.md +22 -0
  167. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/file-receipt.md +25 -0
  168. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-execution.md +23 -0
  169. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-presence.md +23 -0
  170. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/interactive-ssh.md +22 -0
  171. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/local-enrollment.md +23 -0
  172. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/mesh-policy.md +23 -0
  173. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/network-atlas.md +25 -0
  174. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-roster.md +26 -0
  175. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-selection-proof.md +17 -0
  176. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/owner-observations.md +23 -0
  177. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/peer-path.md +23 -0
  178. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/pinned-leader.md +24 -0
  179. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/reachability-evidence-ladder.md +17 -0
  180. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/recorded-login.md +23 -0
  181. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/remote-execution.md +22 -0
  182. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/secret-grants.md +26 -0
  183. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/{card.js → home.js} +93 -22
  184. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/DESIGN.md +86 -0
  185. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +19 -0
  186. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +5 -1
  187. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +6 -6
  188. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -1
  189. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +43 -22
  190. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/chat.mjs +80 -0
  191. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/adapter-docs-freshness.md +16 -0
  192. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/chat-handoff.md +26 -0
  193. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/fleet-maintenance.md +21 -0
  194. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/harness-inventory.md +21 -0
  195. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-access.md +18 -0
  196. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-usage-verification.md +15 -0
  197. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/independent-review.md +17 -0
  198. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/managed-execution.md +20 -0
  199. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/official-documentation.md +18 -0
  200. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/quota-recovery.md +21 -0
  201. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/review-routing-decision.md +16 -0
  202. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/route-work.md +18 -0
  203. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/routing-evidence.md +18 -0
  204. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/run-ledger.md +18 -0
  205. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/home.js +198 -0
  206. package/scaffold/.agent/skills/total-recall/plugins/openwiki/DESIGN.md +75 -0
  207. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +2 -0
  208. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +17 -14
  209. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
  210. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +2 -1
  211. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +10 -44
  212. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +5 -4
  213. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +3 -4
  214. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +23 -18
  215. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +8 -3
  216. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +6 -3
  217. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +3 -5
  218. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +21 -47
  219. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +17 -6
  220. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/auto-refresh.md +18 -0
  221. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/bootstrap-pages.md +18 -0
  222. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generate-wiki.md +21 -0
  223. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generator-settings.md +18 -0
  224. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/host-probe.md +18 -0
  225. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/ingest-preview.md +18 -0
  226. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-hygiene.md +21 -0
  227. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-topology.md +19 -0
  228. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/passage-search.md +19 -0
  229. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/propagation-plan.md +18 -0
  230. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/provider-credentials.md +18 -0
  231. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/refresh-report.md +18 -0
  232. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/task-evidence.md +22 -0
  233. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/upstream-record.md +18 -0
  234. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/watch-upstream.md +18 -0
  235. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/wiki-atlas.md +19 -0
  236. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +261 -513
  237. package/scaffold/.agent/skills/total-recall/plugins/security/DESIGN.md +84 -0
  238. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +4 -2
  239. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -19
  240. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
  241. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +2 -3
  242. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +19 -52
  243. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/device-evidence.md +21 -0
  244. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/evidence-desk.md +18 -0
  245. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/guided-device-review.md +17 -0
  246. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/host-and-network.md +21 -0
  247. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/https-headers.md +17 -0
  248. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/mesh-coverage.md +18 -0
  249. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/private-security-tools.md +16 -0
  250. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/single-origin-header-check.md +17 -0
  251. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/source-review.md +21 -0
  252. package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/ssh-permission-plan.md +17 -0
  253. package/scaffold/.agent/skills/total-recall/plugins/security/ui/home.js +283 -0
  254. package/scaffold/.agent/skills/total-recall/plugins/ssss/DESIGN.md +93 -0
  255. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +2 -2
  256. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -1
  257. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +5 -3
  258. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/conformance.md +26 -0
  259. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/registry.md +37 -0
  260. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/repair.md +42 -0
  261. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/scaffold.md +33 -0
  262. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/status.md +43 -0
  263. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validate.md +34 -0
  264. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validation-diagnostics.md +17 -0
  265. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/workspace-registry-preflight.md +17 -0
  266. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/home.js +348 -0
  267. package/scaffold/.agent/skills/total-recall/references/operations.md +2 -0
  268. package/src/cli/api.mjs +3 -3
  269. package/src/cli/brief.mjs +3 -0
  270. package/src/cli/config.mjs +16 -0
  271. package/src/cli/connect.mjs +2 -7
  272. package/src/cli/plugin/chunks.mjs +145 -0
  273. package/src/cli/plugin/create.mjs +24 -52
  274. package/src/cli/plugin/index.mjs +20 -9
  275. package/src/cli/setup.mjs +2 -9
  276. package/src/cli/start.mjs +1 -0
  277. package/src/cli/startup.mjs +18 -0
  278. package/src/core/app-deploy/design-tokens.mjs +14 -9
  279. package/src/core/app-deploy/ui-elements.mjs +1 -1
  280. package/src/core/brain-registry.mjs +5 -0
  281. package/src/core/brain-updates.mjs +5 -1
  282. package/src/core/daemon-control.mjs +5 -1
  283. package/src/core/dashboard-bundle.mjs +261 -0
  284. package/src/core/ensure-dependencies.mjs +19 -15
  285. package/src/core/memory-brain.mjs +30 -8
  286. package/src/core/plugin-activation.mjs +63 -20
  287. package/src/core/plugin-contracts/{card.mjs → manifest.mjs} +23 -57
  288. package/src/core/plugin-contracts/startup-check.mjs +49 -0
  289. package/src/core/plugin-contracts/ui.mjs +9 -3
  290. package/src/core/plugin-distribution.mjs +6 -6
  291. package/src/core/plugin-lifecycle.mjs +58 -0
  292. package/src/core/plugin-loader.mjs +9 -5
  293. package/src/core/plugin-runner.mjs +21 -10
  294. package/src/core/plugin-startup-checks.mjs +44 -0
  295. package/src/core/plugin-state.mjs +19 -5
  296. package/src/core/plugin-store.mjs +30 -11
  297. package/src/core/project-id.mjs +15 -5
  298. package/src/core/required-plugins.mjs +31 -6
  299. package/src/core/schema.mjs +2 -0
  300. package/src/core/ssss-operation-service.mjs +1 -0
  301. package/src/core/startup-health.mjs +28 -3
  302. package/src/core/testing/plugin-fixture.mjs +22 -21
  303. package/src/core/throttled-fetch.mjs +25 -18
  304. package/src/core/ui-chunk-choice.mjs +77 -0
  305. package/src/core/ui-chunk.registry.json +23 -0
  306. package/src/core/ui-chunks.mjs +434 -0
  307. package/src/core/vault-cache.mjs +58 -59
  308. package/src/core/workspace-compositions.mjs +151 -0
  309. package/src/core/workspace-drawers.mjs +79 -0
  310. package/src/server/api.mjs +4 -27
  311. package/src/server/index.mjs +28 -21
  312. package/src/server/memory-events.mjs +31 -0
  313. package/src/server/plugin-brain-targets.mjs +89 -0
  314. package/src/server/rest.mjs +2 -0
  315. package/src/server/route-inventory.mjs +3 -2
  316. package/src/server/route-manifest.json +52 -4
  317. package/src/server/routes/_shared.mjs +7 -14
  318. package/src/server/routes/brains.mjs +29 -44
  319. package/src/server/routes/dashboard.mjs +26 -0
  320. package/src/server/routes/plugins.mjs +154 -45
  321. package/src/server/routes/ui-chunks.mjs +192 -0
  322. package/frontend/dist/assets/index-DACh1dhx.js +0 -5613
  323. package/frontend/dist/assets/index-DtwsDyFM.css +0 -1
  324. package/frontend/src/components/plugins/card/CardEngine.tsx +0 -108
  325. package/frontend/src/components/plugins/card/CardScenes.tsx +0 -103
  326. package/frontend/src/components/plugins/card/ExecutionPicker.tsx +0 -64
  327. package/frontend/src/components/plugins/card/args.ts +0 -28
  328. package/frontend/src/components/plugins/card/card.css +0 -32
  329. package/frontend/src/components/plugins/card/cli-line.ts +0 -13
  330. package/frontend/src/components/plugins/card/contract.ts +0 -91
  331. package/frontend/src/components/plugins/card/format.ts +0 -49
  332. package/frontend/src/components/plugins/card/runner-context.ts +0 -11
  333. package/frontend/src/components/plugins/card/runner.tsx +0 -43
  334. package/frontend/src/components/plugins/card/theme.ts +0 -35
  335. package/frontend/src/components/plugins/card/use-source.ts +0 -22
  336. package/frontend/src/components/plugins/card/widgets.tsx +0 -94
  337. package/frontend/src/components/plugins/workspace/WorkspaceHost.tsx +0 -23
  338. package/frontend/src/pages/PluginsPage.tsx +0 -1719
  339. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +0 -47
  340. package/scaffold/.agent/skills/plugin/prompts/06-card.md +0 -46
  341. package/scaffold/.agent/skills/plugin/references/card.md +0 -90
  342. package/scaffold/.agent/skills/plugin/subagents/card-designer.md +0 -60
  343. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +0 -67
  344. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +0 -1179
  345. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +0 -32
  346. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +0 -588
  347. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +0 -575
  348. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +0 -32
  349. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +0 -482
  350. package/scaffold/.agent/skills/total-recall/plugins/git/ui/DESIGN.md +0 -100
  351. package/scaffold/.agent/skills/total-recall/plugins/git/ui/card.js +0 -637
  352. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +0 -217
  353. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +0 -32
  354. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +0 -1276
  355. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +0 -45
  356. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +0 -1401
  357. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +0 -4
  358. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +0 -41
  359. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +0 -396
  360. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +0 -98
  361. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/DESIGN.md +0 -15
  362. package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/card.js +0 -182
@@ -0,0 +1,151 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { executePluginState, listPluginStates, PluginStateStore } from './plugin-state-store.mjs';
4
+ import { getUiChunk, UiChunkInputError } from './ui-chunks.mjs';
5
+ import { getPluginById } from './plugin-loader.mjs';
6
+ import { pluginActivation } from './plugin-state.mjs';
7
+
8
+ const OWNER = 'workspace-ui';
9
+ const SAVED_PAGES = 'workspace-pages';
10
+ const ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
11
+ const VERSION = /^[a-f0-9]{64}$/;
12
+ const MAX_PAGE_BLOCKS = 24;
13
+
14
+ function stateFile(brainDir, plugin) {
15
+ if (typeof plugin !== 'string' || plugin.length > 64 || !ID.test(plugin)) throw new UiChunkInputError('Invalid plugin ID.');
16
+ return path.join(brainDir, 'config', 'workspace-compositions', plugin + '.json');
17
+ }
18
+
19
+ function readCompositionState(brainDir, plugin, projectRoot) {
20
+ const store = new PluginStateStore(stateFile(brainDir, plugin), OWNER);
21
+ const owner = getPluginById(plugin, projectRoot);
22
+ if (!owner?.valid || !pluginActivation(owner, { projectRoot }).enabled) {
23
+ throw new UiChunkInputError('The destination plugin must be installed and enabled.');
24
+ }
25
+ const state = store.read({ keys: [] });
26
+ return { keys: state.keys, version: store.version };
27
+ }
28
+
29
+ function readPageBlocks(brainDir, keys, projectRoot) {
30
+ return keys.flatMap(key => {
31
+ const chunk = readPageBlock(brainDir, key, projectRoot);
32
+ if (!chunk) return [];
33
+ const { html: _html, ...metadata } = chunk;
34
+ return [metadata];
35
+ });
36
+ }
37
+
38
+ export function readWorkspaceComposition(brainDir, plugin, projectRoot = process.cwd()) {
39
+ const { keys, version } = readCompositionState(brainDir, plugin, projectRoot);
40
+ return { chunks: readPageBlocks(brainDir, keys, projectRoot), version };
41
+ }
42
+
43
+ function readPageBlock(brainDir, key, projectRoot) {
44
+ if (typeof key !== 'string') return null;
45
+ const [owner, id, ...rest] = key.split(':');
46
+ return ID.test(owner) && typeof id === 'string' && ID.test(id) && !rest.length
47
+ ? getUiChunk(owner, id, { surface: 'page', brainDir, projectRoot }) : null;
48
+ }
49
+
50
+ export async function publishWorkspaceComposition(brainDir, plugin, keys, version, projectRoot = process.cwd()) {
51
+ const file = stateFile(brainDir, plugin);
52
+ const owner = getPluginById(plugin, projectRoot);
53
+ if (!owner?.valid || !pluginActivation(owner, { projectRoot }).enabled ||
54
+ !keys.length || keys.length > 3 || keys.some(key => !readPageBlock(brainDir, key, projectRoot))) {
55
+ throw new UiChunkInputError('The selected page blocks changed. Compose the view again.');
56
+ }
57
+ return executePluginState({ file, plugin: OWNER, action: 'write', value: { keys }, expectedVersion: version });
58
+ }
59
+
60
+ function pageDirectory(brainDir) {
61
+ const config = path.join(brainDir, 'config');
62
+ const directory = path.join(config, SAVED_PAGES);
63
+ for (const candidate of [config, directory]) {
64
+ const stat = fs.lstatSync(candidate, { throwIfNoEntry: false });
65
+ if (stat && !stat.isDirectory()) throw new Error('Unsafe saved page directory');
66
+ }
67
+ return directory;
68
+ }
69
+
70
+ function savedPageFile(brainDir, id) {
71
+ if (typeof id !== 'string' || id.length > 64 || !ID.test(id)) throw new UiChunkInputError('Invalid saved page ID.');
72
+ return path.join(pageDirectory(brainDir), id + '.json');
73
+ }
74
+
75
+ function pageConflict(message) {
76
+ return Object.assign(new Error(message), { status: 409 });
77
+ }
78
+
79
+ export function listWorkspacePages(brainDir) {
80
+ return listPluginStates(pageDirectory(brainDir), SAVED_PAGES)
81
+ .map(({ value: { id, title }, version }) => ({ id, title, version }))
82
+ .sort((a, b) => a.title.localeCompare(b.title) || a.id.localeCompare(b.id));
83
+ }
84
+
85
+ export function readWorkspacePage(brainDir, id, projectRoot = process.cwd()) {
86
+ const store = new PluginStateStore(savedPageFile(brainDir, id), SAVED_PAGES);
87
+ const page = store.read(null);
88
+ if (!page) return null;
89
+ return { id, title: page.title, version: store.version, chunks: readPageBlocks(brainDir, page.keys, projectRoot) };
90
+ }
91
+
92
+ export function prepareWorkspacePage(brainDir, id, title) {
93
+ const file = savedPageFile(brainDir, id);
94
+ const name = typeof title === 'string' ? title.trim() : '';
95
+ if (!name || name.length > 120 || /[\u0000-\u001f\u007f-\u009f]/u.test(title)) {
96
+ throw new UiChunkInputError('Enter a saved page title of up to 120 characters.');
97
+ }
98
+ const store = new PluginStateStore(file, SAVED_PAGES);
99
+ if (store.read(null)) throw pageConflict('That saved page ID already exists.');
100
+ return { title: name, file };
101
+ }
102
+
103
+ function verifyPageBlocks(brainDir, keys, projectRoot) {
104
+ if (!Array.isArray(keys) || !keys.length || keys.length > MAX_PAGE_BLOCKS || new Set(keys).size !== keys.length) {
105
+ throw new UiChunkInputError(`Choose one to ${MAX_PAGE_BLOCKS} distinct page blocks.`);
106
+ }
107
+ const chunks = readPageBlocks(brainDir, keys, projectRoot);
108
+ if (chunks.length !== keys.length) throw pageConflict('The selected page blocks are no longer available.');
109
+ return chunks;
110
+ }
111
+
112
+ export async function createWorkspacePage(brainDir, { id, title, keys }, projectRoot = process.cwd()) {
113
+ const page = prepareWorkspacePage(brainDir, id, title);
114
+ const chunks = verifyPageBlocks(brainDir, keys, projectRoot);
115
+ try {
116
+ const { version } = await executePluginState({
117
+ file: page.file, plugin: SAVED_PAGES, action: 'write',
118
+ value: { id, title: page.title, keys }, expectedVersion: null,
119
+ });
120
+ return { id, title: page.title, version, chunks };
121
+ } catch (error) {
122
+ if (error.message.startsWith('Plugin state version conflict')) throw pageConflict('That saved page ID already exists.');
123
+ throw error;
124
+ }
125
+ }
126
+
127
+ export async function updateWorkspacePage(brainDir, { id, keys, revision }, projectRoot = process.cwd()) {
128
+ if (typeof revision !== 'string' || !VERSION.test(revision)) {
129
+ throw new UiChunkInputError('Supply the current saved page revision.');
130
+ }
131
+ const file = savedPageFile(brainDir, id);
132
+ const store = new PluginStateStore(file, SAVED_PAGES);
133
+ const page = store.read(null);
134
+ if (!page) throw Object.assign(new Error('Saved page unavailable'), { status: 404 });
135
+ if (store.version !== revision) throw pageConflict('The saved page changed. Read its current revision before editing.');
136
+ const chunks = verifyPageBlocks(brainDir, keys, projectRoot);
137
+ const { version } = await executePluginState({
138
+ file, plugin: SAVED_PAGES, action: 'write',
139
+ value: { id, title: page.title, keys }, expectedVersion: revision,
140
+ });
141
+ return { id, title: page.title, version, chunks };
142
+ }
143
+
144
+ export async function saveWorkspaceCompositionPage(brainDir, { id, title, sourcePlugin, sourceVersion }, projectRoot = process.cwd()) {
145
+ if (typeof sourceVersion !== 'string' || !VERSION.test(sourceVersion)) {
146
+ throw new UiChunkInputError('Supply the published Home revision to save.');
147
+ }
148
+ const { keys, version } = readCompositionState(brainDir, sourcePlugin, projectRoot);
149
+ if (version !== sourceVersion) throw pageConflict('The plugin Home changed. Read its current revision before saving.');
150
+ return createWorkspacePage(brainDir, { id, title, keys }, projectRoot);
151
+ }
@@ -0,0 +1,79 @@
1
+ /** Private dashboard chrome settings, shared by the HTTP and CLI adapters. */
2
+ import path from 'node:path';
3
+ import { executePluginState, PluginStateStore } from './plugin-state-store.mjs';
4
+
5
+ const OWNER = 'workspace-ui';
6
+ const ID = /^[a-z][a-z0-9-]{0,63}$/;
7
+ const EMPTY = { html: '', chunks: [], panels: [], shortcuts: [] };
8
+ export const MAX_DRAWER_HTML_BYTES = 64 * 1024;
9
+
10
+ export class DrawerInputError extends Error {}
11
+
12
+ function stateFile(brainDir, edge) {
13
+ if (edge !== 'right' && edge !== 'bottom') throw new DrawerInputError('Choose the right or bottom drawer.');
14
+ return path.join(brainDir, 'config', `workspace-drawer-${edge}.json`);
15
+ }
16
+
17
+ function list(value, name, limit) {
18
+ if (!Array.isArray(value) || value.length > limit) throw new DrawerInputError(`${name} must contain at most ${limit} items.`);
19
+ return value;
20
+ }
21
+
22
+ function shortcutHref(value) {
23
+ if (typeof value !== 'string' || value.length > 1024 || /[\u0000-\u0020\u007f\\]/.test(value)) {
24
+ throw new DrawerInputError('Use a dashboard path or an HTTPS address for each shortcut.');
25
+ }
26
+ const base = 'https://workspace.invalid';
27
+ let url;
28
+ try { url = new URL(value, base); } catch { throw new DrawerInputError('Invalid shortcut address.'); }
29
+ if (value.startsWith('/') && !value.startsWith('//') && url.origin === base && !url.pathname.startsWith('//') &&
30
+ !/%(?:2f|5c)/i.test(url.pathname)) {
31
+ return url.pathname + url.search + url.hash;
32
+ }
33
+ if (value.startsWith('https://') && !url.username && !url.password) return url.href;
34
+ throw new DrawerInputError('Use a dashboard path or an HTTPS address for each shortcut.');
35
+ }
36
+
37
+ export function validateDrawerConfig(value) {
38
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(key => !Object.hasOwn(EMPTY, key))) {
39
+ throw new DrawerInputError('Invalid drawer configuration.');
40
+ }
41
+ const { html = '', chunks = [], panels = [], shortcuts = [] } = value;
42
+ if (typeof html !== 'string' || Buffer.byteLength(html, 'utf8') > MAX_DRAWER_HTML_BYTES) {
43
+ throw new DrawerInputError('Drawer HTML must be at most 64 KiB.');
44
+ }
45
+ const chunkIds = list(chunks, 'Chunks', 8).map(key => {
46
+ if (typeof key !== 'string' || key.split(':').length !== 2 || key.split(':').some(id => !ID.test(id))) {
47
+ throw new DrawerInputError('Each chunk needs a plugin ID and a chunk ID.');
48
+ }
49
+ return key;
50
+ });
51
+ const references = list(panels, 'Panels', 6).map(panel => {
52
+ if (typeof panel?.plugin !== 'string' || typeof panel?.element !== 'string' ||
53
+ !ID.test(panel.plugin) || !ID.test(panel.element)) throw new DrawerInputError('Invalid plugin panel reference.');
54
+ return { plugin: panel.plugin, element: panel.element };
55
+ });
56
+ const links = list(shortcuts, 'Shortcuts', 16).map(shortcut => {
57
+ const label = typeof shortcut?.label === 'string' ? shortcut.label.trim() : '';
58
+ if (!label || label.length > 80 || /[\u0000-\u001f\u007f]/.test(label)) throw new DrawerInputError('Give each shortcut a name of 1–80 characters.');
59
+ return { label, href: shortcutHref(shortcut.href) };
60
+ });
61
+ return { html, chunks: [...new Set(chunkIds)], panels: references, shortcuts: links };
62
+ }
63
+
64
+ export function readDrawerConfig(brainDir, edge) {
65
+ const store = new PluginStateStore(stateFile(brainDir, edge), OWNER);
66
+ const config = validateDrawerConfig(store.read(EMPTY));
67
+ return { config, version: store.version };
68
+ }
69
+
70
+ export async function writeDrawerConfig(brainDir, edge, config, version) {
71
+ if (version !== null && (typeof version !== 'string' || !/^[0-9a-f]{64}$/.test(version))) {
72
+ throw new DrawerInputError('Reload the drawer before saving.');
73
+ }
74
+ const value = validateDrawerConfig(config);
75
+ const result = await executePluginState({
76
+ file: stateFile(brainDir, edge), plugin: OWNER, action: 'write', value, expectedVersion: version,
77
+ });
78
+ return { config: value, version: result.version };
79
+ }
@@ -15,10 +15,10 @@ import { runInSandbox } from '../core/sandbox.mjs';
15
15
  import { resolveAgentDir } from '../cli/agent-dir.mjs';
16
16
  import { removeSessionEmbeddingFromIndex } from '../core/embeddings.mjs';
17
17
  import path from 'path';
18
- import os from 'os';
19
18
  import fs from 'fs';
20
19
  import matter from '../core/frontmatter.mjs';
21
20
  import { agentDir, brainDir, surfaceDir, instructionsFilePath } from '../core/config.mjs';
21
+ import { resolveVaultFromQuery } from './routes/_shared.mjs';
22
22
 
23
23
  const AGENT_DIR = process.env.AGENT_DIR || agentDir;
24
24
  const BRAIN_DIR = process.env.TR_BRAIN || path.join(AGENT_DIR, 'skills', 'total-recall');
@@ -43,32 +43,9 @@ const MODEL_CATALOG_DIR = path.join(ROOT, 'models', 'catalog', 'total-recall');
43
43
  * @returns {string} Absolute path to the memory-vault directory
44
44
  */
45
45
  function resolveVaultDir(brainId) {
46
- if (!brainId || brainId === 'global') {
47
- return VAULT_DIR;
48
- }
49
-
50
- if (brainId.startsWith('project:')) {
51
- const projectName = brainId.slice('project:'.length);
52
- const globalBrainDir = path.join(os.homedir(), '.agent', 'skills', 'total-recall');
53
- const registryPath = path.join(globalBrainDir, 'config', 'project-registry.json');
54
-
55
- if (fs.existsSync(registryPath)) {
56
- try {
57
- const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
58
- const project = registry.find(p => p.name === projectName);
59
- if (project) {
60
- const projectVaultDir = path.join(project.brainDir, 'memory-vault');
61
- if (fs.existsSync(projectVaultDir)) {
62
- return projectVaultDir;
63
- }
64
- }
65
- } catch {
66
- // Registry parse error — fall through to default
67
- }
68
- }
69
- }
70
-
71
- return VAULT_DIR;
46
+ return typeof brainId === 'string' && (brainId === 'global' || brainId.startsWith('project:'))
47
+ ? resolveVaultFromQuery({ query: { brain: brainId } })
48
+ : VAULT_DIR;
72
49
  }
73
50
 
74
51
  function sha256(content) {
@@ -352,6 +352,8 @@ app.get('/health', requireAuthOrLocal, async (req, res) => {
352
352
  cloudflare: cloudflareStatus,
353
353
  emergency_alerts: emergencyAlerts || null,
354
354
  fetch_gate,
355
+ // Whether the served dashboard is as new as this checkout's frontend sources.
356
+ dashboard: dashboardBundle.state(),
355
357
  vfs: {
356
358
  exists: vaultExists,
357
359
  skill_exists: skillExists,
@@ -444,24 +446,30 @@ try {
444
446
 
445
447
  const frontendDist = path.join(ROOT, 'frontend', 'dist');
446
448
 
447
- // Auto-build frontend if dist/ doesn't exist
448
- if (!fs.existsSync(path.join(frontendDist, 'index.html'))) {
449
- const frontendDir = path.join(ROOT, 'frontend');
450
- if (fs.existsSync(path.join(frontendDir, 'package.json'))) {
451
- logger.info('server', 'Frontend not built. Building automatically...');
452
- try {
453
- const { execSync } = await import('node:child_process');
454
- execSync('npm install --no-audit --no-fund 2>/dev/null && npm run build', {
455
- cwd: frontendDir,
456
- stdio: 'pipe',
457
- timeout: 120_000,
458
- });
459
- logger.info('server', 'Frontend build complete.');
460
- } catch (err) {
461
- logger.warn('server', `Frontend auto-build failed: ${err.message?.split('\\n')[0] || 'unknown error'}. Dashboard will show API info instead.`);
449
+ // Live refresh for the memory page on this port (the /stream monitor listens on 127.0.0.1 only).
450
+ const { memoryEventsHandler } = await import('./memory-events.mjs');
451
+ app.get('/api/memory-events', requireAuth, memoryEventsHandler(logEvents));
452
+
453
+ // Keep the served dashboard as new as its sources. A missing or stale bundle is
454
+ // rebuilt in the background while the current one keeps serving; express.static
455
+ // resolves files per request, so the swapped-in build serves without a restart.
456
+ // Building only when index.html was missing left every later frontend commit unserved.
457
+ const { watchDashboardBundle } = await import('../core/dashboard-bundle.mjs');
458
+ const dashboardBundle = watchDashboardBundle({
459
+ root: ROOT,
460
+ report: (state) => {
461
+ if (state.status === 'current' && state.rebuilt) {
462
+ logger.info('server', `Dashboard bundle rebuilt from newer sources (${Math.round(state.durationMs / 1000)} s); serving it now.`);
463
+ } else if (state.status === 'current') {
464
+ logger.info('server', 'Dashboard bundle is current.');
465
+ } else if (state.retryInMs) {
466
+ logger.info('server', `Dashboard bundle is ${state.status}; rebuilding once the sources stop changing.`);
467
+ } else {
468
+ logger.error('server', `Dashboard bundle is ${state.status} and was not rebuilt: ${state.reason}.${state.build?.output ? ` Build output: ${state.build.output.slice(-1500)}` : ''}`);
462
469
  }
463
- }
464
- }
470
+ },
471
+ });
472
+ dashboardBundle.check();
465
473
 
466
474
  app.use(express.static(frontendDist));
467
475
 
@@ -803,8 +811,7 @@ app.get('/chat', (req, res) => {
803
811
 
804
812
  // SPA fallback — serve index.html for all unmatched routes
805
813
  app.get(/^(.*)$/, (req, res) => {
806
- const indexPath = path.join(frontendDist, 'index.html');
807
- res.sendFile(indexPath, (err) => {
814
+ res.sendFile('index.html', { root: frontendDist }, (err) => {
808
815
  if (err) {
809
816
  // Frontend not built yet — return a helpful message
810
817
  res.status(200).json({
@@ -814,7 +821,7 @@ app.get(/^(.*)$/, (req, res) => {
814
821
  memory: 'GET /api/memory',
815
822
  sandbox: 'POST /api/sandbox',
816
823
  health: 'GET /health',
817
- dashboard: 'Build frontend first: cd frontend && npm run build'
824
+ dashboard: `Dashboard bundle is ${dashboardBundle.state().status}${dashboardBundle.state().reason ? `: ${dashboardBundle.state().reason}` : ''}. The server rebuilds it from frontend/ in the background.`
818
825
  }
819
826
  });
820
827
  }
@@ -874,7 +881,7 @@ const server = app.listen(PORT, HOST, () => {
874
881
 
875
882
  // Only meaningful once the socket is up: asking before the callback fires
876
883
  // reads an empty set, which is "unknown", so the watch never armed.
877
- if (isReachableFromOtherDevices() === false) {
884
+ if (bindResolution.usedLoopbackFallback && isReachableFromOtherDevices() === false) {
878
885
  logger.warn(
879
886
  'server',
880
887
  `Bound to loopback only — no other device can reach this brain at :${PORT}. `
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Live refresh for the memory page, on the dashboard's own port.
3
+ *
4
+ * The page listened to `/stream` on the dashboard origin, but the server
5
+ * serves `/stream` only on the 127.0.0.1 monitor port, so the dashboard got
6
+ * its HTML fallback and the live refresh never connected. This stream is
7
+ * authenticated like the rest of the dashboard API and carries only the
8
+ * subsystem name of the events that change what the page shows, never log
9
+ * contents.
10
+ */
11
+ export const MEMORY_EVENT_SUBSYSTEMS = new Set(['conflict-detector', 'rebuild', 'session-watcher']);
12
+ const HEARTBEAT_MS = 25000;
13
+
14
+ /** @param {import('node:events').EventEmitter} events Emits 'log' entries ({ subsystem, ... }). */
15
+ export function memoryEventsHandler(events, { heartbeatMs = HEARTBEAT_MS } = {}) {
16
+ return (req, res) => {
17
+ res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' });
18
+ res.write(': connected\n\n');
19
+ const onLog = (entry) => {
20
+ if (MEMORY_EVENT_SUBSYSTEMS.has(entry?.subsystem)) res.write(`data: ${JSON.stringify({ subsystem: entry.subsystem })}\n\n`);
21
+ };
22
+ events.on('log', onLog);
23
+ // Comment lines keep proxies from closing an idle stream.
24
+ const heartbeat = setInterval(() => res.write(': ping\n\n'), heartbeatMs);
25
+ heartbeat.unref?.();
26
+ req.on('close', () => {
27
+ events.off('log', onLog);
28
+ clearInterval(heartbeat);
29
+ });
30
+ };
31
+ }
@@ -0,0 +1,89 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { dashboardBrainDir, globalBrainDirFor, readBrainToggles, readRegisteredProjects } from '../core/brain-registry.mjs';
4
+ import { projectPluginsDir } from '../core/plugin-loader.mjs';
5
+ import { readProjectId } from '../core/project-id.mjs';
6
+ import { keyHasScope } from './keys.mjs';
7
+
8
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
9
+ const PROJECT_SELECTOR = /^project:[^/\\:,\x00-\x1f\x7f]{1,160}$/u;
10
+
11
+ const targetError = (status, message) => Object.assign(new Error(message), { status });
12
+
13
+ function optionalDirectoriesAreLocal(brainDir) {
14
+ for (const name of ['plugins', 'memory-vault', 'memory-derived']) {
15
+ try {
16
+ if (!fs.lstatSync(path.join(brainDir, name)).isDirectory()) return false;
17
+ } catch (error) {
18
+ if (error.code !== 'ENOENT') return false;
19
+ }
20
+ }
21
+ return true;
22
+ }
23
+
24
+ function resolveProject(row, sharedBrain, toggles) {
25
+ try {
26
+ if (typeof row.path !== 'string' || !path.isAbsolute(row.path) ||
27
+ typeof row.brainDir !== 'string' || !path.isAbsolute(row.brainDir)) return null;
28
+ const projectRoot = fs.realpathSync(row.path);
29
+ const brainDir = path.dirname(projectPluginsDir(projectRoot));
30
+ const identity = path.join(brainDir, 'config', 'brain.json');
31
+ if (brainDir === sharedBrain || fs.realpathSync(row.brainDir) !== brainDir ||
32
+ fs.realpathSync(brainDir) !== brainDir || fs.realpathSync(identity) !== identity ||
33
+ !fs.lstatSync(identity).isFile() || readProjectId(brainDir) !== row.project_id ||
34
+ toggles[path.resolve(row.brainDir)] === false || toggles[brainDir] === false ||
35
+ !optionalDirectoriesAreLocal(brainDir)) return null;
36
+ return { brainDir, projectRoot };
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function authorize(auth, scope) {
43
+ if (auth?.type === 'dashboard_session') return;
44
+ if (auth?.type !== 'pat' || !keyHasScope(auth, scope)) {
45
+ throw targetError(403, 'The token does not grant access to a selected brain.');
46
+ }
47
+ }
48
+
49
+ /** Resolve selected IDs against server-owned registrations before plugin I/O. */
50
+ export function resolvePluginBrainTargets(ids, auth, { registryBrain = globalBrainDirFor() } = {}) {
51
+ if (!Array.isArray(ids) || !ids.length || ids.length > 64 ||
52
+ ids.some(id => typeof id !== 'string' || (id !== 'global' && !PROJECT_SELECTOR.test(id)))) {
53
+ throw targetError(400, 'Select up to 64 global or registered project brain IDs.');
54
+ }
55
+ let sharedBrain, rows, toggles, globalToggles;
56
+ try {
57
+ const projectRegistryBrain = dashboardBrainDir(process.env, registryBrain);
58
+ sharedBrain = fs.realpathSync(registryBrain);
59
+ globalToggles = ids.includes('global') ? readBrainToggles(registryBrain, { strict: true }) : {};
60
+ const needsProjects = ids.some(id => id !== 'global');
61
+ toggles = needsProjects ? readBrainToggles(projectRegistryBrain, { strict: true }) : {};
62
+ rows = needsProjects ? readRegisteredProjects(projectRegistryBrain) : [];
63
+ } catch {
64
+ throw targetError(503, 'Brain registry metadata is unavailable.');
65
+ }
66
+ const resolved = new Set();
67
+ return [...new Set(ids)].map(brainId => {
68
+ if (brainId === 'global') {
69
+ authorize(auth, 'brain:global');
70
+ if (globalToggles[path.resolve(registryBrain)] === false || globalToggles[sharedBrain] === false) {
71
+ throw targetError(404, 'The selected global brain is unavailable.');
72
+ }
73
+ return { brainId, name: 'Global Brain', brainDir: sharedBrain, projectRoot: null };
74
+ }
75
+ const selector = brainId.slice('project:'.length);
76
+ const matches = rows.filter(row => row.name === selector || row.project_id === selector);
77
+ if (matches.length > 1) throw targetError(409, 'A selected project brain ID is ambiguous.');
78
+ const row = matches[0];
79
+ if (typeof row?.project_id !== 'string' || !UUID.test(row.project_id) || rows.filter(candidate => candidate.project_id === row.project_id).length !== 1) {
80
+ throw targetError(404, 'A selected project brain is unavailable.');
81
+ }
82
+ authorize(auth, 'brain:project-' + row.project_id);
83
+ const target = resolveProject(row, sharedBrain, toggles);
84
+ if (!target) throw targetError(404, 'A selected project brain is unavailable.');
85
+ if (resolved.has(target.brainDir)) throw targetError(400, 'Select each project brain once.');
86
+ resolved.add(target.brainDir);
87
+ return { brainId, name: row.name || selector, ...target };
88
+ });
89
+ }
@@ -121,6 +121,7 @@ import updateRouter from './routes/update.mjs';
121
121
  import { rulesRouter } from './routes/rules.mjs';
122
122
  import notificationsRouter from './routes/notifications.mjs';
123
123
  import pluginsRouter from './routes/plugins.mjs';
124
+ import uiChunksRouter from './routes/ui-chunks.mjs';
124
125
  // ollamaUrl removed — CLI agents replace Ollama
125
126
  import {
126
127
  AGENT_DIR,
@@ -235,6 +236,7 @@ router.use(ssssRouter);
235
236
  router.use(rulesRouter);
236
237
  router.use(notificationsRouter);
237
238
  router.use(pluginsRouter);
239
+ router.use(uiChunksRouter);
238
240
  for (const pluginRouter of await (await import('../core/plugin-runtime.mjs')).loadPluginRouters()) router.use(pluginRouter);
239
241
 
240
242
  export { router as restRouter };
@@ -29,13 +29,14 @@ export function extractRoutes(router) {
29
29
 
30
30
  for (const layer of stack) {
31
31
  if (layer.route) {
32
- const pathStr = layer.route.path;
32
+ // Express keeps aliases and regular expressions in Route.path.
33
+ const paths = [layer.route.path].flat(Infinity).map(String);
33
34
  const methods = Object.keys(layer.route.methods)
34
35
  .filter((method) => layer.route.methods[method])
35
36
  .map((method) => method.toUpperCase());
36
37
 
37
38
  for (const method of methods) {
38
- routes.push({ method, path: pathStr });
39
+ for (const path of paths) routes.push({ method, path });
39
40
  }
40
41
  } else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
41
42
  // Sub-router mounted via router.use()
@@ -91,6 +91,14 @@
91
91
  "method": "POST",
92
92
  "path": "/api/daemon/restart"
93
93
  },
94
+ {
95
+ "method": "GET",
96
+ "path": "/api/dashboard/drawers/:edge"
97
+ },
98
+ {
99
+ "method": "PUT",
100
+ "path": "/api/dashboard/drawers/:edge"
101
+ },
94
102
  {
95
103
  "method": "GET",
96
104
  "path": "/api/dashboard/instructions"
@@ -467,10 +475,6 @@
467
475
  "method": "POST",
468
476
  "path": "/api/plugins/:id/branding"
469
477
  },
470
- {
471
- "method": "PATCH",
472
- "path": "/api/plugins/:id/card-face"
473
- },
474
478
  {
475
479
  "method": "GET",
476
480
  "path": "/api/plugins/:id/configuration"
@@ -843,6 +847,34 @@
843
847
  "method": "GET",
844
848
  "path": "/api/tts/status"
845
849
  },
850
+ {
851
+ "method": "GET",
852
+ "path": "/api/ui-chunks"
853
+ },
854
+ {
855
+ "method": "GET",
856
+ "path": "/api/ui-chunks/:plugin/:id"
857
+ },
858
+ {
859
+ "method": "GET",
860
+ "path": "/api/ui-chunks/:plugin/composition/current"
861
+ },
862
+ {
863
+ "method": "GET",
864
+ "path": "/api/ui-chunks/:plugin/design/revision"
865
+ },
866
+ {
867
+ "method": "POST",
868
+ "path": "/api/ui-chunks/compose"
869
+ },
870
+ {
871
+ "method": "POST",
872
+ "path": "/api/ui-chunks/pool"
873
+ },
874
+ {
875
+ "method": "POST",
876
+ "path": "/api/ui-chunks/suggest"
877
+ },
846
878
  {
847
879
  "method": "GET",
848
880
  "path": "/api/update/check"
@@ -939,6 +971,22 @@
939
971
  "method": "POST",
940
972
  "path": "/api/webhooks/test/:provider"
941
973
  },
974
+ {
975
+ "method": "GET",
976
+ "path": "/api/workspace/pages"
977
+ },
978
+ {
979
+ "method": "POST",
980
+ "path": "/api/workspace/pages"
981
+ },
982
+ {
983
+ "method": "GET",
984
+ "path": "/api/workspace/pages/:id"
985
+ },
986
+ {
987
+ "method": "PUT",
988
+ "path": "/api/workspace/pages/:id"
989
+ },
942
990
  {
943
991
  "method": "POST",
944
992
  "path": "/auth/change-password"
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url';
13
13
  import { agentDir, brainDir, globalBrainDir } from '../../core/config.mjs';
14
14
  import { resolveSurfaceDir } from '../../cli/agent-dir.mjs';
15
15
  import { logger } from '../../core/logger.mjs';
16
+ import { dashboardBrainDir, readRegisteredProjects } from '../../core/brain-registry.mjs';
16
17
 
17
18
  export { globalBrainDir };
18
19
  export const AGENT_DIR = process.env.AGENT_DIR || agentDir;
@@ -33,7 +34,7 @@ export const MODEL_CATALOG_DIR = path.join(ROOT, 'models', 'catalog', 'total-rec
33
34
  * Resolve a brain-specific vault directory from the request.
34
35
  *
35
36
  * Reads `req.query.brain` (query param) or `req.body?.brainId` (body field).
36
- * - If the value is `'global'` or missing, returns the default VAULT_DIR.
37
+ * - Missing selection uses the default server vault. Explicit global selection follows the dashboard.
37
38
  * - If it starts with `'project:'`, looks up the project brain in the
38
39
  * project-registry.json (same resolution logic as GET /api/brains/:id/nodes).
39
40
  * - Falls back to VAULT_DIR if the resolved path doesn't exist.
@@ -52,23 +53,15 @@ export function resolveVaultFromQuery(req, { strict = false } = {}) {
52
53
  }
53
54
  }
54
55
 
55
- // No brain specified or explicitly global → default vault
56
- if (!brainId || brainId === 'global') {
57
- return VAULT_DIR;
58
- }
56
+ if (!brainId) return VAULT_DIR;
57
+ if (brainId === 'global') return path.join(dashboardBrainDir(process.env, BRAIN_DIR), 'memory-vault');
59
58
 
60
59
  if (brainId.startsWith('project:')) {
61
60
  const projectName = brainId.slice('project:'.length);
62
- const registryPath = path.join(globalBrainDir, 'config', 'project-registry.json');
63
-
64
- if (!fs.existsSync(registryPath)) {
65
- if (strict) throw Object.assign(new Error('Selected brain is unavailable'), { status: 404 });
66
- return VAULT_DIR;
67
- }
68
-
69
61
  try {
70
- const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
71
- const project = registry.find(p => p.name === projectName);
62
+ const registry = readRegisteredProjects(dashboardBrainDir(process.env, globalBrainDir));
63
+ const matching = registry.filter(p => p.name === projectName || p.project_id === projectName);
64
+ const project = matching.length === 1 ? matching[0] : null;
72
65
  if (project) {
73
66
  const projectVaultDir = path.join(project.brainDir, 'memory-vault');
74
67
  if (fs.existsSync(projectVaultDir)) {