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,24 +1,18 @@
1
- const inlineJson = (value: unknown) => JSON.stringify(value).replace(/</g, '\\u003c')
1
+ import type { PluginInfo } from '../../api/plugins'
2
2
 
3
- export interface PluginScreen { id: string; label: string }
3
+ const inlineJson = (value: unknown) => JSON.stringify(value).replace(/</g, '\\u003c')
4
4
 
5
- /** Bound untrusted screen metadata without deriving any screen from commands. */
6
- export function sanitizePluginScreens(value: unknown): PluginScreen[] {
7
- if (!Array.isArray(value)) return []
8
- const seen = new Set<string>()
9
- return value.slice(0, 128).flatMap(item => {
10
- if (!item || typeof item !== 'object' || typeof item.id !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(item.id)
11
- // eslint-disable-next-line no-control-regex -- rejects labels that contain control characters
12
- || typeof item.label !== 'string' || !item.label.trim() || item.label.length > 160 || /[\u0000-\u001f\u007f]/.test(item.label) || seen.has(item.id)) return []
13
- seen.add(item.id)
14
- return [{ id: item.id, label: item.label.trim() }]
15
- })
5
+ export function browserPluginOperations(plugin: PluginInfo) {
6
+ const manifestCli = plugin.manifest?.cli as { subcommands?: NonNullable<PluginInfo['cli']>['subcommands'] } | undefined
7
+ const blocked = (command: { sensitive_output?: boolean; terminal_only?: boolean }) => command.sensitive_output === true || command.terminal_only === true
8
+ const excluded = new Set(manifestCli?.subcommands?.filter(blocked).map(command => command.name))
9
+ return (plugin.cli?.subcommands || []).filter(command => !blocked(command) && !excluded.has(command.name))
16
10
  }
17
11
 
18
12
  /** Owner code runs in an opaque origin, with no dashboard DOM/storage or direct network. */
19
- export function pluginFrameDocument(source: string, tag: string, channel: string, theme: Record<string, string> = {}, pluginId = '', commandTimeouts: Record<string, number> = {}, operations: readonly string[] = []) {
13
+ export function pluginFrameDocument(source: string, tag: string, channel: string, theme: Record<string, string> = {}, pluginId = '', commandTimeouts: Record<string, number> = {}, operations: readonly string[] = [], credentialPanel = false, authInventory = false, authChanges = false) {
20
14
  return `<!doctype html><html><head>
21
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${channel}' blob:; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com data:; img-src data: https:; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'">
15
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${channel}' blob:; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com data:; img-src data:${authChanges ? '' : ' https:'}; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'">
22
16
  <meta name="referrer" content="no-referrer">
23
17
  <link rel="preconnect" href="https://fonts.googleapis.com">
24
18
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -27,20 +21,16 @@ export function pluginFrameDocument(source: string, tag: string, channel: string
27
21
  </head><body><script nonce="${channel}" type="module">
28
22
  const channel=${inlineJson(channel)};
29
23
  const pending=new Map();let sequence=0;
24
+ const baseStyle=document.createElement('style');
25
+ const designStyle=document.createElement('style');
26
+ baseStyle.textContent=':root{}';
27
+ document.head.append(baseStyle,designStyle);
30
28
  const send=(type,extra={})=>parent.postMessage({channel,type,...extra},'*');
31
- let ownerScreens=[];
32
- const sanitizeScreens=${sanitizePluginScreens.toString()};
33
- function publishScreens(element){
34
- ownerScreens=sanitizeScreens(element.screens);
35
- send('screens',{screens:ownerScreens});
36
- if(ownerScreens.some(screen=>screen.id===element.screen))host.screenChanged(element.screen);
37
- }
38
- window.addEventListener('contextmenu',event=>{event.preventDefault();send('contextmenu');});
39
- for(const [name,value] of Object.entries(${inlineJson(theme)}))document.documentElement.style.setProperty(name,value);
29
+ for(const [name,value] of Object.entries(${inlineJson(theme)}))baseStyle.sheet.cssRules[0].style.setProperty(name,value);
40
30
  window.addEventListener('message',event=>{
41
31
  if(event.source!==parent||event.data?.channel!==channel)return;
42
- if(event.data.type==='show-screen'){
43
- if(ownerScreens.some(screen=>screen.id===event.data.screen))window.dispatchEvent(new CustomEvent('plugin-show-screen',{detail:{pluginId:${inlineJson(pluginId)},screen:event.data.screen}}));
32
+ if(event.data.type==='design'&&typeof event.data.css==='string'){
33
+ designStyle.textContent=event.data.css;
44
34
  return;
45
35
  }
46
36
  if(event.data.type!=='result')return;
@@ -48,22 +38,43 @@ window.addEventListener('message',event=>{
48
38
  pending.delete(event.data.requestId);clearTimeout(request.timer);
49
39
  if(event.data.error)request.reject(new Error(event.data.error));else request.resolve(event.data.result);
50
40
  });
51
- const host=Object.freeze({pluginId:${inlineJson(pluginId)},operations:Object.freeze(${inlineJson(operations)}),screenChanged(screen){if(ownerScreens.some(item=>item.id===screen))send('screen-changed',{screen});},run(subcommand,args=[],options={}){return new Promise((resolve,reject)=>{
41
+ function request(type, extra, timeout=30000){return new Promise((resolve,reject)=>{
52
42
  const requestId=String(++sequence);
43
+ const timer=setTimeout(()=>{pending.delete(requestId);reject(new Error('Operation timed out'));},timeout+5000);
44
+ pending.set(requestId,{resolve,reject,timer});send(type,{requestId,...extra});
45
+ });}
46
+ const host=Object.freeze({pluginId:${inlineJson(pluginId)},operations:Object.freeze(${inlineJson(operations)}),
47
+ ...(${authInventory}?{readInventory:resource=>request('auth-inventory',{resource})}:{}),
48
+ ...(${authChanges}?{changeCredential:change=>request('auth-credential-mutate',{change})}:{}),
49
+ run(subcommand,args=[],options={}){
53
50
  const declared=${inlineJson(commandTimeouts)}[subcommand];
54
51
  const timeout=Number.isInteger(declared)&&declared>=1&&declared<=600000?declared:30000;
55
- const timer=setTimeout(()=>{pending.delete(requestId);reject(new Error('Operation timed out'));},timeout+5000);
56
- pending.set(requestId,{resolve,reject,timer});send('run',{requestId,subcommand,args,...(options.stdin!==undefined?{stdin:options.stdin}:{})});
57
- });}});
52
+ return request('run',{subcommand,args,...(options.stdin!==undefined?{stdin:options.stdin}:{})},timeout);
53
+ }});
58
54
  try{
59
55
  const url=URL.createObjectURL(new Blob([${inlineJson(source)}],{type:'text/javascript'}));
60
56
  try{await import(url);}finally{URL.revokeObjectURL(url);}
61
57
  const tag=${inlineJson(tag)};
62
58
  if(!customElements.get(tag))throw new Error('The plugin did not register its declared interface');
63
- const element=document.createElement(tag);element.host=host;document.body.append(element);
64
- publishScreens(element);
59
+ const element=document.createElement(tag);element.host=host;
60
+ if(${credentialPanel}){
61
+ const message=document.createElement('p');message.setAttribute('role','status');
62
+ message.textContent='Checking saved credentials…';document.body.append(message);
63
+ try{
64
+ element.view=await request('credential-status',{});
65
+ message.textContent='';
66
+ element.addEventListener('save-token',async event=>{
67
+ message.textContent='Checking the credential…';
68
+ try{
69
+ element.view=await request('credential-save',{key:event.detail?.key,value:event.detail?.token});
70
+ message.textContent='Credential saved.';
71
+ }catch(error){message.textContent=error.message;}
72
+ });
73
+ document.body.append(element);
74
+ }catch(error){message.textContent=error.message;}
75
+ }else document.body.append(element);
65
76
  if(typeof element.load==='function'&&!element.hasAttribute('data-loaded')){try{element.load();}catch(_){}}
66
- if('view' in element||tag==='domains-console'||tag==='stripe-panel'){
77
+ if(!${credentialPanel}&&('view' in element||tag==='domains-console'||tag==='stripe-panel')){
67
78
  host.run('view').then(v=>{element.view=v;}).catch(()=>{
68
79
  host.run('status').then(s=>{element.view=s;}).catch(()=>{});
69
80
  });
@@ -0,0 +1,28 @@
1
+ import type { PluginInfo } from '../../api/plugins'
2
+ import { browserPluginOperations } from './plugin-frame'
3
+
4
+ export type Argument = {
5
+ name: string
6
+ label: string
7
+ flag?: string
8
+ type: 'string' | 'number' | 'boolean' | 'choice'
9
+ options?: string[]
10
+ required?: boolean
11
+ secret?: boolean
12
+ }
13
+
14
+ export type Command = ReturnType<typeof browserPluginOperations>[number] & {
15
+ effect?: 'read' | 'write' | 'destructive' | 'send'
16
+ args?: Argument[]
17
+ terminal_only_args?: string[]
18
+ }
19
+
20
+ export function scopedPluginOperations(plugin: PluginInfo, operationIds?: readonly string[]): Command[] {
21
+ const manifest = plugin.manifest?.cli as { subcommands?: Command[] } | undefined
22
+ const declared = new Map(manifest?.subcommands?.map(operation => [operation.name, operation]))
23
+ return browserPluginOperations(plugin)
24
+ .map(operation => ({ ...operation, ...declared.get(operation.name) }))
25
+ .filter(operation => operation.name !== 'on' && operation.name !== 'off' &&
26
+ (!operationIds || operationIds.includes(operation.name)) &&
27
+ !operation.args?.some(arg => arg.secret || (arg.required && operation.terminal_only_args?.includes(arg.flag || ''))))
28
+ }
@@ -0,0 +1,110 @@
1
+ import { useCallback, useEffect, useState, type FormEvent } from 'react'
2
+ import { runPluginCommand, type PluginInfo } from '../../api/plugins'
3
+ import { parsePluginOutput } from '../plugins/PluginPanel'
4
+
5
+ interface Harness { id: string; name: string }
6
+ interface ChatTarget {
7
+ ok: boolean
8
+ configured: boolean
9
+ harness?: Harness
10
+ href?: string
11
+ context?: string
12
+ choices?: Harness[]
13
+ }
14
+
15
+ function launchHref(value?: string) {
16
+ try {
17
+ if (!value) return undefined
18
+ const url = new URL(value)
19
+ if (url.username || url.password) return undefined
20
+ if (url.protocol === 'https:') return url.href
21
+ if (url.protocol === 'codex:' && url.hostname === 'threads' && /^\/(?:new|[0-9a-f-]{36})$/i.test(url.pathname)) return url.href
22
+ } catch { /* An invalid configuration is shown without a launch link. */ }
23
+ return undefined
24
+ }
25
+
26
+ export function DrawerChat({ plugin, activeBrainId }: { plugin?: PluginInfo; activeBrainId: string }) {
27
+ const [target, setTarget] = useState<ChatTarget | null>(null)
28
+ const [error, setError] = useState('')
29
+ const [edit, setEdit] = useState(false)
30
+ const [harness, setHarness] = useState('')
31
+ const [url, setUrl] = useState('')
32
+ const [saving, setSaving] = useState(false)
33
+ const [copied, setCopied] = useState(false)
34
+ const hasTarget = !!plugin?.valid && plugin.activation?.enabled === true &&
35
+ !!plugin.cli?.subcommands?.some(command => command.name === 'chat-target')
36
+ const getTarget = useCallback(async () => {
37
+ const result = await runPluginCommand('meta-harness', 'chat-target', ['--brains', activeBrainId, '--json'])
38
+ const data = parsePluginOutput(result.output) as Partial<ChatTarget> | undefined
39
+ if (!result.success || data?.ok !== true || typeof data.configured !== 'boolean') {
40
+ throw new Error('Could not read the default harness from Metaharness.')
41
+ }
42
+ return data as ChatTarget
43
+ }, [activeBrainId])
44
+ useEffect(() => {
45
+ if (!hasTarget) return
46
+ let current = true
47
+ void getTarget().then(data => { if (current) { setTarget(data); setError(''); setCopied(false) } },
48
+ error => { if (current) { setTarget(null); setError(error.message) } })
49
+ return () => { current = false }
50
+ }, [getTarget, hasTarget])
51
+
52
+ async function save(event: FormEvent<HTMLFormElement>) {
53
+ event.preventDefault()
54
+ setSaving(true)
55
+ setError('')
56
+ try {
57
+ const response = await runPluginCommand('meta-harness', 'chat-default', [
58
+ harness, ...(url.trim() ? ['--url', url.trim()] : []), '--json',
59
+ ])
60
+ const data = parsePluginOutput(response.output) as { ok?: boolean; error?: string } | undefined
61
+ if (!response.success || data?.ok !== true) throw new Error(data?.error || response.error || 'Could not save the default harness.')
62
+ setTarget(await getTarget())
63
+ setEdit(false)
64
+ } catch (error) { setError(error instanceof Error ? error.message : 'Could not save the default harness.') }
65
+ finally { setSaving(false) }
66
+ }
67
+
68
+ if (!hasTarget) return <p className="drawer-note">
69
+ {!plugin ? 'Install Metaharness to open Chat.' : plugin.activation?.enabled !== true
70
+ ? 'Enable Metaharness to open Chat.' : 'This Metaharness version does not provide a default Chat harness yet.'}
71
+ </p>
72
+
73
+ const href = launchHref(target?.href)
74
+ const selectedCount = activeBrainId.split(',').filter(Boolean).length
75
+ return <section className="drawer-stack">
76
+ <div className="drawer-row">
77
+ <div>
78
+ <h3>{target?.harness?.name || 'Default harness'}</h3>
79
+ <p className="drawer-note">{selectedCount === 1 ? '1 brain selected' : `${selectedCount} brains selected`}</p>
80
+ </div>
81
+ {target && <button type="button" className="btn btn-ghost btn-sm" onClick={() => {
82
+ setHarness(target.harness?.id || ''); setUrl(''); setEdit(!edit)
83
+ }}>{edit ? 'Cancel' : 'Configure default'}</button>}
84
+ </div>
85
+ {error && <p role="alert" className="drawer-error">{error}</p>}
86
+ {!target && !error && <p role="status">Loading the default harness…</p>}
87
+ {edit && <form className="drawer-stack" onSubmit={save}>
88
+ <label>Harness
89
+ <select className="select" value={harness} onChange={event => setHarness(event.target.value)} required>
90
+ <option value="">Choose a harness…</option>
91
+ {target?.choices?.map(item => <option key={item.id} value={item.id}>{item.name}</option>)}
92
+ </select>
93
+ </label>
94
+ <label>Custom launch URL (optional)<input className="input" type="text" value={url}
95
+ placeholder="Use the harness default" onChange={event => setUrl(event.target.value)} /></label>
96
+ <div><button className="btn btn-primary" disabled={saving || !harness}>{saving ? 'Saving…' : 'Save default'}</button></div>
97
+ </form>}
98
+ {!edit && target && (target.configured ? <>
99
+ <div className="drawer-actions">
100
+ {href && <a className="btn btn-primary" href={href} rel="noreferrer" target={href.startsWith('https:') ? '_blank' : undefined}>Open Chat</a>}
101
+ {target.context && <button type="button" className="btn btn-ghost" onClick={async () => {
102
+ try { await navigator.clipboard.writeText(target.context!); setCopied(true) }
103
+ catch { setError('Copy the selected brain context below.') }
104
+ }}>{copied ? 'Copied' : 'Copy brain context'}</button>}
105
+ </div>
106
+ {!href && <p className="drawer-note">This harness has no launch URL. Open it directly and paste the selected brain context.</p>}
107
+ {target.context && <details><summary>Selected brain context</summary><pre className="drawer-context">{target.context}</pre></details>}
108
+ </> : <p className="drawer-note">Choose the harness Chat should open for your selected brains.</p>)}
109
+ </section>
110
+ }
@@ -0,0 +1,196 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { Link } from 'react-router-dom'
3
+ import type { PluginInfo } from '../../api/plugins'
4
+ import {
5
+ findWorkspaceChunks, readDrawer, readWorkspaceChunk, saveDrawer,
6
+ type DrawerConfig, type DrawerRevision, type DrawerSide, type PanelRef, type PluginDesign, type WorkspaceChunk,
7
+ } from '../../api/workspace-drawers'
8
+ import { PluginPanel, pluginElements } from '../plugins/PluginPanel'
9
+ import { ScopedPluginCommands } from '../plugins/ScopedPluginCommands'
10
+ import { DrawerMarkup } from './DrawerMarkup'
11
+ import { usePluginDesign } from './usePluginDesign'
12
+
13
+ export function drawerPanels(plugin: PluginInfo) {
14
+ return pluginElements(plugin).filter(item => item.kind !== 'settings')
15
+ }
16
+
17
+ export function DrawerPanel({ plugins, reference, designCss, brainId }: {
18
+ plugins: PluginInfo[] | null; reference: PanelRef; designCss?: string; brainId?: string
19
+ }) {
20
+ if (!plugins) return <p role="status">Loading installed interfaces…</p>
21
+ const plugin = plugins.find(plugin => plugin.id === reference.plugin)
22
+ const panel = plugin && drawerPanels(plugin).find(item => item.id === reference.element)
23
+ if (!plugin || !panel) return <p className="drawer-note">This plugin interface is not installed.</p>
24
+ if (!plugin.valid || plugin.activation?.state === 'error') {
25
+ return <p role="alert" className="drawer-error">{plugin.name} could not load.</p>
26
+ }
27
+ if (plugin.activation?.enabled !== true) return <p className="drawer-note">
28
+ {plugin.name} is off. <Link to={{ search: `?plugin=${encodeURIComponent(plugin.id)}` }}>Open plugin settings</Link>
29
+ </p>
30
+ return <PluginPanel key={`${plugin.id}:${panel.id}`} plugin={plugin} elementId={panel.id} designCss={designCss} brainId={brainId} />
31
+ }
32
+
33
+ export function PublishedChunk({ name, side, plugins, design, designOwner, brainId }: {
34
+ name: string; side: DrawerSide | 'page'; plugins: PluginInfo[] | null; design?: PluginDesign; designOwner?: string; brainId?: string
35
+ }) {
36
+ const ownerDesign = usePluginDesign(designOwner, brainId)
37
+ const theme = ownerDesign?.design || design
38
+ const requestKey = JSON.stringify([name, side, brainId])
39
+ const [result, setResult] = useState<{ key: string; chunk: (WorkspaceChunk & { html: string }) | null }>()
40
+ const current = result?.key === requestKey ? result : undefined
41
+ const chunk = current?.chunk
42
+ useEffect(() => {
43
+ const controller = new AbortController()
44
+ void readWorkspaceChunk(name, side, controller.signal, brainId).then(
45
+ data => { if (!controller.signal.aborted) setResult({ key: requestKey, chunk: data }) },
46
+ () => { if (!controller.signal.aborted) setResult({ key: requestKey, chunk: null }) },
47
+ )
48
+ return () => controller.abort()
49
+ }, [name, side, brainId, requestKey])
50
+ if (current && (!chunk || !chunk.surfaces.includes(side))) return <p className="drawer-note">{name} is unavailable here.</p>
51
+ if (!chunk) return <p role="status">Loading {name}…</p>
52
+ const owner = plugins?.find(plugin => plugin.id === chunk.plugin)
53
+ return <section className="drawer-stack" aria-label={chunk.title}>
54
+ {chunk.html && <DrawerMarkup title={chunk.title} html={chunk.html} design={theme} />}
55
+ {chunk.panel && <DrawerPanel plugins={plugins} reference={{ plugin: chunk.plugin, element: chunk.panel }} designCss={theme?.css ?? ''} brainId={brainId} />}
56
+ {brainId && owner?.valid && owner.activation?.enabled === true && owner.activation.state !== 'error' &&
57
+ !!chunk.operations?.length && <ScopedPluginCommands key={`${requestKey}:${owner.sha256}:${chunk.sha256}`} plugin={owner} brainId={brainId}
58
+ operationIds={chunk.operations} label={`${chunk.title} operations`} />}
59
+ </section>
60
+ }
61
+
62
+ function ShortcutList({ shortcuts }: { shortcuts: DrawerConfig['shortcuts'] }) {
63
+ if (!shortcuts.length) return <p className="drawer-note">Add a shortcut to a dashboard page, plugin, or website.</p>
64
+ return <nav className="drawer-actions" aria-label="Saved shortcuts">
65
+ {shortcuts.map(({ label, href }, index) => href.startsWith('https://')
66
+ ? <a key={index} className="btn btn-ghost" href={href} target="_blank" rel="noopener noreferrer">{label}</a>
67
+ : <Link key={index} className="btn btn-ghost" to={href}>{label}</Link>)}
68
+ </nav>
69
+ }
70
+
71
+ function LayoutEditor({ side, draft, onChange, plugins, brainId }: {
72
+ side: DrawerSide; draft: DrawerConfig; onChange: (next: DrawerConfig) => void; plugins: PluginInfo[] | null; brainId?: string
73
+ }) {
74
+ const [query, setQuery] = useState('')
75
+ const [chunks, setChunks] = useState<WorkspaceChunk[]>([])
76
+ const [catalogError, setCatalogError] = useState(false)
77
+ const [preview, setPreview] = useState(false)
78
+ useEffect(() => {
79
+ const controller = new AbortController()
80
+ const timer = window.setTimeout(() => {
81
+ void findWorkspaceChunks(side, query, controller.signal, undefined, brainId).then(
82
+ data => { if (!controller.signal.aborted) { setChunks(data.chunks); setCatalogError(false) } },
83
+ () => { if (!controller.signal.aborted) { setChunks([]); setCatalogError(true) } },
84
+ )
85
+ }, 180)
86
+ return () => { controller.abort(); window.clearTimeout(timer) }
87
+ }, [side, query, brainId])
88
+ const panels = (plugins || []).filter(plugin => plugin.valid && plugin.activation?.enabled === true)
89
+ .flatMap(plugin => drawerPanels(plugin).map(element => ({ plugin, element })))
90
+ const choices = panels.filter(({ plugin, element }) => !draft.panels.some(ref => ref.plugin === plugin.id && ref.element === element.id))
91
+ return <div className="drawer-stack">
92
+ {side === 'bottom' && <fieldset className="drawer-stack drawer-fields">
93
+ <legend>Shortcuts</legend>
94
+ {draft.shortcuts.map((shortcut, index) => <div className="drawer-shortcut-edit" key={index}>
95
+ <input className="input" required maxLength={80} value={shortcut.label} aria-label={`Shortcut ${index + 1} name`}
96
+ placeholder="Name" onChange={event => onChange({ ...draft, shortcuts: draft.shortcuts.map((link, i) => i === index ? { ...link, label: event.target.value } : link) })} />
97
+ <input className="input" required maxLength={1024} value={shortcut.href} aria-label={`Shortcut ${index + 1} address`}
98
+ placeholder="/memory?plugin=… or https://…" onChange={event => onChange({ ...draft, shortcuts: draft.shortcuts.map((link, i) => i === index ? { ...link, href: event.target.value } : link) })} />
99
+ <button type="button" className="btn btn-ghost btn-sm" aria-label={`Remove shortcut ${index + 1}`}
100
+ onClick={() => onChange({ ...draft, shortcuts: draft.shortcuts.filter((_, i) => i !== index) })}>Remove</button>
101
+ </div>)}
102
+ <div><button type="button" className="btn btn-ghost btn-sm" disabled={draft.shortcuts.length >= 16}
103
+ onClick={() => onChange({ ...draft, shortcuts: [...draft.shortcuts, { label: '', href: '' }] })}>Add shortcut</button></div>
104
+ </fieldset>}
105
+ <label>Drawer HTML
106
+ <textarea className="input drawer-code" rows={7} maxLength={65536} value={draft.html}
107
+ placeholder="<section>…</section>" onChange={event => onChange({ ...draft, html: event.target.value })} />
108
+ </label>
109
+ <div><button type="button" className="btn btn-ghost btn-sm" aria-expanded={preview} disabled={!draft.html.trim()}
110
+ onClick={() => setPreview(!preview)}>{preview ? 'Hide preview' : 'Preview HTML'}</button></div>
111
+ {preview && draft.html.trim() && <DrawerMarkup title="Draft HTML preview" html={draft.html} />}
112
+ <fieldset className="drawer-stack drawer-fields">
113
+ <legend>Plugin chunks</legend>
114
+ {draft.chunks.map(key => <div key={key} className="drawer-row">
115
+ <span>{key}</span>
116
+ <button type="button" className="btn btn-ghost btn-sm" aria-label={`Remove ${key}`}
117
+ onClick={() => onChange({ ...draft, chunks: draft.chunks.filter(item => item !== key) })}>Remove</button>
118
+ </div>)}
119
+ <input className="input" type="search" aria-label="Search installed chunks" placeholder="Search installed chunks…"
120
+ value={query} onChange={event => setQuery(event.target.value)} />
121
+ {catalogError ? <p className="drawer-note">The chunk catalog is unavailable on this server.</p> :
122
+ <select className="select" aria-label="Add chunk" value="" disabled={draft.chunks.length >= 8}
123
+ onChange={event => { if (event.target.value) onChange({ ...draft, chunks: [...draft.chunks, event.target.value] }) }}>
124
+ <option value="">Add an installed chunk…</option>
125
+ {chunks.filter(chunk => !draft.chunks.includes(chunk.key)).map(chunk =>
126
+ <option key={chunk.key} value={chunk.key}>{chunk.title} · {chunk.plugin}</option>)}
127
+ </select>}
128
+ </fieldset>
129
+ <fieldset className="drawer-stack drawer-fields">
130
+ <legend>Plugin interfaces</legend>
131
+ {draft.panels.map(ref => <div key={`${ref.plugin}:${ref.element}`} className="drawer-row">
132
+ <span>{ref.plugin} · {ref.element}</span>
133
+ <button type="button" className="btn btn-ghost btn-sm" aria-label={`Remove ${ref.plugin} ${ref.element}`}
134
+ onClick={() => onChange({ ...draft, panels: draft.panels.filter(item => item !== ref) })}>Remove</button>
135
+ </div>)}
136
+ <select className="select" aria-label="Add plugin interface" value="" disabled={!choices.length || draft.panels.length >= 6}
137
+ onChange={event => {
138
+ const match = choices.find(({ plugin, element }) => `${plugin.id}:${element.id}` === event.target.value)
139
+ if (match) onChange({ ...draft, panels: [...draft.panels, { plugin: match.plugin.id, element: match.element.id }] })
140
+ }}>
141
+ <option value="">Add an installed interface…</option>
142
+ {choices.map(({ plugin, element }) =>
143
+ <option key={`${plugin.id}:${element.id}`} value={`${plugin.id}:${element.id}`}>{plugin.name} · {element.description || element.id}</option>)}
144
+ </select>
145
+ </fieldset>
146
+ </div>
147
+ }
148
+
149
+ export function DrawerLayout({ side, view, plugins, brainId }: {
150
+ side: DrawerSide; view: 'custom' | 'shortcuts'; plugins: PluginInfo[] | null; brainId?: string
151
+ }) {
152
+ const [revision, setRevision] = useState<DrawerRevision | null>(null)
153
+ const [draft, setDraft] = useState<DrawerConfig | null>(null)
154
+ const [error, setError] = useState('')
155
+ const [busy, setBusy] = useState(false)
156
+ const [refresh, setRefresh] = useState(0)
157
+ useEffect(() => {
158
+ const controller = new AbortController()
159
+ void readDrawer(side, controller.signal).then(
160
+ current => { if (!controller.signal.aborted) { setRevision(current); setError('') } },
161
+ error => { if (!controller.signal.aborted) setError(error.message) },
162
+ )
163
+ return () => controller.abort()
164
+ }, [refresh, side])
165
+ return <div className="drawer-stack">
166
+ <div className="drawer-actions">
167
+ {!draft && <button type="button" className="btn btn-primary btn-sm" disabled={!revision}
168
+ onClick={() => { setDraft(revision!.config); setError('') }}>{view === 'shortcuts' ? 'Edit shortcuts' : 'Edit drawer'}</button>}
169
+ {!draft && <button type="button" className="btn btn-ghost btn-sm" onClick={() => setRefresh(refresh + 1)}>Refresh</button>}
170
+ </div>
171
+ {error && <p className="drawer-error" role="alert">{error}</p>}
172
+ {!revision && !error && <p role="status">Loading the saved drawer…</p>}
173
+ {draft && revision && <form className="drawer-stack" onSubmit={async event => {
174
+ event.preventDefault()
175
+ setBusy(true); setError('')
176
+ try {
177
+ setRevision(await saveDrawer(side, { config: draft, version: revision.version }))
178
+ setDraft(null)
179
+ } catch (error) { setError(error instanceof Error ? error.message : 'Could not save the drawer.') }
180
+ finally { setBusy(false) }
181
+ }}>
182
+ <LayoutEditor side={side} draft={draft} onChange={setDraft} plugins={plugins} brainId={brainId} />
183
+ <div className="drawer-actions">
184
+ <button className="btn btn-primary" disabled={busy}>{busy ? 'Saving…' : 'Save drawer'}</button>
185
+ <button type="button" className="btn btn-ghost" disabled={busy} onClick={() => { setDraft(null); setError('') }}>Cancel</button>
186
+ </div>
187
+ </form>}
188
+ {!draft && revision && (view === 'shortcuts' ? <ShortcutList shortcuts={revision.config.shortcuts} /> : <>
189
+ {revision.config.html && <DrawerMarkup title={`${side} drawer custom HTML`} html={revision.config.html} />}
190
+ {revision.config.chunks.map(key => <PublishedChunk key={`${brainId}:${key}:${refresh}`} name={key} side={side} plugins={plugins} brainId={brainId} designOwner={key.split(':')[0]} />)}
191
+ {revision.config.panels.map(ref => <DrawerPanel key={`${brainId}:${ref.plugin}:${ref.element}:${refresh}`} plugins={plugins} reference={ref} brainId={brainId} />)}
192
+ {!revision.config.html && !revision.config.chunks.length && !revision.config.panels.length &&
193
+ <p className="drawer-note">Add HTML or an installed plugin interface to make this drawer yours.</p>}
194
+ </>)}
195
+ </div>
196
+ }
@@ -0,0 +1,49 @@
1
+ import { useMemo } from 'react'
2
+ import type { PluginDesign } from '../../api/workspace-drawers'
3
+
4
+ const TAGS = new Set('a abbr article aside b bdi blockquote br button caption code col colgroup dd del details div dl dt em fieldset figcaption figure footer h1 h2 h3 h4 h5 h6 header hr i img input kbd label legend li main mark meter nav ol optgroup option p pre progress q s samp section select small span strong style sub summary sup table tbody td textarea tfoot th thead time tr u ul var wbr'.split(' '))
5
+ const ATTRIBUTES = new Set('alt checked class cols colspan datetime dir disabled for height hidden id lang max maxlength min multiple name open placeholder role rows rowspan scope selected size step style tabindex title type value width'.split(' '))
6
+ const POLICY = "default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'; object-src 'none'"
7
+
8
+ /** Parse in an inert template; only the reconstructed markup goes into an opaque frame. */
9
+ export function drawerMarkupDocument(source: string, design?: Pick<PluginDesign, 'css' | 'components'>) {
10
+ const template = document.createElement('template')
11
+ template.innerHTML = source
12
+ for (const recipe of [...template.content.querySelectorAll<HTMLTemplateElement>('template[data-design-components]')].slice(0, 8)) {
13
+ const element = recipe.content.firstElementChild
14
+ if (!element || recipe.content.children.length !== 1 || recipe.innerHTML.length > 4096) continue
15
+ const copies = (design?.components || []).slice(0, 64).map(name => {
16
+ const node = element.cloneNode(true) as Element
17
+ node.setAttribute('data-design-component', name)
18
+ if (node.hasAttribute('data-design-label')) node.textContent = name
19
+ for (const label of node.querySelectorAll('[data-design-label]')) label.textContent = name
20
+ return node
21
+ })
22
+ recipe.replaceWith(...copies)
23
+ }
24
+ for (const element of template.content.querySelectorAll('*')) {
25
+ const name = element.localName
26
+ if (!TAGS.has(name) || element.namespaceURI !== 'http://www.w3.org/1999/xhtml') {
27
+ element.remove()
28
+ continue
29
+ }
30
+ for (const attribute of [...element.attributes]) {
31
+ const { name: key, value } = attribute
32
+ if (ATTRIBUTES.has(key) || key === 'data-design-component' || /^aria-[a-z-]+$/.test(key)) continue
33
+ if (name === 'img' && key === 'src' && /^data:image\/(?:png|jpeg|gif|webp);base64,[a-z0-9+/=]+$/i.test(value)) continue
34
+ element.removeAttribute(key)
35
+ }
36
+ }
37
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
38
+ <meta http-equiv="Content-Security-Policy" content="${POLICY}">
39
+ <style>:root{color-scheme:light dark}body{margin:0;padding:var(--spacing-md,16px);font:14px/1.55 var(--typography-body-font-family,var(--font-sans,system-ui)),sans-serif;color:var(--color-text,CanvasText);background:var(--color-background,Canvas)}img{max-width:100%;height:auto}*{box-sizing:border-box}</style>
40
+ <style>${(design?.css || '').replaceAll('<', '')}</style>
41
+ </head><body>${template.innerHTML}</body></html>`
42
+ }
43
+
44
+ export function DrawerMarkup({ title, html, design }: { title: string; html: string; design?: PluginDesign }) {
45
+ const source = useMemo(() => drawerMarkupDocument(html, design), [html, design])
46
+ return <iframe className="drawer-html" title={title} sandbox="" referrerPolicy="no-referrer"
47
+ allow="camera 'none'; microphone 'none'; geolocation 'none'; clipboard-read 'none'; clipboard-write 'none'"
48
+ srcDoc={source} />
49
+ }
@@ -0,0 +1,92 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { findWorkspaceChunks, type WorkspaceChunk } from '../../api/workspace-drawers'
3
+ import type { PluginInfo } from '../../api/plugins'
4
+ import { PublishedChunk } from './DrawerLayout'
5
+
6
+ const pageSize = 20
7
+
8
+ export function PluginChunkLibrary({ plugin, brainId }: {
9
+ plugin: PluginInfo; brainId: string
10
+ }) {
11
+ const [catalog, setCatalog] = useState<{ chunks: WorkspaceChunk[]; total: number } | null>(null)
12
+ const [key, setKey] = useState('')
13
+ const [draft, setDraft] = useState('')
14
+ const [query, setQuery] = useState('')
15
+ const [offset, setOffset] = useState(0)
16
+ const [refresh, setRefresh] = useState(0)
17
+ const [error, setError] = useState('')
18
+
19
+ useEffect(() => {
20
+ const controller = new AbortController()
21
+ void findWorkspaceChunks('page', query, controller.signal, plugin.id, brainId, { limit: pageSize, offset }).then(
22
+ data => {
23
+ if (controller.signal.aborted) return
24
+ if (!data.chunks.length && offset > 0) {
25
+ setOffset(Math.max(0, Math.floor((data.total - 1) / pageSize) * pageSize))
26
+ return
27
+ }
28
+ setCatalog(data)
29
+ setKey(current => data.chunks.some(chunk => chunk.key === current) ? current : data.chunks[0]?.key || '')
30
+ },
31
+ () => {
32
+ if (controller.signal.aborted) return
33
+ setError('Could not load the published blocks.')
34
+ },
35
+ )
36
+ return () => controller.abort()
37
+ }, [plugin.id, brainId, offset, query, refresh])
38
+
39
+ function clearResult() {
40
+ setCatalog(null)
41
+ setError('')
42
+ }
43
+
44
+ function movePage(next: number) {
45
+ clearResult()
46
+ setOffset(next)
47
+ }
48
+
49
+ const selected = catalog?.chunks.find(chunk => chunk.key === key)
50
+
51
+ return <div className="drawer-stack">
52
+ <form className="drawer-actions" onSubmit={event => {
53
+ event.preventDefault()
54
+ clearResult()
55
+ setQuery(draft.trim())
56
+ setOffset(0)
57
+ setRefresh(value => value + 1)
58
+ }}>
59
+ <label>Search blocks
60
+ <input className="input" type="search" value={draft} maxLength={1024}
61
+ onChange={event => setDraft(event.target.value)} />
62
+ </label>
63
+ <button type="submit" className="btn btn-ghost btn-sm">Search</button>
64
+ <button type="button" className="btn btn-ghost btn-sm" onClick={() => {
65
+ clearResult()
66
+ setRefresh(value => value + 1)
67
+ }}>Refresh blocks</button>
68
+ </form>
69
+ <div className="drawer-actions">
70
+ {catalog && catalog.chunks.length > 0 && <label>Block
71
+ <select className="select" value={key} onChange={event => setKey(event.target.value)}>
72
+ {catalog.chunks.map(chunk => <option key={chunk.key} value={chunk.key}>{chunk.title}</option>)}
73
+ </select>
74
+ </label>}
75
+ </div>
76
+ {catalog && catalog.total > 0 && <div className="drawer-actions">
77
+ <span role="status">Showing {offset + 1}–{offset + catalog.chunks.length} of {catalog.total} blocks</span>
78
+ {catalog.total > pageSize && <>
79
+ <button type="button" className="btn btn-ghost btn-sm" disabled={offset === 0}
80
+ onClick={() => movePage(offset - pageSize)}>Previous blocks</button>
81
+ <button type="button" className="btn btn-ghost btn-sm" disabled={offset + pageSize >= catalog.total}
82
+ onClick={() => movePage(offset + pageSize)}>Next blocks</button>
83
+ </>}
84
+ </div>}
85
+ {error && <p role="alert">{error}</p>}
86
+ {!catalog && !error && <p role="status">Loading published blocks…</p>}
87
+ {catalog?.total === 0 && <p className="drawer-note">{query
88
+ ? 'No published blocks match your search.' : 'This plugin has no published page blocks yet.'}</p>}
89
+ {selected && <PublishedChunk key={`${selected.key}:${selected.sha256}:${refresh}`}
90
+ name={selected.key} side="page" plugins={[plugin]} brainId={brainId} designOwner={plugin.id} />}
91
+ </div>
92
+ }