total-recall-brain 3.43.3 → 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 (252) hide show
  1. package/bin/total-recall.mjs +6 -4
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +16 -9
  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 +3 -3
  9. package/scaffold/.agent/skills/plugin/SKILL.md +7 -2
  10. package/scaffold/.agent/skills/plugin/evals/README.md +5 -0
  11. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +1 -0
  12. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +1 -0
  13. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +1 -0
  14. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +1 -0
  15. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +1 -0
  16. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +1 -0
  17. package/scaffold/.agent/skills/plugin/evals/cases/07-missing-reuse.mjs +11 -0
  18. package/scaffold/.agent/skills/plugin/evals/cases/08-unresolved-owner-command.mjs +12 -0
  19. package/scaffold/.agent/skills/plugin/evals/cases/09-missing-brick.mjs +12 -0
  20. package/scaffold/.agent/skills/plugin/prompts/01-function.md +4 -3
  21. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +1 -0
  22. package/scaffold/.agent/skills/plugin/references/surfaces.md +12 -2
  23. package/scaffold/.agent/skills/plugin/scripts/record.mjs +59 -2
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +8 -0
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +15 -14
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -3
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-gh-login.mjs +102 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +38 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
  31. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
  33. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  34. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
  39. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  40. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
  49. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
  50. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +15 -14
  53. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -3
  54. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  55. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +15 -14
  56. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -3
  57. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  58. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +15 -14
  59. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -3
  60. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  61. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
  63. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +17 -16
  64. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +5 -4
  65. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
  87. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +15 -14
  88. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -3
  89. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  90. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +59 -10
  91. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  92. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  93. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +160 -29
  94. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +15 -14
  95. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +4 -4
  96. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +231 -16
  97. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +19 -4
  98. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  100. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +101 -16
  101. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +54 -21
  103. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +40 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +68 -13
  105. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  106. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +64 -15
  107. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  109. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +249 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +6 -12
  112. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +16 -8
  115. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +26 -2
  116. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +6 -1
  117. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +6 -2
  118. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  119. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +28 -12
  121. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
  122. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  129. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  130. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  131. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +110 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  133. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +597 -70
  134. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  135. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
  136. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
  137. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
  138. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +15 -14
  139. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -3
  140. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  141. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  142. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
  143. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +15 -14
  144. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -3
  145. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  146. package/scaffold/.agent/skills/total-recall/plugins/ssss/assets/scaffold.json +5 -5
  147. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +6 -6
  148. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -2
  149. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +1 -1
  150. package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
  151. package/src/cli/agent.mjs +1 -1
  152. package/src/cli/command.mjs +41 -2
  153. package/src/cli/connect.mjs +2 -29
  154. package/src/cli/context.mjs +1 -0
  155. package/src/cli/dream.mjs +93 -69
  156. package/src/cli/harness.mjs +4 -4
  157. package/src/cli/help.mjs +1 -1
  158. package/src/cli/ingest-okf.mjs +4 -4
  159. package/src/cli/init.mjs +0 -25
  160. package/src/cli/lessons.mjs +51 -0
  161. package/src/cli/memory-plugin.mjs +1 -1
  162. package/src/cli/plugin/copies.mjs +64 -14
  163. package/src/cli/plugin/create.mjs +6 -7
  164. package/src/cli/plugin/index.mjs +11 -7
  165. package/src/cli/plugin/install.mjs +10 -15
  166. package/src/cli/plugin/remove.mjs +1 -1
  167. package/src/cli/plugin/search.mjs +2 -2
  168. package/src/cli/preflight.mjs +19 -6
  169. package/src/cli/proposals.mjs +1 -1
  170. package/src/cli/recall.mjs +66 -23
  171. package/src/cli/release.mjs +4 -1
  172. package/src/cli/remember.mjs +3 -14
  173. package/src/cli/server.mjs +18 -0
  174. package/src/cli/startup.mjs +1 -0
  175. package/src/cli/task.mjs +9 -2
  176. package/src/cli/update.mjs +10 -3
  177. package/src/core/access-ledger.mjs +78 -0
  178. package/src/core/agent-manager.mjs +2 -2
  179. package/src/core/automatic-brain-updates.mjs +11 -6
  180. package/src/core/brain-locate.mjs +15 -0
  181. package/src/core/brain-updates.mjs +42 -12
  182. package/src/core/claude-hooks.mjs +3 -1
  183. package/src/core/cleanup-task.mjs +15 -0
  184. package/src/core/cli-commands.mjs +2 -0
  185. package/src/core/cli-maintenance.mjs +4 -1
  186. package/src/core/cli-semantic-index.mjs +23 -3
  187. package/src/core/context-compactor.mjs +7 -2
  188. package/src/core/context-compiler.mjs +26 -1
  189. package/src/core/context-policy.mjs +7 -3
  190. package/src/core/daemon-control.mjs +22 -0
  191. package/src/core/daemon-loop.mjs +39 -62
  192. package/src/core/default-plugins.mjs +1 -1
  193. package/src/core/dream.mjs +33 -105
  194. package/src/core/ensure-dependencies.mjs +148 -0
  195. package/src/core/fast-recall.mjs +2 -2
  196. package/src/core/field-evaluation.mjs +23 -0
  197. package/src/core/gate-mode.mjs +1 -1
  198. package/src/core/global-brain-updates.mjs +1 -1
  199. package/src/core/harness-hooks.mjs +14 -2
  200. package/src/core/lesson-hooks.mjs +74 -0
  201. package/src/core/lesson-memory.mjs +24 -0
  202. package/src/core/lesson-sessions.mjs +52 -0
  203. package/src/core/lessons.mjs +96 -0
  204. package/src/core/mesh-brain-updates.mjs +2 -1
  205. package/src/core/meta-harness.mjs +2 -2
  206. package/src/core/optimizer.mjs +66 -165
  207. package/src/core/plugin-contracts/card.mjs +3 -0
  208. package/src/core/plugin-loader.mjs +12 -3
  209. package/src/core/plugin-runtime.mjs +40 -1
  210. package/src/core/plugin-store.mjs +35 -23
  211. package/src/core/post-mortem.mjs +52 -36
  212. package/src/core/preflight-gate.mjs +23 -2
  213. package/src/core/proposal-applier.mjs +31 -2
  214. package/src/core/repo-sync.mjs +4 -6
  215. package/src/core/required-plugins.mjs +26 -4
  216. package/src/core/rule-length.mjs +23 -0
  217. package/src/core/runtime.mjs +1 -1
  218. package/src/core/scheduler.mjs +35 -43
  219. package/src/core/schema.mjs +1 -2
  220. package/src/core/search.mjs +4 -3
  221. package/src/core/server-restart.mjs +128 -7
  222. package/src/core/server-service.mjs +148 -0
  223. package/src/core/session-watcher.mjs +19 -1
  224. package/src/core/skill-gap.mjs +16 -0
  225. package/src/core/ssss-kernel-bridge.mjs +33 -6
  226. package/src/core/startup-health.mjs +6 -1
  227. package/src/core/surface.mjs +68 -57
  228. package/src/core/task-context.mjs +9 -8
  229. package/src/core/task-executors.mjs +54 -106
  230. package/src/core/validated-write.mjs +5 -0
  231. package/src/core/vault-files.mjs +8 -2
  232. package/src/core/vault.mjs +1 -1
  233. package/src/core/vector-field.mjs +134 -192
  234. package/src/server/index.mjs +3 -1
  235. package/src/server/invoke-core-child.mjs +24 -0
  236. package/src/server/invoke-core-operations.mjs +102 -0
  237. package/src/server/route-manifest.json +0 -56
  238. package/src/server/routes/brains.mjs +0 -18
  239. package/src/server/routes/context.mjs +0 -41
  240. package/src/server/routes/invoke.mjs +26 -1
  241. package/src/server/routes/ssss.mjs +0 -169
  242. package/src/server/routes/system.mjs +0 -15
  243. package/src/server/routes/tasks.mjs +0 -26
  244. package/src/server/routes/vault.mjs +2 -23
  245. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  246. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  247. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  248. package/src/core/append-log.mjs +0 -131
  249. package/src/core/plugin-tasks.mjs +0 -173
  250. package/templates/onboarding-interview.md +0 -82
  251. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
  252. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
