total-recall-brain 3.44.0 → 3.45.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 (212) hide show
  1. package/bin/total-recall.mjs +6 -4
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +13 -8
  4. package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
  5. package/frontend/dist/index.html +1 -1
  6. package/frontend/src/App.tsx +2 -2
  7. package/frontend/src/pages/PluginsPage.tsx +1 -1
  8. package/package.json +1 -1
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  11. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  12. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  14. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  15. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  16. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  17. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
  20. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  21. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  22. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
  25. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  26. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
  27. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  33. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
  34. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  38. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  42. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
  44. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
  45. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
  46. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
  68. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
  69. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
  70. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  71. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
  72. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  73. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  74. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
  75. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
  76. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
  77. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
  78. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
  79. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
  80. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  81. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
  82. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
  84. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
  85. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
  86. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  87. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
  88. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  89. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  90. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
  91. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
  93. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
  96. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
  97. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
  98. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  99. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
  101. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  105. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  106. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  108. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  109. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  110. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
  111. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  112. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
  113. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  114. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
  115. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
  122. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
  123. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  124. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  125. package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
  126. package/src/cli/agent.mjs +1 -1
  127. package/src/cli/command.mjs +41 -2
  128. package/src/cli/dream.mjs +93 -69
  129. package/src/cli/harness.mjs +4 -4
  130. package/src/cli/help.mjs +1 -1
  131. package/src/cli/init.mjs +0 -25
  132. package/src/cli/lessons.mjs +51 -0
  133. package/src/cli/memory-plugin.mjs +1 -1
  134. package/src/cli/plugin/copies.mjs +64 -14
  135. package/src/cli/plugin/create.mjs +6 -7
  136. package/src/cli/plugin/index.mjs +11 -7
  137. package/src/cli/plugin/install.mjs +10 -15
  138. package/src/cli/plugin/remove.mjs +1 -1
  139. package/src/cli/plugin/search.mjs +2 -2
  140. package/src/cli/preflight.mjs +19 -6
  141. package/src/cli/proposals.mjs +1 -1
  142. package/src/cli/recall.mjs +66 -23
  143. package/src/cli/remember.mjs +3 -14
  144. package/src/cli/server.mjs +18 -0
  145. package/src/cli/startup.mjs +1 -0
  146. package/src/cli/task.mjs +9 -2
  147. package/src/cli/update.mjs +6 -2
  148. package/src/core/access-ledger.mjs +78 -0
  149. package/src/core/agent-manager.mjs +2 -2
  150. package/src/core/brain-locate.mjs +15 -0
  151. package/src/core/brain-updates.mjs +31 -11
  152. package/src/core/claude-hooks.mjs +3 -1
  153. package/src/core/cleanup-task.mjs +15 -0
  154. package/src/core/cli-commands.mjs +2 -0
  155. package/src/core/cli-maintenance.mjs +4 -1
  156. package/src/core/cli-semantic-index.mjs +23 -3
  157. package/src/core/context-compiler.mjs +20 -0
  158. package/src/core/daemon-loop.mjs +39 -62
  159. package/src/core/default-plugins.mjs +1 -1
  160. package/src/core/dream.mjs +33 -105
  161. package/src/core/ensure-dependencies.mjs +148 -0
  162. package/src/core/fast-recall.mjs +2 -2
  163. package/src/core/field-evaluation.mjs +23 -0
  164. package/src/core/gate-mode.mjs +1 -1
  165. package/src/core/harness-hooks.mjs +14 -2
  166. package/src/core/lesson-hooks.mjs +74 -0
  167. package/src/core/lesson-memory.mjs +24 -0
  168. package/src/core/lesson-sessions.mjs +52 -0
  169. package/src/core/lessons.mjs +96 -0
  170. package/src/core/meta-harness.mjs +2 -2
  171. package/src/core/optimizer.mjs +66 -165
  172. package/src/core/plugin-contracts/card.mjs +3 -0
  173. package/src/core/plugin-loader.mjs +12 -3
  174. package/src/core/plugin-runtime.mjs +40 -1
  175. package/src/core/plugin-store.mjs +35 -23
  176. package/src/core/post-mortem.mjs +52 -36
  177. package/src/core/preflight-gate.mjs +23 -2
  178. package/src/core/proposal-applier.mjs +31 -2
  179. package/src/core/repo-sync.mjs +4 -6
  180. package/src/core/required-plugins.mjs +26 -4
  181. package/src/core/rule-length.mjs +23 -0
  182. package/src/core/runtime.mjs +1 -1
  183. package/src/core/scheduler.mjs +35 -43
  184. package/src/core/schema.mjs +1 -2
  185. package/src/core/search.mjs +4 -3
  186. package/src/core/server-service.mjs +148 -0
  187. package/src/core/session-watcher.mjs +19 -1
  188. package/src/core/skill-gap.mjs +16 -0
  189. package/src/core/ssss-kernel-bridge.mjs +32 -4
  190. package/src/core/startup-health.mjs +6 -1
  191. package/src/core/surface.mjs +65 -54
  192. package/src/core/task-executors.mjs +54 -106
  193. package/src/core/validated-write.mjs +1 -0
  194. package/src/core/vector-field.mjs +134 -192
  195. package/src/server/invoke-core-child.mjs +24 -0
  196. package/src/server/invoke-core-operations.mjs +102 -0
  197. package/src/server/route-manifest.json +0 -56
  198. package/src/server/routes/brains.mjs +0 -18
  199. package/src/server/routes/context.mjs +0 -41
  200. package/src/server/routes/invoke.mjs +26 -1
  201. package/src/server/routes/ssss.mjs +0 -169
  202. package/src/server/routes/system.mjs +0 -15
  203. package/src/server/routes/tasks.mjs +0 -26
  204. package/src/server/routes/vault.mjs +2 -23
  205. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  206. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  207. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  208. package/src/core/append-log.mjs +0 -131
  209. package/src/core/plugin-tasks.mjs +0 -173
  210. package/templates/onboarding-interview.md +0 -82
  211. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
  212. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
