@sentropic/h2a 0.85.16 → 0.85.18

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 (193) hide show
  1. package/.claude-plugin/plugin.json +29 -0
  2. package/.codex-plugin/plugin.json +35 -0
  3. package/.mcp.json +19 -0
  4. package/README.md +3 -0
  5. package/dist/bin-routing.d.ts +14 -3
  6. package/dist/bin-routing.d.ts.map +1 -1
  7. package/dist/bin-routing.js +31 -6
  8. package/dist/bin-routing.js.map +1 -1
  9. package/dist/bin.js +45 -14
  10. package/dist/bin.js.map +1 -1
  11. package/dist/cli-contract.d.ts.map +1 -1
  12. package/dist/cli-contract.js +41 -1
  13. package/dist/cli-contract.js.map +1 -1
  14. package/dist/cli.d.ts +4 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +146 -18
  17. package/dist/cli.js.map +1 -1
  18. package/dist/index.d.ts +4 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/mcp.d.ts +1 -1
  23. package/dist/mcp.d.ts.map +1 -1
  24. package/dist/mcp.js +1 -0
  25. package/dist/mcp.js.map +1 -1
  26. package/dist/runtime/focus/serve.d.ts +42 -0
  27. package/dist/runtime/focus/serve.d.ts.map +1 -0
  28. package/dist/runtime/focus/serve.js +366 -0
  29. package/dist/runtime/focus/serve.js.map +1 -0
  30. package/dist/runtime/loop/engine/adapters.d.ts +62 -6
  31. package/dist/runtime/loop/engine/adapters.d.ts.map +1 -1
  32. package/dist/runtime/loop/engine/adapters.js +272 -36
  33. package/dist/runtime/loop/engine/adapters.js.map +1 -1
  34. package/dist/runtime/loop/engine/decision.d.ts.map +1 -1
  35. package/dist/runtime/loop/engine/decision.js +20 -2
  36. package/dist/runtime/loop/engine/decision.js.map +1 -1
  37. package/dist/runtime/loop/engine/execute.d.ts +13 -4
  38. package/dist/runtime/loop/engine/execute.d.ts.map +1 -1
  39. package/dist/runtime/loop/engine/execute.js +17 -7
  40. package/dist/runtime/loop/engine/execute.js.map +1 -1
  41. package/dist/runtime/loop/index.d.ts +16 -0
  42. package/dist/runtime/loop/index.d.ts.map +1 -1
  43. package/dist/runtime/loop/index.js +146 -13
  44. package/dist/runtime/loop/index.js.map +1 -1
  45. package/dist/runtime/mcp/agent-launch.d.ts +33 -0
  46. package/dist/runtime/mcp/agent-launch.d.ts.map +1 -0
  47. package/dist/runtime/mcp/agent-launch.js +250 -0
  48. package/dist/runtime/mcp/agent-launch.js.map +1 -0
  49. package/dist/runtime/mcp/handlers.d.ts +8 -0
  50. package/dist/runtime/mcp/handlers.d.ts.map +1 -1
  51. package/dist/runtime/mcp/handlers.js +34 -4
  52. package/dist/runtime/mcp/handlers.js.map +1 -1
  53. package/dist/runtime/mcp/index.d.ts +2 -1
  54. package/dist/runtime/mcp/index.d.ts.map +1 -1
  55. package/dist/runtime/mcp/index.js +2 -1
  56. package/dist/runtime/mcp/index.js.map +1 -1
  57. package/dist/runtime/mcp/server.d.ts +5 -0
  58. package/dist/runtime/mcp/server.d.ts.map +1 -1
  59. package/dist/runtime/mcp/server.js +3 -0
  60. package/dist/runtime/mcp/server.js.map +1 -1
  61. package/dist/runtime/mcp/stdio.d.ts +17 -0
  62. package/dist/runtime/mcp/stdio.d.ts.map +1 -1
  63. package/dist/runtime/mcp/stdio.js +65 -13
  64. package/dist/runtime/mcp/stdio.js.map +1 -1
  65. package/dist/runtime/mcp/tools.d.ts.map +1 -1
  66. package/dist/runtime/mcp/tools.js +96 -5
  67. package/dist/runtime/mcp/tools.js.map +1 -1
  68. package/dist/runtime/path-safety.d.ts +3 -0
  69. package/dist/runtime/path-safety.d.ts.map +1 -0
  70. package/dist/runtime/path-safety.js +12 -0
  71. package/dist/runtime/path-safety.js.map +1 -0
  72. package/dist/runtime/reporting/config.d.ts +21 -0
  73. package/dist/runtime/reporting/config.d.ts.map +1 -0
  74. package/dist/runtime/reporting/config.js +76 -0
  75. package/dist/runtime/reporting/config.js.map +1 -0
  76. package/dist/runtime/reporting/context.d.ts +27 -0
  77. package/dist/runtime/reporting/context.d.ts.map +1 -0
  78. package/dist/runtime/reporting/context.js +265 -0
  79. package/dist/runtime/reporting/context.js.map +1 -0
  80. package/dist/runtime/reporting/index.d.ts +4 -0
  81. package/dist/runtime/reporting/index.d.ts.map +1 -0
  82. package/dist/runtime/reporting/index.js +4 -0
  83. package/dist/runtime/reporting/index.js.map +1 -0
  84. package/dist/runtime/reporting/report-ai.d.ts +49 -0
  85. package/dist/runtime/reporting/report-ai.d.ts.map +1 -0
  86. package/dist/runtime/reporting/report-ai.js +325 -0
  87. package/dist/runtime/reporting/report-ai.js.map +1 -0
  88. package/focus-app/client/_app/immutable/assets/0.BdzHFb9q.css +1 -0
  89. package/focus-app/client/_app/immutable/assets/0.BdzHFb9q.css.br +2 -0
  90. package/focus-app/client/_app/immutable/assets/0.BdzHFb9q.css.gz +0 -0
  91. package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css +1 -0
  92. package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css.br +0 -0
  93. package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css.gz +0 -0
  94. package/focus-app/client/_app/immutable/chunks/Bs3f1UbD.js +2 -0
  95. package/focus-app/client/_app/immutable/chunks/Bs3f1UbD.js.br +0 -0
  96. package/focus-app/client/_app/immutable/chunks/Bs3f1UbD.js.gz +0 -0
  97. package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js +1 -0
  98. package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js.br +0 -0
  99. package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js.gz +0 -0
  100. package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js +2 -0
  101. package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js.br +0 -0
  102. package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js.gz +0 -0
  103. package/focus-app/client/_app/immutable/chunks/Cj6wo28J.js +1 -0
  104. package/focus-app/client/_app/immutable/chunks/Cj6wo28J.js.br +0 -0
  105. package/focus-app/client/_app/immutable/chunks/Cj6wo28J.js.gz +0 -0
  106. package/focus-app/client/_app/immutable/chunks/DEmooz0s.js +1 -0
  107. package/focus-app/client/_app/immutable/chunks/DEmooz0s.js.br +0 -0
  108. package/focus-app/client/_app/immutable/chunks/DEmooz0s.js.gz +0 -0
  109. package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js +2 -0
  110. package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js.br +0 -0
  111. package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js.gz +0 -0
  112. package/focus-app/client/_app/immutable/chunks/ZxFklh9T.js +1 -0
  113. package/focus-app/client/_app/immutable/chunks/ZxFklh9T.js.br +0 -0
  114. package/focus-app/client/_app/immutable/chunks/ZxFklh9T.js.gz +0 -0
  115. package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js +1 -0
  116. package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js.br +0 -0
  117. package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js.gz +0 -0
  118. package/focus-app/client/_app/immutable/chunks/wbPk3Yxo.js +81 -0
  119. package/focus-app/client/_app/immutable/chunks/wbPk3Yxo.js.br +0 -0
  120. package/focus-app/client/_app/immutable/chunks/wbPk3Yxo.js.gz +0 -0
  121. package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js +2 -0
  122. package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js.br +0 -0
  123. package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js.gz +0 -0
  124. package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js +1 -0
  125. package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js.br +2 -0
  126. package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js.gz +0 -0
  127. package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js +5 -0
  128. package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js.br +0 -0
  129. package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js.gz +0 -0
  130. package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js +1 -0
  131. package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js.br +0 -0
  132. package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js.gz +0 -0
  133. package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js +1 -0
  134. package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js.br +0 -0
  135. package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js.gz +0 -0
  136. package/focus-app/client/_app/version.json +1 -0
  137. package/focus-app/client/_app/version.json.br +0 -0
  138. package/focus-app/client/_app/version.json.gz +0 -0
  139. package/focus-app/env.js +70 -0
  140. package/focus-app/env.js.map +1 -0
  141. package/focus-app/h2a-focus.json +114 -0
  142. package/focus-app/handler.js +24 -0
  143. package/focus-app/handler.js.map +1 -0
  144. package/focus-app/index.js +147 -0
  145. package/focus-app/index.js.map +1 -0
  146. package/focus-app/server/chunks/chunks/exports.js-DUTkELZq.js +217 -0
  147. package/focus-app/server/chunks/chunks/exports.js-DUTkELZq.js.map +1 -0
  148. package/focus-app/server/chunks/chunks/false.js-B2gHlHjM.js +4 -0
  149. package/focus-app/server/chunks/chunks/false.js-B2gHlHjM.js.map +1 -0
  150. package/focus-app/server/chunks/chunks/friendly.js-D4fjovnZ.js +246 -0
  151. package/focus-app/server/chunks/chunks/friendly.js-D4fjovnZ.js.map +1 -0
  152. package/focus-app/server/chunks/chunks/index.js-Di21TltP.js +1315 -0
  153. package/focus-app/server/chunks/chunks/index.js-Di21TltP.js.map +1 -0
  154. package/focus-app/server/chunks/chunks/internal.js-33vbd70h.js +126 -0
  155. package/focus-app/server/chunks/chunks/internal.js-33vbd70h.js.map +1 -0
  156. package/focus-app/server/chunks/chunks/root.js-DO1m-gJA.js +2967 -0
  157. package/focus-app/server/chunks/chunks/root.js-DO1m-gJA.js.map +1 -0
  158. package/focus-app/server/chunks/chunks/server.js-Bf8x1V_n.js +16 -0
  159. package/focus-app/server/chunks/chunks/server.js-Bf8x1V_n.js.map +1 -0
  160. package/focus-app/server/chunks/chunks/utils.js-C_3_iViC.js +2545 -0
  161. package/focus-app/server/chunks/chunks/utils.js-C_3_iViC.js.map +1 -0
  162. package/focus-app/server/chunks/chunks/utils2.js-BQzn9ikS.js +38 -0
  163. package/focus-app/server/chunks/chunks/utils2.js-BQzn9ikS.js.map +1 -0
  164. package/focus-app/server/chunks/entries/endpoints/api/actions/launch/_server.ts.js-DUQcf9xd.js +63 -0
  165. package/focus-app/server/chunks/entries/endpoints/api/actions/launch/_server.ts.js-DUQcf9xd.js.map +1 -0
  166. package/focus-app/server/chunks/entries/endpoints/api/decisions/inject/_server.ts.js-j0NNcOSZ.js +100 -0
  167. package/focus-app/server/chunks/entries/endpoints/api/decisions/inject/_server.ts.js-j0NNcOSZ.js.map +1 -0
  168. package/focus-app/server/chunks/entries/fallbacks/error.svelte.js-Dj3sETSX.js +50 -0
  169. package/focus-app/server/chunks/entries/fallbacks/error.svelte.js-Dj3sETSX.js.map +1 -0
  170. package/focus-app/server/chunks/entries/pages/_layout.svelte.js-BoLsbuC2.js +1420 -0
  171. package/focus-app/server/chunks/entries/pages/_layout.svelte.js-BoLsbuC2.js.map +1 -0
  172. package/focus-app/server/chunks/entries/pages/_page.server.ts.js-ChRBj_xK.js +246 -0
  173. package/focus-app/server/chunks/entries/pages/_page.server.ts.js-ChRBj_xK.js.map +1 -0
  174. package/focus-app/server/chunks/entries/pages/_page.svelte.js-QDldMYhk.js +2725 -0
  175. package/focus-app/server/chunks/entries/pages/_page.svelte.js-QDldMYhk.js.map +1 -0
  176. package/focus-app/server/chunks/handler-Cp_5hLyY.js +1474 -0
  177. package/focus-app/server/chunks/handler-Cp_5hLyY.js.map +1 -0
  178. package/focus-app/server/chunks/index.js-CBh7Wjqp.js +4242 -0
  179. package/focus-app/server/chunks/index.js-CBh7Wjqp.js.map +1 -0
  180. package/focus-app/server/chunks/manifest.js-Cm6cC7mC.js +56 -0
  181. package/focus-app/server/chunks/manifest.js-Cm6cC7mC.js.map +1 -0
  182. package/focus-app/server/chunks/nodes/0.js-4wlmdsk8.js +9 -0
  183. package/focus-app/server/chunks/nodes/0.js-4wlmdsk8.js.map +1 -0
  184. package/focus-app/server/chunks/nodes/1.js-B-LpY-lC.js +9 -0
  185. package/focus-app/server/chunks/nodes/1.js-B-LpY-lC.js.map +1 -0
  186. package/focus-app/server/chunks/nodes/2.js-F6jp5eD9.js +19 -0
  187. package/focus-app/server/chunks/nodes/2.js-F6jp5eD9.js.map +1 -0
  188. package/focus-app/shims.js +33 -0
  189. package/focus-app/shims.js.map +1 -0
  190. package/package.json +8 -4
  191. package/skills/h2a/SKILL.md +7 -5
  192. package/skills/harness/SKILL.md +17 -0
  193. package/skills/harness/using-harness/SKILL.md +26 -0
