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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Meta Harness Smart Router
2
+ * Metaharness Smart Router
3
3
  * Manages honest quota/readiness tracking, benchmark matrices, RouteLLM complexity routing,
4
4
  * and cost-vs-frontier harness selection.
5
5
  *
@@ -21,6 +21,7 @@ import { getModelCatalog, joinHarnessModels } from './model-catalog.mjs';
21
21
  import { readReceipts, activeQuotaWalls, activeCredentialBlocks } from './learner.mjs';
22
22
  import { resolvePool, exclusionReason, isLocalExecutionNode } from './pool.mjs';
23
23
  import { readCodexRateLimits, measuredWalls } from './telemetry.mjs';
24
+ import { claudeProofCurrent, INCLUDED_AGENTS, readIncludedPolicy } from './included-usage.mjs';
24
25
  import { derivedDir } from './paths.mjs';
25
26
 
26
27
  /**
@@ -97,7 +98,7 @@ async function cachedMeteredQuota(options) {
97
98
  async function readMeteredQuota(options) {
98
99
  let meteredQuota = { billingModel: 'metered', creditRemainingUSD: null, usageUSD: null, usageDailyUSD: null, usageWeeklyUSD: null, usagePercent: null, quotaNote: 'No OpenRouter key in the auth store', status: 'unconfigured' };
99
100
  try {
100
- const key = options.openRouterKey !== undefined ? options.openRouterKey : (runHostCli(['auth', 'secret', 'get', 'DEVELOPER_OPENROUTER_API_KEY']) || runHostCli(['auth', 'secret', 'get', 'OPENROUTER_API_KEY']));
101
+ const key = options.openRouterKey !== undefined ? options.openRouterKey : runHostCli(['auth', 'secret', 'get', 'OPENROUTER_API_KEY']);
101
102
  if (key) {
102
103
  const response = await (options.fetchImpl || fetch)('https://openrouter.ai/api/v1/auth/key', { headers: { Authorization: `Bearer ${key}` }, signal: AbortSignal.timeout(6000) });
103
104
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
@@ -139,6 +140,7 @@ async function readMeteredQuota(options) {
139
140
  * Never fabricates or hardcodes synthetic subscription percentages.
140
141
  */