@@ -0,0 +1,72 @@
1
+ /** Continue a stored job on its branch, without reusing a deleted checkout. */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { execFile } from 'node:child_process';
6
+ import { promisify } from 'node:util';
7
+ import { configDir } from './paths.mjs';
8
+ import { resolveJob } from './jobs.mjs';
9
+ import { appendCommand, jobPaths } from './control.mjs';
10
+ import { createWorktree, removeWorktree, commitAll, isDirty, provideDependencies } from './worktree.mjs';
11
+ import { dispatchWithReceipt } from './dispatch.mjs';
12
+
13
+ export async function continueJob(target, text, { fork = false, atTurn, dispatchFn, execFileFn = promisify(execFile), timeoutMs = 30000 } = {}) {
14
+ const job = resolveJob(target);
15
+ if (!fork && !job.result) throw new Error('Resume requires a finished job; use reply for a running job.');
16
+ if (typeof text !== 'string' || !text.trim()) throw new Error('A continuation message is required.');
17
+ const owner = job.result?.execution?.node || job.record?.node;
18
+ if (owner && owner !== 'local') {
19
+ const quote = value => `'${String(value).replaceAll("'", "'\\''")}'`;
20
+ const argv = [fork ? 'fork' : 'resume', job.result?.execution?.remoteJob || job.record?.remoteJob || job.id, ...(fork ? atTurn ? ['--at', atTurn] : [] : [text]), '--json'];
21
+ const report = await execFileFn(process.env.TOTAL_RECALL_BIN || 'total-recall', ['mesh', 'exec', owner,
22
+ `total-recall meta-harness ${argv.map(quote).join(' ')}`], { encoding: 'utf8', timeout: 600000 });
23
+ return JSON.parse(report.stdout);
24
+ }
25
+ const attempt = job.result?.attempts?.at(-1) || job.result;
26
+ const file = attempt?.progressFile || job.result?.progressFile || path.join(configDir(), 'jobs', `${job.id}.events.jsonl`);
27
+ let saved, status;
28
+ try {
29
+ saved = JSON.parse(fs.readFileSync(file.replace('.events.jsonl', '.session.json'), 'utf8'));
30
+ status = JSON.parse(fs.readFileSync(file.replace('.events.jsonl', '.status.json'), 'utf8'));
31
+ } catch { throw new Error('This job has no stored session.'); }
32
+ let sessionId = attempt?.sessionId || status.session_id;
33
+ if (!sessionId) throw new Error('This job has no resumable session ID.');
34
+ if (fork && !job.result) {
35
+ const delivery = await appendCommand(job.id, { type: 'fork', atTurn });
36
+ const deadline = Date.now() + timeoutMs;
37
+ let event;
38
+ while (!event && Date.now() < deadline) {
39
+ const rows = fs.readFileSync(jobPaths(job.id).events, 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse);
40
+ const ack = rows.find(row => row.commandId === delivery.command.id && row.kind === 'ack');
41
+ if (ack?.ok === false) throw new Error(ack.text);
42
+ event = rows.find(row => row.commandId === delivery.command.id && row.kind === 'fork');
43
+ if (!event) await new Promise(resolve => setTimeout(resolve, 20));
44
+ }
45
+ if (!event?.sessionId) throw new Error('Fork did not return a session ID before the timeout.');
46
+ sessionId = event.sessionId;
47
+ }
48
+ const parentBranch = attempt?.branch || job.result?.branch || saved.branch;
49
+ if (!fork && (!parentBranch || !parentBranch.startsWith('meta-harness/'))) {
50
+ // A dispatch job worked in its own folder and has no job branch: resume it there, as it ran (A-123).
51
+ if (!saved.workspace) throw new Error('This job has no saved job branch or workspace.');
52
+ const result = await dispatchWithReceipt({ ...saved.request, sessionId, cwd: saved.workspace, contextCwd: saved.workspace,
53
+ contextTask: text, prompt: text, permissions: saved.request?.permissions || { read: true, write: true } },
54
+ { dispatchFn, role: 'Resume' });
55
+ return { ...result, parentJob: job.id, workspace: saved.workspace };
56
+ }
57
+ const branch = fork ? `meta-harness/fork-${randomUUID()}` : parentBranch;
58
+ const cwd = saved.workspace;
59
+ const directory = createWorktree(cwd, { branch, existingBranch: !fork, ref: parentBranch || 'HEAD' });
60
+ let result, keep = false;
61
+ try {
62
+ provideDependencies(cwd, directory);
63
+ result = await dispatchWithReceipt({ ...saved.request, sessionId, forkSession: fork && Boolean(job.result), atTurn,
64
+ cwd: directory, contextCwd: cwd, contextTask: text, prompt: text, isolated: true,
65
+ permissions: { read: true, write: true } }, { dispatchFn, role: fork ? 'Fork' : 'Resume' });
66
+ const commit = commitAll(directory, `meta-harness: ${fork ? 'fork' : 'resume'} ${job.id}`);
67
+ return { ...result, parentJob: job.id, branch, ...(commit ? result.ok ? { commit } : { wip: commit } : {}) };
68
+ } catch (error) {
69
+ keep = isDirty(directory);
70
+ return { ok: false, error: error.message, branch, ...(keep ? { keptWorktree: directory } : {}) };
71
+ } finally { if (!keep) removeWorktree(cwd, directory); }
72
+ }
@@ -26,10 +26,13 @@ export async function setupIntegrations({ node, cwd = process.cwd(), env = proce
26
26
  const target=mesh.findMeshNode(node);
27
27
  if(!target)throw new Error('Maintenance node is not recorded in the mesh');
28
28
  const plugin=getPlugin('meta-harness',cwd) || getPlugin('cli-agents',cwd);
29
- if(!plugin?.valid)throw new Error('Meta-harness (cli-agents) is not installed in the selected workspace');
30
- await ensurePluginRecord(plugin);
29
+ if(!plugin?.valid)throw new Error('Metaharness (cli-agents) is not installed in the selected workspace');
30
+ const record=await ensurePluginRecord(plugin);
31
31
  placement=target.hostname;
32
- await patchPluginRecord(plugin,{task_nodes:{maintenance:placement}});
32
+ const manifest=JSON.parse(fs.readFileSync(new URL('../plugin.json',import.meta.url),'utf8'));
33
+ const taskNodes={...(record?.task_nodes || {})};
34
+ for(const task of manifest.tasks.filter(task=>task.placement==='selected-node'))taskNodes[task.command]=placement;
35
+ await patchPluginRecord(plugin,{task_nodes:taskNodes});
33
36
  }
34
37
  return {ok:true,connected,universalInstructions:'AGENTS.md; every dispatch also receives a freshly generated bootstrap',maintenanceNode:placement};
35
38
  }
