@mono-agent/agent-app 0.11.5 → 0.13.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 (223) hide show
  1. package/README.md +54 -17
  2. package/dist/adapter-send-tools.d.ts.map +1 -1
  3. package/dist/adapter-send-tools.js +126 -34
  4. package/dist/adapter-send-tools.js.map +1 -1
  5. package/dist/app-controller-lifecycle.d.ts.map +1 -1
  6. package/dist/app-controller-lifecycle.js +2 -6
  7. package/dist/app-controller-lifecycle.js.map +1 -1
  8. package/dist/app-controller-maintenance.d.ts.map +1 -1
  9. package/dist/app-controller-maintenance.js +2 -1
  10. package/dist/app-controller-maintenance.js.map +1 -1
  11. package/dist/app-controller-memory-health.d.ts +1 -1
  12. package/dist/app-controller-memory-health.d.ts.map +1 -1
  13. package/dist/app-controller-memory-health.js +5 -1
  14. package/dist/app-controller-memory-health.js.map +1 -1
  15. package/dist/app-controller-responder.d.ts.map +1 -1
  16. package/dist/app-controller-responder.js +8 -3
  17. package/dist/app-controller-responder.js.map +1 -1
  18. package/dist/app-controller-traceability.d.ts.map +1 -1
  19. package/dist/app-controller-traceability.js +15 -1
  20. package/dist/app-controller-traceability.js.map +1 -1
  21. package/dist/app-controller.d.ts +14 -1
  22. package/dist/app-controller.d.ts.map +1 -1
  23. package/dist/app-controller.js +61 -9
  24. package/dist/app-controller.js.map +1 -1
  25. package/dist/audit-runs.d.ts.map +1 -1
  26. package/dist/audit-runs.js +12 -2
  27. package/dist/audit-runs.js.map +1 -1
  28. package/dist/background-runtime.d.ts +26 -0
  29. package/dist/background-runtime.d.ts.map +1 -1
  30. package/dist/background-runtime.js +874 -46
  31. package/dist/background-runtime.js.map +1 -1
  32. package/dist/background.d.ts +12 -2
  33. package/dist/background.d.ts.map +1 -1
  34. package/dist/background.js +195 -40
  35. package/dist/background.js.map +1 -1
  36. package/dist/channel-drivers/live.d.ts +3 -2
  37. package/dist/channel-drivers/live.d.ts.map +1 -1
  38. package/dist/channel-drivers/live.js +3 -2
  39. package/dist/channel-drivers/live.js.map +1 -1
  40. package/dist/channel-drivers/slack.d.ts.map +1 -1
  41. package/dist/channel-drivers/slack.js +6 -0
  42. package/dist/channel-drivers/slack.js.map +1 -1
  43. package/dist/channel-drivers/telegram.d.ts.map +1 -1
  44. package/dist/channel-drivers/telegram.js +20 -4
  45. package/dist/channel-drivers/telegram.js.map +1 -1
  46. package/dist/channel-drivers/tui.d.ts.map +1 -1
  47. package/dist/channel-drivers/tui.js +24 -0
  48. package/dist/channel-drivers/tui.js.map +1 -1
  49. package/dist/channel-runtime-controls.d.ts +23 -0
  50. package/dist/channel-runtime-controls.d.ts.map +1 -0
  51. package/dist/channel-runtime-controls.js +63 -0
  52. package/dist/channel-runtime-controls.js.map +1 -0
  53. package/dist/channel-status-display.d.ts +15 -0
  54. package/dist/channel-status-display.d.ts.map +1 -0
  55. package/dist/channel-status-display.js +48 -0
  56. package/dist/channel-status-display.js.map +1 -0
  57. package/dist/cli-args.d.ts +19 -22
  58. package/dist/cli-args.d.ts.map +1 -1
  59. package/dist/cli-args.js +115 -68
  60. package/dist/cli-args.js.map +1 -1
  61. package/dist/cli-background-command.d.ts +1 -0
  62. package/dist/cli-background-command.d.ts.map +1 -1
  63. package/dist/cli-background-command.js +72 -16
  64. package/dist/cli-background-command.js.map +1 -1
  65. package/dist/cli-commands.d.ts +4 -2
  66. package/dist/cli-commands.d.ts.map +1 -1
  67. package/dist/cli-commands.js +51 -47
  68. package/dist/cli-commands.js.map +1 -1
  69. package/dist/cli-help.d.ts +19 -1
  70. package/dist/cli-help.d.ts.map +1 -1
  71. package/dist/cli-help.js +217 -70
  72. package/dist/cli-help.js.map +1 -1
  73. package/dist/cli-init-command.d.ts.map +1 -1
  74. package/dist/cli-init-command.js +4 -18
  75. package/dist/cli-init-command.js.map +1 -1
  76. package/dist/cli-install-skill-command.d.ts.map +1 -1
  77. package/dist/cli-install-skill-command.js +32 -3
  78. package/dist/cli-install-skill-command.js.map +1 -1
  79. package/dist/cli-runs-command.d.ts +13 -0
  80. package/dist/cli-runs-command.d.ts.map +1 -0
  81. package/dist/cli-runs-command.js +70 -0
  82. package/dist/cli-runs-command.js.map +1 -0
  83. package/dist/cli-sandbox-command.d.ts +1 -1
  84. package/dist/cli-sandbox-command.d.ts.map +1 -1
  85. package/dist/cli-sandbox-command.js +14 -2
  86. package/dist/cli-sandbox-command.js.map +1 -1
  87. package/dist/cli-validate-config-command.d.ts +24 -5
  88. package/dist/cli-validate-config-command.d.ts.map +1 -1
  89. package/dist/cli-validate-config-command.js +120 -41
  90. package/dist/cli-validate-config-command.js.map +1 -1
  91. package/dist/config-reference.d.ts +1 -1
  92. package/dist/config-reference.d.ts.map +1 -1
  93. package/dist/config-reference.js +63 -1
  94. package/dist/config-reference.js.map +1 -1
  95. package/dist/configured-agent.js +3 -1
  96. package/dist/configured-agent.js.map +1 -1
  97. package/dist/docs-mcp-pairing.d.ts +35 -0
  98. package/dist/docs-mcp-pairing.d.ts.map +1 -0
  99. package/dist/docs-mcp-pairing.js +311 -0
  100. package/dist/docs-mcp-pairing.js.map +1 -0
  101. package/dist/doctor.d.ts +6 -0
  102. package/dist/doctor.d.ts.map +1 -1
  103. package/dist/doctor.js +6 -5
  104. package/dist/doctor.js.map +1 -1
  105. package/dist/install-skill.d.ts +2 -0
  106. package/dist/install-skill.d.ts.map +1 -1
  107. package/dist/install-skill.js +10 -4
  108. package/dist/install-skill.js.map +1 -1
  109. package/dist/interaction-bridge.d.ts +2 -0
  110. package/dist/interaction-bridge.d.ts.map +1 -1
  111. package/dist/interaction-bridge.js +46 -1
  112. package/dist/interaction-bridge.js.map +1 -1
  113. package/dist/launchd.d.ts +18 -0
  114. package/dist/launchd.d.ts.map +1 -1
  115. package/dist/launchd.js +69 -0
  116. package/dist/launchd.js.map +1 -1
  117. package/dist/local-configuration.d.ts +2 -2
  118. package/dist/local-configuration.d.ts.map +1 -1
  119. package/dist/local-configuration.js +10 -10
  120. package/dist/local-configuration.js.map +1 -1
  121. package/dist/managed-runtime-publication.d.ts +22 -0
  122. package/dist/managed-runtime-publication.d.ts.map +1 -0
  123. package/dist/managed-runtime-publication.js +91 -0
  124. package/dist/managed-runtime-publication.js.map +1 -0
  125. package/dist/managed-startup.d.ts +8 -0
  126. package/dist/managed-startup.d.ts.map +1 -0
  127. package/dist/managed-startup.js +12 -0
  128. package/dist/managed-startup.js.map +1 -0
  129. package/dist/memory-retrieval.d.ts.map +1 -1
  130. package/dist/memory-retrieval.js +31 -18
  131. package/dist/memory-retrieval.js.map +1 -1
  132. package/dist/metrics.d.ts.map +1 -1
  133. package/dist/metrics.js +12 -2
  134. package/dist/metrics.js.map +1 -1
  135. package/dist/modules/known-tools.d.ts +2 -2
  136. package/dist/modules/known-tools.d.ts.map +1 -1
  137. package/dist/modules/known-tools.js +2 -1
  138. package/dist/modules/known-tools.js.map +1 -1
  139. package/dist/notify-runtime.d.ts +26 -0
  140. package/dist/notify-runtime.d.ts.map +1 -0
  141. package/dist/notify-runtime.js +27 -0
  142. package/dist/notify-runtime.js.map +1 -0
  143. package/dist/notify-tool.d.ts +51 -0
  144. package/dist/notify-tool.d.ts.map +1 -0
  145. package/dist/notify-tool.js +182 -0
  146. package/dist/notify-tool.js.map +1 -0
  147. package/dist/posted-reply-history.d.ts.map +1 -1
  148. package/dist/posted-reply-history.js +7 -0
  149. package/dist/posted-reply-history.js.map +1 -1
  150. package/dist/project-skills.d.ts +1 -1
  151. package/dist/project-skills.d.ts.map +1 -1
  152. package/dist/project-skills.js +9 -7
  153. package/dist/project-skills.js.map +1 -1
  154. package/dist/recipes/base.d.ts +15 -0
  155. package/dist/recipes/base.d.ts.map +1 -0
  156. package/dist/recipes/base.js +51 -0
  157. package/dist/recipes/base.js.map +1 -0
  158. package/dist/recipes/catalog.d.ts +4 -0
  159. package/dist/recipes/catalog.d.ts.map +1 -0
  160. package/dist/recipes/catalog.js +525 -0
  161. package/dist/recipes/catalog.js.map +1 -0
  162. package/dist/recipes/index.d.ts +11 -0
  163. package/dist/recipes/index.d.ts.map +1 -0
  164. package/dist/recipes/index.js +14 -0
  165. package/dist/recipes/index.js.map +1 -0
  166. package/dist/recipes/types.d.ts +70 -0
  167. package/dist/recipes/types.d.ts.map +1 -0
  168. package/dist/recipes/types.js +15 -0
  169. package/dist/recipes/types.js.map +1 -0
  170. package/dist/request-model-override.d.ts +3 -2
  171. package/dist/request-model-override.d.ts.map +1 -1
  172. package/dist/request-model-override.js +13 -7
  173. package/dist/request-model-override.js.map +1 -1
  174. package/dist/run-history.d.ts.map +1 -1
  175. package/dist/run-history.js +35 -22
  176. package/dist/run-history.js.map +1 -1
  177. package/dist/runtime-provenance.d.ts.map +1 -1
  178. package/dist/runtime-provenance.js +10 -5
  179. package/dist/runtime-provenance.js.map +1 -1
  180. package/dist/sessions-command.d.ts +53 -0
  181. package/dist/sessions-command.d.ts.map +1 -0
  182. package/dist/sessions-command.js +309 -0
  183. package/dist/sessions-command.js.map +1 -0
  184. package/dist/setup.d.ts +29 -0
  185. package/dist/setup.d.ts.map +1 -0
  186. package/dist/setup.js +97 -0
  187. package/dist/setup.js.map +1 -0
  188. package/dist/tui-command.d.ts +1 -1
  189. package/dist/tui-command.d.ts.map +1 -1
  190. package/dist/tui-command.js +6 -5
  191. package/dist/tui-command.js.map +1 -1
  192. package/dist/web-command.d.ts +109 -43
  193. package/dist/web-command.d.ts.map +1 -1
  194. package/dist/web-command.js +1108 -237
  195. package/dist/web-command.js.map +1 -1
  196. package/dist/wizard/from-flags.d.ts +0 -1
  197. package/dist/wizard/from-flags.d.ts.map +1 -1
  198. package/dist/wizard/from-flags.js +1 -8
  199. package/dist/wizard/from-flags.js.map +1 -1
  200. package/dist/wizard/index.d.ts +1 -1
  201. package/dist/wizard/index.d.ts.map +1 -1
  202. package/dist/wizard/index.js +1 -1
  203. package/dist/wizard/index.js.map +1 -1
  204. package/dist/wizard/presets.d.ts +3 -12
  205. package/dist/wizard/presets.d.ts.map +1 -1
  206. package/dist/wizard/presets.js +3 -15
  207. package/dist/wizard/presets.js.map +1 -1
  208. package/dist/wizard/prompts.d.ts.map +1 -1
  209. package/dist/wizard/prompts.js +2 -1
  210. package/dist/wizard/prompts.js.map +1 -1
  211. package/dist/wizard/run.d.ts.map +1 -1
  212. package/dist/wizard/run.js +9 -9
  213. package/dist/wizard/run.js.map +1 -1
  214. package/package.json +15 -15
  215. package/schema/mono-agent.config.schema.json +70 -2
  216. package/skills/mono-agent-composer/SKILL.md +14 -6
  217. package/skills/mono-agent-composer/agents/openai.yaml +1 -1
  218. package/skills/mono-agent-composer/references/config-blueprint.md +30 -9
  219. package/skills/mono-agent-composer/references/discovery-questions.md +10 -5
  220. package/skills/mono-agent-composer/references/feature-coverage.md +14 -12
  221. package/skills/mono-agent-composer/references/package-map.md +10 -3
  222. package/skills/mono-agent-composer/references/playbooks.md +8 -8
  223. package/skills/mono-agent-composer/references/validation.md +4 -2