@@ -17,7 +17,7 @@
17
17
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
18
18
  <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,800&family=Cabinet+Grotesk:wght@700;800&family=Chivo+Mono:wght@500;700&family=Cinzel:wght@600;700;900&family=Cormorant+Garamond:ital,wght@0,600;0,700;1,600;1,700&family=DM+Mono:wght@500&family=DM+Sans:wght@500;700&family=Fraunces:opsz,wght@9..144,600;9..144,800&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Lexend:wght@600;800&family=Newsreader:ital,opsz,wght@0,6..72,600;1,6..72,600&family=Orbitron:wght@700;900&family=Outfit:wght@500;700;800&family=Plus+Jakarta+Sans:wght@600;700;800&family=Space+Grotesk:wght@600;700&family=Syne:wght@700;800&display=swap" rel="stylesheet" />
19
19
  <title>Total Recall — Portable Memory</title>
20
- <script type="module" crossorigin src="/assets/index-DD4qONvH.js"></script>
20
+ <script type="module" crossorigin src="/assets/index-DACh1dhx.js"></script>
21
21
  <link rel="stylesheet" crossorigin href="/assets/index-DtwsDyFM.css">
22
22
  </head>
23
23
  <body>
@@ -323,7 +323,7 @@ function MainContent({ activeBrainId }: { activeBrainId: string }) {
323
323
  <Route path="/memory" element={<MemoryPage activeBrainId={activeBrainId} />} />
324
324
  <Route path="/rules" element={<RulesPage activeBrainId={activeBrainId} />} />
325
325
  <Route path="/graph" element={<GraphPage activeBrainId={activeBrainId} />} />
326
- <Route path="/tasks" element={<Navigate to="/plugins?id=user-automations" replace />} />
326
+ <Route path="/tasks" element={<Navigate to="/plugins?id=automations" replace />} />
327
327
  <Route path="/skills" element={<Navigate to="/plugins?id=skill-manager" replace />} />
328
328
  <Route path="/openwiki" element={<Navigate to="/plugins?id=openwiki" replace />} />
329
329
  <Route path="/plugins" element={<PluginsPage activeBrainId={activeBrainId} />} />
@@ -339,7 +339,7 @@ function MainContent({ activeBrainId }: { activeBrainId: string }) {
339
339
  {/* Legacy OS-control-plane routes → core product */}
340
340
  <Route path="/vault" element={<Navigate to="/memory" replace />} />
341
341
  <Route path="/inbox" element={<Navigate to="/tasks" replace />} />
342
- <Route path="/automations" element={<Navigate to="/plugins?id=user-automations" replace />} />
342
+ <Route path="/automations" element={<Navigate to="/plugins?id=automations" replace />} />
343
343
  <Route path="/files" element={<Navigate to="/memory" replace />} />
344
344
  <Route path="/sandbox" element={<Navigate to="/memory" replace />} />
345
345
  <Route path="/models" element={<Navigate to="/settings" replace />} />
@@ -52,7 +52,7 @@ const DEFAULT_CORE_PLUGINS = new Set([
52
52
  "code-quality",
53
53
  "security",
54
54
  "embeddings",
55
- "git-sentinel"
55
+ "git"
56
56
  ])
57
57
 
58
58
  const AGENT_RECIPE_PROMPT = `You are my coding agent. Author a bespoke Total Recall plugin for [DOMAIN] following the Total Recall Plugin Recipe:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.44.0",
3
+ "version": "3.45.0",
4
4
  "description": "The Vibe Coding Brain & Autonomous Software Factory: memory, rules, secrets, and 33 visual plugins automatically managed across all your devices.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "tr-plugin-auth",
3
- "version": "1.2.9",
3
+ "version": "1.2.10",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "tr-plugin-auth",
9
- "version": "1.2.9",
9
+ "version": "1.2.10",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@gregiteen/ssss-cli": "0.14.5",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "devDependencies": {
21
21
  "supertest": "^7.3.1",
22
- "total-recall-brain": "3.43.3",
22
+ "total-recall-brain": "3.44.0",
23
23
  "vitest": "^4.1.12"
24
24
  }
25
25
  },
@@ -6704,9 +6704,9 @@
6704
6704
  }
6705
6705
  },
6706
6706
  "node_modules/total-recall-brain": {
6707
- "version": "3.43.3",
6708
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.43.3.tgz",
6709
- "integrity": "sha512-yjfPN/BQxEEa+iodyFXQov/6fRt1pZg/7RcmgsbpALwBt6Ki3JJjT3tzArBC7NxHQmXgGYyKnCYId6xe/W2rPg==",
6707
+ "version": "3.44.0",
6708
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.44.0.tgz",
6709
+ "integrity": "sha512-VNPaIO9zsSwoxAsJ5ByIQBP9zPpLq4RiQO7Vrex4rBOHFQJT50P7utcZiLMSZTyZJU4C/m5uB8qTCSBMp6+l6Q==",
6710
6710
  "dev": true,
6711
6711
  "license": "MIT",
6712
6712
  "workspaces": [
@@ -6714,7 +6714,7 @@
6714
6714
  ],
6715
6715
  "dependencies": {
6716
6716
  "@simplewebauthn/server": "^13.3.2",
6717
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.4",
6717
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.5",
6718
6718
  "bcrypt": "^6.0.0",
6719
6719
  "better-sqlite3": "^12.11.1",
6720
6720
  "cookie-parser": "^1.4.7",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tr-plugin-auth",
3
- "version": "1.2.9",
3
+ "version": "1.2.10",
4
4
  "description": "White-label Total Recall auth plugin: Passkey biometric FIDO2 and Google OAuth2 authentication.",
5
5
  "type": "module",
6
6
  "main": "cli.mjs",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
3
3
  "id": "auth",
4
4
  "name": "Auth & Secrets",
5
- "version": "1.2.9",
5
+ "version": "1.2.10",
6
6
  "description": "Hardware WebAuthn biometric FIDO2 passkeys, OAuth session security, and access control.",
7
7
  "author": "Total Recall contributors",
8
8
  "license": "MIT",
@@ -294,6 +294,8 @@ function printHelp() {
294
294
  carriers Find shell profiles and /etc files that export the master
295
295
  password to every process; --remove-ambient deletes them
296
296
  once an owner-only carrier holds the same password.
297
+ scan --stdin Name the keys whose stored values occur in the text on stdin
298
+ (values are never printed; exit 1 when one occurs)
297
299
  fingerprint Identify a credential without printing it: length, type
298
300
  prefix, fingerprint and the store keys holding the same
299
301
  value. --from-clipboard | --stdin; --expect-prefix <p>
@@ -773,7 +775,7 @@ export default async function secretCli(argv) {
773
775
  }
774
776
  const brainDir = secretsStoreDir();
775
777
 
776
- if (opts.stdin && opts.command !== 'fingerprint') {
778
+ if (opts.stdin && opts.command !== 'fingerprint' && opts.command !== 'scan') {
777
779
  if (!['set', 'rotate', 'rekey'].includes(opts.command)) {
778
780
  console.error('❌ --stdin is only valid with `set`, `rotate`, `rekey`, or `fingerprint`');
779
781
  await exitAfterFlush(1);
@@ -1292,6 +1294,23 @@ export default async function secretCli(argv) {
1292
1294
  return;
1293
1295
  }
1294
1296
 
1297
+ if (opts.command === 'scan') {
1298
+ // Name every store key whose value occurs in the text on stdin. Values never leave this
1299
+ // process: callers (the git plugin's secret firewall) get key names only. One call per scan.
1300
+ if (!opts.stdin) {
1301
+ console.error('Usage: total-recall auth secret scan --stdin [--json] (exit 1 when a stored value occurs)');
1302
+ await exitAfterFlush(2);
1303
+ }
1304
+ // Diffs and staged files are large; one value check is not, so scan alone gets 64 MB.
1305
+ const text = readAllStdinSync({ maxBytes: 64 * 1024 * 1024 }).toString();
1306
+ const { textContainsSecrets } = await import('./secrets-store.mjs');
1307
+ const result = await textContainsSecrets(brainDir, text);
1308
+ if (opts.json) console.log(JSON.stringify({ ok: !result.leak, leak: result.leak, keys: result.keys, scanned_bytes: Buffer.byteLength(text) }));
1309
+ else console.log(result.leak ? ` ❌ stored values found for: ${result.keys.join(', ')}` : ' ✅ no stored value found');
1310
+ if (result.leak) process.exitCode = 1;
1311
+ return;
1312
+ }
1313
+
1295
1314
  if (opts.command === 'fingerprint') {
1296
1315
  // Identify a credential without seeing it: which store keys hold the same
1297
1316
  // value, its length and its non-secret type prefix. Lets an operator check
@@ -771,13 +771,24 @@ export function listUsageEvents(brainDir, { limit = 100, key_ref = null, days =
771
771
  /**
772
772
  * True if any secret value appears in text (for surface/openwiki conformance).
773
773
  */
774
+ export function isSettingValue(val) {
775
+ if (/^[a-z]+$/.test(val)) return true;
776
+ if (/^(?=.{4,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i.test(val)) return true;
777
+ if (/^[^\s@:/]+@([a-z0-9-]+\.)+[a-z]{2,63}$/i.test(val)) return true;
778
+ if (/^https?:\/\/[^\s@/]+(\/[^\s@]*)?$/i.test(val)) return true;
779
+ return false;
780
+ }
781
+
774
782
  export async function textContainsSecrets(brainDir, text) {
775
783
  if (!text) return { leak: false, keys: [] };
776
784
  const secrets = await loadSecrets(brainDir);
777
785
  const leaked = [];
778
786
  for (const [key, val] of Object.entries(secrets)) {
779
787
  if (key === META_KEY) continue;
780
- if (typeof val === 'string' && val.length >= 8 && text.includes(val)) {
788
+ // Settings, not credentials: a single lowercase word (DEPLOY_ENV=production), a hostname
789
+ // (MAILCOW_HOST), an email address, or a URL without user info. Matching them would refuse
790
+ // ordinary text. A URL that carries user:password@ is still checked, as is every other value.
791
+ if (typeof val === 'string' && val.length >= 8 && !isSettingValue(val) && text.includes(val)) {
781
792
  leaked.push(key);
782
793
  }
783
794
  }
@@ -0,0 +1,7 @@
1
+ # Automation source collection
2
+
3
+ Copy `capability/` into your application. Import `collectSources` from `./capability/index.mjs` and supply unique source IDs, an adapter map, an ISO check timestamp and an optional positive record limit.
4
+
5
+ Each adapter returns records or `{records,status,error?}`. Status is `ok`, `partial`, `unsupported`, `disabled`, `error` or `unauthorized`. The collector returns `automations`, independent source `coverage`, and `checked_at`; one failed source does not erase successful evidence. Missing adapters stay unsupported and oversized inventories become partial.
6
+
7
+ Your application owns authorization, data shape, adapter timeouts, execution and persistence. Supply transports and credentials directly to your own adapters. This capability performs no filesystem, network, process or environment access and prints nothing.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Greg Iteen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,66 @@
1
+ # Automations
2
+
3
+ Inspect automation sources, triggers, observed outcomes and source coverage across the Total Recall mesh. Composable CLI remains the owner of reusable commands; a command definition is labelled as a manual definition until an explicit automation binding exists.
4
+
5
+ ```
6
+ total-recall automations --help
7
+ total-recall automations list --json
8
+ total-recall automations list --local --source task-queue
9
+ total-recall automations status --node <node>
10
+ total-recall automations show <automation-id>
11
+ total-recall automations cancel <local-task-id>
12
+ total-recall automations dream --all-brains --json
13
+ ```
14
+
15
+ Inventory includes plugin schedules, durable tasks, research queue work, daemon maintenance/watchers, SSSS workflow/trigger declarations, reusable composed commands, and operating-system automation declarations. macOS reads user/system launchd definitions and the current user’s crontab. Linux reads installed and loaded system/user timer units and the current user’s crontab. Commands, environment variables, queued payloads and secret values are excluded from OS summaries.
16
+
17
+ Each source reports its own coverage. A missing integration, unreadable definition, denied peer, offline node or old collector remains explicit. External agent-platform automation providers have no authenticated inventory contract in this runtime and report unsupported; the plugin does not invent their state. Inventory can therefore be partial even when visible automation records are valid.
18
+
19
+ A declared schedule does not prove a running scheduler. Last handled slot is not a successful run. Observed execution outcomes come from source-owned records/events; unavailable telemetry stays unknown. Individual daemon watcher registration is not reported by the current owner, so those records remain unknown even when the daemon itself is running.
20
+
21
+ Selected-node tasks report `selected_node` and `placement_state`. A task without a canonical selection is `unplaced`. Peer metadata without selection evidence stays `unknown`. `last_run` includes the observed slot. `last_handled_outcome` reports the event for the exact stored handled slot. It stays unknown when no matching event exists.
22
+
23
+ Full peer reads use mesh's authenticated command transport with a fixed local inventory command. If that read is unavailable, the plugin requests mesh-authenticated plugin metadata and marks coverage partial. It never sends a mesh-sync token to the PAT-protected plugin-run route.
24
+
25
+ The card and panel use the same CLI contracts. Cancellation requires a pending local durable task and an owner that supports a conditional pending-to-cancelled transition. The plugin checks the saved owner state before reporting success. Older hosts can still inventory automations. Mesh peers use the plugin's local inventory operation and can report incomplete coverage. [Owner acceptance tracker](docs/projects/in-progress/ALL_AUTOMATIONS/ALL_AUTOMATIONS_PROJECT_TRACKER.md)
26
+
27
+ ## Scheduled plugin tasks
28
+
29
+ This plugin runs the `tasks` every plugin declares in its manifest. Total Recall core has no scheduler of its own: the daemon starts this plugin's `scheduler` module through `runtime.daemon`, and it ships bundled with Total Recall.
30
+
31
+ - `command` is a subcommand followed by its arguments, so `cleanup --auto --json` runs `cleanup` with `--auto --json`.
32
+ - The timeout is the task's `timeout_ms`, else the subcommand's declared `timeout_ms`, else 60 seconds.
33
+ - Scheduling runs against the global brain in the home folder, so run records and `plugin.task_run` events are where `list` reads them.
34
+ - A failed run logs its reason and the end of its output, and alerts the operator through operator-alerts once per task per day.
35
+ - A plugin switched off runs no tasks; a missed slot within the last day runs once; the first run is the first matching slot after the task is first seen.
36
+
37
+ ## Installation and ownership
38
+
39
+ Use `total-recall plugin install <path-to-this-checkout>` for your own Total Recall workflow. Credentials belong to shared auth. The plugin id and CLI command are `automations`. The package name is `tr-plugin-automations`. The GitHub repository name and folder name change separately.
40
+
41
+ For an application you ship, `total-recall plugin vendor <path-to-this-checkout>` selects the declared capability. Its `collectSources` export takes explicit source adapters and configuration. The Total Recall CLI wrapper, scheduler and platform accounts are host dependencies.
42
+
43
+ ## Dream and decay
44
+
45
+ Automations owns one dream task at `40 3 * * *` in each node's local time. Its placement is `per-node`. Its command is `dream --all-brains`. Decay is a phase of dream. It has no separate schedule.
46
+
47
+ The plugin calls `total-recall dream --all-brains --json` through `spawnSync` with no shell. It checks core's help for `--all-brains` first. Older core versions can ignore unknown flags. The operation fails before execution if core lacks the flag. It also fails for process errors, signals, invalid output and failed brain results. The JSON response reports `exit_code`, `child_exit_code` and `results`. Core must return a nonempty results array, either directly or in a `results` or `brains` field. The child timeout is 30 minutes. The host timeout is 31 minutes.
48
+
49
+ `list` shows the dream task on each node. It reads the last real outcome from that node's task events. A handled slot stays separate from a real outcome. The cloud daemon must coordinate tasks across the mesh through Automations. This checkout change adds no new transport or deployment. Fleet execution requires a compatible core and an installed plugin on each node.
50
+
51
+ ## Development
52
+
53
+ `npm test` runs only owner root/UI specs through `vitest.config.mjs`. Injected skill evaluations run independently in their owner and are not Vitest suites. Tests must use isolated/injected state and must not cancel real tasks. Plugin state is derived; persistent source state and controls stay with their owning APIs.
54
+
55
+ ## Using this from an agent
56
+
57
+ Send questions about what is scheduled here: tasks, triggers and real outcomes across the mesh. A declared schedule does not prove anything ran, so read the outcome. To create a reusable command, use Composable CLI.
58
+
59
+ ```bash
60
+ total-recall automations list --json
61
+ total-recall automations status --node <node>
62
+ total-recall automations show <automation-id>
63
+ total-recall automations cancel <local-task-id>
64
+ ```
65
+
66
+ Raw commands this plugin owns: `crontab -l` and `launchctl list`.
@@ -0,0 +1,231 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import crypto from 'node:crypto';
5
+ import { execFile } from 'node:child_process';
6
+ import { promisify } from 'node:util';
7
+ import { pathToFileURL } from 'node:url';
8
+ import { collectSources } from './capability/index.mjs';
9
+
10
+ const exec = promisify(execFile);
11
+ export const SOURCES = Object.freeze(['plugin-tasks', 'task-queue', 'research-queue', 'daemon', 'workflows', 'composed-commands', 'operating-system', 'agent-platforms']);
12
+ export const coreModule = (root, name) => import(pathToFileURL(path.join(root, 'src/core', `${name}.mjs`)).href);
13
+ const digest = text => crypto.createHash('sha256').update(text).digest('hex').slice(0, 20);
14
+ export const recordId = (node, source, key) => `${source}:${digest(`${node}\0${key}`)}`;
15
+ const text = (value, max = 240) => typeof value === 'string' ? value.slice(0, max) : '';
16
+ const canCancelPending = async (tasks, queueDir) =>
17
+ tasks.TASK_CANCELLATION_SUPPORTS_EXPECTED_STATUS === true
18
+ && typeof tasks.supportsPendingTaskCancellation === 'function'
19
+ && await tasks.supportsPendingTaskCancellation(queueDir) === true;
20
+ export function automation(ctx, source, key, data) {
21
+ return { id: recordId(ctx.node_id, source, key), node_id: ctx.node_id, source,
22
+ name: key, origin: source, trigger: { kind: 'unknown', summary: 'Not reported' },
23
+ enabled: null, state: 'unknown', runner_available: null, last_run: null, actions: [], ...data };
24
+ }
25
+ export function scheduledTasks(plugins = [], ctx = { node_id: 'local' }) {
26
+ return plugins.flatMap(plugin => (Array.isArray(plugin.tasks) ? plugin.tasks : []).map(task => {
27
+ const selected_node = typeof plugin.task_nodes?.[task.command] === 'string' ? plugin.task_nodes[task.command].trim() || null : null;
28
+ const placement_state = task.placement !== 'selected-node' ? 'per-node'
29
+ : !Object.hasOwn(plugin, 'task_nodes') ? 'unknown' : selected_node ? 'selected' : 'unplaced';
30
+ return automation(ctx, 'plugin-tasks', `${plugin.id}:${task.command}`, {
31
+ name: text(task.intent || `${plugin.name || plugin.id} · ${task.command}`), origin: `plugin:${plugin.id}`,
32
+ plugin_id: plugin.id, plugin_name: plugin.name, command: task.command, intent: text(task.intent), schedule: task.schedule,
33
+ trigger: { kind: 'cron', summary: text(task.schedule) }, placement: task.placement || 'per-node',
34
+ selected_node, placement_state,
35
+ enabled: plugin.activation?.enabled ?? null, state: plugin.valid === false ? 'invalid' : plugin.activation?.enabled === false ? 'disabled' : placement_state === 'unplaced' ? 'unplaced' : 'declared',
36
+ runner_available: placement_state === 'unplaced' ? false : null,
37
+ valid: plugin.valid, last_handled_slot: task.last_run || null,
38
+ last_handled_outcome: task.last_run ? { slot: task.last_run, state: 'unknown' } : null,
39
+ });
40
+ }));
41
+ }
42
+
43
+ export async function boundedCommand(file, args, options = {}) {
44
+ try { const result = await exec(file, args, { timeout: 4000, maxBuffer: 2 * 1024 * 1024, encoding: 'utf8', windowsHide: true, ...options }); return result.stdout; }
45
+ catch (error) { const failure = new Error(error.code === 'ENOENT' ? `${file} is unavailable` : `${file} inventory failed (${error.killed ? 'timeout' : error.code || 'error'})`); failure.code = error.code; throw failure; }
46
+ }
47
+
48
+ /** Read only scheduler facts; never return commands, environments or secret-bearing arguments. */
49
+ export async function operatingSystem(ctx) {
50
+ const run = ctx.runCommand || boundedCommand;
51
+ const records = [], warnings = [];
52
+ if (ctx.platform === 'darwin') {
53
+ const dirs = [path.join(ctx.home, 'Library/LaunchAgents'), '/Library/LaunchAgents', '/Library/LaunchDaemons', '/System/Library/LaunchAgents', '/System/Library/LaunchDaemons'];
54
+ let loaded = new Map();
55
+ try { loaded = new Map((await run('/bin/launchctl', ['list'])).split('\n').slice(1).map(line => line.trim().split(/\s+/)).filter(parts => parts.length >= 3).map(([pid, exit, label]) => [label, { pid, exit }])); }
56
+ catch (error) { warnings.push(error.message); }
57
+ for (const dir of dirs) {
58
+ let entries;
59
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (error) { if (error.code !== 'ENOENT') warnings.push(`Cannot inspect ${dir}`); continue; }
60
+ const jobs = entries.filter(item => item.isFile() && item.name.endsWith('.plist'));
61
+ let cursor = 0;
62
+ await Promise.all(Array.from({ length: Math.min(4, jobs.length) }, async () => { while (cursor < jobs.length) {
63
+ const entry = jobs[cursor++];
64
+ const file = path.join(dir, entry.name);
65
+ try {
66
+ if (fs.statSync(file).size > 1024 * 1024) throw new Error('Job definition exceeds size limit');
67
+ const job = JSON.parse(await run('/usr/bin/plutil', ['-convert', 'json', '-o', '-', file]));
68
+ const label = text(job.Label || entry.name), runtime = loaded.get(label);
69
+ const kinds = [job.StartCalendarInterval && 'calendar', job.StartInterval && 'interval', job.WatchPaths && 'file-change', job.QueueDirectories && 'directory', job.KeepAlive && 'keep-alive', job.RunAtLoad && 'boot'].filter(Boolean);
70
+ // Socket/on-demand jobs are automations too; absence of a timer is not absence of a job.
71
+ records.push(automation(ctx, 'operating-system', file, { name: label, origin: `launchd:${label}`,
72
+ trigger: { kind: kinds[0] || 'on-demand', summary: job.StartInterval ? `Every ${Number(job.StartInterval)} seconds${kinds.length > 1 ? `; ${kinds.filter(kind => kind !== 'interval').join(', ')}` : ''}` : kinds.length ? kinds.join(', ') : 'On demand' },
73
+ enabled: job.Disabled === true ? false : null, state: runtime?.pid !== '-' && runtime?.pid ? 'running' : runtime ? 'loaded' : 'declared',
74
+ runner_available: runtime ? true : null, last_exit_code: runtime && runtime.exit !== '-' ? Number(runtime.exit) : null }));
75
+ } catch { warnings.push(`Cannot inspect launchd definition ${entry.name}`); }
76
+ } }));
77
+ }
78
+ } else if (ctx.platform === 'linux') {
79
+ for (const scope of ['system', 'user']) {
80
+ const scopeArgs = scope === 'user' ? ['--user'] : [];
81
+ try {
82
+ const [installed, loaded] = await Promise.all([
83
+ run('systemctl', [...scopeArgs, 'list-unit-files', '--type=timer', '--no-legend', '--no-pager']),
84
+ run('systemctl', [...scopeArgs, 'list-units', '--type=timer', '--all', '--no-legend', '--no-pager', '--plain']),
85
+ ]);
86
+ const active = new Map(loaded.trim().split('\n').map(line => line.trim().split(/\s+/)).filter(parts => parts[0]?.endsWith('.timer')).map(parts => [parts[0], parts[2]]));
87
+ for (const line of installed.trim().split('\n')) {
88
+ const [name, state] = line.trim().split(/\s+/); if (!name?.endsWith('.timer')) continue;
89
+ records.push(automation(ctx, 'operating-system', `${scope}:${name}`, { name, origin: `systemd:${scope}:${name}`, trigger: { kind: 'timer', summary: 'System timer' }, enabled: ['enabled', 'enabled-runtime', 'linked', 'linked-runtime'].includes(state) ? true : ['disabled', 'masked'].includes(state) ? false : null, state: active.get(name) || state, runner_available: active.has(name) ? true : null }));
90
+ }
91
+ } catch (error) { warnings.push(`${scope}: ${error.message}`); }
92
+ }
93
+ } else return { status: 'unsupported', records: [], error: `No operating-system adapter for ${ctx.platform}` };
94
+ try {
95
+ const cron = await run('crontab', ['-l']);
96
+ for (const [index, line] of cron.split('\n').entries()) {
97
+ const clean = line.trim(); if (!clean || clean.startsWith('#') || /^[A-Za-z_][A-Za-z0-9_]*\s*=/.test(clean)) continue;
98
+ const match = clean.match(/^(@\w+|(?:\S+\s+){4}\S+)\s+.+$/); if (!match) { warnings.push(`Unrecognized crontab line ${index + 1}`); continue; }
99
+ records.push(automation(ctx, 'operating-system', `crontab:${digest(clean)}`, { name: `Crontab job ${index + 1}`, origin: 'crontab:current-user', trigger: { kind: 'cron', summary: match[1] }, enabled: true, state: 'declared' }));
100
+ }
101
+ } catch (error) {
102
+ // Exit 1 means either no crontab or an access error; never claim empty coverage without evidence.
103
+ warnings.push(error.message);
104
+ }
105
+ records.sort((left, right) => left.id.localeCompare(right.id));
106
+ return { records, status: warnings.length ? 'partial' : 'ok', ...(warnings.length ? { error: [...new Set(warnings)].slice(0, 12).join('; ') } : {}) };
107
+ }
108
+
109
+ export async function createContext(packageRoot, options = {}) {
110
+ const config = options.config || await coreModule(packageRoot, 'config');
111
+ const active = config.getActiveBrains(options.projectRoot || process.cwd());
112
+ const brains = [active.global, active.project].filter(Boolean);
113
+ const unique = [...new Map(brains.map(brain => [path.resolve(brain.brainDir), brain])).values()];
114
+ return { packageRoot, projectRoot: options.projectRoot || process.cwd(), node_id: options.node_id || os.hostname(), home: options.home || os.homedir(), platform: options.platform || process.platform, brains: unique, config, ...options };
115
+ }
116
+
117
+ export async function sourceAdapters(ctx) {
118
+ const load = name => ctx.modules?.[name] || coreModule(ctx.packageRoot, name);
119
+ return {
120
+ 'plugin-tasks': async () => {
121
+ const store = await load('plugin-store');
122
+ const plugins = store.listInstalledPlugins(ctx.projectRoot).map(plugin => ({ ...plugin }));
123
+ if (plugins.some(plugin => plugin.tasks?.some(task => task.placement === 'selected-node'))) {
124
+ const loader = await load('plugin-loader');
125
+ const discovered = new Map(loader.discoverPlugins(ctx.projectRoot).map(plugin => [plugin.id, plugin]));
126
+ for (const plugin of plugins) {
127
+ if (!plugin.tasks?.some(task => task.placement === 'selected-node')) continue;
128
+ const source = discovered.get(plugin.id);
129
+ if (source) plugin.task_nodes = store.readPluginRecord(source)?.task_nodes || {};
130
+ }
131
+ }
132
+ const records = scheduledTasks(plugins, ctx);
133
+ const events = await load('ssss-operation-service');
134
+ const payloads = [];
135
+ for (const brain of ctx.brains) for (const event of await events.listVfsEvents({ vaultRoot: path.join(brain.brainDir, 'memory-vault') })) {
136
+ let payload = event?.payload || event?.content; if (typeof payload === 'string') { try { payload = JSON.parse(payload); } catch { continue; } }
137
+ if (payload?.kind === 'plugin.task_run' && (!payload.node_id || payload.node_id === ctx.node_id)) payloads.push({ ...payload, at: event.timestamp || event.created_at || payload.at || null });
138
+ }
139
+ const eventTime = event => Date.parse(event.at || event.slot || '') || 0;
140
+ const outcome = event => ({ at: event.at || event.slot || null, slot: event.slot || null,
141
+ state: event.ok === true ? 'success' : event.ok === false ? 'failed' : 'unknown', exit_code: event.exit_code ?? null });
142
+ for (const record of records) {
143
+ const matching = payloads.filter(event => event.plugin_id === record.plugin_id && event.command === record.command)
144
+ .sort((a, b) => eventTime(a) - eventTime(b));
145
+ const last = matching.at(-1);
146
+ if (last) record.last_run = outcome(last);
147
+ const handled = record.last_handled_slot && matching.filter(event => event.slot === record.last_handled_slot).at(-1);
148
+ if (handled) record.last_handled_outcome = outcome(handled);
149
+ }
150
+ return records;
151
+ },
152
+ 'task-queue': async () => {
153
+ const tasks = await load('task-envelope');
154
+ const queues = await Promise.all(ctx.brains.map(async (brain) => {
155
+ const queueDir = tasks.resolveQueueDir(brain.brainDir);
156
+ const canCancel = await canCancelPending(tasks, queueDir);
157
+ return tasks.listTasks(queueDir, { status: 'all' }).map((task) => {
158
+ const record = automation(ctx, 'task-queue', `${brain.layer}:${task.slug}`, { name: text(task.intent || task.slug), origin: `task:${brain.layer}:${task.slug}`, trigger: { kind: 'queue', summary: 'Queued work' }, enabled: !['completed', 'cancelled'].includes(task.status), state: task.status, owner_ref: { layer: brain.layer, slug: task.slug }, last_run: task.completed_at || task.failed_at ? { at: task.completed_at || task.failed_at, state: task.status, exit_code: null } : null });
159
+ if (canCancel && task.status === 'pending') record.actions = [{ command: 'cancel', label: 'Cancel task', args: [record.id], effect: 'write' }];
160
+ return record;
161
+ });
162
+ }));
163
+ return queues.flat();
164
+ },
165
+ 'research-queue': async () => {
166
+ const research = await load('research-queue');
167
+ const records = [];
168
+ for (const brain of ctx.brains) {
169
+ let offset = 0;
170
+ while (true) {
171
+ const page = research.listQueue({ brainDir: brain.brainDir, limit: 500, offset });
172
+ if (!Array.isArray(page.items)) throw new Error('Invalid research queue response');
173
+ records.push(...page.items.map(task => automation(ctx, 'research-queue', `${brain.layer}:${task.id}`, { name: text(task.topic), origin: `research:${brain.layer}:${task.id}`, trigger: { kind: 'queue', summary: task.research_phase || 'Research queue' }, enabled: ['pending', 'in_progress'].includes(task.status), state: task.status, last_run: task.completed_at ? { at: task.completed_at, state: task.status, exit_code: null } : null })));
174
+ offset += page.items.length;
175
+ if (offset >= page.total) break;
176
+ if (!page.items.length) throw new Error('Research pagination made no progress');
177
+ if (records.length >= 10000) return { records, status: 'partial', error: 'Research queue exceeded 10000 records' };
178
+ }
179
+ }
180
+ return records;
181
+ },
182
+ daemon: async () => {
183
+ const [health, update] = await Promise.all([load('startup-health'), load('automatic-brain-updates')]);
184
+ return ctx.brains.flatMap(brain => {
185
+ const observation = health.observeDaemon(brain.brainDir), running = observation.status === 'running';
186
+ const maintenance = update.automaticUpdateStatus(brain.brainDir);
187
+ return [automation(ctx, 'daemon', `${brain.layer}:daemon`, { name: `${brain.layer} background worker`, origin: `daemon:${brain.layer}`, trigger: { kind: 'queue', summary: 'Durable queue and maintenance loop' }, state: observation.status, runner_available: running, enabled: null }),
188
+ automation(ctx, 'daemon', `${brain.layer}:package-updates`, { name: `${brain.layer} package maintenance`, origin: `automatic-brain-updates:${brain.layer}`, trigger: { kind: 'interval', summary: `Every ${maintenance.intervalMs / 60000} minutes; failed checks retry` }, enabled: maintenance.enabled, state: maintenance.enabled ? 'configured' : 'disabled', runner_available: running, last_run: maintenance.last ? { at: maintenance.last.checkedAt ? new Date(maintenance.last.checkedAt).toISOString() : null, state: maintenance.last.failed ? 'failed' : 'success', exit_code: null } : null }),
189
+ ...['vault-watch', 'source-watch', 'session-ingest', 'cli-surfaces', 'repo-sync', 'github-sync', 'obsidian-watch'].map(name => automation(ctx, 'daemon', `${brain.layer}:${name}`, { name: `${brain.layer} ${name}`, origin: `daemon:${brain.layer}:${name}`, trigger: { kind: name.includes('watch') ? 'file-change' : name === 'session-ingest' ? 'boot' : 'maintenance', summary: 'Daemon-managed; individual registration is not reported by owner' }, state: 'unknown', runner_available: running }))];
190
+ });
191
+ },
192
+ workflows: async () => {
193
+ const vfs = await load('vfs-documents');
194
+ return ctx.brains.flatMap(brain => vfs.listVfsDocuments(path.join(brain.brainDir, 'memory-vault')).filter(doc => ['workflow', 'trigger'].includes(doc.type)).flatMap(doc => {
195
+ const triggers = doc.type === 'trigger' ? [{ ...doc, type: doc.source }] : Array.isArray(doc.triggers) && doc.triggers.length ? doc.triggers : [{ type: 'manual' }];
196
+ return triggers.map((trigger, index) => automation(ctx, 'workflows', `${brain.layer}:${doc.vfs_path}:${trigger.id || trigger.trigger_id || index}`, { name: text(doc.title || doc.name || doc.id || doc.slug), origin: `vfs:${brain.layer}:${doc.vfs_path}`, trigger: { kind: text(trigger.type), summary: text(trigger.cron || trigger.interval || trigger.event_type || trigger.type || 'Unknown trigger'), ...(trigger.timezone ? { timezone: text(trigger.timezone) } : {}) }, enabled: doc.type === 'trigger' ? doc.status === 'active' ? true : ['disabled', 'paused'].includes(doc.status) ? false : null : typeof doc.isActive === 'boolean' ? doc.isActive : null, state: 'declared', runner_available: null }));
197
+ }));
198
+ },
199
+ 'composed-commands': async () => {
200
+ const commands = await load('command-surface');
201
+ const dirs = commands.commandScopeDirectories(ctx.projectRoot, ctx.config.globalAgentDir);
202
+ return commands.listSurfaceCommands(dirs).map(command => automation(ctx, 'composed-commands', `${command.scope}:${command.name}`, { name: command.name, origin: `composable-cli:${command.scope}:${command.name}`, trigger: { kind: 'manual', summary: 'Reusable command; requires explicit automation binding' }, state: 'definition', enabled: null }));
203
+ },
204
+ 'operating-system': () => operatingSystem(ctx),
205
+ 'agent-platforms': async () => ({ status: 'unsupported', records: [], error: 'No authenticated agent-platform inventory contract is configured; provider automations cannot be inferred from local command definitions.' }),
206
+ };
207
+ }
208
+
209
+ export async function collectLocalAutomations(packageRoot, options = {}) {
210
+ const ctx = await createContext(packageRoot, options), checked_at = new Date().toISOString();
211
+ const adapters = options.adapters || await sourceAdapters(ctx);
212
+ return { node_id: ctx.node_id, ...await collectSources({ sources: SOURCES, adapters, checkedAt: checked_at }) };
213
+ }
214
+
215
+ export async function cancelAutomation(packageRoot, id, options = {}) {
216
+ const ctx = await createContext(packageRoot, options);
217
+ const tasks = ctx.modules?.['task-envelope'] || await coreModule(packageRoot, 'task-envelope');
218
+ for (const brain of ctx.brains) {
219
+ const queueDir = tasks.resolveQueueDir(brain.brainDir);
220
+ const task = tasks.listTasks(queueDir, { status: 'all' })
221
+ .find((candidate) => recordId(ctx.node_id, 'task-queue', `${brain.layer}:${candidate.slug}`) === id);
222
+ if (!task) continue;
223
+ if (task.status !== 'pending') throw new Error('Task is no longer pending');
224
+ if (!await canCancelPending(tasks, queueDir)) throw new Error('This host cannot safely cancel a pending task');
225
+ const result = await tasks.cancelTask(queueDir, task.slug, { expectedStatus: 'pending' });
226
+ if (!result.success) throw new Error(result.error || 'Task cancellation failed');
227
+ if (tasks.getTask(queueDir, task.slug)?.status !== 'cancelled') throw new Error('Task owner did not confirm cancellation');
228
+ return { success: true, id, state: 'cancelled' };
229
+ }
230
+ throw new Error('Cancellable local automation not found');
231
+ }
@@ -0,0 +1,16 @@
1
+ /** Source collection for an application-owned automation inventory. No host or transport assumptions. */
2
+ export async function collectSources({ sources, adapters, checkedAt, limit = 10000 }) {
3
+ if (!Array.isArray(sources) || !sources.every(source => typeof source === 'string') || new Set(sources).size !== sources.length) throw new Error('Unique source ids are required');
4
+ if (!adapters || typeof adapters !== 'object' || !checkedAt || !Number.isSafeInteger(limit) || limit < 1) throw new Error('Adapters, checkedAt and a positive record limit are required');
5
+ const results = await Promise.all(sources.map(async source => {
6
+ try {
7
+ if (typeof adapters[source] !== 'function') return { source, status: 'unsupported', checked_at: checkedAt, records: [], error: 'Source adapter is unavailable' };
8
+ const value = await adapters[source]();
9
+ const result = Array.isArray(value) ? { records: value, status: 'ok' } : value;
10
+ if (!result || !Array.isArray(result.records) || !['ok', 'partial', 'unsupported', 'disabled', 'error', 'unauthorized'].includes(result.status)) throw new Error('Invalid source result');
11
+ if (result.records.length > limit) return { source, status: 'partial', checked_at: checkedAt, records: result.records.slice(0, limit), error: `Source inventory exceeded ${limit} records` };
12
+ return { ...result, source, checked_at: checkedAt };
13
+ } catch (error) { return { source, status: 'error', checked_at: checkedAt, records: [], error: `Source discovery failed${typeof error.code === 'string' && /^[A-Z_0-9]+$/.test(error.code) ? ` (${error.code})` : ''}` }; }
14
+ }));
15
+ return { automations: results.flatMap(result => result.records), coverage: results.map(({ records, ...coverage }) => coverage), checked_at: checkedAt };
16
+ }