141
142
  export async function inspectQuotas(options = {}) {
143
+ const policy = options.policy || await readIncludedPolicy();
142
144
  const quotas = {};
143
145
  const remote = options.node && options.node !== 'local';
144
146
  const fleet = (options.fleet || fleetTelemetry)();
@@ -162,27 +164,37 @@ export async function inspectQuotas(options = {}) {
162
164
  }
163
165
 
164
166
  // Harness state comes from the selected machine. Quota walls are this machine's receipts.
165
- const inspected = remote ? await (options.inspectNodeFn || inspectNode)(options.node) : await (options.inspect || inspectAgents)({ env: process.env });
167
+ const claudePlan = policy.enabled ? policy.claudePlan?.organization : undefined;
168
+ const inspected = remote ? await (options.inspectNodeFn || inspectNode)(options.node) :
169
+ await (options.inspect || inspectAgents)({ env: process.env, cwd: options.cwd, claudePlan });
166
170
  const codexLimits = remote ? null : readCodexRateLimits();
167
- const walls = remote ? {} : { ...activeQuotaWalls(await readReceipts()), ...measuredWalls(codexLimits) };
171
+ const receipts = remote ? [] : (await readReceipts()).filter(run => !policy.enabled || run.billingMode === 'included');
172
+ const walls = remote ? {} : { ...activeQuotaWalls(receipts), ...(!policy.enabled ? measuredWalls(codexLimits) : {}) };
168
173
  const metered = METERED_HARNESSES;
169
174
 
170
- const blocks = remote ? {} : activeCredentialBlocks(await readReceipts());
171
- for (const agent of inspected.agents || []) {
175
+ const blocks = activeCredentialBlocks(receipts);
176
+ for (const installed of inspected.agents || []) {
177
+ const agent = policy.enabled && !remote && installed.id === 'claude' && !claudePlan ?
178
+ { ...installed, authenticated: claudeProofCurrent(policy) } : installed;
172
179
  const wall = walls[agent.id];
173
180
  const block = blocks[agent.id];
174
- const reason = exclusionReason(agent, wall, block);
181
+ const policyBlocked = policy.enabled && !INCLUDED_AGENTS.includes(agent.id);
182
+ const explicitOpenRouter = !remote && Boolean(policy.openRouter?.[agent.id]);
183
+ const reason = policyBlocked ? (explicitOpenRouter ? 'available through explicit OpenRouter dispatch' : 'outside the included usage route') : exclusionReason(agent, wall, block);
175
184
  let status = 'ready';
176
185
  if (!agent.installed) status = 'not_installed';
186
+ else if (policyBlocked) status = 'blocked';
177
187
  else if (agent.authenticated === false || block) status = 'needs_auth';
178
188
  else if (agent.blockedBy) status = 'blocked';
179
189
  else if (wall) status = 'exhausted';
180
190
  else if (agent.authenticated === 'unknown') status = 'unverified';
181
191
  const quota = {
182
192
  installed: Boolean(agent.installed), version: agent.version || null,
183
- billingModel: agent.id === 'ollama' ? 'local' : metered.has(agent.id) ? 'metered' : 'subscription',
193
+ billingModel: policy.enabled && !remote && !claudePlan && agent.id === 'claude' ? 'included-api-credits' :
194
+ agent.id === 'ollama' ? 'local' : metered.has(agent.id) ? 'metered' : 'subscription',
184
195
  rolling5hUsedPercent: null, weeklyUsedPercent: null, usagePercent: null,
185
196
  quotaSource: 'none', quotaNote: reason || 'No usage figure is available from this harness', status,
197
+ ...(explicitOpenRouter ? { explicitOpenRouter: true } : {}),
186
198
  ...(wall ? { resetsAt: wall.until, wallMessage: wall.message } : {}),
187
199
  };
188
200
  if (agent.id === 'codex' && codexLimits) {
@@ -196,7 +208,7 @@ export async function inspectQuotas(options = {}) {
196
208
  quotas[agent.id] = quota;
197
209
  }
198
210
 
199
- const meteredQuota = options.skipMetered
211
+ const meteredQuota = options.skipMetered || policy.enabled
200
212
  ? { billingModel: 'metered', status: 'not-read', quotaNote: 'Not read: no metered harness is in this route pool' }
201
213
  : await cachedMeteredQuota(options);
202
214
  quotas.metered = meteredQuota;
@@ -241,9 +253,10 @@ export function placeLocalWork(fleet = {}) {
241
253
  }
242
254
 
243
255
  export async function routeTask(taskDescription, options = {}) {
256
+ const policy = await readIncludedPolicy();
244
257
  let complexity = classifyTaskComplexity(taskDescription, options);
245
258
  let classifier = { source: 'heuristic' };
246
- if (options.jev) {
259
+ if (options.jev && !policy.enabled) {
247
260
  try {
248
261
  const decided = classifyWithDecision(taskDescription, options.decisionRun);
249
262
  complexity = { ...complexity, tier: decided.tier, isFrontierRecommended: decided.tier === 'frontier', isLocalRecommended: decided.tier === 'local' };
@@ -260,17 +273,19 @@ export async function routeTask(taskDescription, options = {}) {
260
273
  // fleet telemetry places local work, and the balance matters only to a metered harness.
261
274
  const shared = scoped.inspect ? null : inspectAgents({ env: process.env });
262
275
  const lean = shared ? { ...scoped, inspect: () => shared } : scoped;
263
- const [pool, receipts] = await Promise.all([resolvePool(lean), readReceipts()]);
276
+ const [pool, receipts] = await Promise.all([resolvePool({ ...lean, policy }), readReceipts()]);
264
277
  const quotaReport = await inspectQuotas({
265
278
  ...lean,
279
+ policy,
266
280
  meteredTtlMs: METERED_TTL_MS,
267
- skipMetered: lean.openRouterKey === undefined && !pool.ready.some(h => METERED_HARNESSES.has(h.id)),
281
+ skipMetered: policy.enabled || (lean.openRouterKey === undefined && !pool.ready.some(h => METERED_HARNESSES.has(h.id))),
268
282
  ...(remote || lean.fleet ? {} : { fleet: complexity.tier === 'local' ? cachedFleet : () => ({ ok: false, error: 'Not read: this route places no local work', nodes: [] }) }),
269
283
  });
270
284
  const quotas = quotaReport.quotas;
271
285
  const routing = selectOptimalHarness(taskDescription, pool.ready.map(h => h.id), quotas, { ...options, tierOverride: complexity.tier, empiricalScorecard: computeEmpiricalScorecard(receipts) });
272
286
  const selectedQuota = quotas[routing.selected] || {};
273
287
  const warnings = [];
288
+ if (policy.enabled && options.jev) warnings.push('Included usage policy uses the local task classifier.');
274
289
  if (typeof selectedQuota.usagePercent === 'number' && selectedQuota.usagePercent > 85) warnings.push(`${routing.selected} is at ${selectedQuota.usagePercent}% measured usage.`);
275
290
  if (selectedQuota.status === 'unverified') warnings.push(`${routing.selected} has no login probe; only a completed run proves it works.`);
276
291
  if (selectedQuota.billingModel === 'metered') warnings.push(`${routing.selected} bills per token.`);
@@ -278,6 +293,7 @@ export async function routeTask(taskDescription, options = {}) {
278
293
  return {
279
294
  ok: Boolean(routing.selected), task: taskDescription, classifier, complexity,
280
295
  selected: routing.selected, tier: complexity.tier, candidates: routing.candidates, excluded: pool.excluded,
296
+ ...(policy.enabled && !routing.selected ? { deferred: pool.excluded.find(row => row.failureKind === 'quota') || null } : {}),
281
297
  placement: complexity.tier === 'local' ? placeLocalWork(quotaReport.fleet) : null,
282
298
  scoring: 'Adapter quality and cost scores are configured priors, adjusted by pass rates measured on this machine.',
283
299
  quotaSnapshot: { selectedHarnessStatus: selectedQuota.status || null, selectedHarnessUsage: selectedQuota.usagePercent ?? null, freeMemGB: quotas.system.freeMemGB ?? null },
@@ -1,15 +1,37 @@
1
- /** One native runtime-management boundary for every installed agent. */
1
+ /** Runtime observations and explicit maintenance. Agent work belongs to dispatch. */
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { getActiveAdapters, findBinary, runProcess, agentEnvironment } from './worker.mjs';
6
6
 
7
+ const DSH_OBSERVATIONS = ['status', 'sessions', 'tools', 'models', 'model', 'plugins', 'help'];
8
+ const MAINTENANCE = new Map([
9
+ ['claude', [['auth', 'status']]],
10
+ ['codex', [['login', 'status']]],
11
+ ['cursor', [['status']]],
12
+ ['dsh', [...DSH_OBSERVATIONS.map(operation => [operation]), ['model', '--cache']]],
13
+ ['hermes', [['status'], ['status', '--full'], ['gateway', 'status'], ['skills', 'list'], ['plugins', 'list'], ['auth']]],
14
+ ['ollama', [['list'], ['ps']]],
15
+ ['openclaw', [['status'], ['health'], ['gateway', 'status'], ['plugins', 'list']]],
16
+ ]);
17
+
18
+ export function validateRuntimeOperation(agent, args = []) {
19
+ if (typeof agent !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(agent)) throw new Error('Invalid agent adapter');
20
+ if (!Array.isArray(args) || args.length > 64 || args.some(arg => typeof arg !== 'string' || arg.length > 8192)) throw new Error('Invalid native runtime arguments');
21
+ if (args.some(arg => /(?:api[-_]?key|password|secret|access[-_]?token|refresh[-_]?token)/i.test(arg))) throw new Error('Credential values and mutations belong to the auth owner, never runtime argv');
22
+ const command = args.length ? args : [agent === 'dsh' ? 'status' : '--help'];
23
+ const supported = [['--help'], ['-h'], ['--version'], ...(MAINTENANCE.get(agent) || [])];
24
+ if (!supported.some(tuple => tuple.length === command.length && tuple.every((word, index) => word === command[index]))) {
25
+ throw new Error('Runtime accepts only supported maintenance commands. Use meta-harness dispatch or route --execute for agent work, or the owning plugin for service changes.');
26
+ }
27
+ return command;
28
+ }
29
+
7
30
  export async function manageRuntime(agent, args = [], { env = process.env, probe = runProcess, cwd = process.cwd() } = {}) {
31
+ args = validateRuntimeOperation(agent, args);
8
32
  const adapter = getActiveAdapters().find(item => item.id === agent);
9
33
  if (!adapter) throw new Error('Unknown agent adapter');
10
- if (!Array.isArray(args) || args.length > 64 || args.some(arg => typeof arg !== 'string' || arg.length > 8192)) throw new Error('Invalid native runtime arguments');
11
- if (args.some(arg => /(?:api[-_]?key|password|secret|access[-_]?token|refresh[-_]?token)/i.test(arg))) throw new Error('Credential values and mutations belong to the auth owner, never runtime argv');
12
- if (agent === 'dsh' && ['status','sessions','tools','models','model','plugins','help','--help','-h'].includes(args[0] || 'status')) {
34
+ if (agent === 'dsh' && DSH_OBSERVATIONS.includes(args[0])) {
13
35
  const { run } = await import('./adapters/dsh.mjs');
14
36
  const lines = [], errors = [], log = console.log, error = console.error;
15
37
  const previous = process.exitCode;
@@ -31,6 +53,6 @@ export async function manageRuntime(agent, args = [], { env = process.env, probe
31
53
  }
32
54
  const binary = findBinary(adapter.binary, env);
33
55
  if (!binary) throw new Error(`${adapter.name} is not installed on the selected node`);
34
- const result = await probe(binary, args.length ? args : ['--help'], { cwd, timeoutMs:60000, env:agentEnvironment(env) });
35
- return { ok:result.exitCode===0 && !result.reason, agent, exitCode:result.exitCode, output:result.stdout, error:result.reason || (result.exitCode ? result.stderr || 'Runtime command failed' : undefined) };
56
+ const result = await probe(binary, args, { cwd, timeoutMs:60000, env:agentEnvironment(env) });
57
+ return { ok:result.exitCode===0 && !result.reason, agent, exitCode:result.exitCode, output:result.stdout || (result.exitCode === 0 ? result.stderr : ''), error:result.reason || (result.exitCode ? result.stderr || 'Runtime command failed' : undefined) };
36
58
  }
@@ -0,0 +1,319 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { createInterface } from 'node:readline';
4
+
5
+ // Protocol sources: code.claude.com/docs/en/{cli-reference,headless}.md and
6
+ // agent-sdk/{streaming-vs-single-mode,typescript}.md (read 2026-11-09).
7
+ const controls = ['reply', 'interrupt', 'answer', 'fork', 'goal', 'limits', 'schema', 'review'];
8
+ const ownedValues = new Set(['--input-format', '--output-format', '--session-id', '--name', '-n',
9
+ '--max-turns', '--max-budget-usd', '--json-schema', '--effort', '--fallback-model', '--mcp-config',
10
+ '--permission-prompt-tool']);
11
+ const ownedSwitches = new Set(['-p', '--print', '--verbose', '--replay-user-messages',
12
+ '--include-hook-events', '--forward-subagent-text']);
13
+ const textOf = content => typeof content === 'string' ? content : Array.isArray(content)
14
+ ? content.filter(block => block.type === 'text').map(block => block.text).join('\n') : '';
15
+ const failureKind = (text, subtype) => /error_max_(turns|budget_usd)/.test(subtype || '') ? 'limit'
16
+ : /quota|rate.?limit|usage limit|credit|\b429\b/i.test(text) ? 'quota'
17
+ : /not (?:logged|signed) in|authenticat|invalid api key|\b401\b/i.test(text) ? 'auth' : 'agent';
18
+
19
+ /** Own one Claude stream-json process. Only Node built-ins are used on remote nodes. */
20
+ export function createClaudeDriver({ harness, binary, args = [], cwd, env, job,
21
+ options = {}, onEvent = () => {}, onFrame = () => {}, spawnFn = spawn } = {}) {
22
+ const capabilities = Object.fromEntries(controls.map(key => [key, 'unsupported']));
23
+ let sessionId = options.forkSession ? randomUUID() : options.sessionId || randomUUID();
24
+ let child, lines, started = false, closing = false, exited = false, initialized = false;
25
+ let settled = false, stderr = '', response = '', closePromise, continuation;
26
+ let usage = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
27
+ let resolveReady, rejectReady, resolveDone, resolveExit;
28
+ const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
29
+ ready.catch(() => {}); // start() is the public reader of startup errors.
30
+ let done = new Promise(resolve => { resolveDone = resolve; });
31
+ const exit = new Promise(resolve => { resolveExit = resolve; });
32
+ const signal = name => {
33
+ try { if (process.platform !== 'win32' && child?.pid) process.kill(-child.pid, name); else child?.kill(name); return true; }
34
+ catch { return false; }
35
+ };
36
+ const pending = new Map();
37
+ const requests = new Map();
38
+ const emit = (kind, fields = {}) => { try { onEvent({ kind, text: '', ...fields }); } catch { /* observers do not own the process */ } };
39
+ const rejectPending = error => {
40
+ for (const entry of pending.values()) { clearTimeout(entry.timer); entry.reject(error); }
41
+ pending.clear();
42
+ requests.clear();
43
+ };
44
+ const complete = result => {
45
+ if (settled) return;
46
+ settled = true;
47
+ resolveDone({ ok: false, response, sessionId, usage: { ...usage }, structured: undefined,
48
+ error: null, failureKind: null, ...result });
49
+ };
50
+ const fail = error => {
51
+ const message = String(error?.message || error);
52
+ rejectReady(new Error(message));
53
+ rejectPending(new Error(message));
54
+ emit('error', { text: message });
55
+ complete({ error: message, failureKind: failureKind(message) });
56
+ };
57
+ const writable = () => {
58
+ if (!started || closing || exited || !child?.stdin?.writable) throw new Error('Claude session is not open.');
59
+ };
60
+ const write = frame => {
61
+ writable();
62
+ child.stdin.write(`${JSON.stringify(frame)}\n`, error => { if (error) fail(error); });
63
+ };
64
+ const userMessage = text => ({ type: 'user', uuid: randomUUID(), session_id: sessionId,
65
+ message: { role: 'user', content: text }, parent_tool_use_id: null });
66
+ const acknowledge = (id, value) => {
67
+ const entry = pending.get(id);
68
+ if (!entry) return;
69
+ pending.delete(id); clearTimeout(entry.timer);
70
+ emit('ack', { command: entry.command, requestId: id, via: 'native' });
71
+ entry.resolve(value);
72
+ };
73
+ const handle = frame => {
74
+ onFrame(frame);
75
+ if (!frame || typeof frame !== 'object') throw new Error('Invalid Claude stream message.');
76
+ const parent = frame.parent_tool_use_id;
77
+ const parentFields = parent ? { parent_tool_use_id: parent } : {};
78
+ if (frame.type === 'system' && frame.subtype === 'init') {
79
+ sessionId = frame.session_id || sessionId;
80
+ const reported = Array.isArray(frame.capabilities) ? frame.capabilities : [];
81
+ Object.assign(capabilities, { reply: 'native',
82
+ interrupt: reported.includes('interrupt_receipt_v1') || reported.includes('interrupt_cancel_queued_v1') ? 'native' : 'metaharness',
83
+ answer: 'metaharness', fork: 'native', goal: 'metaharness', limits: 'native', schema: 'native', review: 'metaharness' });
84
+ initialized = true;
85
+ emit('session', { text: `Claude session ${sessionId}`, sessionId, session: sessionId,
86
+ capabilities: { ...capabilities }, nativeCapabilities: [...reported], model: frame.model });
87
+ resolveReady();
88
+ return;
89
+ }
90
+ if (frame.type === 'control_response') {
91
+ const receipt = frame.response;
92
+ const entry = pending.get(receipt?.request_id);
93
+ if (entry?.command !== 'interrupt') return;
94
+ if (receipt.subtype === 'error') {
95
+ pending.delete(receipt.request_id); clearTimeout(entry.timer);
96
+ entry.reject(new Error(receipt.error || 'Claude rejected the interrupt.'));
97
+ } else acknowledge(receipt.request_id, { ack: true, via: 'native', ...receipt.response });
98
+ return;
99
+ }
100
+ if (frame.type === 'control_request') {
101
+ if (frame.request?.subtype === 'can_use_tool') {
102
+ requests.set(frame.request_id, frame.request);
103
+ emit('approval', { text: `Approval for ${frame.request.tool_name}`, requestId: frame.request_id, request: frame.request });
104
+ }
105
+ return;
106
+ }
107
+ if (frame.type === 'system' && frame.subtype === 'api_retry') {
108
+ emit('error', { text: `API retry ${frame.attempt}/${frame.max_retries}: ${frame.error}`,
109
+ retry: true, attempt: frame.attempt, maxRetries: frame.max_retries,
110
+ retryDelayMs: frame.retry_delay_ms, errorStatus: frame.error_status });
111
+ return;
112
+ }
113
+ if (frame.type === 'system' && ['hook_started', 'hook_progress', 'hook_response'].includes(frame.subtype)) {
114
+ emit('hook', { text: frame.output || frame.hook_name || '', name: frame.hook_name,
115
+ phase: frame.subtype.slice(5), hookId: frame.hook_id, hookEvent: frame.hook_event,
116
+ outcome: frame.outcome, stdout: frame.stdout, stderr: frame.stderr, exitCode: frame.exit_code });
117
+ return;
118
+ }
119
+ if (frame.type === 'system' && frame.subtype === 'permission_denied') {
120
+ emit('error', { text: `Permission denied: ${frame.tool_name || frame.message || ''}` });
121
+ return;
122
+ }
123
+ if (frame.type === 'assistant' || frame.type === 'user') {
124
+ const content = frame.message?.content;
125
+ const blocks = Array.isArray(content) ? content : typeof content === 'string' ? [{ type: 'text', text: content }] : [];
126
+ if (frame.type === 'user' && !parent && !blocks.some(block => block.type === 'tool_result')) {
127
+ // Only a replay with our UUID acknowledges delivery. Tool results and subagent
128
+ // prompts must not acknowledge a user's pending message with the same text.
129
+ if (pending.get(frame.uuid)?.command === 'reply') acknowledge(frame.uuid, { ack: true, via: 'native' });
130
+ return;
131
+ }
132
+ for (const block of blocks) {
133
+ if (frame.type === 'assistant' && block.type === 'tool_use') {
134
+ emit('step-start', { text: `${block.name}: ${JSON.stringify(block.input || {})}`,
135
+ toolUseId: block.id, tool: block.name, input: block.input, ...parentFields });
136
+ } else if (block.type === 'tool_result') {
137
+ emit('step-end', { text: textOf(block.content) || (block.is_error ? 'Tool returned an error' : 'Tool finished'),
138
+ toolUseId: block.tool_use_id, isError: block.is_error === true, ...parentFields });
139
+ } else if (block.type === 'text' || (parent && block.type === 'thinking')) {
140
+ const text = block.text || block.thinking || '';
141
+ if (parent) emit('subagent', { text, ...parentFields });
142
+ else if (frame.type === 'assistant') {
143
+ response += text;
144
+ emit('message', { text });
145
+ }
146
+ }
147
+ }
148
+ if (frame.error) emit('error', { text: String(frame.error), ...parentFields });
149
+ return;
150
+ }
151
+ if (frame.type === 'result') {
152
+ sessionId = frame.session_id || sessionId;
153
+ usage = { inputTokens: frame.usage?.input_tokens ?? 0,
154
+ outputTokens: frame.usage?.output_tokens ?? 0, costUsd: frame.total_cost_usd ?? 0 };
155
+ const ok = frame.subtype === 'success' && !frame.is_error;
156
+ const error = ok ? null : frame.errors?.join('\n') || frame.result || frame.subtype || 'Claude reported failure.';
157
+ const result = { ok, response: frame.result ?? response, sessionId, usage: { ...usage },
158
+ ...(options.trackTurns ? { turns: frame.num_turns || 1 } : {}),
159
+ structured: frame.structured_output, error, failureKind: ok ? null : failureKind(error, frame.subtype) };
160
+ emit('usage', { ...usage });
161
+ emit('finish', { text: ok ? 'Finished' : error, ...result });
162
+ complete(result);
163
+ response = '';
164
+ return;
165
+ }
166
+ if (frame.type === 'error') emit('error', { text: String(frame.error?.message || frame.message || frame.error) });
167
+ };
168
+
169
+ function launchArgs() {
170
+ const result = [];
171
+ for (let i = 0; i < args.length; i++) {
172
+ const flag = args[i].split('=')[0];
173
+ if (ownedValues.has(flag)) { if (!args[i].includes('=')) i++; continue; }
174
+ if (ownedSwitches.has(flag)) continue;
175
+ // The MCP prompt tool must get approval requests under an operator/rules policy.
176
+ if (options.approvalPolicy && options.approvalPolicy !== 'auto') {
177
+ if (flag === '--dangerously-skip-permissions') continue;
178
+ if (flag === '--permission-mode') { if (!args[i].includes('=')) i++; continue; }
179
+ }
180
+ result.push(args[i]);
181
+ }
182
+ result.push('-p', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose',
183
+ '--replay-user-messages', '--include-hook-events', '--forward-subagent-text');
184
+ if (options.resumeSession) result.push('--resume', options.sessionId);
185
+ else result.push('--session-id', sessionId);
186
+ if (options.forkSession) {
187
+ if (options.atTurn !== undefined) throw new Error('Claude CLI cannot fork at a selected turn.');
188
+ result.push('--fork-session');
189
+ result.push('--session-id', sessionId);
190
+ }
191
+ const name = options.name ?? (typeof job === 'string' ? job : job?.id ?? job?.job);
192
+ for (const [flag, value] of [['--name', name], ['--max-turns', options.maxTurns],
193
+ ['--max-budget-usd', options.maxBudgetUsd], ['--json-schema', options.resultSchema],
194
+ ['--effort', options.effort], ['--fallback-model', options.fallbackModel], ['--mcp-config', options.mcpConfig]]) {
195
+ if (value !== undefined && value !== null) result.push(flag, typeof value === 'object' ? JSON.stringify(value) : String(value));
196
+ }
197
+ result.push('--permission-prompt-tool', 'mcp__metaharness__approve');
198
+ return result;
199
+ }
200
+
201
+ return {
202
+ capabilities, get done() { return done; },
203
+ async next(text, { resultSchema, maxTurns = options.maxTurns, maxBudgetUsd = options.maxBudgetUsd } = {}) {
204
+ if (continuation) return continuation.next(text, { resultSchema, maxTurns, maxBudgetUsd });
205
+ writable();
206
+ if (!settled) throw new Error('The current turn has not finished.');
207
+ // The schema is a process flag. Reopen the same native session when a
208
+ // goal-check turn needs a different schema from the requested result.
209
+ if (JSON.stringify(resultSchema) !== JSON.stringify(options.resultSchema) || maxTurns !== options.maxTurns || maxBudgetUsd !== options.maxBudgetUsd) {
210
+ await this.close();
211
+ continuation = createClaudeDriver({ harness, binary, args, cwd, env, job, spawnFn, onEvent, onFrame,
212
+ options: { ...options, sessionId, resumeSession: true, forkSession: false, resultSchema, maxTurns, maxBudgetUsd } });
213
+ await continuation.start(text);
214
+ done = continuation.done;
215
+ return done;
216
+ }
217
+ settled = false; response = '';
218
+ done = new Promise(resolve => { resolveDone = resolve; });
219
+ write(userMessage(text));
220
+ return done;
221
+ },
222
+ async start(brief) {
223
+ if (started) return ready;
224
+ if (closing) throw new Error('Claude session is closed.');
225
+ if (typeof brief !== 'string') throw new TypeError('The brief must be text.');
226
+ started = true;
227
+ try {
228
+ child = spawnFn(binary || harness?.binary || 'claude', launchArgs(), {
229
+ cwd, env, shell: false, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'],
230
+ });
231
+ child.once('error', error => { fail(error); if (!child.pid) { exited = true; resolveExit(); } });
232
+ child.stdin.on('error', error => { if (!closing && !exited) fail(error); });
233
+ child.stderr.setEncoding('utf8');
234
+ child.stderr.on('data', chunk => { stderr = (stderr + chunk).slice(-65536); });
235
+ lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
236
+ lines.on('line', line => {
237
+ if (!line.trim()) return;
238
+ try { handle(JSON.parse(line)); }
239
+ catch (error) { fail(new Error(`Invalid Claude stream: ${error.message}`)); signal('SIGTERM'); }
240
+ });
241
+ child.once('close', (code, signal) => {
242
+ exited = true; lines.close(); resolveExit();
243
+ const error = new Error(stderr.trim() || `Claude closed without a result (${signal || code}).`);
244
+ rejectPending(error);
245
+ if (!initialized) rejectReady(error);
246
+ if (!settled) fail(error);
247
+ });
248
+ // The CLI reads its first user message before it emits system/init.
249
+ write(userMessage(brief));
250
+ } catch (error) { exited = true; resolveExit(); fail(error); }
251
+ return ready;
252
+ },
253
+ async send(text) {
254
+ if (continuation) return continuation.send(text);
255
+ if (typeof text !== 'string') throw new TypeError('The reply must be text.');
256
+ writable();
257
+ const frame = userMessage(text);
258
+ return new Promise((resolve, reject) => {
259
+ pending.set(frame.uuid, { command: 'reply', resolve, reject });
260
+ try { write(frame); } catch (error) { pending.delete(frame.uuid); reject(error); }
261
+ });
262
+ },
263
+ async interrupt() {
264
+ if (continuation) return continuation.interrupt();
265
+ writable();
266
+ if (!initialized) throw new Error('Claude session is not live.');
267
+ if (capabilities.interrupt !== 'native') {
268
+ if (!signal('SIGTERM')) throw new Error('Cannot interrupt Claude.');
269
+ emit('ack', { command: 'interrupt', via: 'metaharness' });
270
+ return { ack: true, via: 'metaharness' };
271
+ }
272
+ const requestId = randomUUID();
273
+ return new Promise((resolve, reject) => {
274
+ const timer = setTimeout(() => { pending.delete(requestId); reject(new Error('Claude interrupt receipt timed out.')); }, 30000);
275
+ pending.set(requestId, { command: 'interrupt', resolve, reject, timer });
276
+ try { write({ type: 'control_request', request_id: requestId, request: { subtype: 'interrupt' } }); }
277
+ catch (error) { clearTimeout(timer); pending.delete(requestId); reject(error); }
278
+ });
279
+ },
280
+ async answer(requestId, value) {
281
+ if (continuation) return continuation.answer(requestId, value);
282
+ if (!requests.has(requestId)) throw new Error('No native approval has this ID. Answer MCP requests through the Metaharness inbox.');
283
+ write({ type: 'control_response', response: { subtype: 'success', request_id: requestId, response: value } });
284
+ requests.delete(requestId);
285
+ emit('ack', { command: 'approve', requestId, via: 'native' });
286
+ return { ack: true, via: 'native' };
287
+ },
288
+ async fork({ atTurn } = {}) {
289
+ if (continuation) return continuation.fork({ atTurn });
290
+ if (!initialized) throw new Error('Claude session is not live.');
291
+ if (atTurn !== undefined) throw new Error('Claude CLI cannot fork at a selected turn.');
292
+ const fork = createClaudeDriver({ harness, binary, args,
293
+ cwd, env, job, options: { ...options, sessionId, resumeSession: true, forkSession: true }, spawnFn });
294
+ try {
295
+ // /rename is a documented local print-mode command. It materializes the fork
296
+ // without replaying the brief or asking a model to do more work.
297
+ await fork.start(`/rename ${options.name || 'Metaharness fork'}`);
298
+ const result = await fork.done;
299
+ if (!result.ok) throw new Error(result.error);
300
+ return { sessionId: result.sessionId };
301
+ } finally { await fork.close(); }
302
+ },
303
+ async close() {
304
+ if (continuation) return continuation.close();
305
+ if (closePromise) return closePromise;
306
+ closing = true;
307
+ rejectPending(new Error('Claude session is closed.'));
308
+ if (!started) { exited = true; rejectReady(new Error('Claude session is closed.')); complete({ error: 'Claude session was not started.', failureKind: 'agent' }); resolveExit(); }
309
+ closePromise = (async () => {
310
+ if (exited) return;
311
+ child.stdin.end();
312
+ const term = setTimeout(() => signal('SIGTERM'), 1000);
313
+ const kill = setTimeout(() => signal('SIGKILL'), 2000);
314
+ try { await exit; } finally { signal('SIGKILL'); clearTimeout(term); clearTimeout(kill); }
315
+ })();
316
+ return closePromise;
317
+ },
318
+ };
319
+ }