package/dist/cli.js CHANGED
@@ -40,7 +40,7 @@ import { execFileSync, spawnSync } from "node:child_process";
40
40
  import { generateKeyPairSync } from "node:crypto";
41
41
  import { copyFileSync, existsSync, mkdirSync, readdirSync, realpathSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
42
42
  import { homedir, hostname } from "node:os";
43
- import { dirname, join, resolve as resolvePath } from "node:path";
43
+ import { dirname, isAbsolute, join, resolve as resolvePath } from "node:path";
44
44
  import { createRequire } from "node:module";
45
45
  import { fileURLToPath } from "node:url";
46
46
  // Static import — `@sentropic/track` is a LIGHT library (pure reader/model, no
@@ -68,7 +68,7 @@ import { H2A_OPENCODE_HOST } from "./hosts/opencode.js";
68
68
  import { H2A_CLI_MCP_TOOL_NAMES } from "./mcp.js";
69
69
  import { renderStopHook, claudeStopHookEntry, claudeDriveReceiveHookEntry, isH2ADriveReceiveHook, isH2ARecordHook, codexPluginManifest, codexMarketplaceManifest, codexPluginTrustCommands, H2A_CODEX_PLUGIN_NAME, H2A_HOST_PLUGIN_HOSTS } from "./hosts/plugin.js";
70
70
  import { H2A_STORE_SCHEMA_VERSION, assertHostQualifiedAddress, canonicalAddress, createLocalStore, listPresence, reapAllDeadPresence, reapDeadInstancePresence, readPresence, resolveRecipient, safePathSegment, sanitizeStorePaths, writePresence } from "./runtime/local-files/index.js";
71
- import { runMcpStdio } from "./runtime/mcp/index.js";
71
+ import { H2A_MCP_READY_FILE_ENV, H2A_MCP_READY_NONCE_ENV, runMcpStdio } from "./runtime/mcp/index.js";
72
72
  import { renderK8sSidecar } from "./runtime/deploy/k8s-sidecar.js";
73
73
  import { renderK8sTenant } from "./runtime/deploy/k8s-tenant.js";
74
74
  import { remoteServerForStore, sendRemoteEnvelope } from "./runtime/remote/index.js";
@@ -85,10 +85,11 @@ import { conductorFor } from "./runtime/governance/conductor.js";
85
85
  import { appendConductorClaim } from "./runtime/governance/claims.js";
86
86
  import { conductorLaunchCheck } from "./runtime/governance/launch-check.js";
87
87
  import { lastSpawnRequestAt, recordSpawnRequest, spawnAllowed } from "./runtime/governance/spawns.js";
88
- import { createObjectiveLoop, declareObjectiveLoopDone, joinObjectiveLoop, listLoopEvents, listObjectiveLoops, readObjectiveLoop, reportObjectiveLoop, stopObjectiveLoop } from "./runtime/loop/index.js";
88
+ import { createObjectiveLoop, declareObjectiveLoopDone, joinObjectiveLoop, listLoopEvents, listObjectiveLoops, readObjectiveLoop, reportObjectiveLoop, stopObjectiveLoop, validateLoopLaunchSpec } from "./runtime/loop/index.js";
89
89
  import { runLoopWatch, runTick } from "./runtime/loop/engine/tick.js";
90
90
  import { gatherPendingDecisions } from "./runtime/canevas/gather.js";
91
91
  import { runCanevasServe } from "./runtime/canevas/serve.js";
92
+ import { installTrackReportAiConfig, readH2AReportContext, TrackReportAiConfigConflictError } from "./runtime/reporting/index.js";
92
93
  const HERE = dirname(fileURLToPath(import.meta.url));
93
94
  // `dist/cli.js` lives in `packages/h2a-cli/dist/`; skills are at
94
95
  // `packages/h2a-cli/skills/`. Two levels up from the dist file.
@@ -225,9 +226,9 @@ export function renderCliHelp() {
225
226
  " h2a install-skills --host <claude|codex|gemini|agy> [--scope user|project] [--force]",
226
227
  " h2a deploy k8s-sidecar [--instance <id>] [--host <h>] [--root <path>] [--image <ref>] [--cli-version <ver>] [--write <file>]",
227
228
  " h2a deploy k8s-tenant [--namespace <ns>] [--root <path>] [--replicas <n>] [--storage <size>] [--storage-class <sc>] [--lease-ms <ms>] [--image <ref>] [--cli-version <ver>] [--write <file>]",
228
- " h2a loop create --name <n> --goal <text> [--repo <path[:role]>] [--track <json>] [--agent <host:role:placement>] [--root <path>]",
229
- " h2a loop join <loopId> --instance <id> [--agent-id <id>] [--role <role>] [--root <path>]",
230
- " h2a loop report <loopId> --note <text> [--instance <id>] [--agent-id <id>] [--root <path>]",
229
+ " h2a loop create --name <n> --goal <text> [--repo <path[:role]>] [--track <json>] [--agent <host:role:placement> [--launch-stdin]] [--root <path>]",
230
+ " h2a loop join <loopId> --instance <id> [--agent-id <id>] [--role <role>] [--launch-stdin] [--root <path>]",
231
+ " h2a loop report <loopId> --note <text> [--instance <id>] [--agent-id <id>] [--auto-join] [--root <path>]",
231
232
  " h2a loop done <loopId> [--note <text>] [--instance <id>] [--agent-id <id>] [--root <path>]",
232
233
  " h2a loop stop <loopId> [--reason <text>] [--root <path>]",
233
234
  " h2a loop list [--root <path>]",
@@ -236,9 +237,19 @@ export function renderCliHelp() {
236
237
  " h2a loop attach <loopId> --agent <selector> [--root <path>]",
237
238
  " h2a loop logs <loopId> [--agent <selector>] [--root <path>]",
238
239
  "",
240
+ "Track AI adapters (leaf commands; never call Track report/snapshot):",
241
+ " h2a report-context --workspace-root <absolute-path> [--root <h2a-store>]",
242
+ " h2a report-ai --model <model> --effort <low|medium|high|xhigh> --gateway required (Track context envelope on stdin; one no-tools local-gateway request)",
243
+ " h2a report-ai install-track-config [--force] (atomic user XDG config, mode 0600; preserves overrides by default)",
244
+ "",
245
+ "Focus Web (packaged production app):",
246
+ " h2a focus serve [--repo <path>] [--track-events <path>] [--host <host>] [--port <0-65535>]",
247
+ " h2a focus web [--repo <path>] [--track-events <path>] [--host <host>] [--port <0-65535>] (exact alias)",
248
+ " (defaults to the nearest tracked repo and 127.0.0.1:5178; bare `h2a focus …` remains the Track facade)",
249
+ "",
239
250
  "Track (délégué à @sentropic/track — le suivi/record du travail):",
240
251
  ` h2a ${[...TRACK_FACADE_VERBS].join(" · h2a ")}`,
241
- " (ex: h2a decision new …, h2a report, h2a item ls — voir `track <verbe> --help`)",
252
+ " (ex: h2a decision new …, h2a report, h2a snapshot, h2a item ls — voir `track <verbe> --help`)",
242
253
  "",
243
254
  "Harness (délégué à h2a vendored harness — la méthode code-work / PR-workflow):",
244
255
  " h2a harness <check|verify|init|audit|brainstorm|test|debug|review|plan|branch|skills> …",
@@ -1254,6 +1265,21 @@ export async function runMcpServe(flags, io = {
1254
1265
  warnIfCwdRootFallback(flags, cwd, { stderr: io.stderr, stdout: io.stdout, cwd });
1255
1266
  const root = resolveRoot(flags, cwd);
1256
1267
  const autoOpen = resolveAutoOpen(flags, cwd);
1268
+ const readinessEnv = io.env ?? process.env;
1269
+ const readyFile = readinessEnv[H2A_MCP_READY_FILE_ENV];
1270
+ const readyNonce = readinessEnv[H2A_MCP_READY_NONCE_ENV];
1271
+ let readiness;
1272
+ if (readyFile !== undefined || readyNonce !== undefined) {
1273
+ if (!readyFile ||
1274
+ !isAbsolute(readyFile) ||
1275
+ readyFile.includes("\0") ||
1276
+ !readyNonce ||
1277
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(readyNonce)) {
1278
+ io.stderr.write("h2a mcp-serve: invalid internal readiness challenge\n");
1279
+ return 1;
1280
+ }
1281
+ readiness = { file: readyFile, nonce: readyNonce };
1282
+ }
1257
1283
  // DEC-107/108 (EVO-8 levels 2/3): version handling at boot. **Opt-in** — no
1258
1284
  // network on a default boot. `--auto-upgrade` self-installs @latest then
1259
1285
  // re-execs in place via process.execve (same PID/stdio, host stays connected;
@@ -1270,10 +1296,14 @@ export async function runMcpServe(flags, io = {
1270
1296
  // same-day release isn't masked by the 24h notice cache; the passive
1271
1297
  // `--upgrade-check` notice keeps the 24h throttle.
1272
1298
  const ttlMs = wantAutoUpgrade ? H2A_AUTO_UPGRADE_CHECK_TTL_MS : H2A_UPGRADE_CHECK_TTL_MS;
1273
- const result = checkUpgrade(current, { cachePath: upgradeCachePath(root), ttlMs });
1299
+ const result = checkUpgrade(current, {
1300
+ cachePath: upgradeCachePath(root),
1301
+ ttlMs,
1302
+ ...(io.upgradeRuntime ? { runtime: io.upgradeRuntime } : {})
1303
+ });
1274
1304
  if (result.upgradeAvailable) {
1275
1305
  if (wantAutoUpgrade) {
1276
- const ok = performUpgrade();
1306
+ const ok = performUpgrade(io.upgradeRuntime);
1277
1307
  if (ok && flags["no-restart"] === undefined && canReexec()) {
1278
1308
  io.stderr.write(`h2a mcp-serve: auto-upgraded ${current} → ${result.latest}; restarting into the new version…\n`);
1279
1309
  reexecSelf(); // on success the image is replaced (never returns)
@@ -1326,10 +1356,12 @@ export async function runMcpServe(flags, io = {
1326
1356
  }
1327
1357
  await runMcpStdio({
1328
1358
  root,
1359
+ workspaceRoot: process.cwd(),
1329
1360
  stdin: io.stdin,
1330
1361
  stdout: io.stdout,
1331
1362
  stderr: io.stderr,
1332
1363
  ...(autoOpen ? { autoOpen } : {}),
1364
+ ...(readiness ? { readiness } : {}),
1333
1365
  ...(wake ? { wake } : {}),
1334
1366
  ...(io.signal ? { signal: io.signal } : {})
1335
1367
  });
@@ -1640,6 +1672,16 @@ function collectRepeatedFlag(argv, flag) {
1640
1672
  }
1641
1673
  return out;
1642
1674
  }
1675
+ function parseLoopLaunch(raw) {
1676
+ let value;
1677
+ try {
1678
+ value = JSON.parse(raw);
1679
+ }
1680
+ catch {
1681
+ throw new Error("--launch-stdin must contain valid JSON");
1682
+ }
1683
+ return validateLoopLaunchSpec(value);
1684
+ }
1643
1685
  function selectLoopAgent(loop, selector) {
1644
1686
  if (!selector)
1645
1687
  return undefined;
@@ -1653,7 +1695,17 @@ function cmdLoop(argv, streams) {
1653
1695
  if (sub === "create") {
1654
1696
  const repos = collectRepeatedFlag(argv, "--repo").map(parseLoopRepo);
1655
1697
  const refs = collectRepeatedFlag(argv, "--track").map(parseLoopTrack);
1656
- const agents = collectRepeatedFlag(argv, "--agent").map(parseLoopAgent);
1698
+ let agents = collectRepeatedFlag(argv, "--agent").map(parseLoopAgent);
1699
+ if (flags.launch)
1700
+ throw new Error("--launch is unsafe because prompts must not be passed in argv; use --launch-stdin");
1701
+ if (flags["launch-stdin"] === "true") {
1702
+ if (agents.length !== 1)
1703
+ throw new Error("--launch-stdin requires exactly one --agent on loop create");
1704
+ const launch = parseLoopLaunch(stdinText(streams) ?? "");
1705
+ if (agents[0].host !== launch.profile)
1706
+ throw new Error("--agent host must match --launch-stdin profile");
1707
+ agents = [{ ...agents[0], launch }];
1708
+ }
1657
1709
  const loop = createObjectiveLoop(root, {
1658
1710
  ...(flags.id ? { id: flags.id } : {}),
1659
1711
  name: flags.name,
@@ -1675,11 +1727,15 @@ function cmdLoop(argv, streams) {
1675
1727
  streams.stderr.write("h2a loop join: <loopId> and --instance <id> are required\n");
1676
1728
  return 1;
1677
1729
  }
1730
+ if (flags.launch)
1731
+ throw new Error("--launch is unsafe because prompts must not be passed in argv; use --launch-stdin");
1732
+ const launch = flags["launch-stdin"] === "true" ? parseLoopLaunch(stdinText(streams) ?? "") : undefined;
1678
1733
  const loop = joinObjectiveLoop(root, loopId, {
1679
1734
  instance: flags.instance,
1680
1735
  ...(flags["agent-id"] ? { agentId: flags["agent-id"] } : {}),
1681
1736
  ...(flags.role ? { role: flags.role } : {}),
1682
- ...(flags.required ? { required: flags.required !== "false" } : {})
1737
+ ...(flags.required ? { required: flags.required !== "false" } : {}),
1738
+ ...(launch !== undefined ? { launch } : {})
1683
1739
  });
1684
1740
  streams.stdout.write(`${JSON.stringify(loop, null, 2)}\n`);
1685
1741
  return 0;
@@ -1693,6 +1749,7 @@ function cmdLoop(argv, streams) {
1693
1749
  const loop = reportObjectiveLoop(root, loopId, {
1694
1750
  ...(flags.instance ? { instance: flags.instance } : {}),
1695
1751
  ...(flags["agent-id"] ? { agentId: flags["agent-id"] } : {}),
1752
+ ...(flags["auto-join"] === "true" ? { autoJoin: true } : {}),
1696
1753
  note: flags.note
1697
1754
  });
1698
1755
  streams.stdout.write(`${JSON.stringify(loop, null, 2)}\n`);
@@ -4073,7 +4130,12 @@ function cmdDoctor(flags, streams) {
4073
4130
  }
4074
4131
  }
4075
4132
  }
4076
- // (d) --scan <dir>: find immediate child buses (ONE level deep)
4133
+ // (d) --scan <dir>: find immediate child buses (ONE level deep).
4134
+ // Safety model: a stray bus is ORPHAN only if it has zero live (fresh
4135
+ // heartbeat) presence — checked via the same listPresence() used for the
4136
+ // liveSessions hard check above. A bus with ANY live presence is reported
4137
+ // but NEVER pruned, --prune or not. This is a dry-run-by-default sweep:
4138
+ // the report always lists what --prune WOULD remove; only --prune deletes.
4077
4139
  const strayBuses = [];
4078
4140
  if (flags.scan) {
4079
4141
  try {
@@ -4090,7 +4152,17 @@ function cmdDoctor(flags, streams) {
4090
4152
  catch {
4091
4153
  instances = 0;
4092
4154
  }
4093
- strayBuses.push({ path: candidateBus, instances });
4155
+ // Liveness gate: any fresh presence heartbeat in this bus makes it
4156
+ // ACTIVE, not orphan — never a deletion candidate.
4157
+ let live = false;
4158
+ try {
4159
+ live = listPresence(candidateBus).length > 0;
4160
+ }
4161
+ catch {
4162
+ // Unreadable presence dir — treat conservatively as NOT orphan.
4163
+ live = true;
4164
+ }
4165
+ strayBuses.push({ path: candidateBus, instances, live, orphan: !live });
4094
4166
  }
4095
4167
  }
4096
4168
  }
@@ -4098,15 +4170,21 @@ function cmdDoctor(flags, streams) {
4098
4170
  streams.stderr.write(`h2a doctor: --scan ${flags.scan}: ${error.message}\n`);
4099
4171
  }
4100
4172
  if (strayBuses.length > 0) {
4173
+ const orphanBuses = strayBuses.filter((b) => b.orphan === true);
4174
+ const liveBuses = strayBuses.filter((b) => b.orphan !== true);
4101
4175
  warnings.push({
4102
4176
  check: "strayBuses",
4103
4177
  count: strayBuses.length,
4178
+ orphanCount: orphanBuses.length,
4179
+ liveCount: liveBuses.length,
4104
4180
  buses: strayBuses,
4105
4181
  message: `${strayBuses.length} stray repo-local .h2a bus(es) found under ${flags.scan} — ` +
4106
- `candidate split-brain forks. Each may have agents on a different root.`
4182
+ `candidate split-brain forks. ${orphanBuses.length} orphan (no live presence — ` +
4183
+ `${flags.prune !== undefined ? "removed by" : "would be removed by"} --prune), ` +
4184
+ `${liveBuses.length} live (has active presence — never pruned).`
4107
4185
  });
4108
4186
  if (flags.prune !== undefined) {
4109
- for (const bus of strayBuses) {
4187
+ for (const bus of orphanBuses) {
4110
4188
  const busPath = bus.path;
4111
4189
  try {
4112
4190
  rmSync(busPath, { recursive: true, force: true });
@@ -4116,6 +4194,10 @@ function cmdDoctor(flags, streams) {
4116
4194
  streams.stderr.write(`h2a doctor --prune: cannot remove ${busPath}: ${error.message}\n`);
4117
4195
  }
4118
4196
  }
4197
+ if (liveBuses.length > 0) {
4198
+ streams.stderr.write(`h2a doctor --prune: skipped ${liveBuses.length} stray bus(es) with live presence (never pruned): ` +
4199
+ `${liveBuses.map((b) => b.path).join(", ")}\n`);
4200
+ }
4119
4201
  }
4120
4202
  }
4121
4203
  }
@@ -4883,7 +4965,7 @@ export async function cmdKeepalive(flags, streams) {
4883
4965
  // Verbes spécifiques, sans namespace `track` (dissous comme host/sub).
4884
4966
  export const TRACK_FACADE_VERBS = new Set([
4885
4967
  "decision", "report", "accept", "blocker", "item", "query",
4886
- "consolidate", "priority", "branch", "focus", "ingest", "restructure"
4968
+ "consolidate", "priority", "branch", "focus", "ingest", "restructure", "snapshot"
4887
4969
  ]);
4888
4970
  // ④ de-spawn — a strict SUBSET of the facade verbs routed IN-PROCESS via
4889
4971
  // `@sentropic/track`'s `runCli`, no child process. Every native verb is proven
@@ -4896,14 +4978,14 @@ export const TRACK_FACADE_VERBS = new Set([
4896
4978
  //
4897
4979
  // The native set is split READ-ONLY vs WRITE because the throw-fallback rule
4898
4980
  // differs (RISK: double-write, see `delegateToTrackNative`):
4899
- // • READ-ONLY (`query`/`report`) — a native throw wrote nothing, so falling
4981
+ // • READ-ONLY (`query`/`report`/`snapshot`) — a native throw wrote nothing, so falling
4900
4982
  // back to the spawn facade is safe.
4901
4983
  // • WRITE (the rest) — track's `appendCommand` is ATOMIC under the O_EXCL lock
4902
4984
  // (read→validate→append→writeHead→verify): it throws either BEFORE the
4903
4985
  // append (validation → nothing written) or AFTER a landed write (the
4904
4986
  // verify receipt). A spawn fallback on that throw could DOUBLE-WRITE, so a
4905
4987
  // write NEVER falls back on throw — it propagates the error (rc≠0 + stderr).
4906
- export const TRACK_NATIVE_READONLY_VERBS = new Set(["query", "report"]);
4988
+ export const TRACK_NATIVE_READONLY_VERBS = new Set(["query", "report", "snapshot"]);
4907
4989
  // ④ tranche-2 — the SYNC write verbs, de-spawned in-process. Each dispatches
4908
4990
  // through runCli's mutating switch as a plain sync `cmd…` returning a `number`.
4909
4991
  // `focus` is absent (async); `restructure`/`ingest` are sync file-plan applies.
@@ -4947,6 +5029,44 @@ export async function runTrackMcpServe(flags, io = {
4947
5029
  return 1;
4948
5030
  }
4949
5031
  }
5032
+ function cmdReportContext(flags, streams) {
5033
+ const workspaceRoot = flags["workspace-root"];
5034
+ if (!workspaceRoot || workspaceRoot === "true") {
5035
+ streams.stderr.write("h2a report-context: --workspace-root <absolute-path> is required\n");
5036
+ return 1;
5037
+ }
5038
+ if (!isAbsolute(workspaceRoot)) {
5039
+ streams.stderr.write("h2a report-context: --workspace-root must be an absolute path\n");
5040
+ return 1;
5041
+ }
5042
+ try {
5043
+ const context = readH2AReportContext({
5044
+ storeRoot: resolveRoot(flags, streams.cwd ?? (() => process.cwd())),
5045
+ workspaceRoot
5046
+ });
5047
+ streams.stdout.write(`${JSON.stringify(context)}\n`);
5048
+ return 0;
5049
+ }
5050
+ catch (err) {
5051
+ streams.stderr.write(`h2a report-context: ${err.message}\n`);
5052
+ return 3;
5053
+ }
5054
+ }
5055
+ function cmdInstallTrackReportAiConfig(flags, streams) {
5056
+ try {
5057
+ const result = installTrackReportAiConfig({ force: flags.force === "true" });
5058
+ streams.stdout.write(`${JSON.stringify({ ok: true, ...result })}\n`);
5059
+ return 0;
5060
+ }
5061
+ catch (err) {
5062
+ if (err instanceof TrackReportAiConfigConflictError) {
5063
+ streams.stderr.write(`h2a report-ai install-track-config: ${err.message}\n`);
5064
+ return 2;
5065
+ }
5066
+ streams.stderr.write(`h2a report-ai install-track-config: ${err.message}\n`);
5067
+ return 3;
5068
+ }
5069
+ }
4950
5070
  function resolveTrackBin() {
4951
5071
  // Le champ `exports` de @sentropic/track bloque l'accès à ./package.json,
4952
5072
  // donc on résout l'entrée puis on remonte jusqu'au package.json du package.
@@ -5069,6 +5189,14 @@ export function runCli(argv = process.argv.slice(2), streams = {
5069
5189
  streams.stdout.write(`${JSON.stringify(H2A_CLI_MCP_TOOL_NAMES, null, 2)}\n`);
5070
5190
  return 0;
5071
5191
  }
5192
+ if (command === "report-context")
5193
+ return cmdReportContext(flags, streams);
5194
+ if (command === "report-ai") {
5195
+ if (argv[1] === "install-track-config")
5196
+ return cmdInstallTrackReportAiConfig(flags, streams);
5197
+ streams.stderr.write("h2a report-ai: async adapter command — run via the h2a binary\n");
5198
+ return 1;
5199
+ }
5072
5200
  if (command === "init")
5073
5201
  return cmdInit(flags, streams);
5074
5202
  if (command === "register")