@@ -7,12 +7,15 @@ import process from "node:process";
7
7
  import { listRecordedRuns, listTraceSources } from "@mono-agent/observability";
8
8
  import { describeSensitiveDataExportWarning, phoenixAppBaseUrl, resolveAppTraceRegistryDir, resolveAppTraceStaleAfterMs, } from "./app-config.js";
9
9
  import { formatChannelFactValue } from "./channel-fact-format.js";
10
+ import { formatHumanChannelSections } from "./channel-status-display.js";
11
+ import { hasCompletedManagedStartup } from "./managed-startup.js";
10
12
  import { bootout, bootstrap, buildLaunchdMaintenancePlistXml, buildPlistXml, defaultPathEnv, deriveLaunchdLabel, deriveLaunchdMaintenanceLabel, isLoaded, launchdServiceInfo, launchdPathsFor, MANAGED_LAUNCHD_LOG_MAINTENANCE_ENV, makeLaunchctlRunner, } from "./launchd.js";
11
13
  import { selectBackgroundOperationalEnvironment } from "./background-environment.js";
12
14
  import { beginLaunchdLogMaintenanceIntent, clearLaunchdLogMaintenanceIntent, inspectLaunchdLogs, LAUNCHD_LOG_MAINTENANCE_INTERVAL_SECONDS, markLaunchdLogMaintenanceRestoring, markLaunchdLogMaintenanceStopped, markLaunchdLogMaintenanceStopping, readLaunchdLogMaintenanceIntent, rotateStoppedLaunchdLogs, } from "./launchd-logs.js";
