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
@@ -1,12 +1,13 @@
1
1
  import { useEffect, useState } from 'react'
2
2
  import type { PluginInfo } from '../../api/plugins'
3
3
  import { apiFetch, API_BASE } from '../../api/_base'
4
+ import { readSavedPluginCredentials, savePluginCredential } from '../../api/plugin-credentials'
4
5
 
5
6
  interface Setting { name: string; label: string; type: 'string' | 'number' | 'boolean'; options?: Array<string | number>; required?: boolean; default?: string | number | boolean }
6
7
 
7
8
  /** Credential values are write-only. Existing values never enter browser state. */
8
9
  export function PluginSettings({ plugin }: { plugin: PluginInfo }) {
9
- const [stored, setStored] = useState<Set<string>>(new Set())
10
+ const [stored, setStored] = useState<Set<string> | null>(null)
10
11
  const [values, setValues] = useState<Record<string, string>>({})
11
12
  const [saving, setSaving] = useState('')
12
13
  const [notice, setNotice] = useState('')
@@ -16,13 +17,10 @@ export function PluginSettings({ plugin }: { plugin: PluginInfo }) {
16
17
  useEffect(() => {
17
18
  let disposed = false
18
19
  setValues({}); setNotice(''); setError('')
19
- apiFetch(`${API_BASE}/api/plugins/auth/secrets`).then(async res => {
20
- if (!res.ok) return []
21
- const entries = await res.json()
22
- if (!disposed && Array.isArray(entries)) {
23
- setStored(new Set(entries.map((entry: any) => entry.key)))
24
- }
25
- }).catch((err: any) => { if (!disposed) setError(err.message) })
20
+ setStored(null)
21
+ readSavedPluginCredentials().then(keys => {
22
+ if (!disposed) setStored(keys)
23
+ }).catch((err: Error) => { if (!disposed) setError(err.message) })
26
24
  apiFetch(`${API_BASE}/api/plugins/${encodeURIComponent(plugin.id)}/configuration`).then(async response => {
27
25
  if (!response.ok) throw new Error('Could not load plugin settings')
28
26
  const data = await response.json()
@@ -36,17 +34,9 @@ export function PluginSettings({ plugin }: { plugin: PluginInfo }) {
36
34
  async function save(key: string) {
37
35
  setSaving(key); setError(''); setNotice('')
38
36
  try {
39
- const res = await apiFetch(plugin.manifest?.runtime && (plugin.manifest.runtime as any).modules?.credentials ? `${API_BASE}/api/plugins/${encodeURIComponent(plugin.id)}/credentials` : `${API_BASE}/api/plugins/auth/secrets`, {
40
- method: 'POST',
41
- headers: { 'Content-Type': 'application/json' },
42
- body: JSON.stringify({ key, value: values[key], tags: [`plugin:${plugin.id}`] })
43
- })
44
- if (!res.ok) {
45
- const errData = await res.json().catch(() => ({}))
46
- throw new Error(errData.error || 'Failed to save secret')
47
- }
37
+ await savePluginCredential(plugin, key, values[key])
48
38
  setValues(previous => ({ ...previous, [key]: '' }))
49
- setStored(previous => new Set([...previous, key]))
39
+ setStored(await readSavedPluginCredentials().catch(() => null))
50
40
  setNotice('Credential saved. Open the plugin to verify the connection.')
51
41
  } catch (err: any) { setError(err.message) }
52
42
  finally { setSaving('') }
@@ -83,9 +73,9 @@ export function PluginSettings({ plugin }: { plugin: PluginInfo }) {
83
73
  {(plugin.secrets || []).map((secret, index) => {
84
74
  const label = `Credential ${index + 1}${secret.required ? ' (required)' : ' (optional)'}`
85
75
  return <form key={secret.key} onSubmit={event => { event.preventDefault(); void save(secret.key) }} style={{ marginBottom: 16 }}>
86
- <label style={{ display: 'block', marginBottom: 6 }}>{label} · {stored.has(secret.key) ? 'Saved' : 'Not saved'}
76
+ <label style={{ display: 'block', marginBottom: 6 }}>{label} · {stored?.has(secret.key) ? 'Saved' : stored ? 'Not saved' : 'Status unavailable'}
87
77
  <input className="input" type="password" autoComplete="new-password" value={values[secret.key] || ''}
88
- placeholder={stored.has(secret.key) ? 'Paste a replacement' : 'Paste credential'}
78
+ placeholder={stored?.has(secret.key) ? 'Paste a replacement' : 'Paste credential'}
89
79
  onChange={event => setValues(previous => ({ ...previous, [secret.key]: event.target.value }))} />
90
80
  </label>
91
81
  <button className="btn btn-sm btn-primary" disabled={!values[secret.key]?.trim() || !!saving}>{saving === secret.key ? 'Saving…' : 'Save credential'}</button>
@@ -0,0 +1,367 @@
1
+ import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'
2
+ import { useLocation, useSearchParams } from 'react-router-dom'
3
+ import {
4
+ fetchBundledPlugins, fetchPeerPlugins, fetchPlugins, installPlugin, removePlugin,
5
+ setPluginActivation, setPluginShared, type BrainPluginActivation, type BundledPlugin,
6
+ type PeersResponse, type PluginInfo
7
+ } from '../../api/plugins'
8
+ import { PluginPanel, pluginElements } from './PluginPanel'
9
+ import { PluginSettings } from './PluginSettings'
10
+ import { PluginChunkLibrary } from '../workspace/PluginChunkLibrary'
11
+ import { PluginHome } from '../workspace/PluginHome'
12
+ import { readSavedPluginCredentials } from '../../api/plugin-credentials'
13
+ import { ScopedPluginCommands } from './ScopedPluginCommands'
14
+
15
+ type Notice = { text: string; error?: boolean }
16
+ type OpenAttempt = { pluginId: string; brainId: string; error?: string }
17
+
18
+ interface WorkspaceProps {
19
+ activeBrainId: string
20
+ renderShell: (folder: ReactNode, page: ReactNode, plugins: PluginInfo[] | null) => ReactNode
21
+ onOpenPlugin: () => void
22
+ }
23
+
24
+ function pluginOrigin(plugin: PluginInfo) {
25
+ if (plugin.core) return 'Core'
26
+ if (plugin.scope === 'scaffold' || plugin.source.kind === 'bundled') return 'Bundled'
27
+ if (plugin.source.kind === 'peer' || plugin.source.kind === 'public') return 'Shared'
28
+ return 'User'
29
+ }
30
+
31
+ export default function PluginWorkspace({ activeBrainId, renderShell, onOpenPlugin }: WorkspaceProps) {
32
+ const [params, setParams] = useSearchParams()
33
+ const { pathname } = useLocation()
34
+ const [expanded, setExpanded] = useState<boolean>()
35
+ const [inventory, setInventory] = useState<{ selection: string; plugins: PluginInfo[] }>()
36
+ const [failure, setFailure] = useState<{ selection: string; message: string }>()
37
+ const [openAttempt, setOpenAttempt] = useState<OpenAttempt>()
38
+ const [browse, setBrowse] = useState(false)
39
+ const request = useRef(0)
40
+ const currentSelection = useRef(activeBrainId)
41
+ const plugins = inventory?.selection === activeBrainId ? inventory.plugins : null
42
+ const loadError = failure?.selection === activeBrainId ? failure.message : undefined
43
+ const brainIds = activeBrainId.split(',').filter(Boolean)
44
+ const brainId = brainIds.length === 1 ? brainIds[0] : undefined
45
+ const id = pathname === '/plugins' || (pathname === '/memory' && params.get('page')) ? null : params.get('plugin')
46
+ const open = expanded ?? Boolean(id || params.has('plugins'))
47
+ const selected = id ? plugins?.find(plugin => plugin.id === id || plugin.id === `tr-plugin-${id}` || plugin.name === id) : undefined
48
+
49
+ const refresh = useCallback(async () => {
50
+ if (currentSelection.current !== activeBrainId) return
51
+ const current = ++request.current
52
+ try {
53
+ const installed = await fetchPlugins(activeBrainId)
54
+ if (request.current !== current) return
55
+ setInventory({ selection: activeBrainId, plugins: [...installed].sort((a, b) => a.name.localeCompare(b.name)) })
56
+ setFailure(undefined)
57
+ setOpenAttempt(attempt => attempt?.error ? undefined : attempt)
58
+ } catch (error) {
59
+ if (request.current !== current) return
60
+ setFailure({ selection: activeBrainId, message: error instanceof Error ? error.message : 'Could not load plugins.' })
61
+ }
62
+ }, [activeBrainId])
63
+
64
+ useEffect(() => {
65
+ currentSelection.current = activeBrainId
66
+ void refresh()
67
+ return () => { request.current++ }
68
+ }, [activeBrainId, refresh])
69
+
70
+ function select(pluginId: string | null) {
71
+ const next = new URLSearchParams(params)
72
+ if (pluginId) {
73
+ next.set('plugin', pluginId)
74
+ next.delete('page')
75
+ } else next.delete('plugin')
76
+ setParams(next, { replace: !pluginId })
77
+ }
78
+
79
+ async function openArmed(plugin: PluginInfo) {
80
+ if (!brainId?.startsWith('project:') || plugin.activation?.mode !== 'on-use') return
81
+ if (openAttempt?.brainId === brainId && openAttempt.pluginId === plugin.id && !openAttempt.error) return
82
+ const attempt = { pluginId: plugin.id, brainId }
83
+ setOpenAttempt(attempt)
84
+ const result = await setPluginActivation(plugin.id, 'use', [brainId])
85
+ const error = result.success ? undefined : result.results?.[0]?.error || result.error || 'Could not open this plugin.'
86
+ await refresh()
87
+ setOpenAttempt(current => current === attempt ? (error ? { ...attempt, error } : undefined) : current)
88
+ }
89
+
90
+ const folder = <>
91
+ <button type="button" className="nav-link plugin-folder-toggle" id="nav-plugins"
92
+ aria-expanded={open} aria-controls="plugin-folder" onClick={() => setExpanded(!open)}>
93
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
94
+ <path d="M3 7a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" />
95
+ </svg>
96
+ Plugins <span className="plugin-folder-chevron" aria-hidden="true">{open ? '▾' : '›'}</span>
97
+ </button>
98
+ <div id="plugin-folder" hidden={!open}>
99
+ {loadError ? <div className="plugin-folder-message" role="alert">
100
+ {loadError} <button className="btn btn-ghost btn-sm" onClick={() => void refresh()}>Retry</button>
101
+ </div> : plugins === null ? <p className="plugin-folder-message" role="status">Loading plugins…</p> :
102
+ <ul className="plugin-folder-list" aria-label="Installed plugins">
103
+ {plugins.map(plugin => <li key={plugin.id}>
104
+ <button type="button" className={`nav-link plugin-folder-item ${selected?.id === plugin.id ? 'active' : ''}`}
105
+ aria-current={selected?.id === plugin.id || undefined}
106
+ onClick={() => { select(plugin.id); onOpenPlugin(); void openArmed(plugin) }}>
107
+ <span>{plugin.name}</span><small className="plugin-origin" aria-hidden="true">{pluginOrigin(plugin)}</small>
108
+ </button>
109
+ </li>)}
110
+ {!plugins.length && <li className="plugin-folder-message">No plugins installed</li>}
111
+ </ul>}
112
+ <button type="button" className="nav-link plugin-folder-item" onClick={() => { setBrowse(true); onOpenPlugin() }}>
113
+ Browse and install
114
+ </button>
115
+ </div>
116
+ </>
117
+ let page: ReactNode = null
118
+ if (selected) page = <PluginDetail key={`${activeBrainId}:${selected.id}`} plugin={selected} plugins={plugins} stale={!!loadError} brainId={brainId}
119
+ opening={openAttempt?.pluginId === selected.id && openAttempt.brainId === brainId ? openAttempt : undefined}
120
+ onUse={() => void openArmed(selected)} onClose={() => select(null)} onChange={refresh} />
121
+ else if (id && (loadError || plugins)) page = <section className="plugin-workspace-page" aria-label="Plugin unavailable">
122
+ <p role="alert">{loadError || `No installed plugin matches ${id}.`}</p>
123
+ <button type="button" className="btn btn-ghost" onClick={() => select(null)}>Close</button>
124
+ </section>
125
+
126
+ return <>
127
+ {renderShell(folder, page, loadError ? null : plugins)}
128
+ {browse && <PluginInstaller onClose={() => setBrowse(false)} onChange={refresh} />}
129
+ </>
130
+ }
131
+
132
+ function PluginDialog({ title, children, onClose }: { title: string; children: ReactNode; onClose: () => void }) {
133
+ return <div className="plugin-drawer-backdrop" onClick={onClose}>
134
+ <section className="plugin-drawer" role="dialog" aria-modal="true" aria-label={title}
135
+ onClick={event => event.stopPropagation()} onKeyDown={event => {
136
+ if (event.key === 'Escape') { event.stopPropagation(); onClose() }
137
+ }}>
138
+ <header className="plugin-drawer-header">
139
+ <h2>{title}</h2>
140
+ <button type="button" className="btn btn-ghost btn-sm" autoFocus onClick={onClose}>Close</button>
141
+ </header>
142
+ {children}
143
+ </section>
144
+ </div>
145
+ }
146
+
147
+ function PluginDetail({ plugin, plugins, brainId, stale, opening, onUse, onChange, onClose }: {
148
+ plugin: PluginInfo; plugins: PluginInfo[] | null; brainId?: string; stale: boolean; opening?: OpenAttempt;
149
+ onUse: () => void; onChange: () => Promise<void>; onClose: () => void
150
+ }) {
151
+ const panels = pluginElements(plugin).filter(element => element.kind !== 'settings')
152
+ const activation = plugin.activation
153
+ const error = !plugin.valid || activation?.state === 'error'
154
+ const enabled = activation?.enabled === true && !error
155
+ const rows = plugin.activationByBrain ?? (brainId && activation && activation.state !== 'mixed'
156
+ ? [{ ...activation, state: activation.state, brainId, name: brainId === 'global' ? 'Global Brain' : brainId.slice('project:'.length) }]
157
+ : [])
158
+ const armed = rows.find(row => row.brainId === brainId && row.mode === 'on-use')
159
+ const canManage = plugin.servingAvailable !== false
160
+ const [tab, setTab] = useState<'home' | 'interface' | 'settings' | 'commands' | 'blocks'>('home')
161
+ const [initialCommand, setInitialCommand] = useState<string>()
162
+ const [panelId, setPanelId] = useState(panels[0]?.id)
163
+ const [changing, setChanging] = useState(false)
164
+ const busy = changing || Boolean(opening && !opening.error)
165
+ const [notice, setNotice] = useState<Notice>()
166
+ const [cleanupRetries, setCleanupRetries] = useState<string[]>([])
167
+ const [uninstall, setUninstall] = useState(false)
168
+ const [savedCredentials, setSavedCredentials] = useState<Set<string> | null>(null)
169
+ const panel = panels.find(item => item.id === panelId) ?? panels[0]
170
+ function openCommand(name: string) {
171
+ setInitialCommand(name)
172
+ setTab('commands')
173
+ }
174
+ const declaredCredentials = plugin.secrets || []
175
+ const credentialCount = declaredCredentials.length
176
+ useEffect(() => {
177
+ if (!credentialCount) return
178
+ let disposed = false
179
+ void readSavedPluginCredentials().then(keys => {
180
+ if (!disposed) setSavedCredentials(keys)
181
+ }).catch(() => { if (!disposed) setSavedCredentials(null) })
182
+ return () => { disposed = true }
183
+ }, [plugin.id, credentialCount, tab])
184
+ const credentialLabel = !credentialCount ? 'Dashboard session'
185
+ : !savedCredentials ? 'Credential status unavailable'
186
+ : declaredCredentials.some(secret => secret.required && !savedCredentials.has(secret.key)) ? 'Credential required'
187
+ : declaredCredentials.some(secret => savedCredentials.has(secret.key)) ? 'Saved in Auth' : 'Optional credential'
188
+
189
+ async function change(action: BrainPluginActivation | 'share' | 'uninstall', value?: boolean | 'on-use') {
190
+ setChanging(true)
191
+ setNotice(undefined)
192
+ try {
193
+ if (typeof action === 'object') {
194
+ const result = await setPluginActivation(plugin.id, value ?? !action.enabled, [action.brainId])
195
+ const target = result.results?.find(item => item.brainId === action.brainId)
196
+ if (target?.cleanup?.ok === false) {
197
+ setCleanupRetries(current => current.includes(action.brainId) ? current : [...current, action.brainId])
198
+ } else if (result.success || target?.cleanup?.ok === true) {
199
+ setCleanupRetries(current => current.filter(id => id !== action.brainId))
200
+ }
201
+ if (!result.success) {
202
+ setNotice({ text: target?.error ? `${target.name}. ${target.error}` : result.error || 'Could not update activation.', error: true })
203
+ }
204
+ } else {
205
+ const result = action === 'share' ? await setPluginShared(plugin.id, !plugin.shared) : await removePlugin(plugin.id, plugin.scope === 'global')
206
+ if (!result.success) setNotice({ text: result.error || 'Could not update the plugin.', error: true })
207
+ if (action === 'uninstall' && result.success) onClose()
208
+ }
209
+ // The record can persist even when recompilation reports a failure.
210
+ await onChange()
211
+ } finally { setChanging(false) }
212
+ }
213
+
214
+ return <section className="plugin-workspace-page" aria-label={plugin.name}>
215
+ <header className="plugin-drawer-header">
216
+ <h1>{plugin.name}</h1>
217
+ <button type="button" className="btn btn-ghost btn-sm" onClick={onClose}>Close</button>
218
+ </header>
219
+ <p className="plugin-muted">{plugin.description}</p>
220
+ <dl className="plugin-detail-status">
221
+ <div><dt>Brain activation</dt><dd>
222
+ <span className={`badge ${error ? 'badge-error' : enabled ? 'badge-success' : ''}`}>
223
+ {stale ? 'Unknown' : activation?.state === 'mixed' ? 'Mixed' : error ? 'Error' : activation?.mode === 'on-use' ? 'On use' : enabled ? 'Active' : activation ? 'Off' : 'Unknown'}
224
+ </span>
225
+ </dd></div>
226
+ <div><dt>Authentication</dt><dd>{credentialLabel}</dd></div>
227
+ <div><dt>Provider connection</dt><dd>Unreported</dd></div>
228
+ <div><dt>Installed</dt><dd>v{plugin.version} · {pluginOrigin(plugin)} · {plugin.scope}</dd></div>
229
+ </dl>
230
+ {!!rows.length && <ul className="plugin-folder-list" aria-label="Activation by brain">
231
+ {rows.map(row => <li className="plugin-drawer-actions" key={row.brainId}>
232
+ <span>{row.name}{row.brainId === 'global' && <small className="plugin-muted"> · Shared default</small>}</span>
233
+ {row.state === 'unavailable' || row.state === 'error' ? <span>{row.state === 'error' ? 'Error' : 'Unavailable'}</span> :
234
+ row.brainId.startsWith('project:') ? <select className="select"
235
+ style={{ minHeight: 44, width: 'auto' }}
236
+ aria-label={`${plugin.name} activation for ${row.name}`} aria-busy={busy || undefined}
237
+ disabled={stale || busy || row.required} value={row.mode || (row.enabled ? 'on' : 'off')}
238
+ onChange={event => {
239
+ const mode = event.currentTarget.value
240
+ void change(row, mode === 'on-use' ? mode : mode === 'on')
241
+ }}>
242
+ <option value="off">Off</option>
243
+ <option value="on-use">On use</option>
244
+ <option value="on">On</option>
245
+ </select> :
246
+ <button type="button" role="switch" aria-checked={row.enabled}
247
+ aria-label={rows.length === 1 ? `${plugin.name} activation` : `${plugin.name} activation for ${row.name}`}
248
+ title={row.required ? 'Required plugin' : undefined} className="btn btn-ghost btn-sm"
249
+ disabled={stale || busy || row.required} onClick={() => void change(row)}>
250
+ {stale ? 'Unknown' : busy ? 'Updating…' : row.enabled ? 'On' : 'Off'}
251
+ </button>}
252
+ {!row.enabled && cleanupRetries.includes(row.brainId) && <button type="button"
253
+ className="btn btn-ghost btn-sm" disabled={stale || busy}
254
+ aria-label={`Retry cleanup for ${row.name}`} onClick={() => void change(row, false)}>Retry cleanup</button>}
255
+ </li>)}
256
+ </ul>}
257
+ {stale && <p role="alert">The installed state could not be refreshed.</p>}
258
+ {error && <p role="alert">{plugin.errors[0] || 'The activation policy could not be read.'}</p>}
259
+ {opening?.error && !enabled && !notice && <p role="alert">{opening.error}</p>}
260
+ {notice && <p role={notice.error ? 'alert' : 'status'}>{notice.text}</p>}
261
+ {!brainId ? <p className="plugin-muted">Select one brain to open this plugin’s tools.</p> : <>
262
+ <nav className="plugin-drawer-actions" aria-label={`${plugin.name} views`}>
263
+ <button className={`btn ${tab === 'home' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'home'}
264
+ onClick={() => setTab('home')}>Home</button>
265
+ {panel && enabled && <button className={`btn ${tab === 'interface' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'interface'}
266
+ onClick={() => setTab('interface')}>Interface</button>}
267
+ {enabled && !stale && <button className={`btn ${tab === 'blocks' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'blocks'}
268
+ onClick={() => setTab('blocks')}>Blocks</button>}
269
+ {canManage && <button className={`btn ${tab === 'settings' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'settings'}
270
+ onClick={() => setTab('settings')}>Settings</button>}
271
+ {plugin.cli && <button className={`btn ${tab === 'commands' ? 'btn-primary' : 'btn-ghost'}`} aria-pressed={tab === 'commands'}
272
+ onClick={() => setTab('commands')}>Commands</button>}
273
+ </nav>
274
+ {tab === 'home' && (enabled && !stale
275
+ ? <PluginHome plugin={plugin} plugins={plugins} brainId={brainId} onCommand={openCommand} />
276
+ : armed && !stale ? <div className="plugin-drawer-actions">
277
+ <span className="plugin-muted">This plugin activates when you open it.</span>
278
+ <button type="button" className="btn btn-primary" disabled={busy}
279
+ onClick={onUse}>{busy ? 'Opening…' : 'Open plugin'}</button>
280
+ </div> : <p className="plugin-muted">An active plugin is needed to open its Home tools.</p>)}
281
+ {tab === 'interface' && enabled && panel && <>
282
+ {panels.length > 1 && <label>Panel
283
+ <select className="select" value={panel.id} onChange={event => setPanelId(event.target.value)}>
284
+ {panels.map(item => <option key={item.id} value={item.id}>{item.description || item.id}</option>)}
285
+ </select>
286
+ </label>}
287
+ <PluginPanel key={panel.id} plugin={plugin} elementId={panel.id} brainId={brainId} readOnly={stale} />
288
+ </>}
289
+ {tab === 'settings' && canManage && <>
290
+ {!panel && <p className="plugin-muted">This package has no installed workspace panel.</p>}
291
+ {plugin.valid && <PluginSettings plugin={plugin} />}
292
+ <details>
293
+ <summary>Manage installation</summary>
294
+ <div className="plugin-drawer-actions">
295
+ <button className="btn btn-ghost" disabled={busy || stale || !plugin.valid} onClick={() => void change('share')}>
296
+ {plugin.shared ? 'Stop sharing' : 'Share on mesh'}
297
+ </button>
298
+ {!uninstall ? <button className="btn btn-ghost" disabled={busy || stale || activation?.required}
299
+ onClick={() => setUninstall(true)}>Uninstall…</button>
300
+ : <button className="btn btn-ghost" disabled={busy || stale} onClick={() => void change('uninstall')}>
301
+ Confirm uninstall
302
+ </button>}
303
+ </div>
304
+ </details>
305
+ </>}
306
+ {tab === 'commands' && plugin.cli && <ScopedPluginCommands key={plugin.sha256} plugin={plugin} brainId={brainId}
307
+ enabled={enabled && !stale} initialCommand={initialCommand} />}
308
+ {tab === 'blocks' && enabled && !stale && <PluginChunkLibrary key={`${plugin.id}:${brainId}`}
309
+ plugin={plugin} brainId={brainId} />}
310
+ </>}
311
+ </section>
312
+ }
313
+
314
+ function PluginInstaller({ onChange, onClose }: { onChange: () => Promise<void>; onClose: () => void }) {
315
+ const [source, setSource] = useState('')
316
+ const [bundled, setBundled] = useState<BundledPlugin[]>([])
317
+ const [peers, setPeers] = useState<PeersResponse | null>()
318
+ const [busy, setBusy] = useState(false)
319
+ const [notice, setNotice] = useState<Notice>()
320
+ useEffect(() => {
321
+ let disposed = false
322
+ void fetchBundledPlugins().then(result => { if (!disposed) setBundled(result) })
323
+ return () => { disposed = true }
324
+ }, [])
325
+
326
+ async function install() {
327
+ setBusy(true)
328
+ setNotice(undefined)
329
+ try {
330
+ const result = await installPlugin({ source: source.trim(), global: true })
331
+ setNotice({ text: result.message || result.error || (result.success ? 'Plugin installed.' : 'Install failed.'), error: !result.success })
332
+ if (result.success) {
333
+ setSource('')
334
+ setBundled(await fetchBundledPlugins())
335
+ }
336
+ await onChange()
337
+ } finally { setBusy(false) }
338
+ }
339
+
340
+ return <PluginDialog title="Browse and install plugins" onClose={onClose}>
341
+ <p className="plugin-muted">Installs into the shared plugin folder on this host.</p>
342
+ <form className="plugin-drawer-form" onSubmit={event => { event.preventDefault(); void install() }}>
343
+ <label>Plugin source<input className="input" value={source} onChange={event => setSource(event.target.value)}
344
+ placeholder="Plugin ID, folder, Git URL, or peer link" required /></label>
345
+ <button type="submit" className="btn btn-primary" disabled={busy || !source.trim()}>Install</button>
346
+ </form>
347
+ {notice && <p role={notice.error ? 'alert' : 'status'}>{notice.text}</p>}
348
+ <details><summary>Bundled plugins</summary>
349
+ <ul className="plugin-folder-list">{bundled.map(plugin => <li key={plugin.id}>
350
+ <button className="btn btn-ghost" disabled={busy || plugin.installed} onClick={() => setSource(plugin.id)}>
351
+ {plugin.name}{plugin.installed ? ' (installed)' : ''}
352
+ </button>
353
+ </li>)}</ul>
354
+ </details>
355
+ <details><summary>Mesh peers</summary>
356
+ <button className="btn btn-ghost" onClick={async () => setPeers(await fetchPeerPlugins())}>Load shared plugins</button>
357
+ {peers === null && <p role="status">Could not load mesh peers.</p>}
358
+ {peers?.peers.map(peer => <div key={peer.hostname}>
359
+ <p>{peer.hostname} · {peer.status === 'ok' ? 'online' : peer.error || peer.status}</p>
360
+ <ul className="plugin-folder-list">{peer.plugins.map(plugin => <li key={plugin.id}>
361
+ <button className="btn btn-ghost" disabled={busy || plugin.same_as_installed}
362
+ onClick={() => setSource(`peer:${peer.hostname}/${plugin.id}`)}>{plugin.name}</button>
363
+ </li>)}</ul>
364
+ </div>)}
365
+ </details>
366
+ </PluginDialog>
367
+ }
@@ -0,0 +1,142 @@
1
+ import { useRef, useState } from 'react'
2
+ import { runPluginCommand, type PluginInfo } from '../../api/plugins'
3
+ import { scopedPluginOperations, type Argument, type Command } from './scoped-plugin-operations'
4
+
5
+ type InputValues = Record<string, string | boolean>
6
+ type Run = { command: string; args: string[] }
7
+
8
+ function browserArguments(command: Command) {
9
+ return (command.args || []).filter(arg => !command.terminal_only_args?.includes(arg.flag || ''))
10
+ }
11
+
12
+ function buildArguments(command: Command, values: InputValues, extra: string): string[] {
13
+ const args: string[] = []
14
+ let omittedPosition: Argument | undefined
15
+ for (const arg of browserArguments(command)) {
16
+ const input = values[arg.name]
17
+ const value = arg.type === 'boolean' ? (input === true ? 'true' : '') : typeof input === 'string' ? input : ''
18
+ if (!value.trim()) {
19
+ if (arg.required) throw new Error(`Enter ${arg.label}.`)
20
+ if (!arg.flag) omittedPosition ??= arg
21
+ continue
22
+ }
23
+ if (arg.flag) args.push(arg.flag)
24
+ else if (omittedPosition) throw new Error(`Enter ${omittedPosition.label} before ${arg.label}.`)
25
+ if (arg.type !== 'boolean' || !arg.flag) args.push(value)
26
+ }
27
+ if (extra.trim()) {
28
+ let parsed: unknown
29
+ try { parsed = JSON.parse(extra) }
30
+ catch { throw new Error('Arguments must be a JSON array of strings.') }
31
+ if (!Array.isArray(parsed) || parsed.some(value => typeof value !== 'string')) {
32
+ throw new Error('Arguments must be a JSON array of strings.')
33
+ }
34
+ args.push(...parsed)
35
+ }
36
+ if (args.some(arg => arg.includes('\0'))) throw new Error('An argument contains an invalid character.')
37
+ if (args.some(arg => command.terminal_only_args?.includes(arg.split('=', 1)[0]))) {
38
+ throw new Error('Use the owning CLI for terminal-only arguments.')
39
+ }
40
+ return args
41
+ }
42
+
43
+ export function ScopedPluginCommands({ plugin, brainId, enabled = true, operationIds, initialCommand, label }: {
44
+ plugin: PluginInfo
45
+ brainId: string
46
+ enabled?: boolean
47
+ operationIds?: readonly string[]
48
+ initialCommand?: string
49
+ label?: string
50
+ }) {
51
+ const commands = scopedPluginOperations(plugin, operationIds)
52
+ const [selected, setSelected] = useState(initialCommand)
53
+ const command = commands.find(item => item.name === selected) ?? commands[0]
54
+ const [values, setValues] = useState<InputValues>({})
55
+ const [extra, setExtra] = useState('')
56
+ const [pending, setPending] = useState<Run>()
57
+ const [output, setOutput] = useState<{ text: string; error: boolean }>()
58
+ const [busy, setBusy] = useState(false)
59
+ const running = useRef(false)
60
+ const canRun = enabled && plugin.valid && plugin.activation?.enabled === true &&
61
+ plugin.activation.state !== 'error' && Boolean(brainId) && !brainId.includes(',')
62
+ if (!command) return null
63
+ const fields = browserArguments(command)
64
+
65
+ function clearResult() {
66
+ setPending(undefined)
67
+ setOutput(undefined)
68
+ }
69
+
70
+ function changeValue(name: string, value: string | boolean) {
71
+ setValues(current => ({ ...current, [name]: value }))
72
+ clearResult()
73
+ }
74
+
75
+ async function run(request: Run) {
76
+ if (!canRun || running.current || !commands.some(item => item.name === request.command)) return
77
+ running.current = true
78
+ setBusy(true)
79
+ clearResult()
80
+ try {
81
+ const result = await runPluginCommand(plugin.id, request.command, request.args, { brainId })
82
+ setOutput({
83
+ text: result.output || result.error || (result.success ? 'Command completed.' : 'Command failed.'),
84
+ error: !result.success,
85
+ })
86
+ } catch (error) {
87
+ setOutput({ text: error instanceof Error ? error.message : 'Command failed.', error: true })
88
+ } finally {
89
+ running.current = false
90
+ setBusy(false)
91
+ }
92
+ }
93
+
94
+ const argumentInput = <label>Arguments (JSON array)
95
+ <textarea className="input" rows={2} value={extra} placeholder='["--name", "Value with spaces"]'
96
+ onChange={event => { setExtra(event.target.value); clearResult() }} />
97
+ </label>
98
+
99
+ return <form className="plugin-drawer-form" aria-label={label || `${plugin.name} commands`}
100
+ onSubmit={event => {
101
+ event.preventDefault()
102
+ if (!canRun || running.current || pending) return
103
+ try {
104
+ const request = { command: command.name, args: buildArguments(command, values, extra) }
105
+ if (command.effect === 'read') void run(request)
106
+ else { setOutput(undefined); setPending(request) }
107
+ } catch (error) {
108
+ setOutput({ text: error instanceof Error ? error.message : 'Invalid arguments.', error: true })
109
+ }
110
+ }}>
111
+ <fieldset className="plugin-drawer-form" disabled={!canRun || busy} style={{ border: 0, margin: 0, padding: 0 }}>
112
+ <label>Command
113
+ <select className="select" value={command.name} onChange={event => {
114
+ setSelected(event.target.value); setValues({}); setExtra(''); clearResult()
115
+ }}>
116
+ {commands.map(item => <option key={item.name} value={item.name}>{item.name}</option>)}
117
+ </select>
118
+ </label>
119
+ {command.description && <p className="plugin-muted">{command.description}</p>}
120
+ {fields.map(arg => <label key={arg.name}>{arg.label}
121
+ {arg.type === 'choice' ? <select className="select" required={arg.required} value={String(values[arg.name] ?? '')}
122
+ onChange={event => changeValue(arg.name, event.target.value)}>
123
+ <option value="">Choose…</option>
124
+ {arg.options?.map(option => <option key={option} value={option}>{option}</option>)}
125
+ </select> : arg.type === 'boolean' ? <input type="checkbox" required={arg.required} checked={values[arg.name] === true}
126
+ onChange={event => changeValue(arg.name, event.target.checked)} /> :
127
+ <input className="input" type={arg.type === 'number' ? 'number' : 'text'} step={arg.type === 'number' ? 'any' : undefined}
128
+ required={arg.required} value={String(values[arg.name] ?? '')} onChange={event => changeValue(arg.name, event.target.value)} />}
129
+ </label>)}
130
+ {fields.length ? <details><summary>Additional arguments</summary>{argumentInput}</details> : argumentInput}
131
+ {pending ? <div className="drawer-stack" role="group" aria-label="Confirm command">
132
+ <p>Confirm <strong>{pending.command}</strong> in the selected brain.</p>
133
+ {pending.args.length > 0 && <pre className="plugin-command-output">{JSON.stringify(pending.args, null, 2)}</pre>}
134
+ <div className="drawer-actions">
135
+ <button type="button" className="btn btn-primary" onClick={() => void run(pending)}>Confirm and run</button>
136
+ <button type="button" className="btn btn-ghost" onClick={() => setPending(undefined)}>Cancel</button>
137
+ </div>
138
+ </div> : <button type="submit" className="btn btn-primary">{busy ? 'Running…' : 'Run command'}</button>}
139
+ </fieldset>
140
+ {output && <pre className="plugin-command-output" role={output.error ? 'alert' : 'status'}>{output.text}</pre>}
141
+ </form>
142
+ }