@@ -1,5 +1,5 @@
1
- /** A stopped run takes its agents, checkouts, unfinished branches and open threads with it. */
2
- import { stopActiveProcesses } from './worker.mjs';
1
+ /** Stop agents first. Let the operation save its work and return before the process exits. */
2
+ import { cancelExecution, cancellationResult } from './worker.mjs';
3
3
  import { removeActiveWorktrees } from './worktree.mjs';
4
4
  import { failOpenThreads } from './coordinator.mjs';
5
5
 
@@ -9,16 +9,20 @@ let installed = false;
9
9
  export function stopOnTermination({ graceMs = 500 } = {}) {
10
10
  if (installed) return;
11
11
  installed = true;
12
- let stopping = false;
13
12
  for (const [signal, code] of Object.entries(SIGNALS)) process.on(signal, () => {
14
- if (stopping) return;
15
- stopping = true;
16
- stopActiveProcesses('SIGTERM');
17
- setTimeout(() => {
18
- stopActiveProcesses('SIGKILL');
19
- removeActiveWorktrees();
20
- failOpenThreads();
21
- process.exit(code);
22
- }, graceMs);
13
+ if (cancellationResult()) return;
14
+ cancelExecution(signal, graceMs);
15
+ process.exitCode = code;
23
16
  });
17
+ // Also cover callers that use the lifecycle helper without the CLI wrapper.
18
+ process.once('beforeExit', () => { if (cancellationResult()) finishCancellation(); });
19
+ }
20
+
21
+ export function finishCancellation(result) {
22
+ const cancelled = cancellationResult();
23
+ if (!cancelled) return result;
24
+ const saved = removeActiveWorktrees({ preserveChanges: true });
25
+ failOpenThreads('The run was cancelled.');
26
+ process.exitCode = cancelled.exitCode;
27
+ return { ...result, ...cancelled, ...(saved.length ? { saved } : {}) };
24
28
  }