13
15
  import { ensureManagedBackgroundRuntime, MANAGED_BACKGROUND_WORKER_ENV, } from "./background-runtime.js";
14
16
  import { backgroundSnapshotFromMetadata, captureBackgroundSnapshot, encodeBackgroundSnapshot, sameBackgroundSnapshot, } from "./background-snapshot.js";
15
17
  import { resolveConfiguredManagedRuntimePackages } from "./managed-runtime-packages.js";
18
+ import { acquireManagedRuntimePublicationBarrier } from "./managed-runtime-publication.js";
16
19
  import { processIncarnationFromJson, } from "./process-incarnation.js";
17
20
  import { acquireOwnerPrivateLock, validateOwnerPrivateLockInputs } from "./owner-private-lock.js";
18
21
  import { buildRunsHealthDisplay, RUNS_HEALTH_MAX_RUNS } from "./runs-health.js";
@@ -146,6 +149,10 @@ export function defaultBackgroundDeps() {
146
149
  env: target.configurationEnvironment,
147
150
  }),
148
151
  acquireLifecycleLock: acquireFilesystemLifecycleLock,
152
+ acquireRuntimePublicationBarrier: (target) => acquireManagedRuntimePublicationBarrier({
153
+ label: target.label,
154
+ managedRoot: dirname(target.paths.logDir),
155
+ }),
149
156
  probeTui: probeTuiEndpoint,
150
157
  stdout: (text) => void process.stdout.write(text),
151
158
  stderr: (text) => void process.stderr.write(text),
@@ -156,12 +163,13 @@ export function defaultBackgroundDeps() {
156
163
  }),
157
164
  };
158
165
  }
159
- const DEFAULT_POLL = { timeoutMs: 18_000, intervalMs: 400 };
160
- export async function startBackground(target, deps, poll = DEFAULT_POLL) {
166
+ const DEFAULT_CONTROL_POLL = { timeoutMs: 18_000, intervalMs: 400 };
167
+ const DEFAULT_READINESS_POLL = { timeoutMs: 60_000, intervalMs: 400 };
168
+ export async function startBackground(target, deps, poll) {
161
169
  return (await ensureBackgroundReady(target, deps, poll)).ok ? 0 : 1;
162
170
  }
163
171
  /** Restart is behaviourally identical: ensure a single fresh running instance. */
164
- export async function restartBackground(target, deps, poll = DEFAULT_POLL) {
172
+ export async function restartBackground(target, deps, poll) {
165
173
  return (await ensureBackgroundReady(target, deps, poll)).ok ? 0 : 1;
166
174
  }
167
175
  /**
@@ -169,7 +177,7 @@ export async function restartBackground(target, deps, poll = DEFAULT_POLL) {
169
177
  * retaining one lifecycle lock. This closes the force-restart gap in which a
170
178
  * concurrent start could previously enter while the session store was purged.
171
179
  */
172
- export async function forceRestartBackground(target, deps, whileStopped, poll = DEFAULT_POLL) {
180
+ export async function forceRestartBackground(target, deps, whileStopped, poll) {
173
181
  const release = await deps.acquireLifecycleLock(target);
174
182
  if (release === undefined) {
175
183
  deps.stderr(ui.errorLine(`Another lifecycle command is already active for ${target.label}.`));
@@ -177,11 +185,12 @@ export async function forceRestartBackground(target, deps, whileStopped, poll =
177
185
  return 1;
178
186
  }
179
187
  try {
180
- const stopCode = await stopBackgroundUnlocked(target, deps, poll);
188
+ const controlPoll = poll ?? DEFAULT_CONTROL_POLL;
189
+ const stopCode = await stopBackgroundUnlocked(target, deps, controlPoll);
181
190
  if (stopCode !== 0)
182
191
  return stopCode;
183
192
  await whileStopped();
184
- return (await ensureBackgroundReadyUnlocked(target, deps, poll)).ok ? 0 : 1;
193
+ return (await ensureBackgroundReadyUnlocked(target, deps, controlPoll, poll ?? DEFAULT_READINESS_POLL)).ok ? 0 : 1;
185
194
  }
186
195
  finally {
187
196
  await release().catch((error) => {
@@ -195,7 +204,7 @@ export async function forceRestartBackground(target, deps, whileStopped, poll =
195
204
  * intentionally stopped service; the sole exception is recovery authorized by
196
205
  * its own durable lifecycle intent after a prior maintainer died post-bootout.
197
206
  */
198
- export async function maintainLaunchdLogs(target, deps, poll = DEFAULT_POLL) {
207
+ export async function maintainLaunchdLogs(target, deps, poll = DEFAULT_CONTROL_POLL) {
199
208
  const release = await deps.acquireLifecycleLock(target);
200
209
  if (release === undefined) {
201
210
  // Controller start/restart/stop owns the same lock. Skipping this periodic
@@ -398,11 +407,11 @@ function maintenanceErrorMessage(error) {
398
407
  }
399
408
  /**
400
409
  * Ensure the canonical per-config LaunchAgent is running and return the fresh
401
- * trace source that proved it reached `startup-complete`. This is the shared
410
+ * trace source that proved it completed the full startup lifecycle. This is the shared
402
411
  * lifecycle boundary for CLI start/restart and remote configuration handoffs;
403
412
  * callers must not open a console when the result is not ok.
404
413
  */
405
- export async function ensureBackgroundReady(target, deps, poll = DEFAULT_POLL) {
414
+ export async function ensureBackgroundReady(target, deps, poll) {
406
415
  const release = await deps.acquireLifecycleLock(target);
407
416
  if (release === undefined) {
408
417
  deps.stderr(ui.errorLine(`Another lifecycle command is already active for ${target.label}.`));
@@ -410,7 +419,7 @@ export async function ensureBackgroundReady(target, deps, poll = DEFAULT_POLL) {
410
419
  return { ok: false, action: "start", reason: "ownership" };
411
420
  }
412
421
  try {
413
- return await ensureBackgroundReadyUnlocked(target, deps, poll);
422
+ return await ensureBackgroundReadyUnlocked(target, deps, poll ?? DEFAULT_CONTROL_POLL, poll ?? DEFAULT_READINESS_POLL);
414
423
  }
415
424
  finally {
416
425
  await release().catch((error) => {
@@ -418,18 +427,58 @@ export async function ensureBackgroundReady(target, deps, poll = DEFAULT_POLL) {
418
427
  });
419
428
  }
420
429
  }
421
- async function ensureBackgroundReadyUnlocked(target, deps, poll) {
430
+ async function ensureBackgroundReadyUnlocked(target, deps, controlPoll, readinessPoll) {
422
431
  if (target.expectedSnapshot === undefined) {
423
432
  deps.stderr(ui.errorLine("Refusing to launch a managed worker without an approved background snapshot."));
424
433
  deps.stderr(ui.hint("No LaunchAgent changes were made. Retry from the wizard or `mono-agent start`."));
425
434
  return { ok: false, action: "start", reason: "snapshot" };
426
435
  }
427
- const uid = deps.getuid();
428
436
  if (!(await snapshotStillMatches(target, deps))) {
429
437
  reportSnapshotDrift(target, deps);
430
438
  return { ok: false, action: "start", reason: "snapshot" };
431
439
  }
440
+ let publicationBarrier;
441
+ try {
442
+ publicationBarrier = await (deps.acquireRuntimePublicationBarrier
443
+ ?? ((input) => acquireManagedRuntimePublicationBarrier({
444
+ label: input.label,
445
+ managedRoot: dirname(input.paths.logDir),
446
+ })))(target);
447
+ }
448
+ catch (error) {
449
+ reportLifecycleException(target, deps, "establish the managed runtime publication barrier", error);
450
+ return { ok: false, action: "start", reason: "ownership" };
451
+ }
452
+ if (publicationBarrier === undefined) {
453
+ deps.stderr(ui.errorLine(`Another runtime publication is already active for ${target.label}.`));
454
+ return { ok: false, action: "start", reason: "ownership" };
455
+ }
456
+ return await ensureBackgroundReadyWithPublicationBarrier(target, deps, controlPoll, readinessPoll, publicationBarrier);
457
+ }
458
+ async function ensureBackgroundReadyWithPublicationBarrier(target, deps, controlPoll, readinessPoll, publicationBarrier) {
459
+ let barrierReleased = false;
460
+ const releaseBarrier = async () => {
461
+ if (barrierReleased)
462
+ return;
463
+ await publicationBarrier.release();
464
+ barrierReleased = true;
465
+ };
466
+ try {
467
+ return await ensureBackgroundReadyAfterPublicationBarrier(target, deps, controlPoll, readinessPoll, releaseBarrier);
468
+ }
469
+ finally {
470
+ if (!barrierReleased) {
471
+ await releaseBarrier().catch((error) => {
472
+ deps.stderr(ui.errorLine(`Could not release runtime publication barrier for ${target.label}: ${error instanceof Error ? error.message : String(error)}`));
473
+ });
474
+ }
475
+ }
476
+ }
477
+ async function ensureBackgroundReadyAfterPublicationBarrier(target, deps, controlPoll, readinessPoll, releaseBarrier) {
478
+ const uid = deps.getuid();
432
479
  let launchTarget;
480
+ const runtimeStartedAt = deps.now();
481
+ deps.stdout(ui.hint("Verifying the durable managed runtime…"));
433
482
  try {
434
483
  const additionalPackages = await deps.resolveManagedRuntimePackages?.(target) ?? [];
435
484
  const runtime = await deps.ensureManagedRuntime({
@@ -441,7 +490,10 @@ async function ensureBackgroundReadyUnlocked(target, deps, poll) {
441
490
  ...target,
442
491
  cliPath: runtime.cliPath,
443
492
  nodePath: runtime.nodePath,
493
+ managedRuntimeLaunchProof: runtime.launchProof,
444
494
  };
495
+ deps.stdout(ui.hint(`Managed runtime ready (${runtimeVerificationLabel(runtime.verificationMode)}, `
496
+ + `${formatLifecycleDuration(deps.now() - runtimeStartedAt)}).`));
445
497
  }
446
498
  catch (error) {
447
499
  reportLifecycleException(target, deps, "install and verify the durable managed runtime", error);
@@ -475,6 +527,7 @@ async function ensureBackgroundReadyUnlocked(target, deps, poll) {
475
527
  }
476
528
  let outcome;
477
529
  let prepared = false;
530
+ deps.stdout(ui.hint("Replacing the managed worker…"));
478
531
  try {
479
532
  let interruptedMaintenance = await deps.readLaunchdLogMaintenanceIntent(launchTarget.paths);
480
533
  if (interruptedMaintenance?.phase === "stopping" || interruptedMaintenance?.phase === "restoring") {
@@ -483,7 +536,7 @@ async function ensureBackgroundReadyUnlocked(target, deps, poll) {
483
536
  throw new Error("Interrupted launchd-log maintenance lost its writer PID before durable stopped-writer proof; refusing restart.");
484
537
  }
485
538
  }
486
- outcome = await bootstrapOrRestart(launchTarget, deps, uid, poll, async () => {
539
+ outcome = await bootstrapOrRestart(launchTarget, deps, uid, controlPoll, async () => {
487
540
  const stoppedIntent = interruptedMaintenance;
488
541
  if (stoppedIntent?.phase === "stopped") {
489
542
  interruptedMaintenance = await deps.markLaunchdLogMaintenanceStopping(launchTarget.paths, stoppedIntent);
@@ -504,6 +557,9 @@ async function ensureBackgroundReadyUnlocked(target, deps, poll) {
504
557
  await writePlists(launchTarget, deps);
505
558
  sinceMs = deps.now();
506
559
  prepared = true;
560
+ // The replacement plist now contains the finalized-runtime proof. Let
561
+ // launchd respawn only after the stopped-window commit is complete.
562
+ await releaseBarrier();
507
563
  });
508
564
  }
509
565
  catch (error) {
@@ -519,18 +575,20 @@ async function ensureBackgroundReadyUnlocked(target, deps, poll) {
519
575
  let ready;
520
576
  try {
521
577
  ready = await pollInstanceReady(launchTarget, deps, {
522
- ...poll,
578
+ ...readinessPoll,
523
579
  sinceMs,
524
580
  ...(launchTarget.requireTui === true ? { requireTui: true } : {}),
525
581
  });
526
582
  }
527
583
  catch (error) {
528
- reportLifecycleException(launchTarget, deps, "read the worker readiness trace", error);
584
+ reportReadinessException(deps, error);
585
+ const stopped = await cleanUpUnreadyBackground(launchTarget, deps, controlPoll);
586
+ reportReadinessCleanup(launchTarget, deps, stopped);
529
587
  return { ok: false, action, reason: "readiness" };
530
588
  }
531
589
  if (ready === undefined) {
532
590
  if (!(await snapshotStillMatches(launchTarget, deps))) {
533
- const stopped = await stopBackgroundUnlocked(launchTarget, deps, poll);
591
+ const stopped = await stopBackgroundUnlocked(launchTarget, deps, controlPoll);
534
592
  reportSnapshotDrift(launchTarget, deps);
535
593
  if (stopped === 0) {
536
594
  deps.stderr(ui.style.dim("The drifted LaunchAgent was stopped before returning control.") + "\n");
@@ -540,13 +598,27 @@ async function ensureBackgroundReadyUnlocked(target, deps, poll) {
540
598
  }
541
599
  return { ok: false, action, reason: "snapshot" };
542
600
  }
543
- reportTimeout(launchTarget, deps);
601
+ reportTimeout(deps);
602
+ const stopped = await cleanUpUnreadyBackground(launchTarget, deps, controlPoll);
603
+ reportReadinessCleanup(launchTarget, deps, stopped);
544
604
  return { ok: false, action, reason: "timeout" };
545
605
  }
546
606
  const completedAction = outcome.restarted ? "restarted" : "started";
547
607
  printInstanceInfo(ready, launchTarget, deps, completedAction);
548
608
  return { ok: true, action: completedAction, source: ready };
549
609
  }
610
+ function runtimeVerificationLabel(mode) {
611
+ switch (mode) {
612
+ case "fast-reuse": return "warm reuse";
613
+ case "full-reuse": return "full verification";
614
+ case "installed": return "installed";
615
+ case "repaired": return "repaired";
616
+ }
617
+ }
618
+ function formatLifecycleDuration(milliseconds) {
619
+ const safe = Math.max(0, milliseconds);
620
+ return safe < 1_000 ? `${Math.round(safe)} ms` : `${(safe / 1_000).toFixed(1)} s`;
621
+ }
550
622
  async function prepareLaunchdDirectories(target, deps) {
551
623
  // launchd will not create the log file's parent directory, so make both dirs
552
624
  // before writing the plist that references them.
@@ -558,6 +630,9 @@ async function writePlists(target, deps) {
558
630
  if (target.expectedSnapshot === undefined) {
559
631
  throw new Error("A managed LaunchAgent requires an approved background snapshot.");
560
632
  }
633
+ if (target.managedRuntimeLaunchProof === undefined) {
634
+ throw new Error("A managed LaunchAgent requires a finalized runtime launch proof.");
635
+ }
561
636
  const maintenance = maintenancePathsForTarget(target);
562
637
  const mainXml = buildPlistXml({
563
638
  label: target.label,
@@ -567,6 +642,7 @@ async function writePlists(target, deps) {
567
642
  cwd: target.cwd,
568
643
  ...(target.envFile === undefined ? {} : { envFile: target.envFile }),
569
644
  expectedBackgroundSnapshot: encodeBackgroundSnapshot(target.expectedSnapshot),
645
+ expectedManagedRuntimeLaunch: target.managedRuntimeLaunchProof,
570
646
  stdoutPath: target.paths.stdoutPath,
571
647
  stderrPath: target.paths.stderrPath,
572
648
  environment: target.environment,
@@ -586,7 +662,7 @@ async function writePlists(target, deps) {
586
662
  await deps.writeFile(target.paths.plistPath, mainXml);
587
663
  await deps.writeFile(maintenance.plistPath, maintenanceXml);
588
664
  }
589
- async function ensureOwnerPrivateLaunchdDirectory(path) {
665
+ export async function ensureOwnerPrivateLaunchdDirectory(path) {
590
666
  const parent = dirname(path);
591
667
  const parentDetails = await lstat(parent);
592
668
  assertOwnerDirectory(parentDetails, parent, "LaunchAgent parent");
@@ -624,7 +700,7 @@ async function ensureOwnerPrivateLaunchdDirectory(path) {
624
700
  await handle.close();
625
701
  }
626
702
  }
627
- async function writeOwnerPrivateLaunchdFile(path, data) {
703
+ export async function writeOwnerPrivateLaunchdFile(path, data) {
628
704
  let existing;
629
705
  try {
630
706
  existing = await lstat(path);
@@ -913,7 +989,7 @@ function lifecycleFailure(result, detail) {
913
989
  function uniquePids(values) {
914
990
  return [...new Set(values.filter((value) => value !== undefined && value > 0))];
915
991
  }
916
- export async function stopBackground(target, deps, poll = DEFAULT_POLL) {
992
+ export async function stopBackground(target, deps, poll = DEFAULT_CONTROL_POLL) {
917
993
  const release = await deps.acquireLifecycleLock(target);
918
994
  if (release === undefined) {
919
995
  deps.stderr(ui.errorLine(`Another lifecycle command is already active for ${target.label}.`));
@@ -1015,13 +1091,27 @@ async function stopBackgroundUnlocked(target, deps, poll) {
1015
1091
  deps.stdout(ui.keyValue([["config", target.configPath]]));
1016
1092
  return 0;
1017
1093
  }
1018
- export async function statusBackground(target, deps) {
1094
+ export async function statusBackground(target, deps, options = {}) {
1019
1095
  const result = await deps.listTraceSources({ registryDir: target.registryDir, staleAfterMs: target.staleAfterMs });
1020
1096
  const classified = await Promise.all(result.sources.map(async (source) => ({
1021
1097
  source,
1022
1098
  matches: await matchesConfig(source, target.configPath),
1023
1099
  })));
1024
1100
  const current = classified.find((entry) => entry.matches)?.source;
1101
+ // Only surface other instances that are live or crashed — cleanly stopped
1102
+ // manifests linger in the registry and would just be noise.
1103
+ const others = classified
1104
+ .filter((entry) => !entry.matches && entry.source.health !== "stopped")
1105
+ .map((entry) => entry.source);
1106
+ if (options.json === true) {
1107
+ const instance = current === undefined ? null : await assembleInstanceStatus(current, target, deps);
1108
+ deps.stdout(`${JSON.stringify({
1109
+ ok: current?.health === "running",
1110
+ instance,
1111
+ others: others.map(assembleOtherInstanceStatus),
1112
+ })}\n`);
1113
+ return current?.health === "running" ? 0 : 1;
1114
+ }
1025
1115
  if (current === undefined) {
1026
1116
  deps.stdout(ui.style.dim(`No running mono-agent instance for ${target.configPath}.`) + "\n");
1027
1117
  deps.stdout(ui.hint(`Start it with: mono-agent start${commandFlags(target)}`));
@@ -1030,11 +1120,6 @@ export async function statusBackground(target, deps) {
1030
1120
  writeInstanceDetail(current, target, deps);
1031
1121
  await writeRunsHealthDetail(current, deps);
1032
1122
  }
1033
- // Only surface other instances that are live or crashed — cleanly stopped
1034
- // manifests linger in the registry and would just be noise.
1035
- const others = classified
1036
- .filter((entry) => !entry.matches && entry.source.health !== "stopped")
1037
- .map((entry) => entry.source);
1038
1123
  if (others.length > 0) {
1039
1124
  deps.stdout("\n" + ui.rule("Other mono-agent instances"));
1040
1125
  for (const source of others) {
@@ -1043,6 +1128,62 @@ export async function statusBackground(target, deps) {
1043
1128
  }
1044
1129
  return current?.health === "running" ? 0 : 1;
1045
1130
  }
1131
+ /**
1132
+ * The machine-readable instance record for `status --json`. It mirrors the
1133
+ * fields the human `writeInstanceDetail`/`writeRunsHealthDetail` render, reading
1134
+ * only the already-computed trace-source manifest (content-free, safe to publish)
1135
+ * and the same runs-health probe the human path performs — no extra probes.
1136
+ */
1137
+ async function assembleInstanceStatus(source, target, deps) {
1138
+ const metadata = source.metadata ?? {};
1139
+ const observability = metadata.observability;
1140
+ const sandbox = metadata.sandbox;
1141
+ const session = metadata.session;
1142
+ const channels = metadata.channels;
1143
+ return {
1144
+ sourceId: source.sourceId,
1145
+ label: source.label,
1146
+ launchdLabel: target.label,
1147
+ pid: source.pid ?? null,
1148
+ health: source.health,
1149
+ status: source.status,
1150
+ configPath: target.configPath,
1151
+ startedAt: source.startedAt,
1152
+ updatedAt: source.updatedAt,
1153
+ ...(source.artifactDir === undefined ? {} : { artifactDir: source.artifactDir }),
1154
+ ...(source.transports === undefined ? {} : { transports: source.transports }),
1155
+ logs: { stdout: target.paths.stdoutPath, stderr: target.paths.stderrPath },
1156
+ ...(isPlainRecord(observability) ? { observability } : {}),
1157
+ ...(isPlainRecord(sandbox) ? { sandbox } : {}),
1158
+ ...(isPlainRecord(session) ? { session } : {}),
1159
+ ...(isPlainRecord(channels) ? { channels } : {}),
1160
+ ...(source.memoryHealth === undefined ? {} : { memoryHealth: source.memoryHealth }),
1161
+ runsHealth: await assembleRunsHealthStatus(source, deps),
1162
+ };
1163
+ }
1164
+ async function assembleRunsHealthStatus(source, deps) {
1165
+ if (source.artifactDir === undefined || source.artifactDir.trim().length === 0) {
1166
+ return null;
1167
+ }
1168
+ const { totalRuns, warnings } = await deps.listRecordedRuns({
1169
+ artifactDir: source.artifactDir,
1170
+ maxRuns: RUNS_HEALTH_MAX_RUNS,
1171
+ scope: "agent",
1172
+ });
1173
+ return { totalRuns, warnings };
1174
+ }
1175
+ function assembleOtherInstanceStatus(source) {
1176
+ return {
1177
+ sourceId: source.sourceId,
1178
+ label: source.label,
1179
+ health: source.health,
1180
+ pid: source.pid ?? null,
1181
+ ...(source.configPath === undefined ? {} : { configPath: source.configPath }),
1182
+ };
1183
+ }
1184
+ function isPlainRecord(value) {
1185
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1186
+ }
1046
1187
  export async function tailLogs(target, deps, options) {
1047
1188
  const args = [
1048
1189
  "-n",
@@ -1146,9 +1287,27 @@ function reportLifecycleException(target, deps, operation, error) {
1146
1287
  deps.stderr(ui.style.dim("The committed agent files were preserved.") + "\n");
1147
1288
  reportLaunchRecovery(target, deps);
1148
1289
  }
1149
- function reportTimeout(target, deps) {
1290
+ function reportReadinessException(deps, error) {
1291
+ deps.stderr(ui.errorLine(`Failed to read the worker readiness trace: ${error instanceof Error ? error.message : String(error)}`));
1292
+ deps.stderr(ui.style.dim("The committed agent files were preserved.") + "\n");
1293
+ }
1294
+ function reportTimeout(deps) {
1150
1295
  deps.stderr(ui.errorLine("mono-agent did not report ready within the timeout."));
1151
- deps.stderr(ui.style.dim("The committed agent files were preserved. It may still be starting, or it may have failed.") + "\n");
1296
+ deps.stderr(ui.style.dim("The committed agent files were preserved.") + "\n");
1297
+ }
1298
+ async function cleanUpUnreadyBackground(target, deps, poll) {
1299
+ try {
1300
+ return await stopBackgroundUnlocked(target, { ...deps, stdout: () => undefined }, poll) === 0;
1301
+ }
1302
+ catch (error) {
1303
+ deps.stderr(ui.errorLine(`Failed while trying to stop the unready LaunchAgent: ${error instanceof Error ? error.message : String(error)}`));
1304
+ return false;
1305
+ }
1306
+ }
1307
+ function reportReadinessCleanup(target, deps, stopped) {
1308
+ deps.stderr(stopped
1309
+ ? ui.style.dim("The unready LaunchAgent and scheduled maintenance were stopped and their definitions removed before returning control.") + "\n"
1310
+ : ui.style.yellow("The unready LaunchAgent could not be proven stopped; a worker or maintenance helper may still be running.") + "\n");
1152
1311
  reportLaunchRecovery(target, deps);
1153
1312
  }
1154
1313
  function reportLaunchRecovery(target, deps) {
@@ -1190,10 +1349,10 @@ function writeInstanceDetail(source, target, deps) {
1190
1349
  deps.stdout(` ${line}\n`);
1191
1350
  }
1192
1351
  }
1193
- const channelLines = formatChannels(source);
1194
- if (channelLines.length > 0) {
1195
- deps.stdout(ui.rule("channels"));
1196
- for (const line of channelLines) {
1352
+ const channelSections = formatChannels(source);
1353
+ for (const section of channelSections) {
1354
+ deps.stdout(ui.rule(section.title));
1355
+ for (const line of section.lines) {
1197
1356
  deps.stdout(`${line}\n`);
1198
1357
  }
1199
1358
  }
@@ -1247,7 +1406,7 @@ async function matchesConfig(source, configPath) {
1247
1406
  return await canonicalBackgroundConfigPath(process.cwd(), source.configPath) === configPath;
1248
1407
  }
1249
1408
  function isReady(source, requireTui) {
1250
- if (source.health !== "running" || metadataReason(source) !== "startup-complete")
1409
+ if (source.health !== "running" || !hasCompletedManagedStartup(source))
1251
1410
  return false;
1252
1411
  if (source.memoryHealth?.status === "unhealthy")
1253
1412
  return false;
@@ -1268,10 +1427,6 @@ function channelRecords(source) {
1268
1427
  return Object.values(channels)
1269
1428
  .filter((value) => value !== null && typeof value === "object");
1270
1429
  }
1271
- function metadataReason(source) {
1272
- const reason = source.metadata?.reason;
1273
- return typeof reason === "string" ? reason : undefined;
1274
- }
1275
1430
  function startedAtMs(source) {
1276
1431
  const parsed = Date.parse(source.startedAt);
1277
1432
  return Number.isFinite(parsed) ? parsed : 0;
@@ -1539,10 +1694,10 @@ function formatChannels(source) {
1539
1694
  if (channels === null || typeof channels !== "object") {
1540
1695
  return [];
1541
1696
  }
1542
- return Object.entries(channels).map(([id, value]) => {
1697
+ return formatHumanChannelSections(Object.entries(channels).map(([id, value]) => {
1543
1698
  const { kind, text } = describeChannel(value);
1544
- return ` ${ui.channelBadge(kind)}${ui.style.bold(id.padEnd(11))} ${text}`;
1545
- });
1699
+ return { id, kind, text };
1700
+ }));
1546
1701
  }
1547
1702
  export function describeChannel(value) {
1548
1703
  if (value === null || typeof value !== "object") {