@@ -48,7 +48,7 @@ function mutate(work) {
48
48
  for (let attempt = 0; attempt < 8; attempt += 1) {
49
49
  const store = storeForThreads();
50
50
  const state = readState(store);
51
- const result = work(state.threads);
51
+ const result = work(state.threads, state);
52
52
  validateThreads(state.threads);
53
53
  try {
54
54
  store.write({ ...state, version: '2.0.0', updatedAt: new Date().toISOString(), threads: state.threads });
@@ -117,3 +117,34 @@ export function deleteThread(id) {
117
117
  return true;
118
118
  });
119
119
  }
120
+
121
+ // Exact titles from test/threads.test.mjs and test/coordinator.test.mjs.
122
+ // Real multiply-job threads do not match, including failed end-to-end attempts.
123
+ const FIXTURE_TITLES = new Set([
124
+ 'Implement Multi-Platform Threads', 'Canonical state', 'concurrent',
125
+ 'Implement token bucket', 'task one', 'task two', 'task three', 'task four', 'failed verification',
126
+ ]);
127
+ const CLEANUP_ID = 'a118-fixture-threads-v1';
128
+
129
+ /** Delete the known fixture titles once. The same SSSS operation records every removal. */
130
+ export function cleanupFixtureThreads({ dryRun = false } = {}) {
131
+ const plan = (threads, state) => {
132
+ const previous = state.cleanups?.find(row => row.id === CLEANUP_ID);
133
+ if (previous) return { ok: true, alreadyApplied: true, record: previous };
134
+ const removed = threads.filter(thread => FIXTURE_TITLES.has(thread.title))
135
+ .map(({ id, title, status }) => ({ id, title, status }));
136
+ const kept = threads.filter(thread => !FIXTURE_TITLES.has(thread.title)).map(thread => thread.id);
137
+ return { ok: true, record: { id: CLEANUP_ID, timestamp: new Date().toISOString(), sources: ['test/threads.test.mjs', 'test/coordinator.test.mjs'], removed, kept } };
138
+ };
139
+ const state = readState(storeForThreads());
140
+ const preview = plan(state.threads, state);
141
+ if (dryRun || preview.alreadyApplied) return { ...preview, dryRun };
142
+ return mutate((threads, current) => {
143
+ const result = plan(threads, current);
144
+ if (result.alreadyApplied) return result;
145
+ const ids = new Set(result.record.removed.map(row => row.id));
146
+ threads.splice(0, threads.length, ...threads.filter(thread => !ids.has(thread.id)));
147
+ current.cleanups = [...(current.cleanups || []), result.record];
148
+ return result;
149
+ });
150
+ }
@@ -0,0 +1,110 @@
1
+ /** SSSS-backed queue for work deferred by included usage limits. */
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { configDir } from './paths.mjs';
5
+ import { destinationDispatchRequest } from './dispatch-request.mjs';
6
+ import { readIncludedPolicy } from './included-usage.mjs';
7
+
8
+ async function queueStore() {
9
+ const { PluginStateStore } = await import('./ssss-store.mjs');
10
+ return new PluginStateStore(path.join(configDir(), 'usage-queue.json'), 'meta-harness');
11
+ }
12
+
13
+ function readRows(store) {
14
+ const jobs = store.read([]);
15
+ if (!Array.isArray(jobs) || jobs.some(job => !job?.id || typeof job.task !== 'string' || !path.isAbsolute(job.workspace || '') ||
16
+ !['waiting', 'running', 'completed', 'cancelled', 'failed'].includes(job.status))) {
17
+ throw new Error('Invalid included usage queue.');
18
+ }
19
+ return jobs;
20
+ }
21
+
22
+ async function mutateQueue(change) {
23
+ for (let attempt = 0; attempt < 8; attempt += 1) {
24
+ const store = await queueStore();
25
+ const jobs = readRows(store);
26
+ const result = change(jobs);
27
+ try { store.write(jobs); return result; }
28
+ catch (error) { if (!error.message.includes('version conflict') || attempt === 7) throw error; }
29
+ }
30
+ }
31
+
32
+ function publicJob({ replay, task, ...job }) {
33
+ return { ...job, task: task.slice(0, 160), canRetry: Boolean(replay) && !job.needsReview };
34
+ }
35
+
36
+ export async function queueIncludedJob(request, result, { role, replay, launchPrevented = false } = {}) {
37
+ const workspace = request.contextCwd || request.cwd;
38
+ const direct = !replay && role === 'Dispatch' && workspace === request.cwd;
39
+ if (direct) replay = { kind: 'dispatch', request: destinationDispatchRequest(request) };
40
+ const job = {
41
+ id: randomUUID(), status: 'waiting', agent: request.agent, workspace, role: role || 'Dispatch',
42
+ task: String(request.contextTask || request.prompt).slice(0, direct ? 160 : 65536),
43
+ reason: 'included-usage-exhausted',
44
+ resetsAt: Number.isFinite(Date.parse(result.resetsAt)) ? result.resetsAt : null,
45
+ queuedAt: new Date().toISOString(), attempts: 0,
46
+ needsReview: !replay || (!launchPrevented && request.permissions?.write === true),
47
+ replay: replay || null,
48
+ };
49
+ return mutateQueue(jobs => {
50
+ if (jobs.filter(item => ['waiting', 'running'].includes(item.status)).length >= 200) throw new Error('Included usage queue is full.');
51
+ const history = jobs.filter(item => !['waiting', 'running'].includes(item.status)).slice(0, -50);
52
+ for (const item of history) jobs.splice(jobs.indexOf(item), 1);
53
+ jobs.push(job);
54
+ if (Buffer.byteLength(JSON.stringify(jobs)) > 4 * 1024 * 1024) throw new Error('Included usage queue exceeds its storage bound.');
55
+ return publicJob(job);
56
+ });
57
+ }
58
+
59
+ export async function listIncludedJobs() {
60
+ const jobs = readRows(await queueStore());
61
+ return jobs.map(publicJob);
62
+ }
63
+
64
+ function updateJob(jobs, id, change) {
65
+ const index = jobs.findIndex(job => job.id === id);
66
+ if (index < 0) throw new Error('Included usage job was not found.');
67
+ return jobs[index] = change(jobs[index]);
68
+ }
69
+
70
+ export async function cancelIncludedJob(id) {
71
+ return mutateQueue(jobs => publicJob(updateJob(jobs, id, job => {
72
+ if (job.status !== 'waiting') throw new Error('Only a waiting job can be cancelled.');
73
+ return { ...job, status: 'cancelled', replay: null };
74
+ })));
75
+ }
76
+
77
+ /** Claim atomically before retry, so two callers cannot start the same job. */
78
+ export async function retryIncludedJob(id, { dispatch, route, now = Date.now() } = {}) {
79
+ if (!(await readIncludedPolicy()).enabled) throw new Error('Included usage policy must be enabled to retry.');
80
+ const claimed = await mutateQueue(jobs => updateJob(jobs, id, job => {
81
+ if (job.status !== 'waiting') throw new Error('Only a waiting job can be retried.');
82
+ if (job.needsReview || !job.replay) throw new Error('Review the prior attempt and resubmit this task from its original workspace.');
83
+ if (Date.parse(job.resetsAt) > now) throw new Error('The provider-reported reset time has not arrived.');
84
+ return { ...job, status: 'running', attempts: job.attempts + 1, startedAt: new Date(now).toISOString() };
85
+ }));
86
+ let result;
87
+ try {
88
+ if (claimed.replay.kind === 'dispatch') {
89
+ dispatch ??= (await import('./dispatch.mjs')).dispatchWithReceipt;
90
+ result = await dispatch(claimed.replay.request, { role: 'Dispatch', retryId: id });
91
+ } else if (claimed.replay.kind === 'route') {
92
+ route ??= (await import('./cascade.mjs')).executeRoutedTask;
93
+ result = await route(claimed.task, { ...claimed.replay.controls, cwd: claimed.workspace, gate: claimed.replay.gate, gateOptions: claimed.replay.gateOptions,
94
+ route: { selected: claimed.agent }, pool: [claimed.agent], retryId: id });
95
+ } else throw new Error('Unknown included usage job operation.');
96
+ } catch (error) {
97
+ result = { ok: false, failureKind: 'execution', error: error.message };
98
+ }
99
+ const held = ['quota', 'billing-policy', 'auth'].includes(result.failureKind);
100
+ const updated = await mutateQueue(jobs => updateJob(jobs, id, job => {
101
+ if (job.status !== 'running') throw new Error('Included usage job lost its claim.');
102
+ return { ...job, status: result.ok ? 'completed' : held ? 'waiting' : 'failed',
103
+ resetsAt: held && Number.isFinite(Date.parse(result.resetsAt)) ? result.resetsAt : null,
104
+ needsReview: job.needsReview || result.needsReview === true ||
105
+ (held && !result.launchPrevented && (job.replay.kind === 'route' || job.replay.request?.permissions?.write === true)),
106
+ lastError: result.ok ? null : result.error,
107
+ replay: held ? job.replay : null, finishedAt: new Date().toISOString() };
108
+ }));
109
+ return { ...result, job: publicJob(updated) };
110
+ }
@@ -47,8 +47,8 @@ async function verifyEvents(documentPath, primitive, expectedCount) {
47
47
  async function verifyTarget(targetArgument) {
48
48
  const target = fs.realpathSync(path.resolve(targetArgument));
49
49
  const readJson = file => JSON.parse(fs.readFileSync(readTargetFile(target, file), 'utf8'));
50
- assert.equal(readJson('package.json').name, 'tr-plugin-meta-harness', 'Select a Meta Harness checkout');
51
- assert.equal(readJson('plugin.json').id, owner, 'The target must own Meta Harness state');
50
+ assert.equal(readJson('package.json').name, 'tr-plugin-meta-harness', 'Select a Metaharness checkout');
51
+ assert.equal(readJson('plugin.json').id, owner, 'The target must own Metaharness state');
52
52
 
53
53
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'meta-harness-ssss-gate-'));
54
54
  const previousBrain = process.env.TR_SECRETS_BRAIN;
@@ -94,7 +94,7 @@ async function verifyTarget(targetArgument) {
94
94
 
95
95
  try {
96
96
  const [flag, target, ...rest] = process.argv.slice(2);
97
- if (flag !== '--target' || !target || rest.length) throw new Error('Supply an explicit Meta Harness checkout with --target <directory>');
97
+ if (flag !== '--target' || !target || rest.length) throw new Error('Supply an explicit Metaharness checkout with --target <directory>');
98
98
  process.stdout.write(`${JSON.stringify(await verifyTarget(target))}\n`);
99
99
  } catch (error) {
100
100
  process.stdout.write(`${JSON.stringify({ ok: false, error: error.message })}\n`);