@mono-agent/agent-app 0.11.2 → 0.11.4

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 (374) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +222 -26
  3. package/dist/adapter-send-tools-main.js +7 -1
  4. package/dist/adapter-send-tools-main.js.map +1 -1
  5. package/dist/adapter-send-tools.d.ts +19 -1
  6. package/dist/adapter-send-tools.d.ts.map +1 -1
  7. package/dist/adapter-send-tools.js +127 -13
  8. package/dist/adapter-send-tools.js.map +1 -1
  9. package/dist/app-config.d.ts.map +1 -1
  10. package/dist/app-config.js +15 -2
  11. package/dist/app-config.js.map +1 -1
  12. package/dist/app-controller-channels.d.ts +8 -0
  13. package/dist/app-controller-channels.d.ts.map +1 -0
  14. package/dist/app-controller-channels.js +207 -0
  15. package/dist/app-controller-channels.js.map +1 -0
  16. package/dist/app-controller-continuation.d.ts +26 -0
  17. package/dist/app-controller-continuation.d.ts.map +1 -0
  18. package/dist/app-controller-continuation.js +198 -0
  19. package/dist/app-controller-continuation.js.map +1 -0
  20. package/dist/app-controller-lifecycle.d.ts +6 -0
  21. package/dist/app-controller-lifecycle.d.ts.map +1 -0
  22. package/dist/app-controller-lifecycle.js +81 -0
  23. package/dist/app-controller-lifecycle.js.map +1 -0
  24. package/dist/app-controller-maintenance.d.ts +13 -0
  25. package/dist/app-controller-maintenance.d.ts.map +1 -0
  26. package/dist/app-controller-maintenance.js +180 -0
  27. package/dist/app-controller-maintenance.js.map +1 -0
  28. package/dist/app-controller-memory-health.d.ts +12 -0
  29. package/dist/app-controller-memory-health.d.ts.map +1 -0
  30. package/dist/app-controller-memory-health.js +160 -0
  31. package/dist/app-controller-memory-health.js.map +1 -0
  32. package/dist/app-controller-memory.d.ts +9 -0
  33. package/dist/app-controller-memory.d.ts.map +1 -0
  34. package/dist/app-controller-memory.js +83 -0
  35. package/dist/app-controller-memory.js.map +1 -0
  36. package/dist/app-controller-responder.d.ts +20 -0
  37. package/dist/app-controller-responder.d.ts.map +1 -0
  38. package/dist/app-controller-responder.js +246 -0
  39. package/dist/app-controller-responder.js.map +1 -0
  40. package/dist/app-controller-traceability.d.ts +25 -0
  41. package/dist/app-controller-traceability.d.ts.map +1 -0
  42. package/dist/app-controller-traceability.js +418 -0
  43. package/dist/app-controller-traceability.js.map +1 -0
  44. package/dist/app-controller-utils.d.ts +38 -0
  45. package/dist/app-controller-utils.d.ts.map +1 -0
  46. package/dist/app-controller-utils.js +185 -0
  47. package/dist/app-controller-utils.js.map +1 -0
  48. package/dist/app-controller.d.ts +400 -0
  49. package/dist/app-controller.d.ts.map +1 -0
  50. package/dist/app-controller.js +336 -0
  51. package/dist/app-controller.js.map +1 -0
  52. package/dist/app.d.ts +7 -115
  53. package/dist/app.d.ts.map +1 -1
  54. package/dist/app.js +6 -1912
  55. package/dist/app.js.map +1 -1
  56. package/dist/artifact-retention.d.ts +4 -0
  57. package/dist/artifact-retention.d.ts.map +1 -1
  58. package/dist/artifact-retention.js +38 -0
  59. package/dist/artifact-retention.js.map +1 -1
  60. package/dist/backfill.d.ts +3 -1
  61. package/dist/backfill.d.ts.map +1 -1
  62. package/dist/backfill.js +5 -1
  63. package/dist/backfill.js.map +1 -1
  64. package/dist/background-runtime.d.ts +27 -0
  65. package/dist/background-runtime.d.ts.map +1 -1
  66. package/dist/background-runtime.js +383 -205
  67. package/dist/background-runtime.js.map +1 -1
  68. package/dist/background-worker-lease.d.ts.map +1 -1
  69. package/dist/background-worker-lease.js +41 -241
  70. package/dist/background-worker-lease.js.map +1 -1
  71. package/dist/background.d.ts +31 -9
  72. package/dist/background.d.ts.map +1 -1
  73. package/dist/background.js +543 -272
  74. package/dist/background.js.map +1 -1
  75. package/dist/channel-drivers/cron.d.ts +11 -0
  76. package/dist/channel-drivers/cron.d.ts.map +1 -0
  77. package/dist/channel-drivers/cron.js +168 -0
  78. package/dist/channel-drivers/cron.js.map +1 -0
  79. package/dist/channel-drivers/live.d.ts +11 -0
  80. package/dist/channel-drivers/live.d.ts.map +1 -0
  81. package/dist/channel-drivers/live.js +47 -0
  82. package/dist/channel-drivers/live.js.map +1 -0
  83. package/dist/channel-drivers/native-notify.d.ts +27 -0
  84. package/dist/channel-drivers/native-notify.d.ts.map +1 -0
  85. package/dist/channel-drivers/native-notify.js +94 -0
  86. package/dist/channel-drivers/native-notify.js.map +1 -0
  87. package/dist/channel-drivers/openai-api.d.ts +7 -0
  88. package/dist/channel-drivers/openai-api.d.ts.map +1 -0
  89. package/dist/channel-drivers/openai-api.js +63 -0
  90. package/dist/channel-drivers/openai-api.js.map +1 -0
  91. package/dist/channel-drivers/shared.d.ts +4 -0
  92. package/dist/channel-drivers/shared.d.ts.map +1 -0
  93. package/dist/channel-drivers/shared.js +5 -0
  94. package/dist/channel-drivers/shared.js.map +1 -0
  95. package/dist/channel-drivers/slack.d.ts +14 -0
  96. package/dist/channel-drivers/slack.d.ts.map +1 -0
  97. package/dist/channel-drivers/slack.js +227 -0
  98. package/dist/channel-drivers/slack.js.map +1 -0
  99. package/dist/channel-drivers/telegram.d.ts +11 -0
  100. package/dist/channel-drivers/telegram.d.ts.map +1 -0
  101. package/dist/channel-drivers/telegram.js +244 -0
  102. package/dist/channel-drivers/telegram.js.map +1 -0
  103. package/dist/channel-drivers/tui.d.ts +15 -0
  104. package/dist/channel-drivers/tui.d.ts.map +1 -0
  105. package/dist/channel-drivers/tui.js +108 -0
  106. package/dist/channel-drivers/tui.js.map +1 -0
  107. package/dist/channel-drivers/webhook.d.ts +7 -0
  108. package/dist/channel-drivers/webhook.d.ts.map +1 -0
  109. package/dist/channel-drivers/webhook.js +115 -0
  110. package/dist/channel-drivers/webhook.js.map +1 -0
  111. package/dist/channels.d.ts +21 -81
  112. package/dist/channels.d.ts.map +1 -1
  113. package/dist/channels.js +11 -1174
  114. package/dist/channels.js.map +1 -1
  115. package/dist/cli-args.d.ts +121 -0
  116. package/dist/cli-args.d.ts.map +1 -0
  117. package/dist/cli-args.js +532 -0
  118. package/dist/cli-args.js.map +1 -0
  119. package/dist/cli-background-command.d.ts +59 -0
  120. package/dist/cli-background-command.d.ts.map +1 -0
  121. package/dist/cli-background-command.js +475 -0
  122. package/dist/cli-background-command.js.map +1 -0
  123. package/dist/cli-cancellation.d.ts +6 -0
  124. package/dist/cli-cancellation.d.ts.map +1 -0
  125. package/dist/cli-cancellation.js +23 -0
  126. package/dist/cli-cancellation.js.map +1 -0
  127. package/dist/cli-commands.d.ts +12 -0
  128. package/dist/cli-commands.d.ts.map +1 -0
  129. package/dist/cli-commands.js +306 -0
  130. package/dist/cli-commands.js.map +1 -0
  131. package/dist/cli-help.d.ts +11 -0
  132. package/dist/cli-help.d.ts.map +1 -0
  133. package/dist/cli-help.js +262 -0
  134. package/dist/cli-help.js.map +1 -0
  135. package/dist/cli-init-command.d.ts +60 -0
  136. package/dist/cli-init-command.d.ts.map +1 -0
  137. package/dist/cli-init-command.js +1910 -0
  138. package/dist/cli-init-command.js.map +1 -0
  139. package/dist/cli-install-skill-command.d.ts +3 -0
  140. package/dist/cli-install-skill-command.d.ts.map +1 -0
  141. package/dist/cli-install-skill-command.js +51 -0
  142. package/dist/cli-install-skill-command.js.map +1 -0
  143. package/dist/cli-sandbox-command.d.ts +10 -0
  144. package/dist/cli-sandbox-command.d.ts.map +1 -0
  145. package/dist/cli-sandbox-command.js +100 -0
  146. package/dist/cli-sandbox-command.js.map +1 -0
  147. package/dist/cli-validate-config-command.d.ts +36 -0
  148. package/dist/cli-validate-config-command.d.ts.map +1 -0
  149. package/dist/cli-validate-config-command.js +317 -0
  150. package/dist/cli-validate-config-command.js.map +1 -0
  151. package/dist/cli.d.ts +6 -248
  152. package/dist/cli.d.ts.map +1 -1
  153. package/dist/cli.js +6 -3792
  154. package/dist/cli.js.map +1 -1
  155. package/dist/config-reference.d.ts +2 -1
  156. package/dist/config-reference.d.ts.map +1 -1
  157. package/dist/config-reference.js +34 -8
  158. package/dist/config-reference.js.map +1 -1
  159. package/dist/configured-agent.d.ts +23 -0
  160. package/dist/configured-agent.d.ts.map +1 -1
  161. package/dist/configured-agent.js +92 -13
  162. package/dist/configured-agent.js.map +1 -1
  163. package/dist/continuation-dispatch-worker.d.ts +21 -0
  164. package/dist/continuation-dispatch-worker.d.ts.map +1 -0
  165. package/dist/continuation-dispatch-worker.js +425 -0
  166. package/dist/continuation-dispatch-worker.js.map +1 -0
  167. package/dist/continuation-http-routes.d.ts +15 -0
  168. package/dist/continuation-http-routes.d.ts.map +1 -0
  169. package/dist/continuation-http-routes.js +265 -0
  170. package/dist/continuation-http-routes.js.map +1 -0
  171. package/dist/continuation-operator-api.d.ts +18 -0
  172. package/dist/continuation-operator-api.d.ts.map +1 -0
  173. package/dist/continuation-operator-api.js +164 -0
  174. package/dist/continuation-operator-api.js.map +1 -0
  175. package/dist/continuation-origin-store.d.ts +19 -0
  176. package/dist/continuation-origin-store.d.ts.map +1 -0
  177. package/dist/continuation-origin-store.js +273 -0
  178. package/dist/continuation-origin-store.js.map +1 -0
  179. package/dist/continuation-service-errors.d.ts +18 -0
  180. package/dist/continuation-service-errors.d.ts.map +1 -0
  181. package/dist/continuation-service-errors.js +35 -0
  182. package/dist/continuation-service-errors.js.map +1 -0
  183. package/dist/continuation-service-helpers.d.ts +39 -0
  184. package/dist/continuation-service-helpers.d.ts.map +1 -0
  185. package/dist/continuation-service-helpers.js +308 -0
  186. package/dist/continuation-service-helpers.js.map +1 -0
  187. package/dist/continuation-service-origin.d.ts +55 -0
  188. package/dist/continuation-service-origin.d.ts.map +1 -0
  189. package/dist/continuation-service-origin.js +325 -0
  190. package/dist/continuation-service-origin.js.map +1 -0
  191. package/dist/continuation-service-types.d.ts +110 -0
  192. package/dist/continuation-service-types.d.ts.map +1 -0
  193. package/dist/continuation-service-types.js +11 -0
  194. package/dist/continuation-service-types.js.map +1 -0
  195. package/dist/continuation-service.d.ts +4 -97
  196. package/dist/continuation-service.d.ts.map +1 -1
  197. package/dist/continuation-service.js +9 -1525
  198. package/dist/continuation-service.js.map +1 -1
  199. package/dist/continuation-store-fs.d.ts +15 -0
  200. package/dist/continuation-store-fs.d.ts.map +1 -0
  201. package/dist/continuation-store-fs.js +247 -0
  202. package/dist/continuation-store-fs.js.map +1 -0
  203. package/dist/continuation-store-policy.d.ts +21 -0
  204. package/dist/continuation-store-policy.d.ts.map +1 -0
  205. package/dist/continuation-store-policy.js +278 -0
  206. package/dist/continuation-store-policy.js.map +1 -0
  207. package/dist/continuation-store-records.d.ts +9 -0
  208. package/dist/continuation-store-records.d.ts.map +1 -0
  209. package/dist/continuation-store-records.js +221 -0
  210. package/dist/continuation-store-records.js.map +1 -0
  211. package/dist/continuation-store-types.d.ts +173 -0
  212. package/dist/continuation-store-types.d.ts.map +1 -0
  213. package/dist/continuation-store-types.js +26 -0
  214. package/dist/continuation-store-types.js.map +1 -0
  215. package/dist/continuation-store.d.ts +4 -138
  216. package/dist/continuation-store.d.ts.map +1 -1
  217. package/dist/continuation-store.js +145 -1049
  218. package/dist/continuation-store.js.map +1 -1
  219. package/dist/doctor.d.ts +10 -5
  220. package/dist/doctor.d.ts.map +1 -1
  221. package/dist/doctor.js +629 -58
  222. package/dist/doctor.js.map +1 -1
  223. package/dist/init.d.ts +2 -0
  224. package/dist/init.d.ts.map +1 -1
  225. package/dist/init.js +92 -239
  226. package/dist/init.js.map +1 -1
  227. package/dist/interaction-bridge.d.ts +19 -0
  228. package/dist/interaction-bridge.d.ts.map +1 -1
  229. package/dist/interaction-bridge.js +130 -0
  230. package/dist/interaction-bridge.js.map +1 -1
  231. package/dist/launchd-logs.d.ts +91 -0
  232. package/dist/launchd-logs.d.ts.map +1 -0
  233. package/dist/launchd-logs.js +1499 -0
  234. package/dist/launchd-logs.js.map +1 -0
  235. package/dist/launchd.d.ts +27 -0
  236. package/dist/launchd.d.ts.map +1 -1
  237. package/dist/launchd.js +89 -10
  238. package/dist/launchd.js.map +1 -1
  239. package/dist/memory-command.d.ts +2 -0
  240. package/dist/memory-command.d.ts.map +1 -1
  241. package/dist/memory-command.js +161 -48
  242. package/dist/memory-command.js.map +1 -1
  243. package/dist/memory-recall-settings.d.ts +64 -0
  244. package/dist/memory-recall-settings.d.ts.map +1 -0
  245. package/dist/memory-recall-settings.js +51 -0
  246. package/dist/memory-recall-settings.js.map +1 -0
  247. package/dist/memory-recall.d.ts +7 -95
  248. package/dist/memory-recall.d.ts.map +1 -1
  249. package/dist/memory-recall.js +8 -143
  250. package/dist/memory-recall.js.map +1 -1
  251. package/dist/memory-rituals.d.ts +3 -3
  252. package/dist/memory-rituals.d.ts.map +1 -1
  253. package/dist/memory-rituals.js +16 -111
  254. package/dist/memory-rituals.js.map +1 -1
  255. package/dist/modules/catalog.js +2 -2
  256. package/dist/modules/catalog.js.map +1 -1
  257. package/dist/modules/known-tools.d.ts +8 -6
  258. package/dist/modules/known-tools.d.ts.map +1 -1
  259. package/dist/modules/known-tools.js +8 -6
  260. package/dist/modules/known-tools.js.map +1 -1
  261. package/dist/notify-destinations.d.ts +5 -0
  262. package/dist/notify-destinations.d.ts.map +1 -1
  263. package/dist/notify-destinations.js +7 -1
  264. package/dist/notify-destinations.js.map +1 -1
  265. package/dist/owner-private-lock.d.ts +95 -0
  266. package/dist/owner-private-lock.d.ts.map +1 -0
  267. package/dist/owner-private-lock.js +566 -0
  268. package/dist/owner-private-lock.js.map +1 -0
  269. package/dist/pi-oauth-login.d.ts +2 -0
  270. package/dist/pi-oauth-login.d.ts.map +1 -1
  271. package/dist/pi-oauth-login.js +39 -5
  272. package/dist/pi-oauth-login.js.map +1 -1
  273. package/dist/posted-message-index.d.ts +35 -16
  274. package/dist/posted-message-index.d.ts.map +1 -1
  275. package/dist/posted-message-index.js +1068 -52
  276. package/dist/posted-message-index.js.map +1 -1
  277. package/dist/posted-reply-history.d.ts +17 -0
  278. package/dist/posted-reply-history.d.ts.map +1 -0
  279. package/dist/posted-reply-history.js +169 -0
  280. package/dist/posted-reply-history.js.map +1 -0
  281. package/dist/proactive-notify.d.ts.map +1 -1
  282. package/dist/proactive-notify.js +6 -4
  283. package/dist/proactive-notify.js.map +1 -1
  284. package/dist/project-skills.d.ts +4 -0
  285. package/dist/project-skills.d.ts.map +1 -1
  286. package/dist/project-skills.js +99 -130
  287. package/dist/project-skills.js.map +1 -1
  288. package/dist/provider-setup.d.ts +0 -1
  289. package/dist/provider-setup.d.ts.map +1 -1
  290. package/dist/provider-setup.js +0 -3
  291. package/dist/provider-setup.js.map +1 -1
  292. package/dist/readiness-probe-worker.d.ts +61 -1
  293. package/dist/readiness-probe-worker.d.ts.map +1 -1
  294. package/dist/readiness-probe-worker.js +54 -58
  295. package/dist/readiness-probe-worker.js.map +1 -1
  296. package/dist/readiness-probe.d.ts +6 -9
  297. package/dist/readiness-probe.d.ts.map +1 -1
  298. package/dist/readiness-probe.js +30 -56
  299. package/dist/readiness-probe.js.map +1 -1
  300. package/dist/redact-secrets.d.ts +15 -0
  301. package/dist/redact-secrets.d.ts.map +1 -0
  302. package/dist/redact-secrets.js +65 -0
  303. package/dist/redact-secrets.js.map +1 -0
  304. package/dist/run-history.js +34 -24
  305. package/dist/run-history.js.map +1 -1
  306. package/dist/runtime-provenance.d.ts +8 -0
  307. package/dist/runtime-provenance.d.ts.map +1 -0
  308. package/dist/runtime-provenance.js +225 -0
  309. package/dist/runtime-provenance.js.map +1 -0
  310. package/dist/sandbox-manager.d.ts +4 -1
  311. package/dist/sandbox-manager.d.ts.map +1 -1
  312. package/dist/sandbox-manager.js +114 -273
  313. package/dist/sandbox-manager.js.map +1 -1
  314. package/dist/secure-file-replace.d.ts +49 -0
  315. package/dist/secure-file-replace.d.ts.map +1 -0
  316. package/dist/secure-file-replace.js +270 -0
  317. package/dist/secure-file-replace.js.map +1 -0
  318. package/dist/seen-conversations.d.ts +32 -5
  319. package/dist/seen-conversations.d.ts.map +1 -1
  320. package/dist/seen-conversations.js +145 -15
  321. package/dist/seen-conversations.js.map +1 -1
  322. package/dist/sessions.d.ts +20 -0
  323. package/dist/sessions.d.ts.map +1 -1
  324. package/dist/sessions.js +41 -6
  325. package/dist/sessions.js.map +1 -1
  326. package/dist/wizard/index.d.ts +2 -2
  327. package/dist/wizard/index.d.ts.map +1 -1
  328. package/dist/wizard/index.js +1 -1
  329. package/dist/wizard/index.js.map +1 -1
  330. package/dist/wizard/presets.d.ts +2 -1
  331. package/dist/wizard/presets.d.ts.map +1 -1
  332. package/dist/wizard/presets.js +2 -1
  333. package/dist/wizard/presets.js.map +1 -1
  334. package/dist/wizard/run.d.ts +0 -26
  335. package/dist/wizard/run.d.ts.map +1 -1
  336. package/dist/wizard/run.js +0 -53
  337. package/dist/wizard/run.js.map +1 -1
  338. package/package.json +22 -17
  339. package/schema/mono-agent.config.schema.json +45 -10
  340. package/skills/mono-agent-composer/SKILL.md +3 -3
  341. package/skills/mono-agent-composer/references/config-blueprint.md +123 -29
  342. package/skills/mono-agent-composer/references/discovery-questions.md +10 -8
  343. package/skills/mono-agent-composer/references/feature-coverage.md +90 -76
  344. package/skills/mono-agent-composer/references/package-map.md +6 -4
  345. package/skills/mono-agent-composer/references/playbooks.md +48 -27
  346. package/skills/mono-agent-composer/references/validation.md +16 -3
  347. package/dist/notify-runtime.d.ts +0 -26
  348. package/dist/notify-runtime.d.ts.map +0 -1
  349. package/dist/notify-runtime.js +0 -27
  350. package/dist/notify-runtime.js.map +0 -1
  351. package/dist/notify-tool.d.ts +0 -51
  352. package/dist/notify-tool.d.ts.map +0 -1
  353. package/dist/notify-tool.js +0 -182
  354. package/dist/notify-tool.js.map +0 -1
  355. package/dist/recipes/base.d.ts +0 -15
  356. package/dist/recipes/base.d.ts.map +0 -1
  357. package/dist/recipes/base.js +0 -51
  358. package/dist/recipes/base.js.map +0 -1
  359. package/dist/recipes/catalog.d.ts +0 -4
  360. package/dist/recipes/catalog.d.ts.map +0 -1
  361. package/dist/recipes/catalog.js +0 -525
  362. package/dist/recipes/catalog.js.map +0 -1
  363. package/dist/recipes/index.d.ts +0 -11
  364. package/dist/recipes/index.d.ts.map +0 -1
  365. package/dist/recipes/index.js +0 -14
  366. package/dist/recipes/index.js.map +0 -1
  367. package/dist/recipes/types.d.ts +0 -70
  368. package/dist/recipes/types.d.ts.map +0 -1
  369. package/dist/recipes/types.js +0 -15
  370. package/dist/recipes/types.js.map +0 -1
  371. package/dist/setup.d.ts +0 -29
  372. package/dist/setup.d.ts.map +0 -1
  373. package/dist/setup.js +0 -97
  374. package/dist/setup.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,3796 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { createHash } from "node:crypto";
3
- import { stat } from "node:fs/promises";
4
- import { createRequire } from "node:module";
5
- import { basename, dirname, resolve } from "node:path";
6
- import process from "node:process";
7
- import { emitKeypressEvents } from "node:readline";
8
- import { fileURLToPath } from "node:url";
9
- import { buildMonoAgentConfigView, EFFORT_LEVELS, findJsonSecretConfigWarnings, findRemovedConfigWarnings, MAX_AGENT_NAME_LENGTH, readMonoAgentConfigJson, redactMonoAgentConfig, } from "@mono-agent/config";
10
- import { listRecordedRuns } from "@mono-agent/observability";
11
- import { describeSandboxEffectiveState, parseMonoRuntimeModelReference, sandboxEffectiveStateWarning, } from "@mono-agent/runtime-adapter";
12
- import { startMonoAgentApp } from "./app.js";
13
- import { describeSensitiveDataExportWarning, isAppCoreConfigError, loadAppCoreConfig, phoenixAppBaseUrl, } from "./app-config.js";
14
- import { runAuditRuns } from "./audit-runs.js";
15
- import { runBackfill } from "./backfill.js";
16
- import { acquireBackgroundWorkerLease, canonicalBackgroundConfigPath, defaultBackgroundDeps, ensureBackgroundReady, forceRestartBackground, managedBackgroundEnvironment, resolveInstanceTarget, restartBackground, startBackground, statusBackground, stopBackground, tailLogs, } from "./background.js";
17
- import { MANAGED_BACKGROUND_WORKER_ENV, sanitizeManagedBackgroundWorkerEnvironment, } from "./background-runtime.js";
18
- import { captureBackgroundSnapshot, decodeBackgroundSnapshot, captureDurableBackgroundInputs, loadDurableBackgroundEnvironment, materializeBackgroundRuntimeInputs, } from "./background-snapshot.js";
19
- import { collectChannelConfigViews } from "./channel-config-view.js";
20
- import { formatChannelFactValue } from "./channel-fact-format.js";
21
- import { resolveChannelDrivers } from "./channels.js";
22
- import { findUnknownAppConfigWarnings } from "./config-reference.js";
23
- import { validateMonoAgentFolder } from "./doctor.js";
24
- import { effectiveFirstRunEnvironment, evaluateFirstRunConfigurationReadiness, evaluateFirstRunReadiness, hasSensitivePersistedEnvironmentValue, piAuthPathBackgroundConflict, readCliConfigSnapshot, readCliDotenvFile, readCliDotenvSnapshot, resolveEffectivePiAuthPath, selectedSecretEnvironmentConflicts, selectedSecretValues, unexpectedPersistedMonoAgentOverrides, validateWizardPlanInStaging, withExactProcessEnvironment, } from "./first-run-readiness.js";
25
- import { initMonoAgentFolder, SecretEnvConcurrentModificationError, verifySecretEnvPersistenceGuard, } from "./init.js";
26
- import { installComposerSkill } from "./install-skill.js";
27
- import { deriveLaunchdLabel, launchdPathsFor } from "./launchd.js";
28
- import { checkManagedProjectSkills, updateManagedProjectSkills } from "./project-skills.js";
29
- import { runMetrics } from "./metrics.js";
30
- import { buildRunsHealthDisplay, RUNS_HEALTH_MAX_RUNS } from "./runs-health.js";
31
- import { purgeSessions } from "./sessions.js";
32
- import { composeWizardPlan, referencedSetupModelRefs } from "./wizard/answers.js";
33
- import { answersFromCli, isWithChannel } from "./wizard/from-flags.js";
34
- import { findPreset, PRESET_CATALOG, presetAnswers, presetIds, RECIPE_TO_PRESET } from "./wizard/presets.js";
35
- import { runInitWizard, runSetupRepairWizard } from "./wizard/run.js";
36
- import { detectProviderCredentialStates, executeProviderSetupPlan, isProviderSetupPiApiKeyAction, planProviderSetup, providerSetupActionCommandLine, } from "./provider-setup.js";
37
- import { readinessProbeTimeoutMs, runAllRouteReadinessProbe } from "./readiness-probe.js";
38
- import { checkSandboxRuntime, sandboxRuntimeStatus, setupManagedSrt, } from "./sandbox-manager.js";
39
- import * as p from "@clack/prompts";
40
- import * as ui from "./ui.js";
41
- const DEFAULT_LOG_LINES = 200;
42
- // Node's maximum setInterval/setTimeout delay (2^31 - 1 ms, ~24.8 days). A
43
- // referenced timer at this delay keeps the foreground event loop alive without
44
- // busy-waiting; larger values silently overflow to a 1ms delay.
45
- const KEEP_ALIVE_INTERVAL_MS = 2_147_483_647;
46
- const BACKGROUND_COMMANDS = ["start", "restart", "stop", "status", "logs"];
47
- const KNOWN_COMMANDS = ["init", "setup", "validate", "doctor", "auth", "sandbox", "config", "recipes", "presets", "start", "restart", "stop", "status", "logs", "tui", "web", "install-skill", "backfill", "audit-runs", "metrics", "memory", "continuations"];
48
- export function parseCliArgs(argv) {
49
- const [command, ...rest] = argv;
50
- if (command === undefined || command === "help" || command === "--help" || command === "-h") {
51
- return { command: "help", positionals: [], force: false, foreground: false, follow: false, all: false, dryRun: false, includeMemory: false };
52
- }
53
- if (command === "version" || command === "--version" || command === "-v") {
54
- return { command: "version", positionals: [], force: false, foreground: false, follow: false, all: false, dryRun: false, includeMemory: false };
55
- }
56
- if (!KNOWN_COMMANDS.includes(command)) {
57
- throw new Error(`Unknown command \`${command}\`. Expected ${KNOWN_COMMANDS.join(", ")}.`);
58
- }
59
- // `doctor`/`setup`/`recipes` are aliases; normalize here so every downstream
60
- // path (routing, env-file resolution, --consumer) applies unchanged. `doctor`
61
- // → `validate`, `setup` → `init`, `recipes` → `presets`.
62
- const cmd = (command === "doctor"
63
- ? "validate"
64
- : command === "setup"
65
- ? "init"
66
- : command === "recipes"
67
- ? "presets"
68
- : command);
69
- const isLogs = cmd === "logs";
70
- let configPath;
71
- let name;
72
- let model;
73
- let fallbackModels;
74
- const fallbacks = [];
75
- let canAssignFallbackEffort = false;
76
- let routeSafety;
77
- let effort;
78
- let memory;
79
- let preset;
80
- let recipe;
81
- let withChannels;
82
- let yes = false;
83
- let auth = false;
84
- let piAuthPath;
85
- let apiKeyStdin = false;
86
- let codexAuthMode;
87
- const positionals = [];
88
- let envFile;
89
- let expectedBackgroundSnapshot;
90
- let target;
91
- let force = false;
92
- let foreground = false;
93
- let follow = false;
94
- let lines;
95
- let run;
96
- let all = false;
97
- let since;
98
- let until;
99
- let dryRun = false;
100
- let includeMemory = false;
101
- let artifactDir;
102
- let groupBy;
103
- let consumerPath;
104
- let agent;
105
- let conversation;
106
- let local = false;
107
- let configure = false;
108
- let project = false;
109
- let check = false;
110
- let update = false;
111
- let staleAfterMs;
112
- let json = false;
113
- let strict = false;
114
- let limit;
115
- let cursor;
116
- let idsFile;
117
- let reason;
118
- let planPath;
119
- let backupPath;
120
- let host;
121
- let port;
122
- let open;
123
- let allowNonLoopback;
124
- let showAuthUrl;
125
- let maxRunsPerInstance;
126
- for (let i = 0; i < rest.length; i += 1) {
127
- const flag = rest[i];
128
- if (flag !== "--fallback-effort")
129
- canAssignFallbackEffort = false;
130
- switch (flag) {
131
- case "--config":
132
- configPath = requireValue(rest, ++i, flag);
133
- break;
134
- case "--run":
135
- run = requireValue(rest, ++i, flag);
136
- break;
137
- case "--all":
138
- all = true;
139
- break;
140
- case "--since":
141
- since = requireValue(rest, ++i, flag);
142
- break;
143
- case "--until":
144
- until = requireValue(rest, ++i, flag);
145
- break;
146
- case "--dry-run":
147
- dryRun = true;
148
- break;
149
- case "--include-memory":
150
- includeMemory = true;
151
- break;
152
- case "--artifact-dir":
153
- artifactDir = requireValue(rest, ++i, flag);
154
- break;
155
- case "--artifacts":
156
- artifactDir = requireValue(rest, ++i, flag);
157
- break;
158
- case "--by": {
159
- const raw = requireValue(rest, ++i, flag);
160
- if (raw !== "model" && raw !== "channel" && raw !== "failureKind") {
161
- throw new Error("--by must be model, channel, or failureKind.");
162
- }
163
- groupBy = raw;
164
- break;
165
- }
166
- case "--consumer":
167
- consumerPath = requireValue(rest, ++i, flag);
168
- break;
169
- case "--agent":
170
- agent = requireValue(rest, ++i, flag);
171
- break;
172
- case "--conversation":
173
- conversation = requireValue(rest, ++i, flag);
174
- break;
175
- case "--local":
176
- local = true;
177
- break;
178
- case "--configure":
179
- configure = true;
180
- break;
181
- case "--project":
182
- project = true;
183
- break;
184
- case "--check":
185
- check = true;
186
- break;
187
- case "--update":
188
- update = true;
189
- break;
190
- case "--stale-after-ms": {
191
- const raw = requireValue(rest, ++i, flag);
192
- const parsed = Number(raw);
193
- if (!Number.isInteger(parsed) || parsed < 1) {
194
- throw new Error("--stale-after-ms must be a positive integer.");
195
- }
196
- staleAfterMs = parsed;
197
- break;
198
- }
199
- case "--json":
200
- json = true;
201
- break;
202
- case "--strict":
203
- strict = true;
204
- break;
205
- case "--limit": {
206
- const raw = requireValue(rest, ++i, flag);
207
- const parsed = Number(raw);
208
- const maximum = cmd === "continuations" ? 500 : 100;
209
- if (!Number.isInteger(parsed) || parsed < 1 || parsed > maximum) {
210
- throw new Error(`--limit must be an integer between 1 and ${String(maximum)}.`);
211
- }
212
- limit = parsed;
213
- break;
214
- }
215
- case "--cursor":
216
- cursor = requireValue(rest, ++i, flag).trim();
217
- if (cursor.length === 0 || cursor.length > 512)
218
- throw new Error("--cursor must be 1-512 characters.");
219
- break;
220
- case "--ids-file":
221
- idsFile = requireValue(rest, ++i, flag);
222
- break;
223
- case "--reason":
224
- reason = requireValue(rest, ++i, flag);
225
- break;
226
- case "--plan":
227
- planPath = requireValue(rest, ++i, flag);
228
- break;
229
- case "--backup":
230
- backupPath = requireValue(rest, ++i, flag);
231
- break;
232
- case "--host":
233
- host = requireValue(rest, ++i, flag);
234
- break;
235
- case "--port": {
236
- const raw = requireValue(rest, ++i, flag);
237
- const parsed = Number(raw);
238
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
239
- throw new Error("--port must be an integer between 0 and 65535.");
240
- }
241
- port = parsed;
242
- break;
243
- }
244
- case "--no-open":
245
- open = false;
246
- break;
247
- case "--allow-non-loopback":
248
- allowNonLoopback = true;
249
- break;
250
- case "--show-auth-url":
251
- showAuthUrl = true;
252
- break;
253
- case "--max-runs": {
254
- const raw = requireValue(rest, ++i, flag);
255
- const parsed = Number(raw);
256
- if (!Number.isInteger(parsed) || parsed < 1) {
257
- throw new Error("--max-runs must be a positive integer.");
258
- }
259
- maxRunsPerInstance = parsed;
260
- break;
261
- }
262
- case "--model":
263
- model = requireValue(rest, ++i, flag);
264
- break;
265
- case "--name":
266
- name = requireValue(rest, ++i, flag).trim();
267
- if (Array.from(name).length === 0
268
- || Array.from(name).length > MAX_AGENT_NAME_LENGTH
269
- || /[\u0000-\u001f\u007f]/u.test(name)) {
270
- throw new Error(`--name must be 1-${MAX_AGENT_NAME_LENGTH} characters on one line.`);
271
- }
272
- break;
273
- case "--fallback-models":
274
- fallbackModels = requireValue(rest, ++i, flag)
275
- .split(",")
276
- .map((entry) => entry.trim())
277
- .filter((entry) => entry.length > 0);
278
- break;
279
- case "--fallback": {
280
- const fallbackModel = requireValue(rest, ++i, flag).trim();
281
- if (fallbacks.some((entry) => entry.model === fallbackModel)) {
282
- throw new Error(`Duplicate --fallback model \`${fallbackModel}\`.`);
283
- }
284
- fallbacks.push({ model: fallbackModel });
285
- canAssignFallbackEffort = true;
286
- break;
287
- }
288
- case "--fallback-effort": {
289
- if (!canAssignFallbackEffort || fallbacks.length === 0) {
290
- throw new Error("--fallback-effort must immediately follow the --fallback it configures.");
291
- }
292
- const raw = requireValue(rest, ++i, flag);
293
- if (raw !== "provider-default" && !EFFORT_LEVELS.includes(raw)) {
294
- throw new Error(`--fallback-effort must be provider-default or ${EFFORT_LEVELS.join(", ")}.`);
295
- }
296
- if (raw !== "provider-default") {
297
- const current = fallbacks[fallbacks.length - 1];
298
- fallbacks[fallbacks.length - 1] = {
299
- ...current,
300
- effort: raw,
301
- };
302
- }
303
- canAssignFallbackEffort = false;
304
- break;
305
- }
306
- case "--route-safety": {
307
- const raw = requireValue(rest, ++i, flag);
308
- if (raw !== "uniform" && raw !== "per-route-native") {
309
- throw new Error("--route-safety must be uniform or per-route-native.");
310
- }
311
- routeSafety = raw;
312
- break;
313
- }
314
- case "--effort": {
315
- const raw = requireValue(rest, ++i, flag);
316
- if (!EFFORT_LEVELS.includes(raw)) {
317
- throw new Error(`--effort must be ${EFFORT_LEVELS.join(", ")}.`);
318
- }
319
- effort = raw;
320
- break;
321
- }
322
- case "--memory": {
323
- const raw = requireValue(rest, ++i, flag);
324
- if (raw !== "lite" && raw !== "journal" && raw !== "bujo") {
325
- throw new Error("--memory must be lite, journal, or bujo.");
326
- }
327
- memory = raw;
328
- break;
329
- }
330
- case "--preset":
331
- preset = requireValue(rest, ++i, flag);
332
- break;
333
- case "--recipe":
334
- recipe = requireValue(rest, ++i, flag);
335
- break;
336
- case "--yes":
337
- yes = true;
338
- break;
339
- case "--auth":
340
- auth = true;
341
- break;
342
- case "--pi-auth-path":
343
- piAuthPath = requireValue(rest, ++i, flag);
344
- break;
345
- case "--api-key-stdin":
346
- apiKeyStdin = true;
347
- break;
348
- case "--codex-auth": {
349
- const raw = requireValue(rest, ++i, flag);
350
- if (raw !== "browser" && raw !== "device") {
351
- throw new Error("--codex-auth must be browser or device.");
352
- }
353
- codexAuthMode = raw;
354
- break;
355
- }
356
- case "--with":
357
- withChannels = requireValue(rest, ++i, flag)
358
- .split(",")
359
- .map((entry) => entry.trim())
360
- .filter((entry) => entry.length > 0);
361
- break;
362
- case "--env-file":
363
- envFile = requireValue(rest, ++i, flag);
364
- break;
365
- case "--expected-background-snapshot":
366
- expectedBackgroundSnapshot = requireValue(rest, ++i, flag);
367
- break;
368
- case "--target": {
369
- const raw = requireValue(rest, ++i, flag);
370
- if (raw !== "claude" && raw !== "codex" && raw !== "both") {
371
- throw new Error("--target must be claude, codex, or both.");
372
- }
373
- target = raw;
374
- break;
375
- }
376
- case "--force":
377
- force = true;
378
- break;
379
- case "--foreground":
380
- foreground = true;
381
- break;
382
- case "--follow":
383
- follow = true;
384
- break;
385
- // `-f` is foreground for start, follow for logs.
386
- case "-f":
387
- if (isLogs) {
388
- follow = true;
389
- }
390
- else {
391
- foreground = true;
392
- }
393
- break;
394
- case "--lines": {
395
- const raw = requireValue(rest, ++i, flag);
396
- const parsed = Number(raw);
397
- if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100_000) {
398
- throw new Error("--lines must be a positive integer between 1 and 100000.");
399
- }
400
- lines = parsed;
401
- break;
402
- }
403
- default:
404
- if (flag === undefined) {
405
- break;
406
- }
407
- if (flag.startsWith("--")) {
408
- throw new Error(`Unknown flag \`${flag}\` for \`mono-agent ${command}\`.`);
409
- }
410
- // Non-flag tokens are positional arguments (e.g. `recipes show <id>`).
411
- positionals.push(flag);
412
- break;
413
- }
414
- }
415
- if (consumerPath !== undefined && cmd !== "validate" && cmd !== "audit-runs") {
416
- throw new Error("--consumer is only supported for `mono-agent validate` and `mono-agent audit-runs`.");
417
- }
418
- if ((local || configure) && cmd !== "tui") {
419
- throw new Error("--local and --configure are only supported for `mono-agent tui`.");
420
- }
421
- if (configure && local) {
422
- throw new Error("--configure attaches to the authoritative background agent; omit --local.");
423
- }
424
- if ((project || check || update) && cmd !== "install-skill") {
425
- throw new Error("--project, --check, and --update are only supported for `mono-agent install-skill`.");
426
- }
427
- if ((check || update) && !project) {
428
- throw new Error("--check and --update require `mono-agent install-skill --project`.");
429
- }
430
- if (check && update) {
431
- throw new Error("Choose either --check or --update for project skills.");
432
- }
433
- if ((host !== undefined
434
- || port !== undefined
435
- || open !== undefined
436
- || allowNonLoopback !== undefined
437
- || showAuthUrl !== undefined
438
- || maxRunsPerInstance !== undefined) &&
439
- cmd !== "web") {
440
- throw new Error("--host, --port, --no-open, --allow-non-loopback, --show-auth-url, and --max-runs are only supported for `mono-agent web`.");
441
- }
442
- if (includeMemory && cmd !== "audit-runs" && cmd !== "metrics" && cmd !== "backfill" && cmd !== "web") {
443
- throw new Error("--include-memory is only supported for `mono-agent audit-runs`, `mono-agent metrics`, `mono-agent backfill`, and `mono-agent web`.");
444
- }
445
- if (limit !== undefined && cmd !== "memory" && cmd !== "continuations") {
446
- throw new Error("--limit is only supported for `mono-agent memory` and `mono-agent continuations list`.");
447
- }
448
- if ((limit !== undefined || cursor !== undefined) && cmd === "continuations" && (positionals[0] ?? "list") !== "list") {
449
- throw new Error("--limit and --cursor are only supported for `mono-agent continuations list`.");
450
- }
451
- if (cursor !== undefined && cmd !== "continuations") {
452
- throw new Error("--cursor is only supported for `mono-agent continuations list`.");
453
- }
454
- if (strict && (cmd !== "memory" || (positionals[0] ?? "stats") !== "audit")) {
455
- throw new Error("--strict is only supported for `mono-agent memory audit`.");
456
- }
457
- if ((idsFile !== undefined || reason !== undefined || planPath !== undefined || backupPath !== undefined)
458
- && (cmd !== "memory" || positionals[0] !== "forget")) {
459
- throw new Error("--ids-file, --reason, --plan, and --backup are only supported for `mono-agent memory forget`.");
460
- }
461
- if (auth && cmd !== "init") {
462
- throw new Error("--auth is only supported for `mono-agent init`.");
463
- }
464
- if (piAuthPath !== undefined && cmd !== "auth") {
465
- throw new Error("--pi-auth-path is only supported for `mono-agent auth`.");
466
- }
467
- if (apiKeyStdin && cmd !== "auth") {
468
- throw new Error("--api-key-stdin is only supported for `mono-agent auth login <provider>`.");
469
- }
470
- if (codexAuthMode !== undefined && cmd !== "init" && cmd !== "auth") {
471
- throw new Error("--codex-auth is only supported for `mono-agent init` and `mono-agent auth login codex`.");
472
- }
473
- if (fallbackModels !== undefined && fallbacks.length > 0) {
474
- throw new Error("Use either legacy --fallback-models or repeated --fallback flags, not both.");
475
- }
476
- if (fallbackModels !== undefined && new Set(fallbackModels).size !== fallbackModels.length) {
477
- throw new Error("--fallback-models contains a duplicate model reference.");
478
- }
479
- const selectedFallbackModels = fallbackModels ?? fallbacks.map((fallback) => fallback.model);
480
- if (model !== undefined && selectedFallbackModels.includes(model)) {
481
- throw new Error(`Primary --model \`${model}\` cannot also be a fallback.`);
482
- }
483
- return {
484
- command: cmd,
485
- ...(configPath === undefined ? {} : { configPath }),
486
- ...(name === undefined ? {} : { name }),
487
- ...(model === undefined ? {} : { model }),
488
- ...(fallbackModels === undefined ? {} : { fallbackModels }),
489
- ...(fallbacks.length === 0 ? {} : { fallbacks }),
490
- ...(routeSafety === undefined ? {} : { routeSafety }),
491
- ...(effort === undefined ? {} : { effort }),
492
- ...(memory === undefined ? {} : { memory }),
493
- ...(preset === undefined ? {} : { preset }),
494
- ...(recipe === undefined ? {} : { recipe }),
495
- ...(withChannels === undefined ? {} : { withChannels }),
496
- ...(yes ? { yes } : {}),
497
- ...(auth ? { auth } : {}),
498
- ...(piAuthPath === undefined ? {} : { piAuthPath }),
499
- ...(apiKeyStdin ? { apiKeyStdin } : {}),
500
- ...(codexAuthMode === undefined ? {} : { codexAuthMode }),
501
- positionals,
502
- ...(envFile === undefined ? {} : { envFile }),
503
- ...(expectedBackgroundSnapshot === undefined ? {} : { expectedBackgroundSnapshot }),
504
- ...(target === undefined ? {} : { target }),
505
- force,
506
- foreground,
507
- follow,
508
- ...(lines === undefined ? {} : { lines }),
509
- ...(run === undefined ? {} : { run }),
510
- all,
511
- ...(since === undefined ? {} : { since }),
512
- ...(until === undefined ? {} : { until }),
513
- dryRun,
514
- includeMemory,
515
- ...(artifactDir === undefined ? {} : { artifactDir }),
516
- ...(groupBy === undefined ? {} : { groupBy }),
517
- ...(consumerPath === undefined ? {} : { consumerPath }),
518
- ...(staleAfterMs === undefined ? {} : { staleAfterMs }),
519
- ...(json ? { json } : {}),
520
- ...(strict ? { strict } : {}),
521
- ...(limit === undefined ? {} : { limit }),
522
- ...(cursor === undefined ? {} : { cursor }),
523
- ...(idsFile === undefined ? {} : { idsFile }),
524
- ...(reason === undefined ? {} : { reason }),
525
- ...(planPath === undefined ? {} : { planPath }),
526
- ...(backupPath === undefined ? {} : { backupPath }),
527
- ...(agent === undefined ? {} : { agent }),
528
- ...(conversation === undefined ? {} : { conversation }),
529
- ...(local ? { local } : {}),
530
- ...(configure ? { configure } : {}),
531
- ...(project ? { project } : {}),
532
- ...(check ? { check } : {}),
533
- ...(update ? { update } : {}),
534
- ...(host === undefined ? {} : { host }),
535
- ...(port === undefined ? {} : { port }),
536
- ...(open === undefined ? {} : { open }),
537
- ...(allowNonLoopback === undefined ? {} : { allowNonLoopback }),
538
- ...(showAuthUrl === undefined ? {} : { showAuthUrl }),
539
- ...(maxRunsPerInstance === undefined ? {} : { maxRunsPerInstance }),
540
- };
541
- }
542
2
  /**
543
- * Loads env vars from a dotenv file when it exists; already-set variables are
544
- * never overwritten, so exported shell variables take precedence. Returns
545
- * false when the file is missing or unreadable.
3
+ * Stable public and executable CLI surface.
4
+ *
5
+ * Command routing and implementations live in `cli-commands.ts`; keeping this
6
+ * facade deliberately tiny lets callers continue importing `./cli.js` while
7
+ * command families are extracted behind that boundary in reviewable steps.
546
8
  */
547
- export function loadCliEnvFile(path) {
548
- try {
549
- process.loadEnvFile(path);
550
- return true;
551
- }
552
- catch {
553
- return false;
554
- }
555
- }
556
- function resolveValidateContext(args, invocationCwd) {
557
- const cwd = resolve(invocationCwd, args.consumerPath ?? ".");
558
- return {
559
- cwd,
560
- configPath: resolve(cwd, args.configPath ?? "mono-agent.config.json"),
561
- envFilePath: resolve(cwd, args.envFile ?? ".env"),
562
- allowFilesystemWrites: args.consumerPath === undefined,
563
- };
564
- }
565
- function requireValue(args, index, flag) {
566
- const value = args[index];
567
- if (value === undefined || value.startsWith("--")) {
568
- throw new Error(`Flag ${flag} requires a value.`);
569
- }
570
- return value;
571
- }
572
- const HELP_COMMANDS = [
573
- {
574
- signature: "mono-agent init [--preset <id>] [--with <csv>] [--yes] [--auth] [--dry-run]\n" +
575
- " [--name <display-name>] [--model <ref>] [--effort <level>]\n" +
576
- " [--fallback <ref> [--fallback-effort <provider-default|level>]]...\n" +
577
- " [--fallback-models <csv>] [--route-safety uniform|per-route-native]\n" +
578
- " [--codex-auth browser|device] [--memory lite|journal|bujo]",
579
- lines: [
580
- "Scaffold a mono-agent in the current folder. On a TTY with no flags, launches",
581
- "the step-by-step wizard; with --yes or any flag, writes the default/preset",
582
- "scaffold non-interactively. --preset seeds a blueprint, --with adds channels,",
583
- `Effort levels: ${EFFORT_LEVELS.join(", ")}; an omitted fallback effort uses that provider's default.`,
584
- "--auth runs supported provider auth/preflight before writing; --codex-auth device supports headless hosts.",
585
- "--dry-run previews only. Existing scaffold/config files are not overwritten;",
586
- "guided secret setup may securely update .env and .gitignore after explicit review.",
587
- ],
588
- },
589
- {
590
- signature: "mono-agent setup",
591
- lines: ["Alias of `init`."],
592
- },
593
- {
594
- signature: "mono-agent presets list | show <id>",
595
- lines: [
596
- "List the built-in setup presets, or show one's generated config,",
597
- ".env.example, and follow-up checklist.",
598
- ],
599
- },
600
- {
601
- signature: "mono-agent validate [--preset <id>] [--consumer <path>] [--config <path>] [--env-file <path>] [--json]",
602
- lines: [
603
- "Load every config section and report what would run, wait, or fail.",
604
- "--consumer validates another agent folder read-only, including its .env.",
605
- "With --preset, also report whether the preset's capabilities are live.",
606
- "`mono-agent doctor` is an alias for this command.",
607
- ],
608
- },
609
- {
610
- signature: "mono-agent auth login <provider|codex> [--pi-auth-path <path>] [--api-key-stdin]\n" +
611
- " [--codex-auth browser|device] [--config <path>]",
612
- lines: [
613
- "Run a supported bundled Pi provider login, or direct Codex browser/device login.",
614
- "Pi credentials are promoted with owner-only no-clobber checks.",
615
- "API-key providers prompt securely on a TTY; --api-key-stdin explicitly reads a redirected secret.",
616
- "Path precedence: --pi-auth-path, MONO_AGENT_PI_AUTH_PATH, providers.piAuthPath, then Pi's default.",
617
- "Supported Pi targets: anthropic, github-copilot, openai-codex, and opencode-go.",
618
- ],
619
- },
620
- {
621
- signature: "mono-agent sandbox status | setup | check",
622
- lines: [
623
- "Inspect, install, or functionally prove the pinned SRT sandbox runtime.",
624
- "Managed setup is macOS-only and installs into the user's cache; it never changes PATH,",
625
- "global npm packages, system packages, or another user's files.",
626
- ],
627
- },
628
- {
629
- signature: "mono-agent config [--config <path>] [--env-file <path>]",
630
- lines: [
631
- "Print the resolved config field-by-field, tagging each value with where",
632
- "it came from (env / json / default), plus the channel summary. Read-only.",
633
- ],
634
- },
635
- {
636
- signature: "mono-agent start [--config <path>] [--env-file <path>] [--foreground|-f]",
637
- lines: [
638
- "Start the agent as a background macOS service (launchd), print its",
639
- "instance info, and return. Re-running restarts the running instance.",
640
- "Refuses to start without a valid mono-agent.config.json in the folder.",
641
- "Use --foreground (-f) to run in the blocking foreground instead.",
642
- ],
643
- },
644
- {
645
- signature: "mono-agent restart [--config <path>] [--force]",
646
- lines: [
647
- "Restart the background instance for this config (starts it if stopped).",
648
- "--force also clears the persisted pi sessions so it starts with fresh",
649
- "conversations instead of resuming saved ones (durable memory is untouched).",
650
- ],
651
- },
652
- {
653
- signature: "mono-agent stop [--config <path>]",
654
- lines: ["Stop the background instance and remove its LaunchAgent."],
655
- },
656
- {
657
- signature: "mono-agent status [--config <path>]",
658
- lines: ["Show this config's instance plus any other running instances."],
659
- },
660
- {
661
- signature: "mono-agent logs [--config <path>] [--follow|-f] [--lines <n>]",
662
- lines: ["Print (and optionally follow) the background instance's log files."],
663
- },
664
- {
665
- signature: "mono-agent tui [--agent <label|sourceId>] [--conversation <id>]\n" +
666
- " [--configure | --local]",
667
- lines: [
668
- "Open the operator console from any directory: live chat with full",
669
- "thinking/tool/telemetry insight, recorded-run replay, and config view.",
670
- "Discovers running agents via the trace-source registry; one running",
671
- "agent connects directly, several open a picker.",
672
- "--configure opens the guided configuration chat on the authoritative",
673
- "background agent; --local is an ordinary in-process chat only.",
674
- ],
675
- },
676
- {
677
- signature: "mono-agent web [--host <addr>] [--port <n>] [--no-open] [--allow-non-loopback] [--show-auth-url] [--include-memory] [--max-runs <n>]",
678
- lines: [
679
- "Serve the read-only Session Recorder web PWA from any directory: a live",
680
- "flight-recorder over every agent's runs (prompt, reasoning, tools, cost).",
681
- "Discovers running agents via the trace-source registry — the same",
682
- "mechanism as `tui` — and streams new/updated runs in real time.",
683
- "--include-memory also shows memory-maintenance runs. --max-runs (default",
684
- "200) bounds the in-memory working set; the UI still pages the full",
685
- "on-disk history via \"Load older\".",
686
- "Non-loopback service use requires MONO_AGENT_WEB_AUTH_TOKEN; an",
687
- "interactive run may generate one. --show-auth-url reveals a configured",
688
- "token only to an interactive terminal. Authenticated URLs are not",
689
- "auto-opened through process arguments.",
690
- ],
691
- },
692
- {
693
- signature: "mono-agent install-skill [--target claude|codex|both] [--force]\n" +
694
- " --project (--check|--update)",
695
- lines: [
696
- "Copy the bundled mono-agent-composer skill into ~/.claude/skills and",
697
- "~/.agents/skills (default: both). Refuses to overwrite without --force.",
698
- "Project mode checks or safely updates the two managed skills generated",
699
- "by init; modified copies are never overwritten and updates retain backups.",
700
- ],
701
- },
702
- {
703
- signature: "mono-agent backfill (--run <id> | --all) [--since <iso>] [--until <iso>]\n" +
704
- " [--include-memory] [--dry-run] [--config <path>] [--env-file <path>]",
705
- lines: [
706
- "Export already-recorded agent-run artifacts to the configured Phoenix exporter",
707
- "with their historical timestamps. Trace ids are deterministic per run, so",
708
- "re-running overwrites rather than duplicating. --dry-run maps and",
709
- "serializes without sending. --include-memory adds memory-run artifacts",
710
- "for --all; explicit --run can target a memory run directly.",
711
- ],
712
- },
713
- {
714
- signature: "mono-agent audit-runs [--artifact-dir <path> | --consumer <path>]\n" +
715
- " [--include-memory] [--config <path>] [--env-file <path>] [--stale-after-ms <n>] [--json]",
716
- lines: [
717
- "Read local agent-run summary artifacts without rewriting them. Reports parse",
718
- "failures, status and failure-kind histograms, stale running summaries,",
719
- "and per-failure-kind rates. --include-memory includes memory-run artifacts.",
720
- ],
721
- },
722
- {
723
- signature: "mono-agent metrics [--artifacts <path>] [--since <iso>] [--until <iso>]\n" +
724
- " [--include-memory] [--by model|channel|failureKind] [--json] [--config <path>] [--env-file <path>]",
725
- lines: [
726
- "Aggregate local agent-run summaries into status rates, failure-kind rates,",
727
- "duration percentiles, and total/per-run cost. Read-only and offline.",
728
- "--include-memory includes memory-run artifacts.",
729
- ],
730
- },
731
- {
732
- signature: "mono-agent memory [stats|today|show <date>|search <query>|top|audit|inspect [id]|retry [id]|resolve <id> <reason>|rebuild|rollback|adopt-replay]\n" +
733
- "mono-agent memory forget prepare --ids-file <file> --reason <slug> --plan <file>\n" +
734
- "mono-agent memory forget apply --plan <file> | forget restore --backup <dir>\n" +
735
- " [--limit <n>] [--strict] [--json] [--config <path>] [--env-file <path>]",
736
- lines: [
737
- "Preview the configured memory store from an agent folder. Reads the",
738
- "memory block from mono-agent.config.json, not the standalone memory-bujo",
739
- "env workflow. Human-first output by default; audit --strict --json is a",
740
- "metadata-only health gate. Intake inspect/retry/resolve never print payload content.",
741
- "adopt-replay is an explicit stopped-agent, SSH-safe BuJo trust-on-first-use",
742
- "operation. It returns metadata only and requires rebuild before restart.",
743
- "forget uses an explicit, content-free plan plus a full owner-private backup;",
744
- "apply and restore require the configured agent to be stopped.",
745
- ],
746
- },
747
- {
748
- signature: "mono-agent continuations [list [--limit <n>] [--cursor <opaque>]|health|retry <id>|cancel <id>|resolve <id> delivered|not-delivered|dead-lettered [delivery-id]] [--json]",
749
- lines: [
750
- "Inspect and operate the authenticated durable-continuation service.",
751
- "Ambiguous delivery cannot be retried until it is explicitly resolved;",
752
- "no command accepts or changes a channel destination.",
753
- ],
754
- },
755
- ];
756
- const HELP_NOTES = `Background mode runs the agent under launchd, keeping it alive across logins
757
- (auto-restarting only on crash) until you run stop. Secrets are read from the
758
- .env file in the working directory, the same as foreground mode. The background
759
- commands require macOS; elsewhere use start --foreground.
760
-
761
- Init model references look like pi:<provider>:<model>, claude:claude-sonnet-4-6,
762
- codex:gpt-5.6-terra, codex:gpt-5.6-sol, or opencode:<provider>:<model>. The init wizard
763
- selects the live provider-declared default when available and falls back offline to
764
- codex:gpt-5.6-terra. Direct and Pi OpenAI-Codex Sol choices remain selectable.
765
- Direct GPT-5.6 routes require Codex CLI 0.144.0 or newer. Guided Pi authentication
766
- covers Anthropic, GitHub Copilot, OpenAI Codex, and OpenCode-Go. Claude remains selectable;
767
- direct opencode:<provider>:<model> refs are for
768
- hand-authored runtime backend config and are rejected by guided selection/readiness.
769
-
770
- Mixed fallback chains are allowed. runtime.routeSafety=uniform (the default)
771
- requires one compatibility-preserving contract across every route;
772
- per-route-native makes each route's exact safety boundary explicit (Pi SRT,
773
- Claude provider-owned permissions, Codex native sandbox, or OpenCode native).
774
-
775
- Native mono-agent srt policy is enforced by Pi-owned tools. In uniform mode,
776
- Claude, direct Codex, and direct OpenCode cannot silently weaken that policy.
777
- In per-route-native mode, validate reports each provider-owned safety contract
778
- and rejects capabilities that the selected route cannot represent.
779
- Direct OpenCode's bridge cannot enforce an explicit runtime.effort; omit effort and
780
- configure runtime.permissionMode deliberately for hand-authored direct routes.
781
- It is per-run/non-resumable and rejects MCP (including auto-provisioned memory
782
- or send tools), positive maxTurns, index skill disclosure, structured output,
783
- live input, fast mode, and native subagents instead of silently dropping them.
784
-
785
- A .env file in the current folder is loaded automatically when present;
786
- already-exported shell variables take precedence.
787
- `;
788
- /** Build the colorized help screen (plain text when color is disabled). */
789
- export function renderHelp() {
790
- let out = ui.banner("mono-agent", "config-first agent host") + "\n";
791
- out += ui.heading("Usage");
792
- for (const entry of HELP_COMMANDS) {
793
- const [first, ...rest] = entry.signature.split("\n");
794
- out += ` ${ui.style.bold(ui.style.cyan(first ?? ""))}\n`;
795
- for (const cont of rest) {
796
- out += ` ${ui.style.cyan(cont)}\n`;
797
- }
798
- for (const line of entry.lines) {
799
- out += ` ${ui.style.dim(line)}\n`;
800
- }
801
- out += "\n";
802
- }
803
- out += ui.style.dim(HELP_NOTES);
804
- return out;
805
- }
806
- /**
807
- * The published CLI version, read from this package's own package.json at
808
- * runtime. `../package.json` resolves the same from both `src/cli.ts` (tests) and
809
- * the built `dist/cli.js` (one level below the package root in both layouts), and
810
- * npm always ships package.json in the tarball. Best-effort: a read failure yields
811
- * "unknown" rather than crashing `--version`.
812
- */
813
- export function monoAgentVersion() {
814
- try {
815
- const pkg = createRequire(import.meta.url)("../package.json");
816
- return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
817
- }
818
- catch {
819
- return "unknown";
820
- }
821
- }
822
- export async function runCli(argv) {
823
- let args;
824
- try {
825
- args = parseCliArgs(argv);
826
- }
827
- catch (error) {
828
- if (argv[0] === "memory" && argv.includes("adopt-replay")) {
829
- const { writeReplayAdoptionCliFailure } = await import("./memory-command.js");
830
- writeReplayAdoptionCliFailure(argv.includes("--json"), "replay_adoption_usage");
831
- return 2;
832
- }
833
- if (argv[0] === "memory" && argv.includes("forget")) {
834
- const { writeMemoryForgetFailure } = await import("./memory-command.js");
835
- const operationIndex = argv.indexOf("forget") + 1;
836
- writeMemoryForgetFailure(argv.includes("--json"), argv[operationIndex] ?? "unknown", "forget_usage");
837
- return 2;
838
- }
839
- process.stderr.write(ui.errorLine(error instanceof Error ? error.message : String(error)));
840
- process.stdout.write(`\n${renderHelp()}`);
841
- return 2;
842
- }
843
- // Only the internal launchd foreground shape may honor the managed-worker
844
- // marker. A hostile/global launchctl environment must not sanitize unrelated
845
- // commands such as `mono-agent validate` or `mono-agent status`.
846
- const managedBackgroundWorker = args.command === "start" && args.foreground && process.env[MANAGED_BACKGROUND_WORKER_ENV] === "1";
847
- if (args.expectedBackgroundSnapshot !== undefined && !managedBackgroundWorker) {
848
- process.stderr.write(ui.errorLine("--expected-background-snapshot is reserved for the managed LaunchAgent worker."));
849
- return 2;
850
- }
851
- if (managedBackgroundWorker && args.expectedBackgroundSnapshot === undefined) {
852
- process.stderr.write(ui.errorLine("Managed LaunchAgent worker is missing its approved background snapshot."));
853
- return 0;
854
- }
855
- if (managedBackgroundWorker) {
856
- sanitizeManagedBackgroundWorkerEnvironment(process.env);
857
- }
858
- if (args.command === "memory"
859
- && args.positionals[0] === "adopt-replay"
860
- && hasUnsupportedReplayAdoptionFlag(argv)) {
861
- const { writeReplayAdoptionCliFailure } = await import("./memory-command.js");
862
- writeReplayAdoptionCliFailure(args.json === true, "replay_adoption_usage");
863
- return 2;
864
- }
865
- if (args.command === "memory"
866
- && args.positionals[0] === "forget"
867
- && hasUnsupportedMemoryForgetFlag(argv)) {
868
- const { writeMemoryForgetFailure } = await import("./memory-command.js");
869
- writeMemoryForgetFailure(args.json === true, args.positionals[1] ?? "unknown", "forget_usage");
870
- return 2;
871
- }
872
- const invocationCwd = process.cwd();
873
- // Capture the exported shell before dotenv loading. Guided init retains only
874
- // worker-operational values and reports shell/background credential drift;
875
- // normal CLI commands still get the established shell-over-dotenv precedence.
876
- const shellEnv = { ...process.env };
877
- const envFilePath = args.command === "validate"
878
- ? resolveValidateContext(args, invocationCwd).envFilePath
879
- : resolve(invocationCwd, args.envFile ?? ".env");
880
- let dotenvEnv = {};
881
- if (args.command === "init") {
882
- try {
883
- dotenvEnv = await readCliDotenvFile(envFilePath);
884
- }
885
- catch (error) {
886
- process.stderr.write(ui.errorLine(`Cannot read ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`));
887
- return 1;
888
- }
889
- }
890
- loadCliEnvFile(envFilePath);
891
- switch (args.command) {
892
- case "help":
893
- process.stdout.write(renderHelp());
894
- return 0;
895
- case "version":
896
- process.stdout.write(`mono-agent ${monoAgentVersion()}\n`);
897
- return 0;
898
- case "init":
899
- return await runInit(args, { shellEnv, dotenvEnv, dotenvPath: envFilePath });
900
- case "validate":
901
- return await runValidate(args);
902
- case "auth":
903
- return await runAuth(args);
904
- case "sandbox":
905
- return await runSandboxCommand(args);
906
- case "config":
907
- return await runConfig(args);
908
- case "presets":
909
- return runPresets(args);
910
- case "start":
911
- return await runStart(args, undefined, managedBackgroundWorker);
912
- case "restart":
913
- case "stop":
914
- case "status":
915
- case "logs":
916
- return await runBackgroundCommand(args, args.command);
917
- case "tui": {
918
- // Lazy import: the operator console (and pi-tui) load only on demand.
919
- const { runTui } = await import("./tui-command.js");
920
- return await runTui({
921
- configPath: resolve(process.cwd(), args.configPath ?? "mono-agent.config.json"),
922
- cwd: process.cwd(),
923
- env: process.env,
924
- ...(args.envFile === undefined ? {} : { envFile: args.envFile }),
925
- ...(args.agent === undefined ? {} : { agent: args.agent }),
926
- ...(args.conversation === undefined ? {} : { conversationId: args.conversation }),
927
- ...(args.local === true ? { local: true } : {}),
928
- ...(args.configure === true ? { configure: true } : {}),
929
- });
930
- }
931
- case "web": {
932
- // Lazy import: the web server (and express/session-web) load only on demand.
933
- const { runWeb } = await import("./web-command.js");
934
- return await runWeb({
935
- configPath: resolve(process.cwd(), args.configPath ?? "mono-agent.config.json"),
936
- cwd: process.cwd(),
937
- env: process.env,
938
- ...(args.host === undefined ? {} : { host: args.host }),
939
- ...(args.port === undefined ? {} : { port: args.port }),
940
- ...(args.open === undefined ? {} : { open: args.open }),
941
- ...(args.allowNonLoopback === undefined ? {} : { allowNonLoopback: args.allowNonLoopback }),
942
- ...(args.showAuthUrl === undefined ? {} : { showAuthUrl: args.showAuthUrl }),
943
- includeMemory: args.includeMemory,
944
- ...(args.maxRunsPerInstance === undefined ? {} : { maxRunsPerInstance: args.maxRunsPerInstance }),
945
- });
946
- }
947
- case "install-skill":
948
- return await runInstallSkill(args);
949
- case "continuations": {
950
- const { runContinuationCommand } = await import("./continuation-command.js");
951
- return await runContinuationCommand({
952
- cwd: process.cwd(),
953
- configPath: resolve(process.cwd(), args.configPath ?? "mono-agent.config.json"),
954
- env: process.env,
955
- positionals: args.positionals,
956
- ...(args.json === true ? { json: true } : {}),
957
- ...(args.limit === undefined ? {} : { limit: args.limit }),
958
- ...(args.cursor === undefined ? {} : { cursor: args.cursor }),
959
- });
960
- }
961
- case "backfill":
962
- return await runBackfill({
963
- ...(args.configPath === undefined ? {} : { configPath: args.configPath }),
964
- ...(args.run === undefined ? {} : { run: args.run }),
965
- all: args.all,
966
- ...(args.since === undefined ? {} : { since: args.since }),
967
- ...(args.until === undefined ? {} : { until: args.until }),
968
- dryRun: args.dryRun,
969
- includeMemory: args.includeMemory,
970
- });
971
- case "audit-runs":
972
- return await runAuditRuns({
973
- ...(args.configPath === undefined ? {} : { configPath: args.configPath }),
974
- ...(args.artifactDir === undefined ? {} : { artifactDir: args.artifactDir }),
975
- ...(args.consumerPath === undefined ? {} : { consumerPath: args.consumerPath }),
976
- ...(args.staleAfterMs === undefined ? {} : { staleAfterMs: args.staleAfterMs }),
977
- json: args.json === true,
978
- includeMemory: args.includeMemory,
979
- });
980
- case "metrics":
981
- return await runMetrics({
982
- ...(args.configPath === undefined ? {} : { configPath: args.configPath }),
983
- ...(args.artifactDir === undefined ? {} : { artifactDir: args.artifactDir }),
984
- ...(args.since === undefined ? {} : { since: args.since }),
985
- ...(args.until === undefined ? {} : { until: args.until }),
986
- ...(args.groupBy === undefined ? {} : { groupBy: args.groupBy }),
987
- json: args.json === true,
988
- includeMemory: args.includeMemory,
989
- });
990
- case "memory": {
991
- // Lazy import: the memory preview path pulls SQLite/backend clients only on demand.
992
- const { runMemoryCommand } = await import("./memory-command.js");
993
- return await runMemoryCommand({
994
- cwd: process.cwd(),
995
- env: process.env,
996
- ...(args.configPath === undefined ? {} : { configPath: args.configPath }),
997
- positionals: args.positionals,
998
- json: args.json === true,
999
- strict: args.strict === true,
1000
- ...(args.limit === undefined ? {} : { limit: args.limit }),
1001
- ...(args.idsFile === undefined ? {} : { idsFile: args.idsFile }),
1002
- ...(args.reason === undefined ? {} : { reason: args.reason }),
1003
- ...(args.planPath === undefined ? {} : { planPath: args.planPath }),
1004
- ...(args.backupPath === undefined ? {} : { backupPath: args.backupPath }),
1005
- });
1006
- }
1007
- }
1008
- }
1009
- /**
1010
- * `adopt-replay` is a stopped-store trust boundary, so command-global flags
1011
- * must never be silently ignored. In particular, `--dry-run` cannot be parsed
1012
- * successfully and then execute the mutation. Validate the raw invocation so
1013
- * future parser flags also fail closed unless they are explicitly admitted.
1014
- */
1015
- function hasUnsupportedReplayAdoptionFlag(argv) {
1016
- for (let index = 1; index < argv.length; index += 1) {
1017
- const token = argv[index];
1018
- if (token === "--config" || token === "--env-file") {
1019
- index += 1;
1020
- continue;
1021
- }
1022
- if (token === "--json")
1023
- continue;
1024
- if (token?.startsWith("-") === true)
1025
- return true;
1026
- }
1027
- return false;
1028
- }
1029
- /** Forget is mutating, so every accepted command-global flag is explicit. */
1030
- function hasUnsupportedMemoryForgetFlag(argv) {
1031
- const valueFlags = new Set(["--config", "--env-file", "--ids-file", "--reason", "--plan", "--backup"]);
1032
- for (let index = 1; index < argv.length; index += 1) {
1033
- const token = argv[index];
1034
- if (token !== undefined && valueFlags.has(token)) {
1035
- index += 1;
1036
- continue;
1037
- }
1038
- if (token === "--json")
1039
- continue;
1040
- if (token?.startsWith("-") === true)
1041
- return true;
1042
- }
1043
- return false;
1044
- }
1045
- const DEFAULT_SANDBOX_COMMAND_DEPENDENCIES = {
1046
- status: sandboxRuntimeStatus,
1047
- setup: setupManagedSrt,
1048
- check: checkSandboxRuntime,
1049
- };
1050
- /** App-owned sandbox lifecycle surface; safe to inject in focused CLI tests. */
1051
- export async function runSandboxCommand(args, dependencies = DEFAULT_SANDBOX_COMMAND_DEPENDENCIES) {
1052
- const [subcommand, ...extra] = args.positionals;
1053
- if ((subcommand !== "status" && subcommand !== "setup" && subcommand !== "check") || extra.length > 0) {
1054
- process.stderr.write(ui.errorLine("[sandbox_usage] Usage: mono-agent sandbox status | setup | check."));
1055
- return 2;
1056
- }
1057
- if (subcommand === "status") {
1058
- try {
1059
- printSandboxRuntimeStatus(await dependencies.status());
1060
- return 0;
1061
- }
1062
- catch (error) {
1063
- process.stderr.write(ui.errorLine(`[sandbox_status_failed] ${reasonOf(error)}`));
1064
- return 1;
1065
- }
1066
- }
1067
- return await withScopedSandboxCancellation(async (signal) => {
1068
- try {
1069
- if (subcommand === "setup") {
1070
- process.stdout.write(ui.heading("Sandbox setup"));
1071
- process.stdout.write(ui.style.dim("Installing the pinned SRT copy in the user cache; no PATH, global npm, or system-package changes will be made.\n"));
1072
- const result = await dependencies.setup({ signal, verify: true });
1073
- printSandboxSetupResult(result);
1074
- return 0;
1075
- }
1076
- process.stdout.write(ui.heading("Sandbox check"));
1077
- const result = await dependencies.check({ signal });
1078
- printSandboxCheckResult(result);
1079
- return 0;
1080
- }
1081
- catch (error) {
1082
- if (signal.aborted || isAbortLike(error)) {
1083
- process.stderr.write(ui.errorLine("[sandbox_interrupted] Sandbox operation was interrupted; no partial success was claimed."));
1084
- process.stderr.write(ui.hint(`Retry safely with \`mono-agent sandbox ${subcommand}\`.\n`));
1085
- return 130;
1086
- }
1087
- const code = subcommand === "setup" ? "sandbox_setup_failed" : "sandbox_check_failed";
1088
- process.stderr.write(ui.errorLine(`[${code}] ${reasonOf(error)}`));
1089
- process.stderr.write(ui.hint(`Retry with \`mono-agent sandbox ${subcommand}\` after resolving the error.\n`));
1090
- return 1;
1091
- }
1092
- });
1093
- }
1094
- function printSandboxRuntimeStatus(status) {
1095
- process.stdout.write(ui.heading("Sandbox status"));
1096
- process.stdout.write(` State: ${status.state}\n`);
1097
- process.stdout.write(` Source: ${status.source}\n`);
1098
- process.stdout.write(` Cache: ${status.installRoot}\n`);
1099
- process.stdout.write(` Detail: ${status.message}\n`);
1100
- }
1101
- function printSandboxCheckResult(result) {
1102
- printSandboxRuntimeStatus(result.status);
1103
- process.stdout.write(ui.heading("Functional enforcement"));
1104
- for (const check of result.checks) {
1105
- process.stdout.write(`${check.ok ? ui.badge("ok") : ui.badge("error")}${check.id}: ${check.detail}\n`);
1106
- }
1107
- }
1108
- function printSandboxSetupResult(result) {
1109
- printSandboxRuntimeStatus(result.status);
1110
- const action = result.repaired ? "repaired" : result.installed ? "installed" : "already installed";
1111
- process.stdout.write(`${ui.badge("ok")}Managed SRT ${action}; integrity verification passed.\n`);
1112
- if (result.check !== undefined)
1113
- printSandboxCheckResult(result.check);
1114
- }
1115
- async function withScopedSandboxCancellation(task) {
1116
- const controller = new AbortController();
1117
- let interrupts = 0;
1118
- const interrupt = () => {
1119
- interrupts += 1;
1120
- controller.abort();
1121
- };
1122
- const onKeypress = (_value, key) => {
1123
- if (key?.name === "escape")
1124
- interrupt();
1125
- };
1126
- process.on("SIGINT", interrupt);
1127
- const restoreKeypress = attachScopedKeypress(onKeypress);
1128
- try {
1129
- const result = await task(controller.signal);
1130
- return interrupts > 1 ? 130 : result;
1131
- }
1132
- finally {
1133
- process.off("SIGINT", interrupt);
1134
- restoreKeypress();
1135
- }
1136
- }
1137
- function isAbortLike(error) {
1138
- return error instanceof Error && (error.name === "AbortError" || /abort|cancel/iu.test(error.message));
1139
- }
1140
- function attachScopedKeypress(listener) {
1141
- if (!process.stdin.isTTY)
1142
- return () => undefined;
1143
- emitKeypressEvents(process.stdin);
1144
- const input = process.stdin;
1145
- const wasRaw = input.isRaw === true;
1146
- const wasFlowing = input.readableFlowing;
1147
- input.setRawMode?.(true);
1148
- input.resume();
1149
- input.on("keypress", listener);
1150
- return () => {
1151
- input.off("keypress", listener);
1152
- input.setRawMode?.(wasRaw);
1153
- if (wasFlowing !== true)
1154
- input.pause();
1155
- };
1156
- }
1157
- async function runInit(args, environment) {
1158
- const cwd = process.cwd();
1159
- // On an interactive TTY with no overriding flags, walk the step-by-step wizard;
1160
- // any flag (or a piped/non-TTY invocation) takes the silent default/preset path.
1161
- const wantsWizard = shouldRunInitWizard(args, process.stdin.isTTY === true, process.stdout.isTTY === true);
1162
- if (wantsWizard) {
1163
- // Existing-config pre-check — don't walk the wizard into a guaranteed no-op.
1164
- if (await pathExists(resolve(cwd, "mono-agent.config.json"))) {
1165
- process.stdout.write(ui.hint("Found an existing mono-agent.config.json — `mono-agent init` never overwrites. Run `mono-agent validate`, or start in an empty folder.\n"));
1166
- return 0;
1167
- }
1168
- let resolvedPiAuthPath = resolveEffectivePiAuthPath({
1169
- cwd,
1170
- ...(nonEmptyEnv(environment.dotenvEnv.MONO_AGENT_PI_AUTH_PATH)
1171
- ? { envPath: environment.dotenvEnv.MONO_AGENT_PI_AUTH_PATH }
1172
- : {}),
1173
- });
1174
- const initial = await runInitWizard({
1175
- cwd,
1176
- piAuthPath: resolvedPiAuthPath,
1177
- persistedEnv: environment.dotenvEnv,
1178
- });
1179
- if (initial.status === "cancelled")
1180
- return 1;
1181
- let answers = initial.answers;
1182
- let moduleSecrets = { ...initial.moduleSecrets };
1183
- let providerEnvironmentSecrets = { ...initial.providerEnvironmentSecrets };
1184
- let providerSetupSecrets = { ...initial.providerSetupSecrets };
1185
- let piApiKeyPersistenceByProvider = { ...initial.piApiKeyPersistenceByProvider };
1186
- let credentialStates = { ...initial.credentialStates };
1187
- let pendingProviderSetup = initial.runProviderSetup;
1188
- let selectedCodexAuthMode = "browser";
1189
- let readinessProgress;
1190
- let sandboxMutationCompleted = false;
1191
- let deferredFailure;
1192
- firstRun: for (;;) {
1193
- let dotenvSnapshot = { env: {}, fingerprint: "unreadable" };
1194
- let failure = deferredFailure;
1195
- let configurationRecoveryStep;
1196
- let invalidPlanStage;
1197
- deferredFailure = undefined;
1198
- try {
1199
- dotenvSnapshot = await readCliDotenvSnapshot(environment.dotenvPath);
1200
- }
1201
- catch {
1202
- failure ??= dotenvReadinessFailure("The persisted .env could not be read safely. Fix it before retrying setup.");
1203
- }
1204
- const plan = composeWizardPlan(answers, {
1205
- dirBasename: basename(cwd),
1206
- skillsRootExists: await pathExists(resolve(cwd, "skills")),
1207
- });
1208
- resolvedPiAuthPath = resolveEffectivePiAuthPath({
1209
- cwd,
1210
- ...(nonEmptyEnv(dotenvSnapshot.env.MONO_AGENT_PI_AUTH_PATH)
1211
- ? { envPath: dotenvSnapshot.env.MONO_AGENT_PI_AUTH_PATH }
1212
- : {}),
1213
- ...(nonEmptyEnv(plan.configJson.providers?.piAuthPath)
1214
- ? { configPath: plan.configJson.providers.piAuthPath }
1215
- : {}),
1216
- });
1217
- const effectiveEnv = effectiveFirstRunEnvironment({
1218
- shellEnv: environment.shellEnv,
1219
- dotenvEnv: dotenvSnapshot.env,
1220
- enteredSecrets: { ...moduleSecrets, ...providerEnvironmentSecrets },
1221
- resolvedPiAuthPath,
1222
- });
1223
- // Re-submit every selected durable value, not only values typed during this
1224
- // wizard session. That lets the secure merge tighten an existing .env to
1225
- // 0600 while preserving its non-empty operator-owned values verbatim.
1226
- const selectedSecrets = {
1227
- ...selectedSecretValues(plan, effectiveEnv),
1228
- ...providerEnvironmentSecrets,
1229
- };
1230
- const secureExistingDotenv = hasSensitivePersistedEnvironmentValue(dotenvSnapshot.env);
1231
- const conflicts = selectedSecretEnvironmentConflicts(plan, environment.shellEnv, dotenvSnapshot.env, moduleSecrets);
1232
- const persistedOverrides = unexpectedPersistedMonoAgentOverrides(plan, dotenvSnapshot.env);
1233
- if (failure === undefined && persistedOverrides.length > 0) {
1234
- failure = {
1235
- ok: false,
1236
- kind: "invalid_plan",
1237
- message: `Persisted .env contains mono-agent config override${persistedOverrides.length === 1 ? "" : "s"}: ` +
1238
- `${persistedOverrides.join(", ")}. Remove ${persistedOverrides.length === 1 ? "it" : "them"} so the ` +
1239
- "generated config is the exact config validated and started.",
1240
- };
1241
- }
1242
- if (failure === undefined && conflicts.length > 0) {
1243
- failure = {
1244
- ok: false,
1245
- kind: "invalid_plan",
1246
- message: `Selected secret${conflicts.length === 1 ? "" : "s"} ${conflicts.join(", ")} ` +
1247
- "differ between the exported shell, persisted .env, or newly entered value. " +
1248
- "Unset the shell value or make every source match, then retry.",
1249
- };
1250
- }
1251
- if (failure === undefined && piAuthPathBackgroundConflict({
1252
- cwd,
1253
- shellPath: environment.shellEnv.MONO_AGENT_PI_AUTH_PATH,
1254
- dotenvPath: dotenvSnapshot.env.MONO_AGENT_PI_AUTH_PATH,
1255
- ...(nonEmptyEnv(plan.configJson.providers?.piAuthPath)
1256
- ? { configPath: plan.configJson.providers.piAuthPath }
1257
- : {}),
1258
- })) {
1259
- failure = {
1260
- ok: false,
1261
- kind: "invalid_plan",
1262
- message: "The exported MONO_AGENT_PI_AUTH_PATH selects a different credential store than a background start. " +
1263
- "Persist the same path in .env or providers.piAuthPath, or unset the shell override, then retry.",
1264
- };
1265
- }
1266
- if (failure === undefined && pendingProviderSetup) {
1267
- pendingProviderSetup = false;
1268
- const modelRefs = referencedSetupModelRefs(plan);
1269
- const credentialObservation = await withScopedPreflightCancellation(async (abortSignal) => ({
1270
- states: await detectProviderCredentialStates({
1271
- modelRefs,
1272
- cwd,
1273
- piAuthPath: resolvedPiAuthPath,
1274
- persistedEnv: dotenvSnapshot.env,
1275
- abortSignal,
1276
- }),
1277
- interrupted: abortSignal.aborted,
1278
- }));
1279
- if (credentialObservation.interrupted) {
1280
- pendingProviderSetup = true;
1281
- deferredFailure = {
1282
- ok: false,
1283
- kind: "cancelled",
1284
- message: "Provider status detection was interrupted. No agent files were written.",
1285
- interrupted: true,
1286
- };
1287
- continue firstRun;
1288
- }
1289
- credentialStates = credentialObservation.states;
1290
- const plannedSetup = planProviderSetup({
1291
- modelRefs,
1292
- cwd,
1293
- piAuthPath: resolvedPiAuthPath,
1294
- credentialStates,
1295
- piApiKeyPersistenceByProvider,
1296
- });
1297
- if (plannedSetup.actions.some((action) => action.id === "codex-login")) {
1298
- const selected = await selectCodexAuthMode(selectedCodexAuthMode);
1299
- if (selected === undefined)
1300
- return 1;
1301
- selectedCodexAuthMode = selected;
1302
- }
1303
- const environmentApiKeys = environmentProviderApiKeys(plannedSetup, effectiveEnv);
1304
- const missingEnvironmentKeys = plannedSetup.actions
1305
- .filter(isProviderSetupPiApiKeyAction)
1306
- .filter((action) => action.persistence === "environment" && environmentApiKeys[action.id] === undefined)
1307
- .map((action) => action.envVar);
1308
- if (missingEnvironmentKeys.length > 0) {
1309
- for (const envVar of missingEnvironmentKeys) {
1310
- const answer = await p.password({
1311
- message: `Enter ${envVar} for the agent's owner-only .env`,
1312
- validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
1313
- clearOnError: true,
1314
- });
1315
- if (p.isCancel(answer))
1316
- return 1;
1317
- providerEnvironmentSecrets[envVar] = answer;
1318
- }
1319
- pendingProviderSetup = true;
1320
- continue firstRun;
1321
- }
1322
- const setup = await withScopedPreflightCancellation((abortSignal) => withExactProcessEnvironment(effectiveEnv, () => runProviderSetupBeforeInit({
1323
- modelRefs,
1324
- cwd,
1325
- auth: true,
1326
- dryRun: false,
1327
- piAuthPath: resolvedPiAuthPath,
1328
- apiKeys: { ...providerSetupSecrets, ...environmentApiKeys },
1329
- codexAuthMode: selectedCodexAuthMode,
1330
- credentialStates,
1331
- persistedEnv: dotenvSnapshot.env,
1332
- piApiKeyPersistenceByProvider,
1333
- abortSignal,
1334
- })), { keypress: false });
1335
- if (setup === "fatal")
1336
- return 130;
1337
- if (setup === "interrupted") {
1338
- pendingProviderSetup = true;
1339
- failure = {
1340
- ok: false,
1341
- kind: "cancelled",
1342
- message: "Provider setup was interrupted. No agent files were written.",
1343
- interrupted: true,
1344
- };
1345
- }
1346
- else if (setup === "failed") {
1347
- // A plain retry must revisit provider setup rather than falling
1348
- // through to a guaranteed-failing model turn.
1349
- pendingProviderSetup = true;
1350
- failure = {
1351
- ok: false,
1352
- kind: "provider_failed",
1353
- message: "Provider setup did not complete. No agent files were written.",
1354
- };
1355
- }
1356
- }
1357
- if (failure === undefined) {
1358
- if (answers.sandbox) {
1359
- const sandboxPreflight = await runGuidedSandboxPreflight(sandboxMutationCompleted);
1360
- sandboxMutationCompleted = sandboxMutationCompleted || sandboxPreflight.ok;
1361
- if (!sandboxPreflight.ok)
1362
- failure = sandboxPreflight;
1363
- }
1364
- }
1365
- if (failure === undefined) {
1366
- const configurationGate = await runConfigurationPreflightWithSpinner({
1367
- cwd,
1368
- answers,
1369
- plan,
1370
- env: effectiveEnv,
1371
- secretValues: selectedSecrets,
1372
- secureExistingDotenv,
1373
- });
1374
- if (configurationGate.interrupted === true) {
1375
- failure = {
1376
- ok: false,
1377
- kind: "cancelled",
1378
- message: "Configuration preflight was interrupted. No agent files were written.",
1379
- interrupted: true,
1380
- };
1381
- }
1382
- else if (!configurationGate.ready) {
1383
- configurationRecoveryStep = focusedConfigurationRepairStep(configurationGate.failedSectionIds);
1384
- invalidPlanStage = "configuration";
1385
- failure = {
1386
- ok: false,
1387
- kind: "invalid_plan",
1388
- message: `Configuration preflight did not pass: ${configurationGate.reasons.join(" ")}`,
1389
- };
1390
- }
1391
- }
1392
- if (failure === undefined) {
1393
- const readiness = await runReadinessProbeWithSpinner({
1394
- plan,
1395
- effectiveEnv,
1396
- resolvedPiAuthPath,
1397
- ...(readinessProgress === undefined ? {} : {
1398
- resume: {
1399
- planFingerprint: readinessProgress.planFingerprint,
1400
- successfulRouteKeys: readinessProgress.successfulRouteKeys,
1401
- },
1402
- }),
1403
- });
1404
- readinessProgress = mergeReadinessProgress(readinessProgress, readiness, plan);
1405
- failure = readiness;
1406
- }
1407
- readyAttempt: if (failure.ok) {
1408
- const stagedGate = await runFinalReadinessValidationWithSpinner({
1409
- cwd,
1410
- answers,
1411
- plan,
1412
- env: effectiveEnv,
1413
- secretValues: selectedSecrets,
1414
- secureExistingDotenv,
1415
- verifiedCredentialModelRefs: readinessProgress?.verifiedModelRefs ?? [],
1416
- });
1417
- if (stagedGate.interrupted === true) {
1418
- failure = {
1419
- ok: false,
1420
- kind: "cancelled",
1421
- message: "Final readiness validation was interrupted. No agent files were written.",
1422
- interrupted: true,
1423
- };
1424
- break readyAttempt;
1425
- }
1426
- if (stagedGate.ready) {
1427
- const drift = await firstRunDotenvDrift(environment.dotenvPath, dotenvSnapshot);
1428
- if (drift !== undefined) {
1429
- failure = drift;
1430
- break readyAttempt;
1431
- }
1432
- let result;
1433
- try {
1434
- result = await initMonoAgentFolder({
1435
- dir: cwd,
1436
- answers,
1437
- env: effectiveEnv,
1438
- secretValues: selectedSecrets,
1439
- secureExistingDotenv,
1440
- requireConfigCreation: true,
1441
- });
1442
- }
1443
- catch (error) {
1444
- const recovery = secretPersistenceRecoveryMessage(error);
1445
- process.stderr.write(ui.errorLine(`The validated scaffold could not be committed safely. The agent was not started; inspect the destination before retrying.${recovery}`));
1446
- return 1;
1447
- }
1448
- let committedConfigSnapshot;
1449
- try {
1450
- committedConfigSnapshot = await readCliConfigSnapshot(result.configPath);
1451
- }
1452
- catch {
1453
- printIncompleteSetup(["The committed config could not be read back as the regular file setup created."], result.configPath);
1454
- return 1;
1455
- }
1456
- if (committedConfigSnapshot.contents !== `${JSON.stringify(result.plan.configJson, null, 2)}\n`) {
1457
- printIncompleteSetup(["The committed config does not match the exact plan setup validated."], result.configPath);
1458
- return 1;
1459
- }
1460
- let committedDotenvSnapshot;
1461
- try {
1462
- committedDotenvSnapshot = await readCliDotenvSnapshot(environment.dotenvPath);
1463
- }
1464
- catch {
1465
- printIncompleteSetup(["The committed .env could not be read back safely; no readiness claim is safe."], result.configPath);
1466
- return 1;
1467
- }
1468
- // A missing default .env is a valid, durable empty environment. Keep
1469
- // proving its absence in the approved snapshot, but do not turn that
1470
- // default into an explicit --env-file argument: the managed worker
1471
- // treats an explicitly named missing file as an operator error.
1472
- const backgroundEnvFile = args.envFile === undefined
1473
- && committedDotenvSnapshot.fingerprint === "missing"
1474
- ? undefined
1475
- : environment.dotenvPath;
1476
- const postWriteConflicts = selectedSecretEnvironmentConflicts(result.plan, environment.shellEnv, committedDotenvSnapshot.env, moduleSecrets);
1477
- const postWriteOverrides = unexpectedPersistedMonoAgentOverrides(result.plan, committedDotenvSnapshot.env);
1478
- const postWritePiAuthConflict = piAuthPathBackgroundConflict({
1479
- cwd,
1480
- shellPath: environment.shellEnv.MONO_AGENT_PI_AUTH_PATH,
1481
- dotenvPath: committedDotenvSnapshot.env.MONO_AGENT_PI_AUTH_PATH,
1482
- ...(nonEmptyEnv(result.plan.configJson.providers?.piAuthPath)
1483
- ? { configPath: result.plan.configJson.providers.piAuthPath }
1484
- : {}),
1485
- });
1486
- if (postWriteConflicts.length > 0 || postWriteOverrides.length > 0 || postWritePiAuthConflict) {
1487
- printIncompleteSetup(["The committed .env no longer matches the values and generated config that setup approved."], result.configPath);
1488
- return 1;
1489
- }
1490
- const postWriteEnv = effectiveFirstRunEnvironment({
1491
- shellEnv: environment.shellEnv,
1492
- dotenvEnv: committedDotenvSnapshot.env,
1493
- resolvedPiAuthPath,
1494
- });
1495
- if (!sameConcreteEnvironment(effectiveEnv, postWriteEnv)) {
1496
- printIncompleteSetup(["The durable environment changed after the primary-model check. Retry setup before claiming readiness."], result.configPath);
1497
- return 1;
1498
- }
1499
- let committedBackgroundSnapshot;
1500
- let committedBackgroundEnvironment;
1501
- let committedBackgroundConfigSourceFingerprint;
1502
- try {
1503
- const durableInputs = await captureDurableBackgroundInputs({
1504
- cwd,
1505
- configPath: result.configPath,
1506
- envFile: environment.dotenvPath,
1507
- operationalEnvironment: managedBackgroundEnvironment(postWriteEnv),
1508
- });
1509
- committedBackgroundSnapshot = durableInputs.snapshot;
1510
- committedBackgroundEnvironment = durableInputs.environment;
1511
- committedBackgroundConfigSourceFingerprint = durableInputs.configSourceFingerprint;
1512
- }
1513
- catch {
1514
- printIncompleteSetup(["The complete committed config, dotenv, Identity, Soul, and MCP snapshot could not be proven safely."], result.configPath);
1515
- return 1;
1516
- }
1517
- if (committedBackgroundConfigSourceFingerprint !== committedConfigSnapshot.fingerprint) {
1518
- printIncompleteSetup(["The committed config or durable environment changed before the background snapshot was captured."], result.configPath);
1519
- return 1;
1520
- }
1521
- printInitResult(result);
1522
- let report;
1523
- try {
1524
- report = await validateMonoAgentFolder({
1525
- env: postWriteEnv,
1526
- cwd,
1527
- configPath: result.configPath,
1528
- liveness: true,
1529
- verifiedCredentialModelRefs: readinessProgress?.verifiedModelRefs ?? [],
1530
- });
1531
- }
1532
- catch {
1533
- printIncompleteSetup(["Post-write validation could not complete; no readiness claim is safe."], result.configPath);
1534
- return 1;
1535
- }
1536
- process.stdout.write("\n" + ui.heading("Validation"));
1537
- for (const section of report.sections)
1538
- process.stdout.write(formatSection(section));
1539
- process.stdout.write(renderPlanCompleteness(result.plan.validateExpectations, "Selected capabilities", report));
1540
- const configuredSecrets = configuredSecretNames(result, postWriteEnv);
1541
- printSecretsChecklist(result.plan.secrets, configuredSecrets);
1542
- const finalGate = evaluateFirstRunReadiness({
1543
- plan: result.plan,
1544
- report,
1545
- secretPersistence: result.secretPersistence,
1546
- verifiedCredentialModelRefs: readinessProgress?.verifiedModelRefs ?? [],
1547
- });
1548
- if (!finalGate.ready) {
1549
- printIncompleteSetup(finalGate.reasons, result.configPath);
1550
- return 1;
1551
- }
1552
- const postValidationConfigDrift = await firstRunConfigDrift(result.configPath, committedConfigSnapshot);
1553
- if (postValidationConfigDrift !== undefined) {
1554
- printIncompleteSetup([postValidationConfigDrift.message], result.configPath);
1555
- return 1;
1556
- }
1557
- const postValidationDrift = await firstRunDotenvDrift(environment.dotenvPath, committedDotenvSnapshot);
1558
- if (postValidationDrift !== undefined) {
1559
- printIncompleteSetup([postValidationDrift.message], result.configPath);
1560
- return 1;
1561
- }
1562
- const postValidationSecretGuard = await firstRunSecretEnvGuardFailure(environment.dotenvPath, result.secretPersistence.status === "persisted");
1563
- if (postValidationSecretGuard !== undefined) {
1564
- printIncompleteSetup([postValidationSecretGuard.message], result.configPath);
1565
- return 1;
1566
- }
1567
- process.stdout.write(ui.badge("ok") + ui.style.green("All runtime route checks passed — every selected model produced a real no-tool response.\n") +
1568
- ui.badge("ok") + ui.style.green("Agent configuration validated — every selected capability passed full validation.\n"));
1569
- const preTuiConfigDrift = await firstRunConfigDrift(result.configPath, committedConfigSnapshot);
1570
- if (preTuiConfigDrift !== undefined) {
1571
- printIncompleteSetup([preTuiConfigDrift.message], result.configPath);
1572
- return 1;
1573
- }
1574
- const preTuiDotenvDrift = await firstRunDotenvDrift(environment.dotenvPath, committedDotenvSnapshot);
1575
- if (preTuiDotenvDrift !== undefined) {
1576
- printIncompleteSetup([preTuiDotenvDrift.message], result.configPath);
1577
- return 1;
1578
- }
1579
- const preTuiSecretGuard = await firstRunSecretEnvGuardFailure(environment.dotenvPath, result.secretPersistence.status === "persisted");
1580
- if (preTuiSecretGuard !== undefined) {
1581
- printIncompleteSetup([preTuiSecretGuard.message], result.configPath);
1582
- return 1;
1583
- }
1584
- if (process.platform !== "darwin") {
1585
- printUnsupportedGuidedInitHandoff(result.configPath, backgroundEnvFile);
1586
- return 0;
1587
- }
1588
- process.stdout.write(ui.hint("Starting the authoritative background agent before configuration chat…"));
1589
- let background;
1590
- try {
1591
- const resolvedBackgroundTarget = await resolveInstanceTarget({
1592
- args: {
1593
- configPath: result.configPath,
1594
- ...(backgroundEnvFile === undefined ? {} : { envFile: backgroundEnvFile }),
1595
- },
1596
- env: { ...committedBackgroundEnvironment },
1597
- cwd,
1598
- cliPath: fileURLToPath(import.meta.url),
1599
- requireTui: true,
1600
- });
1601
- const backgroundTarget = {
1602
- ...resolvedBackgroundTarget,
1603
- expectedSnapshot: committedBackgroundSnapshot,
1604
- };
1605
- background = await ensureBackgroundReady(backgroundTarget, defaultBackgroundDeps());
1606
- }
1607
- catch (error) {
1608
- printUnexpectedGuidedBackgroundFailure(result.configPath, backgroundEnvFile, error);
1609
- return 1;
1610
- }
1611
- if (!background.ok) {
1612
- process.stderr.write(ui.hint("The validated agent files were preserved, but configuration chat was not opened because the background agent is not ready."));
1613
- return 1;
1614
- }
1615
- process.stdout.write(ui.badge("ok") + ui.style.green("Agent ready — the background process reported startup complete.\n") +
1616
- ui.badge("ok") + ui.style.green("Opening configuration chat on the authoritative background agent.\n"));
1617
- const { runTui } = await import("./tui-command.js");
1618
- return await withExactProcessEnvironment(committedBackgroundEnvironment, () => runTui({
1619
- configPath: result.configPath,
1620
- cwd,
1621
- env: committedBackgroundEnvironment,
1622
- ...(backgroundEnvFile === undefined ? {} : { envFile: backgroundEnvFile }),
1623
- agent: background.source.sourceId,
1624
- configure: true,
1625
- }));
1626
- }
1627
- configurationRecoveryStep = focusedConfigurationRepairStep(stagedGate.failedSectionIds);
1628
- invalidPlanStage = "final_readiness";
1629
- failure = {
1630
- ok: false,
1631
- kind: "invalid_plan",
1632
- message: `Runtime route checks passed, but the complete agent is not ready: ${stagedGate.reasons.join(" ")}`,
1633
- };
1634
- }
1635
- if (failure.ok)
1636
- throw new Error("First-run recovery reached without a failure.");
1637
- if (failure.interrupted === true || failure.kind === "cancelled") {
1638
- interruptedRecoveryMenu: for (;;) {
1639
- const interruptedRecovery = await selectInterruptedFirstRunRecovery();
1640
- if (interruptedRecovery === "cancel")
1641
- return 1;
1642
- if (interruptedRecovery === "restart") {
1643
- readinessProgress = undefined;
1644
- break interruptedRecoveryMenu;
1645
- }
1646
- if (interruptedRecovery === "edit") {
1647
- const repaired = await runSetupRepairWizard({
1648
- cwd,
1649
- answers,
1650
- piAuthPath: resolvedPiAuthPath,
1651
- persistedEnv: dotenvSnapshot.env,
1652
- moduleSecrets,
1653
- providerSetupSecrets,
1654
- providerEnvironmentSecrets,
1655
- piApiKeyPersistenceByProvider,
1656
- credentialStates,
1657
- runProviderSetup: pendingProviderSetup,
1658
- });
1659
- if (repaired.status === "cancelled")
1660
- continue interruptedRecoveryMenu;
1661
- answers = repaired.answers;
1662
- moduleSecrets = { ...repaired.moduleSecrets };
1663
- providerSetupSecrets = { ...repaired.providerSetupSecrets };
1664
- providerEnvironmentSecrets = { ...repaired.providerEnvironmentSecrets };
1665
- piApiKeyPersistenceByProvider = { ...repaired.piApiKeyPersistenceByProvider };
1666
- credentialStates = { ...repaired.credentialStates };
1667
- pendingProviderSetup = repaired.runProviderSetup;
1668
- if (pendingProviderSetup)
1669
- readinessProgress = undefined;
1670
- }
1671
- break interruptedRecoveryMenu;
1672
- }
1673
- continue firstRun;
1674
- }
1675
- if (failure.message.startsWith("[sandbox_preflight_failed]")) {
1676
- sandboxRecoveryMenu: for (;;) {
1677
- const recovery = await selectSandboxPreflightRecovery();
1678
- if (recovery === "cancel")
1679
- return 1;
1680
- if (recovery === "edit") {
1681
- const edited = await runSetupRepairWizard({
1682
- cwd,
1683
- answers,
1684
- piAuthPath: resolvedPiAuthPath,
1685
- persistedEnv: dotenvSnapshot.env,
1686
- moduleSecrets,
1687
- providerSetupSecrets,
1688
- providerEnvironmentSecrets,
1689
- piApiKeyPersistenceByProvider,
1690
- credentialStates,
1691
- runProviderSetup: pendingProviderSetup,
1692
- });
1693
- if (edited.status === "cancelled")
1694
- continue sandboxRecoveryMenu;
1695
- answers = edited.answers;
1696
- moduleSecrets = { ...edited.moduleSecrets };
1697
- providerSetupSecrets = { ...edited.providerSetupSecrets };
1698
- providerEnvironmentSecrets = { ...edited.providerEnvironmentSecrets };
1699
- piApiKeyPersistenceByProvider = { ...edited.piApiKeyPersistenceByProvider };
1700
- credentialStates = { ...edited.credentialStates };
1701
- pendingProviderSetup = edited.runProviderSetup;
1702
- if (pendingProviderSetup)
1703
- readinessProgress = undefined;
1704
- }
1705
- break sandboxRecoveryMenu;
1706
- }
1707
- continue firstRun;
1708
- }
1709
- let recoveryFailure = failure;
1710
- recoveryMenu: for (;;) {
1711
- p.log.error(`[${recoveryFailure.kind}] ${recoveryFailure.message}`);
1712
- const recovery = await selectFirstRunRecovery(recoveryFailure, configurationRecoveryStep, invalidPlanStage);
1713
- if (recovery === "cancel")
1714
- return 1;
1715
- if (recovery === "save") {
1716
- let saved;
1717
- try {
1718
- saved = await initMonoAgentFolder({
1719
- dir: cwd,
1720
- answers,
1721
- env: effectiveEnv,
1722
- secretValues: selectedSecrets,
1723
- secureExistingDotenv,
1724
- requireConfigCreation: true,
1725
- });
1726
- }
1727
- catch (error) {
1728
- const recovery = secretPersistenceRecoveryMessage(error);
1729
- process.stderr.write(ui.errorLine(`The incomplete scaffold could not be committed safely; inspect the destination and retry.${recovery}`));
1730
- return 1;
1731
- }
1732
- printInitResult(saved);
1733
- let durableSavedEnv = {};
1734
- if (saved.secretPersistence.status === "persisted") {
1735
- try {
1736
- durableSavedEnv = (await readCliDotenvSnapshot(environment.dotenvPath)).env;
1737
- }
1738
- catch {
1739
- durableSavedEnv = {};
1740
- }
1741
- }
1742
- printSecretsChecklist(saved.plan.secrets, configuredSecretNames(saved, durableSavedEnv));
1743
- printIncompleteSetup([recoveryFailure.message], saved.configPath);
1744
- return 1;
1745
- }
1746
- if (recovery === "edit") {
1747
- const repaired = await runSetupRepairWizard({
1748
- cwd,
1749
- answers,
1750
- piAuthPath: resolvedPiAuthPath,
1751
- persistedEnv: dotenvSnapshot.env,
1752
- ...(configurationRecoveryStep === undefined ? {} : { initialStep: configurationRecoveryStep }),
1753
- moduleSecrets,
1754
- providerSetupSecrets,
1755
- providerEnvironmentSecrets,
1756
- piApiKeyPersistenceByProvider,
1757
- credentialStates,
1758
- runProviderSetup: pendingProviderSetup,
1759
- });
1760
- if (repaired.status === "cancelled")
1761
- continue recoveryMenu;
1762
- answers = repaired.answers;
1763
- moduleSecrets = { ...repaired.moduleSecrets };
1764
- providerSetupSecrets = { ...repaired.providerSetupSecrets };
1765
- providerEnvironmentSecrets = { ...repaired.providerEnvironmentSecrets };
1766
- piApiKeyPersistenceByProvider = { ...repaired.piApiKeyPersistenceByProvider };
1767
- credentialStates = { ...repaired.credentialStates };
1768
- pendingProviderSetup = repaired.runProviderSetup;
1769
- if (pendingProviderSetup)
1770
- readinessProgress = undefined;
1771
- continue firstRun;
1772
- }
1773
- if (recovery === "model") {
1774
- const repaired = await runSetupRepairWizard({
1775
- cwd,
1776
- answers,
1777
- piAuthPath: resolvedPiAuthPath,
1778
- persistedEnv: dotenvSnapshot.env,
1779
- initialStep: 1,
1780
- moduleSecrets,
1781
- providerSetupSecrets,
1782
- providerEnvironmentSecrets,
1783
- piApiKeyPersistenceByProvider,
1784
- credentialStates,
1785
- runProviderSetup: pendingProviderSetup,
1786
- });
1787
- if (repaired.status === "cancelled")
1788
- continue recoveryMenu;
1789
- answers = repaired.answers;
1790
- moduleSecrets = { ...repaired.moduleSecrets };
1791
- providerSetupSecrets = { ...repaired.providerSetupSecrets };
1792
- providerEnvironmentSecrets = { ...repaired.providerEnvironmentSecrets };
1793
- piApiKeyPersistenceByProvider = { ...repaired.piApiKeyPersistenceByProvider };
1794
- credentialStates = { ...repaired.credentialStates };
1795
- pendingProviderSetup = repaired.runProviderSetup;
1796
- if (pendingProviderSetup)
1797
- readinessProgress = undefined;
1798
- continue firstRun;
1799
- }
1800
- if (recovery === "auth") {
1801
- // Authentication can replace credential bytes without changing the
1802
- // route/config fingerprint. Every route must be proven again.
1803
- readinessProgress = undefined;
1804
- if (referencedSetupModelRefs(plan).some((ref) => ref.startsWith("codex:"))) {
1805
- const selected = await selectCodexAuthMode(selectedCodexAuthMode);
1806
- if (selected === undefined)
1807
- return 1;
1808
- selectedCodexAuthMode = selected;
1809
- }
1810
- const setupPlan = planProviderSetup({
1811
- modelRefs: referencedSetupModelRefs(plan),
1812
- cwd,
1813
- piAuthPath: resolvedPiAuthPath,
1814
- codexAuthMode: selectedCodexAuthMode,
1815
- forceAuthentication: true,
1816
- });
1817
- const prompted = await promptProviderSetupSecrets(setupPlan, providerSetupSecrets, piApiKeyPersistenceByProvider, providerEnvironmentSecrets);
1818
- if (prompted === undefined)
1819
- return 1;
1820
- providerSetupSecrets = prompted.apiKeys;
1821
- piApiKeyPersistenceByProvider = prompted.persistenceByProvider;
1822
- providerEnvironmentSecrets = prompted.environmentSecrets;
1823
- readinessProgress = undefined;
1824
- const selectedSetupPlan = planProviderSetup({
1825
- modelRefs: referencedSetupModelRefs(plan),
1826
- cwd,
1827
- piAuthPath: resolvedPiAuthPath,
1828
- codexAuthMode: selectedCodexAuthMode,
1829
- forceAuthentication: true,
1830
- piApiKeyPersistenceByProvider,
1831
- });
1832
- const environmentApiKeys = environmentProviderApiKeys(selectedSetupPlan, { ...effectiveEnv, ...providerEnvironmentSecrets });
1833
- const missingEnvironmentKeys = selectedSetupPlan.actions
1834
- .filter(isProviderSetupPiApiKeyAction)
1835
- .filter((action) => action.persistence === "environment" && environmentApiKeys[action.id] === undefined)
1836
- .map((action) => action.envVar);
1837
- if (missingEnvironmentKeys.length > 0) {
1838
- recoveryFailure = {
1839
- ok: false,
1840
- kind: "provider_failed",
1841
- message: `Add ${missingEnvironmentKeys.join(", ")} to the durable owner-only .env, then retry authentication. No agent files were written.`,
1842
- };
1843
- continue recoveryMenu;
1844
- }
1845
- const setup = await withScopedPreflightCancellation((abortSignal) => withExactProcessEnvironment(effectiveEnv, () => runProviderSetupBeforeInit({
1846
- modelRefs: referencedSetupModelRefs(plan),
1847
- cwd,
1848
- auth: true,
1849
- dryRun: false,
1850
- piAuthPath: resolvedPiAuthPath,
1851
- apiKeys: { ...providerSetupSecrets, ...environmentApiKeys },
1852
- codexAuthMode: selectedCodexAuthMode,
1853
- forceAuthentication: true,
1854
- piApiKeyPersistenceByProvider,
1855
- abortSignal,
1856
- })), { keypress: false });
1857
- if (setup === "fatal")
1858
- return 130;
1859
- if (setup === "interrupted") {
1860
- pendingProviderSetup = true;
1861
- deferredFailure = {
1862
- ok: false,
1863
- kind: "cancelled",
1864
- message: "Provider setup was interrupted. No agent files were written.",
1865
- interrupted: true,
1866
- };
1867
- continue firstRun;
1868
- }
1869
- if (setup === "failed") {
1870
- pendingProviderSetup = true;
1871
- recoveryFailure = {
1872
- ok: false,
1873
- kind: "provider_failed",
1874
- message: "Provider setup still needs attention. No agent files were written.",
1875
- };
1876
- continue recoveryMenu;
1877
- }
1878
- pendingProviderSetup = false;
1879
- }
1880
- // "retry" deliberately reruns only the live checks. Provider setup is a
1881
- // separate explicit recovery action and is never repeated automatically.
1882
- continue firstRun;
1883
- }
1884
- }
1885
- }
1886
- const presetId = resolveInitPresetId(args);
1887
- if (presetId === "unknown") {
1888
- return 1;
1889
- }
1890
- const withChannels = resolveWithChannels(args);
1891
- if (withChannels === "invalid") {
1892
- return 1;
1893
- }
1894
- const answers = answersFromCli({
1895
- ...(args.model === undefined ? {} : { model: args.model }),
1896
- ...(args.name === undefined ? {} : { name: args.name }),
1897
- ...(args.fallbackModels === undefined ? {} : { fallbackModels: args.fallbackModels }),
1898
- ...(args.fallbacks === undefined ? {} : { fallbacks: args.fallbacks }),
1899
- ...(args.routeSafety === undefined ? {} : { routeSafety: args.routeSafety }),
1900
- ...(args.effort === undefined ? {} : { effort: args.effort }),
1901
- ...(args.memory === undefined ? {} : { memory: args.memory }),
1902
- ...(withChannels === undefined ? {} : { withChannels }),
1903
- ...(presetId === undefined ? {} : { presetId }),
1904
- });
1905
- const previewPlan = composeWizardPlan(answers, {
1906
- dirBasename: basename(process.cwd()),
1907
- skillsRootExists: await pathExists(resolve(process.cwd(), "skills")),
1908
- });
1909
- const nonInteractivePiAuthPath = resolveEffectivePiAuthPath({
1910
- cwd,
1911
- ...(nonEmptyEnv(environment.shellEnv.MONO_AGENT_PI_AUTH_PATH)
1912
- ? { envPath: environment.shellEnv.MONO_AGENT_PI_AUTH_PATH }
1913
- : nonEmptyEnv(environment.dotenvEnv.MONO_AGENT_PI_AUTH_PATH)
1914
- ? { envPath: environment.dotenvEnv.MONO_AGENT_PI_AUTH_PATH }
1915
- : {}),
1916
- ...(nonEmptyEnv(previewPlan.configJson.providers?.piAuthPath)
1917
- ? { configPath: previewPlan.configJson.providers.piAuthPath }
1918
- : {}),
1919
- });
1920
- const nonInteractiveEnvironment = effectiveFirstRunEnvironment({
1921
- shellEnv: environment.shellEnv,
1922
- dotenvEnv: environment.dotenvEnv,
1923
- resolvedPiAuthPath: nonInteractivePiAuthPath,
1924
- });
1925
- const setup = await withScopedPreflightCancellation((abortSignal) => withExactProcessEnvironment(nonInteractiveEnvironment, () => runProviderSetupBeforeInit({
1926
- modelRefs: referencedSetupModelRefs(previewPlan),
1927
- cwd,
1928
- auth: args.auth === true,
1929
- dryRun: args.dryRun,
1930
- persistedEnv: environment.dotenvEnv,
1931
- piAuthPath: nonInteractivePiAuthPath,
1932
- ...(args.codexAuthMode === undefined ? {} : { codexAuthMode: args.codexAuthMode }),
1933
- abortSignal,
1934
- })), { keypress: false });
1935
- if (setup === "interrupted" || setup === "fatal") {
1936
- return 130;
1937
- }
1938
- if (setup === "failed") {
1939
- return 1;
1940
- }
1941
- const result = await initMonoAgentFolder({
1942
- dir: cwd,
1943
- answers,
1944
- dryRun: args.dryRun,
1945
- // Scaffold-only init has no immediate launchd-minimal worker proof. Reject
1946
- // both shell and persisted memory-identity overrides instead of creating a
1947
- // generation at a different path/tier than a follow-up validate would use.
1948
- env: { ...environment.shellEnv, ...environment.dotenvEnv },
1949
- });
1950
- printInitResult(result);
1951
- printSecretsChecklist(result.plan.secrets, new Set());
1952
- printNextSteps(result.configPath);
1953
- return 0;
1954
- }
1955
- const FIRST_RUN_STAGING_FAILURE_MAX_LENGTH = 500;
1956
- const FIRST_RUN_SENSITIVE_ENV_NAME = /(api.?key|credential|password|secret|token)/iu;
1957
- function throwIfFirstRunPreflightAborted(signal) {
1958
- if (signal?.aborted !== true)
1959
- return;
1960
- const error = new Error("Preflight was interrupted.");
1961
- error.name = "AbortError";
1962
- throw error;
1963
- }
1964
- function firstRunStagingFailureDetail(error, sensitiveValues = []) {
1965
- let message = reasonOf(error);
1966
- for (const value of [...new Set(sensitiveValues)].filter((candidate) => candidate.length >= 4).sort((left, right) => right.length - left.length)) {
1967
- message = message.replaceAll(value, "[secret-redacted]");
1968
- }
1969
- const normalized = message
1970
- .replace(/\bBearer\s+[^\s,;]+/giu, "Bearer [secret-redacted]")
1971
- .replace(/\b(api[ _-]?key|access[ _-]?token|auth[ _-]?token|password|secret)(\s*[=:]\s*)([^\s,;]+)/giu, (_match, label, separator) => `${label}${separator}[secret-redacted]`)
1972
- .replace(/\s+/gu, " ")
1973
- .trim();
1974
- if (normalized.length === 0)
1975
- return "Unknown staging failure.";
1976
- return normalized.length <= FIRST_RUN_STAGING_FAILURE_MAX_LENGTH
1977
- ? normalized
1978
- : `${normalized.slice(0, FIRST_RUN_STAGING_FAILURE_MAX_LENGTH - 1).trimEnd()}…`;
1979
- }
1980
- function firstRunStagingSensitiveValues(env, explicit) {
1981
- return [
1982
- ...Object.entries(env)
1983
- .filter((entry) => FIRST_RUN_SENSITIVE_ENV_NAME.test(entry[0])
1984
- && typeof entry[1] === "string"
1985
- && entry[1].length > 0)
1986
- .map(([, value]) => value),
1987
- ...Object.values(explicit),
1988
- ];
1989
- }
1990
- async function runConfigurationPreflightWithSpinner(options) {
1991
- process.stdout.write("\n" + ui.heading("Configuration preflight"));
1992
- const spinner = p.spinner();
1993
- try {
1994
- return await withScopedPreflightCancellation(async (abortSignal) => {
1995
- spinner.start("Validating generated files and selected capabilities before runtime calls");
1996
- const gate = await assessPrewriteFirstRunConfigurationReadiness({ ...options, abortSignal });
1997
- if (abortSignal.aborted || spinner.isCancelled) {
1998
- spinner.cancel("Configuration preflight interrupted");
1999
- return interruptedConfigurationAssessment();
2000
- }
2001
- if (gate.ready) {
2002
- spinner.stop("Selected capabilities are ready for runtime checks");
2003
- }
2004
- else {
2005
- spinner.error("Configuration preflight needs attention");
2006
- }
2007
- return gate;
2008
- });
2009
- }
2010
- catch (error) {
2011
- if (!isAbortLike(error))
2012
- throw error;
2013
- spinner.cancel("Configuration preflight interrupted");
2014
- return interruptedConfigurationAssessment();
2015
- }
2016
- }
2017
- function interruptedConfigurationAssessment() {
2018
- return {
2019
- ready: false,
2020
- reasons: ["Configuration preflight was interrupted."],
2021
- failedSectionIds: [],
2022
- interrupted: true,
2023
- };
2024
- }
2025
- async function runFinalReadinessValidationWithSpinner(options) {
2026
- process.stdout.write("\n" + ui.heading("Final readiness validation"));
2027
- const spinner = p.spinner();
2028
- try {
2029
- return await withScopedPreflightCancellation(async (abortSignal) => {
2030
- spinner.start("Revalidating the effective files after runtime route checks");
2031
- const gate = await assessPrewriteFirstRunReadiness({ ...options, abortSignal });
2032
- if (abortSignal.aborted || spinner.isCancelled) {
2033
- spinner.cancel("Final readiness validation interrupted");
2034
- return interruptedFinalReadinessAssessment();
2035
- }
2036
- if (gate.ready)
2037
- spinner.stop("Effective files and runtime routes are ready");
2038
- else
2039
- spinner.error("Final readiness validation needs attention");
2040
- return gate;
2041
- });
2042
- }
2043
- catch (error) {
2044
- if (!isAbortLike(error))
2045
- throw error;
2046
- spinner.cancel("Final readiness validation interrupted");
2047
- return interruptedFinalReadinessAssessment();
2048
- }
2049
- }
2050
- function interruptedFinalReadinessAssessment() {
2051
- return {
2052
- ready: false,
2053
- reasons: ["Final readiness validation was interrupted."],
2054
- failedSectionIds: [],
2055
- interrupted: true,
2056
- };
2057
- }
2058
- async function assessPrewriteFirstRunConfigurationReadiness(options) {
2059
- try {
2060
- throwIfFirstRunPreflightAborted(options.abortSignal);
2061
- const preview = await initMonoAgentFolder({
2062
- dir: options.cwd,
2063
- answers: options.answers,
2064
- env: options.env,
2065
- secretValues: options.secretValues,
2066
- secureExistingDotenv: options.secureExistingDotenv,
2067
- dryRun: true,
2068
- });
2069
- throwIfFirstRunPreflightAborted(options.abortSignal);
2070
- const report = await validateWizardPlanInStaging({
2071
- plan: options.plan,
2072
- sourceCwd: options.cwd,
2073
- env: options.env,
2074
- verifiedCredentialModelRefs: [],
2075
- ...(options.abortSignal === undefined ? {} : { abortSignal: options.abortSignal }),
2076
- });
2077
- const gate = evaluateFirstRunConfigurationReadiness({
2078
- plan: options.plan,
2079
- report,
2080
- secretPersistence: preview.secretPersistence,
2081
- });
2082
- return {
2083
- ...gate,
2084
- failedSectionIds: configurationFailureSectionIds(options.plan, report, true),
2085
- };
2086
- }
2087
- catch (error) {
2088
- throwIfFirstRunPreflightAborted(options.abortSignal);
2089
- if (error instanceof Error && error.name === "AbortError")
2090
- throw error;
2091
- return {
2092
- ready: false,
2093
- reasons: [
2094
- `The complete generated configuration could not be validated safely in staging: ${firstRunStagingFailureDetail(error, firstRunStagingSensitiveValues(options.env, options.secretValues))}`,
2095
- ],
2096
- failedSectionIds: [],
2097
- };
2098
- }
2099
- }
2100
- async function assessPrewriteFirstRunReadiness(options) {
2101
- try {
2102
- throwIfFirstRunPreflightAborted(options.abortSignal);
2103
- const preview = await initMonoAgentFolder({
2104
- dir: options.cwd,
2105
- answers: options.answers,
2106
- env: options.env,
2107
- secretValues: options.secretValues,
2108
- secureExistingDotenv: options.secureExistingDotenv,
2109
- dryRun: true,
2110
- });
2111
- throwIfFirstRunPreflightAborted(options.abortSignal);
2112
- const report = await validateWizardPlanInStaging({
2113
- plan: options.plan,
2114
- sourceCwd: options.cwd,
2115
- env: options.env,
2116
- verifiedCredentialModelRefs: options.verifiedCredentialModelRefs,
2117
- ...(options.abortSignal === undefined ? {} : { abortSignal: options.abortSignal }),
2118
- });
2119
- const gate = evaluateFirstRunReadiness({
2120
- plan: options.plan,
2121
- report,
2122
- secretPersistence: preview.secretPersistence,
2123
- verifiedCredentialModelRefs: options.verifiedCredentialModelRefs,
2124
- });
2125
- return {
2126
- ...gate,
2127
- failedSectionIds: configurationFailureSectionIds(options.plan, report, false),
2128
- };
2129
- }
2130
- catch (error) {
2131
- throwIfFirstRunPreflightAborted(options.abortSignal);
2132
- if (error instanceof Error && error.name === "AbortError")
2133
- throw error;
2134
- return {
2135
- ready: false,
2136
- reasons: [
2137
- `The complete generated plan could not be validated safely in staging: ${firstRunStagingFailureDetail(error, firstRunStagingSensitiveValues(options.env, options.secretValues))}`,
2138
- ],
2139
- failedSectionIds: [],
2140
- };
2141
- }
2142
- }
2143
- function configurationFailureSectionIds(plan, report, deferWaitingCredentials) {
2144
- const byId = new Map(report.sections.map((section) => [section.id, section]));
2145
- const ids = new Set();
2146
- for (const expectation of plan.validateExpectations) {
2147
- const actual = byId.get(expectation.sectionId)?.status;
2148
- if (actual === expectation.mustBe
2149
- || (deferWaitingCredentials && expectation.sectionId === "credentials" && actual === "waiting"))
2150
- continue;
2151
- ids.add(expectation.sectionId);
2152
- }
2153
- if (!report.ok) {
2154
- for (const section of report.sections) {
2155
- if (section.status === "error")
2156
- ids.add(section.id);
2157
- }
2158
- }
2159
- return [...ids];
2160
- }
2161
- function nonEmptyEnv(value) {
2162
- return typeof value === "string" && value.trim().length > 0;
2163
- }
2164
- function sameConcreteEnvironment(left, right) {
2165
- const concreteEntries = (env) => Object.entries(env)
2166
- .filter((entry) => typeof entry[1] === "string")
2167
- .sort(([leftName], [rightName]) => leftName.localeCompare(rightName));
2168
- const leftEntries = concreteEntries(left);
2169
- const rightEntries = concreteEntries(right);
2170
- return leftEntries.length === rightEntries.length && leftEntries.every(([name, value], index) => rightEntries[index]?.[0] === name && rightEntries[index]?.[1] === value);
2171
- }
2172
- function dotenvReadinessFailure(message) {
2173
- return { ok: false, kind: "invalid_plan", message };
2174
- }
2175
- async function firstRunDotenvDrift(path, expected) {
2176
- let current;
2177
- try {
2178
- current = await readCliDotenvSnapshot(path);
2179
- }
2180
- catch {
2181
- return dotenvReadinessFailure("The persisted .env became unreadable during setup. Readiness cannot be claimed.");
2182
- }
2183
- if (current.fingerprint === expected.fingerprint)
2184
- return undefined;
2185
- return dotenvReadinessFailure("The persisted .env changed while setup was validating the agent. Review the change, then retry so the exact durable values can be checked.");
2186
- }
2187
- async function firstRunConfigDrift(path, expected) {
2188
- let current;
2189
- try {
2190
- current = await readCliConfigSnapshot(path);
2191
- }
2192
- catch {
2193
- return dotenvReadinessFailure("The committed config became unreadable or unsafe during setup. Readiness cannot be claimed.");
2194
- }
2195
- if (current.fingerprint === expected.fingerprint)
2196
- return undefined;
2197
- return dotenvReadinessFailure("The committed config changed while setup was validating the agent. Review the change, then retry so the exact plan can be checked.");
2198
- }
2199
- async function firstRunSecretEnvGuardFailure(path, required) {
2200
- if (!required)
2201
- return undefined;
2202
- try {
2203
- if (await verifySecretEnvPersistenceGuard(path))
2204
- return undefined;
2205
- }
2206
- catch {
2207
- // Fall through to one stable, non-secret-bearing operator message.
2208
- }
2209
- return dotenvReadinessFailure("The committed .env is no longer owner-only, safely ignored, and untracked. Readiness cannot be claimed.");
2210
- }
2211
- function secretPersistenceRecoveryMessage(error) {
2212
- if (!(error instanceof SecretEnvConcurrentModificationError)) {
2213
- return "";
2214
- }
2215
- return ` ${error.message}`;
2216
- }
2217
- function readinessPlanIdentity(plan) {
2218
- const displayed = readinessRoutesForDisplay(plan);
2219
- const immutable = displayed.map((route, index) => ({
2220
- index,
2221
- model: route.model,
2222
- effort: route.effort ?? null,
2223
- }));
2224
- return {
2225
- fingerprint: createHash("sha256")
2226
- .update(JSON.stringify({ version: 1, routes: immutable }))
2227
- .digest("hex"),
2228
- routes: displayed.map((route, index) => ({
2229
- index,
2230
- ...route,
2231
- key: createHash("sha256")
2232
- .update(JSON.stringify({ version: 1, index, model: route.model, effort: route.effort ?? null }))
2233
- .digest("hex"),
2234
- })),
2235
- };
2236
- }
2237
- function mergeReadinessProgress(previous, result, plan) {
2238
- const identity = readinessPlanIdentity(plan);
2239
- const fingerprint = result.planFingerprint ?? identity.fingerprint;
2240
- const successfulKeys = new Set(previous?.planFingerprint === fingerprint ? previous.successfulRouteKeys : []);
2241
- const verifiedRefs = new Set(previous?.planFingerprint === fingerprint ? previous.verifiedModelRefs : []);
2242
- const reported = result.routes ?? (result.ok
2243
- ? identity.routes.map((route) => ({ ...route, status: "verified" }))
2244
- : []);
2245
- for (const route of reported) {
2246
- if (route.status === "verified" || route.status === "skipped_verified") {
2247
- successfulKeys.add(route.key);
2248
- verifiedRefs.add(route.model);
2249
- }
2250
- }
2251
- const currentRefs = new Set(identity.routes.map((route) => route.model));
2252
- return {
2253
- planFingerprint: fingerprint,
2254
- successfulRouteKeys: [...successfulKeys],
2255
- verifiedModelRefs: [...verifiedRefs].filter((ref) => currentRefs.has(ref)),
2256
- };
2257
- }
2258
- async function runGuidedSandboxPreflight(installedEarlier) {
2259
- process.stdout.write("\n" + ui.heading("Sandbox preflight"));
2260
- return await withScopedPreflightCancellation(async (signal) => {
2261
- try {
2262
- process.stdout.write(ui.style.dim(installedEarlier
2263
- ? "Rechecking the pinned managed SRT copy and its functional enforcement postcondition.\n"
2264
- : "Installing the pinned managed SRT copy in the private user cache, then running the functional enforcement check.\n"));
2265
- const setup = await setupManagedSrt({ signal, verify: true });
2266
- if (setup.status.source !== "managed" || setup.status.state !== "ready" || setup.check === undefined) {
2267
- throw new Error("Managed SRT setup did not return a ready managed functional-check result.");
2268
- }
2269
- process.stdout.write(`${ui.badge("ok")}Managed SRT ${setup.repaired ? "repaired" : setup.installed ? "installed" : "verified"}; functional postcondition passed.\n`);
2270
- return { ok: true };
2271
- }
2272
- catch (error) {
2273
- if (signal.aborted || isAbortLike(error)) {
2274
- process.stderr.write(ui.errorLine("Preflight was interrupted."));
2275
- return {
2276
- ok: false,
2277
- kind: "cancelled",
2278
- message: "Sandbox preflight was interrupted. No agent files were written.",
2279
- interrupted: true,
2280
- };
2281
- }
2282
- return {
2283
- ok: false,
2284
- kind: "provider_failed",
2285
- message: `[sandbox_preflight_failed] ${reasonOf(error)} No agent files were written; retry setup or edit the sandbox choice.`,
2286
- };
2287
- }
2288
- });
2289
- }
2290
- async function runReadinessProbeWithSpinner(options) {
2291
- const routes = readinessRoutesForDisplay(options.plan);
2292
- process.stdout.write("\n" + ui.heading("Runtime readiness"));
2293
- routes.forEach((route, index) => {
2294
- const timeoutMs = readinessProbeTimeoutMs(parseMonoRuntimeModelReference(route.model));
2295
- process.stdout.write(` Route ${index + 1}/${routes.length}: ${route.model} ` +
2296
- ui.style.dim(`(effort: ${route.effort ?? "provider-default"}; up to ${Math.ceil(timeoutMs / 1_000)}s)`) +
2297
- "\n");
2298
- });
2299
- process.stdout.write(ui.style.dim("Running real no-tool checks sequentially. Press Esc or Ctrl-C once to interrupt safely.\n"));
2300
- return await withScopedPreflightCancellation(async (signal) => {
2301
- try {
2302
- const result = await runAllRouteReadinessProbe({
2303
- plan: options.plan,
2304
- hostEnv: options.effectiveEnv,
2305
- secretValues: selectedSecretValues(options.plan, options.effectiveEnv),
2306
- resolvedPiAuthPath: options.resolvedPiAuthPath,
2307
- abortSignal: signal,
2308
- ...(options.resume === undefined ? {} : { resume: options.resume }),
2309
- onRouteStart: (route) => {
2310
- process.stdout.write(` Checking route ${route.index + 1}/${route.total}: ${route.model} ` +
2311
- ui.style.dim(`(effort: ${route.effort ?? "provider-default"})`) +
2312
- "\n");
2313
- },
2314
- onRouteComplete: (route) => {
2315
- const ok = route.status === "verified" || route.status === "skipped_verified";
2316
- process.stdout.write(`${ok ? ui.badge("ok") : route.status === "interrupted" ? ui.badge("waiting") : ui.badge("error")}` +
2317
- `Route ${route.index + 1}/${routes.length} ${route.status.replaceAll("_", " ")}\n`);
2318
- },
2319
- });
2320
- process.stdout.write(ui.heading("Readiness summary"));
2321
- printReadinessRouteSummary(result, routes);
2322
- return result;
2323
- }
2324
- catch (error) {
2325
- if (signal.aborted || isAbortLike(error)) {
2326
- process.stderr.write(ui.errorLine("Preflight was interrupted."));
2327
- return {
2328
- ok: false,
2329
- kind: "cancelled",
2330
- message: "Preflight was interrupted before the current route completed.",
2331
- interrupted: true,
2332
- };
2333
- }
2334
- process.stderr.write(ui.errorLine("[readiness_probe_failed] Runtime readiness could not run."));
2335
- return {
2336
- ok: false,
2337
- kind: "probe_failed",
2338
- message: "Runtime readiness could not run. Review provider authentication and retry.",
2339
- };
2340
- }
2341
- });
2342
- }
2343
- function readinessRoutesForDisplay(plan) {
2344
- const runtime = (plan.configJson.runtime ?? {});
2345
- const primaryEffort = typeof runtime.effort === "string" ? runtime.effort : undefined;
2346
- const routes = [];
2347
- if (typeof runtime.model === "string") {
2348
- routes.push({ model: runtime.model, ...(primaryEffort === undefined ? {} : { effort: primaryEffort }) });
2349
- }
2350
- if (Array.isArray(runtime.fallbacks) && runtime.fallbacks.length > 0) {
2351
- for (const raw of runtime.fallbacks) {
2352
- if (typeof raw !== "object" || raw === null || Array.isArray(raw))
2353
- continue;
2354
- const entry = raw;
2355
- if (typeof entry.model !== "string")
2356
- continue;
2357
- routes.push({
2358
- model: entry.model,
2359
- ...(typeof entry.effort === "string" ? { effort: entry.effort } : {}),
2360
- });
2361
- }
2362
- }
2363
- else if (Array.isArray(runtime.fallbackModels)) {
2364
- for (const model of runtime.fallbackModels) {
2365
- if (typeof model === "string") {
2366
- routes.push({ model, ...(primaryEffort === undefined ? {} : { effort: primaryEffort }) });
2367
- }
2368
- }
2369
- }
2370
- return routes;
2371
- }
2372
- function printReadinessRouteSummary(result, planned) {
2373
- const reported = result.routes ?? planned.map((route, index) => ({
2374
- key: `${index}:${route.model}`,
2375
- index,
2376
- ...route,
2377
- status: result.ok ? "verified" : result.kind === "cancelled" ? "interrupted" : "failed",
2378
- ...(!result.ok ? { kind: result.kind, message: result.message } : {}),
2379
- }));
2380
- for (const route of reported) {
2381
- const badge = route.status === "verified" || route.status === "skipped_verified"
2382
- ? ui.badge("ok")
2383
- : route.status === "interrupted"
2384
- ? ui.badge("waiting")
2385
- : ui.badge("error");
2386
- const state = route.status === "skipped_verified" ? "verified earlier" : route.status.replaceAll("_", " ");
2387
- process.stdout.write(`${badge}Route ${route.index + 1}/${planned.length}: ${route.model} ` +
2388
- ui.style.dim(`(effort: ${route.effort ?? "provider-default"})`) +
2389
- ` — ${state}${route.message === undefined ? "" : `: ${route.message}`}\n`);
2390
- }
2391
- if (!result.ok && result.interrupted === true) {
2392
- process.stderr.write(ui.errorLine("Preflight was interrupted."));
2393
- }
2394
- }
2395
- async function withScopedPreflightCancellation(task, options = {}) {
2396
- const controller = new AbortController();
2397
- let interruptCount = 0;
2398
- const interrupt = () => {
2399
- interruptCount += 1;
2400
- controller.abort();
2401
- if (interruptCount > 1)
2402
- process.exitCode = 130;
2403
- };
2404
- const onKeypress = (_value, key) => {
2405
- if (key?.name === "escape" || (key?.ctrl === true && key.name === "c"))
2406
- interrupt();
2407
- };
2408
- process.on("SIGINT", interrupt);
2409
- const restoreKeypress = options.keypress === false
2410
- ? () => undefined
2411
- : attachScopedKeypress(onKeypress);
2412
- try {
2413
- return await task(controller.signal);
2414
- }
2415
- finally {
2416
- process.off("SIGINT", interrupt);
2417
- restoreKeypress();
2418
- }
2419
- }
2420
- function focusedConfigurationRepairStep(sectionIds) {
2421
- const mapped = new Set();
2422
- for (const id of sectionIds) {
2423
- if (id === "agent")
2424
- mapped.add(0);
2425
- else if (id === "runtime" || id === "credentials")
2426
- mapped.add(1);
2427
- else if (id === "memory" || id.startsWith("memory:"))
2428
- mapped.add(3);
2429
- else if (id === "context" || id.startsWith("channel:"))
2430
- mapped.add(4);
2431
- else if (id === "tools")
2432
- mapped.add(5);
2433
- else if (id === "sandbox")
2434
- mapped.add(6);
2435
- else if (id === "observability")
2436
- mapped.add(7);
2437
- }
2438
- return mapped.size === 1 ? [...mapped][0] : undefined;
2439
- }
2440
- function configurationRecoveryEditLabel(step) {
2441
- switch (step) {
2442
- case 0: return "Edit agent name";
2443
- case 1: return "Edit model routes";
2444
- case 3: return "Edit memory";
2445
- case 4: return "Edit capability details";
2446
- case 5: return "Edit tools";
2447
- case 6: return "Edit route safety and sandbox";
2448
- case 7: return "Edit observability";
2449
- default: return "Edit setup choices";
2450
- }
2451
- }
2452
- async function selectSandboxPreflightRecovery() {
2453
- const recovery = await p.select({
2454
- message: "Sandbox preflight did not pass. How would you like to recover?",
2455
- initialValue: "retry",
2456
- options: [
2457
- { value: "retry", label: "Retry sandbox setup and check" },
2458
- { value: "edit", label: "Change safety or other choices" },
2459
- { value: "cancel", label: "Cancel without writing" },
2460
- ],
2461
- });
2462
- return p.isCancel(recovery) ? "cancel" : recovery;
2463
- }
2464
- async function selectInterruptedFirstRunRecovery() {
2465
- const recovery = await p.select({
2466
- message: "Preflight was interrupted. What would you like to do?",
2467
- initialValue: "resume",
2468
- options: [
2469
- { value: "resume", label: "Resume preflight", hint: "keeps successful auth, SRT setup, and route checks" },
2470
- { value: "restart", label: "Restart all checks", hint: "keeps successful auth and SRT installation" },
2471
- { value: "edit", label: "Edit setup choices" },
2472
- { value: "cancel", label: "Cancel without writing" },
2473
- ],
2474
- });
2475
- return p.isCancel(recovery) ? "cancel" : recovery;
2476
- }
2477
- async function selectFirstRunRecovery(failure, configurationRepairStep, invalidPlanStage) {
2478
- const sharedTail = [
2479
- { value: "save", label: "Save incomplete", hint: "does not call the agent ready or start it" },
2480
- { value: "cancel", label: "Cancel without writing" },
2481
- ];
2482
- const providerSetupFailed = failure.kind === "provider_failed"
2483
- && /^Provider setup (?:did not complete|still needs attention)\./u.test(failure.message);
2484
- const message = failure.kind === "invalid_plan"
2485
- ? invalidPlanStage === "final_readiness"
2486
- ? "Final readiness validation did not pass. What would you like to do?"
2487
- : "Configuration preflight did not pass. What would you like to do?"
2488
- : providerSetupFailed
2489
- ? "Provider setup did not pass. What would you like to do?"
2490
- : "Runtime readiness did not pass. What would you like to do?";
2491
- const options = failure.kind === "invalid_plan"
2492
- ? [
2493
- { value: "edit", label: configurationRecoveryEditLabel(configurationRepairStep) },
2494
- {
2495
- value: "retry",
2496
- label: invalidPlanStage === "final_readiness"
2497
- ? "Retry final readiness validation"
2498
- : "Retry configuration preflight",
2499
- },
2500
- ...sharedTail,
2501
- ]
2502
- : providerSetupFailed
2503
- ? [
2504
- { value: "auth", label: "Repair authentication" },
2505
- { value: "retry", label: "Retry provider setup" },
2506
- { value: "model", label: "Edit model routes" },
2507
- ...sharedTail,
2508
- ]
2509
- : failure.kind === "provider_failed"
2510
- ? [
2511
- { value: "retry", label: "Retry failed route" },
2512
- { value: "auth", label: "Repair authentication" },
2513
- { value: "model", label: "Edit model routes" },
2514
- ...sharedTail,
2515
- ]
2516
- : failure.kind === "unsupported_guided_probe"
2517
- ? [
2518
- { value: "model", label: "Edit model routes" },
2519
- ...sharedTail,
2520
- ]
2521
- : [
2522
- { value: "retry", label: "Retry runtime checks" },
2523
- { value: "model", label: "Edit model routes" },
2524
- ...sharedTail,
2525
- ];
2526
- const recovery = await p.select({
2527
- message,
2528
- initialValue: options[0]?.value ?? "cancel",
2529
- options: [...options],
2530
- });
2531
- return p.isCancel(recovery) ? "cancel" : recovery;
2532
- }
2533
- async function promptProviderSetupSecrets(plan, existing, existingPersistence = {}, existingEnvironmentSecrets = {}) {
2534
- const values = { ...existing };
2535
- const persistenceByProvider = { ...existingPersistence };
2536
- const environmentSecrets = { ...existingEnvironmentSecrets };
2537
- for (const action of plan.actions) {
2538
- if (!isProviderSetupPiApiKeyAction(action))
2539
- continue;
2540
- const reviewedPersistence = existingPersistence[action.provider];
2541
- const persistence = reviewedPersistence ?? await p.select({
2542
- message: `How should ${action.label} receive ${action.envVar}?`,
2543
- initialValue: "secure-store",
2544
- options: [
2545
- { value: "secure-store", label: "Store securely in Pi auth.json", hint: "owner-only credential store" },
2546
- { value: "environment", label: `Use environment variable ${action.envVar}`, hint: "save it to the agent's owner-only .env" },
2547
- ],
2548
- });
2549
- if (p.isCancel(persistence))
2550
- return undefined;
2551
- if (persistence === "environment") {
2552
- delete values[action.id];
2553
- persistenceByProvider[action.provider] = "environment";
2554
- const answer = await p.password({
2555
- message: `Enter ${action.envVar} for the agent's owner-only .env`,
2556
- validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
2557
- clearOnError: true,
2558
- });
2559
- if (p.isCancel(answer))
2560
- return undefined;
2561
- environmentSecrets[action.envVar] = answer;
2562
- continue;
2563
- }
2564
- persistenceByProvider[action.provider] = "secure-store";
2565
- delete environmentSecrets[action.envVar];
2566
- const answer = await p.password({
2567
- message: `Enter ${action.label} (${action.envVar})`,
2568
- validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
2569
- clearOnError: true,
2570
- });
2571
- if (p.isCancel(answer))
2572
- return undefined;
2573
- values[action.id] = answer;
2574
- }
2575
- return { apiKeys: values, persistenceByProvider, environmentSecrets };
2576
- }
2577
- function environmentProviderApiKeys(plan, env) {
2578
- const values = {};
2579
- for (const action of plan.actions) {
2580
- if (!isProviderSetupPiApiKeyAction(action) || action.persistence !== "environment")
2581
- continue;
2582
- const value = env[action.envVar];
2583
- if (nonEmptyEnv(value))
2584
- values[action.id] = value;
2585
- }
2586
- return values;
2587
- }
2588
- async function selectCodexAuthMode(initialValue) {
2589
- const selected = await p.select({
2590
- message: "How should Codex authenticate on this machine?",
2591
- initialValue,
2592
- options: [
2593
- { value: "browser", label: "Browser login", hint: "opens a localhost callback server" },
2594
- { value: "device", label: "Device-code login", hint: "recommended for remote or headless machines" },
2595
- ],
2596
- });
2597
- return p.isCancel(selected) ? undefined : selected;
2598
- }
2599
- function configuredSecretNames(result, effectiveEnv) {
2600
- const names = new Set();
2601
- for (const secret of result.plan.secrets) {
2602
- if (nonEmptyEnv(effectiveEnv[secret.envVar]))
2603
- names.add(secret.envVar);
2604
- }
2605
- return names;
2606
- }
2607
- function printIncompleteSetup(reasons, configPath) {
2608
- process.stderr.write(ui.hint("INCOMPLETE SETUP: no readiness claim was made and the agent was not started.\n"));
2609
- for (const reason of reasons)
2610
- process.stderr.write(ui.style.yellow(` - ${reason}\n`));
2611
- process.stderr.write(ui.hint(`Review ${configPath}, run \`mono-agent validate\`, then retry the first turn.\n`));
2612
- }
2613
- export function shouldRunInitWizard(args, stdinIsTty, stdoutIsTty) {
2614
- if (!stdinIsTty || !stdoutIsTty || args.command !== "init" || args.positionals.length > 0) {
2615
- return false;
2616
- }
2617
- if (args.force || args.foreground || args.follow || args.all || args.dryRun || args.includeMemory) {
2618
- return false;
2619
- }
2620
- // A bare parsed init has only these required/default keys. Treat every
2621
- // optional key—current or future—as an overriding flag so the documented
2622
- // "any flag is scaffold-only" contract cannot silently drift again.
2623
- const bareKeys = new Set([
2624
- "command",
2625
- "positionals",
2626
- "force",
2627
- "foreground",
2628
- "follow",
2629
- "all",
2630
- "dryRun",
2631
- "includeMemory",
2632
- ]);
2633
- return Object.keys(args).every((key) => bareKeys.has(key));
2634
- }
2635
- export async function runProviderSetupBeforeInit(options) {
2636
- const credentialStates = options.credentialStates !== undefined
2637
- ? options.credentialStates
2638
- : options.forceAuthentication === true || !options.auth || options.dryRun
2639
- ? undefined
2640
- : await detectProviderCredentialStates({
2641
- modelRefs: options.modelRefs,
2642
- cwd: options.cwd,
2643
- ...(options.piAuthPath === undefined ? {} : { piAuthPath: options.piAuthPath }),
2644
- ...(options.persistedEnv === undefined ? {} : { persistedEnv: options.persistedEnv }),
2645
- ...(options.abortSignal === undefined ? {} : { abortSignal: options.abortSignal }),
2646
- });
2647
- const plan = planProviderSetup({
2648
- ...options,
2649
- ...(credentialStates === undefined ? {} : { credentialStates }),
2650
- ...(options.codexAuthMode === undefined ? {} : { codexAuthMode: options.codexAuthMode }),
2651
- ...(options.forceAuthentication === undefined ? {} : { forceAuthentication: options.forceAuthentication }),
2652
- });
2653
- if (plan.actions.length === 0) {
2654
- return "skipped";
2655
- }
2656
- if (options.dryRun) {
2657
- process.stdout.write("\n" + ui.heading("Provider setup"));
2658
- process.stdout.write(ui.style.dim("Dry run - provider auth/preflight commands were not launched.\n"));
2659
- printProviderSetupPlan(plan);
2660
- return "skipped";
2661
- }
2662
- if (!options.auth) {
2663
- return "skipped";
2664
- }
2665
- process.stdout.write("\n" + ui.heading("Provider setup"));
2666
- printProviderSetupPlan(plan);
2667
- if (options.abortSignal !== undefined) {
2668
- process.stdout.write(ui.style.dim("Press Ctrl-C once to interrupt authentication safely.\n"));
2669
- }
2670
- const results = await (options.execute ?? ((setupPlan) => executeProviderSetupPlan(setupPlan, {
2671
- ...(options.apiKeys === undefined ? {} : { apiKeys: options.apiKeys }),
2672
- ...(options.abortSignal === undefined ? {} : { abortSignal: options.abortSignal }),
2673
- })))(plan);
2674
- const interrupted = options.abortSignal?.aborted === true;
2675
- for (const result of results) {
2676
- const badge = interrupted && result.status === "failed"
2677
- ? ui.badge("waiting")
2678
- : result.status === "ok"
2679
- ? ui.badge("ok")
2680
- : result.status === "skipped"
2681
- ? ui.style.dim("- ")
2682
- : ui.badge("error");
2683
- process.stdout.write(`${badge}${result.action.label}: ${result.detail}\n`);
2684
- }
2685
- if (results.some((result) => result.failureKind !== undefined)) {
2686
- process.stderr.write(ui.errorLine("Provider setup ended in an unconfirmed process or credential-cleanup state. Follow the reported manual cleanup guidance before retrying; automatic recovery is disabled."));
2687
- return "fatal";
2688
- }
2689
- if (interrupted) {
2690
- process.stderr.write(ui.errorLine("Provider setup was interrupted."));
2691
- return "interrupted";
2692
- }
2693
- if (results.some((result) => result.status === "failed")) {
2694
- process.stderr.write(ui.errorLine("Provider setup failed; init stopped before writing files."));
2695
- return "failed";
2696
- }
2697
- return "ok";
2698
- }
2699
- async function runAuth(args) {
2700
- const [subcommand, provider, ...extra] = args.positionals;
2701
- if (subcommand !== "login" || provider === undefined || extra.length > 0) {
2702
- process.stderr.write(ui.errorLine("Usage: mono-agent auth login <provider|codex> [--pi-auth-path <path>] [--api-key-stdin] [--codex-auth browser|device] [--config <path>]."));
2703
- return 2;
2704
- }
2705
- const cwd = process.cwd();
2706
- const configPath = await canonicalBackgroundConfigPath(cwd, args.configPath);
2707
- const directCodex = provider === "codex";
2708
- if (directCodex && args.piAuthPath !== undefined) {
2709
- process.stderr.write(ui.errorLine("--pi-auth-path does not apply to direct Codex login."));
2710
- return 2;
2711
- }
2712
- let configuredPiAuthPath;
2713
- try {
2714
- configuredPiAuthPath = directCodex ? resolve(cwd, ".pi", "auth.json") : await resolvePiAuthPathForLogin({
2715
- configPath,
2716
- cwd,
2717
- ...(process.env.MONO_AGENT_PI_AUTH_PATH === undefined
2718
- ? {}
2719
- : { envPath: process.env.MONO_AGENT_PI_AUTH_PATH }),
2720
- ...(args.piAuthPath === undefined ? {} : { piAuthPath: args.piAuthPath }),
2721
- });
2722
- }
2723
- catch (error) {
2724
- process.stderr.write(ui.errorLine(`Cannot resolve the Pi auth path from ${configPath}: ${error instanceof Error ? error.message : String(error)}`));
2725
- return 1;
2726
- }
2727
- const plan = planProviderSetup({
2728
- modelRefs: [directCodex ? "codex:gpt-5.6-terra" : `pi:${provider}:credential-setup`],
2729
- cwd,
2730
- piAuthPath: configuredPiAuthPath,
2731
- forceAuthentication: true,
2732
- ...(args.codexAuthMode === undefined ? {} : { codexAuthMode: args.codexAuthMode }),
2733
- });
2734
- if (plan.actions.length === 0) {
2735
- process.stderr.write(ui.errorLine(`Provider \`${provider}\` has no interactive auth method in the bundled ${directCodex ? "Codex" : "Pi"} provider catalog.`));
2736
- return 2;
2737
- }
2738
- const apiKeyActions = plan.actions.filter(isProviderSetupPiApiKeyAction);
2739
- if (args.apiKeyStdin === true && apiKeyActions.length !== 1) {
2740
- process.stderr.write(ui.errorLine("--api-key-stdin is only supported when the selected provider has one bundled API-key login action."));
2741
- return 2;
2742
- }
2743
- process.stdout.write("\n" + ui.heading(directCodex ? "Codex authentication" : "Pi authentication"));
2744
- printProviderSetupPlan(plan);
2745
- let apiKeys;
2746
- const apiKeyAction = apiKeyActions[0];
2747
- if (apiKeyAction !== undefined) {
2748
- let apiKey;
2749
- if (args.apiKeyStdin === true) {
2750
- if (process.stdin.isTTY === true) {
2751
- process.stderr.write(ui.errorLine("--api-key-stdin requires redirected standard input. Omit the flag to enter the key in a masked prompt."));
2752
- return 2;
2753
- }
2754
- try {
2755
- apiKey = await readApiKeyFromStdin(process.stdin);
2756
- }
2757
- catch {
2758
- process.stderr.write(ui.errorLine(`Could not read a valid ${apiKeyAction.envVar} value from standard input; no credentials were written.`));
2759
- return 1;
2760
- }
2761
- }
2762
- else {
2763
- if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
2764
- process.stderr.write(ui.errorLine(`Cannot securely prompt for ${apiKeyAction.envVar} without an interactive TTY. ` +
2765
- `Run this command in a terminal, or pipe the value explicitly with --api-key-stdin; no credentials were written.`));
2766
- return 1;
2767
- }
2768
- const answer = await p.password({
2769
- message: `Enter ${apiKeyAction.label} (${apiKeyAction.envVar})`,
2770
- validate: (value) => apiKeyInputProblem(value ?? ""),
2771
- clearOnError: true,
2772
- });
2773
- if (p.isCancel(answer)) {
2774
- process.stderr.write(ui.errorLine("Authentication was cancelled; no credentials were written."));
2775
- return 130;
2776
- }
2777
- apiKey = answer.trim();
2778
- }
2779
- apiKeys = { [apiKeyAction.id]: apiKey };
2780
- }
2781
- process.stdout.write(ui.style.dim("Press Ctrl-C once to interrupt authentication safely.\n"));
2782
- const execution = await withScopedPreflightCancellation(async (abortSignal) => ({
2783
- results: await executeProviderSetupPlan(plan, {
2784
- ...(apiKeys === undefined ? {} : { apiKeys }),
2785
- abortSignal,
2786
- }),
2787
- interrupted: abortSignal.aborted,
2788
- }), { keypress: false });
2789
- const { results } = execution;
2790
- for (const result of results) {
2791
- const badge = execution.interrupted && result.status === "failed"
2792
- ? ui.badge("waiting")
2793
- : result.status === "ok"
2794
- ? ui.badge("ok")
2795
- : ui.badge("error");
2796
- process.stdout.write(`${badge}${result.action.label}: ${result.detail}\n`);
2797
- }
2798
- if (results.some((result) => result.failureKind !== undefined)) {
2799
- process.stderr.write(ui.errorLine("Provider setup ended in an unconfirmed process or credential-cleanup state. Follow the reported manual cleanup guidance before retrying; automatic recovery is disabled."));
2800
- return 130;
2801
- }
2802
- if (execution.interrupted) {
2803
- process.stderr.write(ui.errorLine("Authentication was interrupted; temporary credentials were cleaned up."));
2804
- return 130;
2805
- }
2806
- return results.every((result) => result.status === "ok") ? 0 : 1;
2807
- }
2808
- const MAX_STANDALONE_API_KEY_BYTES = 65_536;
2809
- function apiKeyInputProblem(value) {
2810
- const normalized = value.trim();
2811
- if (normalized.length === 0)
2812
- return "API key is required.";
2813
- if (normalized.includes("\0") || /[\r\n]/u.test(normalized))
2814
- return "API key must be a single non-empty line.";
2815
- if (Buffer.byteLength(normalized, "utf8") > MAX_STANDALONE_API_KEY_BYTES)
2816
- return "API key is too large.";
2817
- return undefined;
2818
- }
2819
- /**
2820
- * Read one explicitly redirected API key without consulting ambient provider
2821
- * environment variables. A single trailing line ending from `echo` is accepted;
2822
- * embedded newlines, NUL bytes, empty input, and unbounded input fail closed.
2823
- */
2824
- export async function readApiKeyFromStdin(input) {
2825
- const chunks = [];
2826
- let size = 0;
2827
- for await (const chunk of input) {
2828
- const bytes = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
2829
- size += bytes.byteLength;
2830
- if (size > MAX_STANDALONE_API_KEY_BYTES + 2)
2831
- throw new Error("API key input is too large.");
2832
- chunks.push(bytes);
2833
- }
2834
- const value = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/u, "");
2835
- const problem = apiKeyInputProblem(value);
2836
- if (problem !== undefined)
2837
- throw new Error(problem);
2838
- return value.trim();
2839
- }
2840
- export async function resolvePiAuthPathForLogin(options) {
2841
- // A missing config is represented by readMonoAgentConfigJson as `missing`; a
2842
- // malformed or unreadable config throws and must remain visible to operators.
2843
- const result = await readMonoAgentConfigJson(options.configPath);
2844
- const configured = result.missing ? undefined : result.json.providers?.piAuthPath;
2845
- return resolveEffectivePiAuthPath({
2846
- cwd: options.cwd ?? dirname(resolve(options.configPath)),
2847
- ...(nonEmptyEnv(options.piAuthPath) ? { explicitPath: options.piAuthPath } : {}),
2848
- ...(nonEmptyEnv(options.envPath) ? { envPath: options.envPath } : {}),
2849
- ...(nonEmptyEnv(configured) ? { configPath: configured } : {}),
2850
- });
2851
- }
2852
- function printProviderSetupPlan(plan) {
2853
- for (const action of plan.actions) {
2854
- process.stdout.write(` ${action.label}: ${providerSetupActionCommandLine(action)} ${ui.style.dim(`(cwd: ${action.cwd})`)}\n`);
2855
- }
2856
- }
2857
- /**
2858
- * Resolve the preset id for `init`: `--preset` wins, `--recipe` is a deprecated
2859
- * alias mapped to the preset that replaced it. Returns the preset id to compose
2860
- * from, `undefined` for the default scaffold, or `"unknown"` after emitting the
2861
- * error/hint (an unknown preset, or a retired recipe with no replacement).
2862
- */
2863
- function resolveInitPresetId(args) {
2864
- if (args.preset !== undefined) {
2865
- const preset = findPreset(args.preset);
2866
- if (preset === undefined) {
2867
- process.stderr.write(ui.errorLine(`Unknown preset \`${args.preset}\`.`));
2868
- process.stderr.write(ui.hint(`Available presets: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
2869
- return "unknown";
2870
- }
2871
- return preset.id;
2872
- }
2873
- if (args.recipe !== undefined) {
2874
- const preset = RECIPE_TO_PRESET.get(args.recipe);
2875
- if (preset === undefined) {
2876
- process.stderr.write(ui.errorLine(`Recipe \`${args.recipe}\` was retired; the wizard composes capabilities directly.`));
2877
- process.stderr.write(ui.hint(`Use \`mono-agent init --preset <id>\` (${presetIds().join(", ")}), or \`mono-agent init\` for the step-by-step wizard.`));
2878
- process.stderr.write(ui.hint("See the mono-agent-composer skill and docs/playbooks for capability recipes."));
2879
- return "unknown";
2880
- }
2881
- process.stderr.write(ui.hint(`--recipe is deprecated; using preset ${preset.id}. See \`mono-agent presets list\`.`));
2882
- return preset.id;
2883
- }
2884
- return undefined;
2885
- }
2886
- function resolveWithChannels(args) {
2887
- if (args.withChannels === undefined) {
2888
- return undefined;
2889
- }
2890
- const invalid = args.withChannels.filter((channel) => !isWithChannel(channel));
2891
- if (invalid.length > 0) {
2892
- process.stderr.write(ui.errorLine(`Unknown --with channel(s): ${invalid.join(", ")}.`));
2893
- process.stderr.write(ui.hint("Valid channels: telegram, slack, webhook, openaiApi, cron."));
2894
- return "invalid";
2895
- }
2896
- return args.withChannels.filter(isWithChannel);
2897
- }
2898
- /** Safe reporting rows: paths and outcomes only, never secret contents. */
2899
- export function initChangeDisplayRows(result) {
2900
- const labels = {
2901
- created: "created",
2902
- updated: "updated",
2903
- unchanged: "kept",
2904
- "planned-create": "would create",
2905
- "planned-update": "would update",
2906
- };
2907
- return result.changes.map((change) => ({
2908
- label: labels[change.kind],
2909
- path: change.path,
2910
- unchanged: change.kind === "unchanged",
2911
- }));
2912
- }
2913
- /** Human-readable, value-free proof of where the wizard Role did (or did not) land. */
2914
- export function identityRoleDisplayLine(identityRole) {
2915
- switch (identityRole.status) {
2916
- case "created":
2917
- return `Role saved to ${identityRole.path} → ${identityRole.section}. ` +
2918
- `Edit ${identityRole.path} → ${identityRole.section} later to change it.`;
2919
- case "preserved":
2920
- return `${identityRole.path} already existed and was preserved; the entered Role was not written. ` +
2921
- `Add or edit ${identityRole.path} → ${identityRole.section} to set it.`;
2922
- case "planned-create":
2923
- return `Dry run: Role would be saved to ${identityRole.path} → ${identityRole.section}.`;
2924
- }
2925
- }
2926
- export function secretChecklistDisplayRows(secrets, configured) {
2927
- return secrets.map((secret) => ({
2928
- envVar: secret.envVar,
2929
- label: secret.label,
2930
- description: secret.description,
2931
- status: configured.has(secret.envVar) ? "configured" : secret.required ? "missing" : "optional",
2932
- }));
2933
- }
2934
- function printInitResult(result) {
2935
- if (result.dryRun) {
2936
- process.stdout.write(ui.style.dim("Dry run — nothing was written.\n"));
2937
- }
2938
- for (const row of initChangeDisplayRows(result)) {
2939
- const prefix = row.unchanged ? " " : ui.badge("ok");
2940
- const rendered = row.unchanged
2941
- ? ui.style.dim(row.label.padEnd(12))
2942
- : ui.style.green(row.label.padEnd(12));
2943
- // Sensitive files are safe to identify by path; their contents are never
2944
- // included in this result or printed here.
2945
- process.stdout.write(`${prefix}${rendered} ${row.path}\n`);
2946
- }
2947
- process.stdout.write(`\n${identityRoleDisplayLine(result.identityRole)}\n`);
2948
- if (result.knowledgeFiles.length > 0) {
2949
- process.stdout.write(`\nIdentity references existing knowledge: ${ui.style.cyan(result.knowledgeFiles.join(", "))}\n`);
2950
- }
2951
- // Internal `provider:*` modules are auto-added for local models; they are an
2952
- // implementation detail, not a user-facing capability, so exclude them here.
2953
- const capabilities = result.plan.selectedModules.filter((module) => module.kind !== "provider");
2954
- if (capabilities.length > 0) {
2955
- process.stdout.write("\n" + ui.heading("Capabilities"));
2956
- for (const module of capabilities) {
2957
- process.stdout.write(` ${ui.style.cyan(module.title)} ${ui.style.dim(`(risk: ${riskColor(module.riskLevel)})`)}\n`);
2958
- }
2959
- }
2960
- if (result.secretPersistence.status === "persisted") {
2961
- process.stdout.write("\n" + ui.style.dim(result.secretPersistence.changed
2962
- ? "Required secrets were securely merged into .env (mode 0600).\n"
2963
- : "Required secrets were already securely configured in .env.\n"));
2964
- }
2965
- else if (result.secretPersistence.status === "planned") {
2966
- process.stdout.write("\n" + ui.style.dim("Dry run: required secrets would be securely merged into .env.\n"));
2967
- }
2968
- else if (result.secretPersistence.status === "refused") {
2969
- process.stderr.write(ui.hint(`Automatic secret persistence was refused${result.secretPersistence.reason === undefined ? "" : ` (${result.secretPersistence.reason})`}. No secret value was written.\n` +
2970
- (result.secretPersistence.detail === undefined ? "" : `${result.secretPersistence.detail}\n`)));
2971
- }
2972
- else if (result.plan.envExample !== undefined) {
2973
- process.stdout.write("\n" + ui.style.dim("Use .env.example as a reference and add missing values to .env; do not overwrite an existing .env.\n"));
2974
- }
2975
- }
2976
- function printSecretsChecklist(secrets, configured = new Set()) {
2977
- process.stdout.write("\n" + ui.heading("Secrets checklist"));
2978
- if (secrets.length === 0) {
2979
- process.stdout.write(ui.style.dim("No secrets required by the selected capabilities.\n"));
2980
- return;
2981
- }
2982
- process.stdout.write(ui.style.dim("Secret values are never written to config JSON and are never printed.\n"));
2983
- for (const secret of secretChecklistDisplayRows(secrets, configured)) {
2984
- const status = secret.status === "configured"
2985
- ? ui.style.green(secret.status)
2986
- : ui.style.yellow(secret.status);
2987
- process.stdout.write(` ${ui.style.bold(secret.envVar)} ${ui.style.dim(`- ${secret.label}: ${secret.description}`)} ${status}\n`);
2988
- }
2989
- }
2990
- function printNextSteps(configPath) {
2991
- const startCommand = process.platform === "darwin" ? "mono-agent start" : "mono-agent start --foreground";
2992
- const tuiCommand = process.platform === "darwin"
2993
- ? `mono-agent tui --configure ${ui.style.dim("(after the agent reports ready)")}`
2994
- : `mono-agent tui ${ui.style.dim("(ordinary chat after foreground startup; edit config files manually)")}`;
2995
- process.stdout.write("\n" +
2996
- ui.heading("Next steps") +
2997
- ` ${ui.style.bold("1.")} Edit ${configPath} ${ui.style.dim("(model, channels, skills, memory, sandbox)")}\n` +
2998
- ` ${ui.style.bold("2.")} mono-agent validate\n` +
2999
- ` ${ui.style.bold("3.")} ${startCommand}\n` +
3000
- ` ${ui.style.bold("4.")} ${tuiCommand}\n`);
3001
- }
3002
- function printUnsupportedGuidedInitHandoff(configPath, envFile) {
3003
- const flags = guidedHandoffFlags(configPath, envFile);
3004
- process.stdout.write("\n" +
3005
- ui.heading("Manual start required") +
3006
- ui.style.yellow("Automatic background start and configuration chat require macOS launchd.\n") +
3007
- ui.style.dim("The validated agent files were preserved, but no agent process was started and readiness is not claimed.\n") +
3008
- ` ${ui.style.bold("Configure manually:")} edit ${configPath} and IDENTITY.md, then run mono-agent validate${flags}\n` +
3009
- ` ${ui.style.bold("Terminal 1:")} mono-agent start --foreground${flags}\n` +
3010
- ` ${ui.style.bold("Terminal 2:")} mono-agent tui${flags} ${ui.style.dim("(ordinary chat after startup completes)")}\n` +
3011
- ui.style.dim("Conversational configuration requires the managed macOS background lifecycle.\n"));
3012
- }
3013
- function printUnexpectedGuidedBackgroundFailure(configPath, envFile, error) {
3014
- const flags = guidedHandoffFlags(configPath, envFile);
3015
- const paths = launchdPathsFor(deriveLaunchdLabel(configPath));
3016
- process.stderr.write(ui.errorLine(`The background lifecycle failed unexpectedly: ${error instanceof Error ? error.message : String(error)}`));
3017
- process.stderr.write(ui.style.dim("The validated agent files were preserved, but configuration chat was not opened. Retry or inspect with:\n"));
3018
- process.stderr.write(` ${ui.style.gray("logs: ")} ${paths.stderrPath}\n` +
3019
- ` ${paths.stdoutPath}\n` +
3020
- ` ${ui.style.gray("retry: ")} mono-agent start${flags}\n` +
3021
- ` ${ui.style.gray("status:")} mono-agent status${flags}\n` +
3022
- ` ${ui.style.gray("follow:")} mono-agent logs${flags} --follow\n`);
3023
- }
3024
- function guidedHandoffFlags(configPath, envFile) {
3025
- return ` --config ${shellCommandArgument(configPath)}`
3026
- + (envFile === undefined ? "" : ` --env-file ${shellCommandArgument(envFile)}`);
3027
- }
3028
- function shellCommandArgument(value) {
3029
- return /^[a-zA-Z0-9_./:@%+=,-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
3030
- }
3031
- async function runInstallSkill(args) {
3032
- if (args.project === true) {
3033
- try {
3034
- if (args.update === true) {
3035
- const result = await updateManagedProjectSkills(process.cwd());
3036
- for (const path of result.updated) {
3037
- process.stdout.write(`${ui.badge("ok")}${ui.style.green("updated")} ${path}\n`);
3038
- }
3039
- if (result.backupDir !== undefined) {
3040
- process.stdout.write(`${ui.badge("ok")}backup ${result.backupDir}\n`);
3041
- }
3042
- if (result.updated.length === 0)
3043
- process.stdout.write(`${ui.badge("ok")}project skills are current\n`);
3044
- return 0;
3045
- }
3046
- const result = await checkManagedProjectSkills(process.cwd());
3047
- for (const status of result.statuses) {
3048
- const badge = status.status === "ready" ? ui.badge("ok") : ui.badge("error");
3049
- process.stdout.write(`${badge}${status.name}: ${status.status} (${status.path})\n`);
3050
- }
3051
- if (!result.ok && args.check !== true) {
3052
- process.stderr.write(ui.errorLine("Project skills need attention. Run `mono-agent install-skill --project --update`; modified copies require manual reconciliation."));
3053
- }
3054
- return result.ok ? 0 : 1;
3055
- }
3056
- catch (error) {
3057
- process.stderr.write(ui.errorLine(error instanceof Error ? error.message : String(error)));
3058
- return 1;
3059
- }
3060
- }
3061
- let result;
3062
- try {
3063
- result = await installComposerSkill({
3064
- target: args.target ?? "both",
3065
- force: args.force,
3066
- });
3067
- }
3068
- catch (error) {
3069
- process.stderr.write(ui.errorLine(error instanceof Error ? error.message : String(error)));
3070
- return 1;
3071
- }
3072
- for (const path of result.installed) {
3073
- process.stdout.write(`${ui.badge("ok")}${ui.style.green("installed")} ${path}\n`);
3074
- }
3075
- return 0;
3076
- }
3077
- async function runValidate(args) {
3078
- const context = resolveValidateContext(args, process.cwd());
3079
- const report = await validateMonoAgentFolder({
3080
- env: process.env,
3081
- cwd: context.cwd,
3082
- configPath: context.configPath,
3083
- allowFilesystemWrites: context.allowFilesystemWrites,
3084
- });
3085
- const preset = resolveValidatePreset(args);
3086
- if (preset === "unknown") {
3087
- if (args.json === true) {
3088
- process.stdout.write(`${JSON.stringify({ ok: false, sections: report.sections })}\n`);
3089
- }
3090
- return 1;
3091
- }
3092
- let presetResult;
3093
- if (preset !== undefined) {
3094
- const plan = composeWizardPlan(presetAnswers(preset), {
3095
- dirBasename: basename(context.cwd),
3096
- skillsRootExists: false,
3097
- });
3098
- presetResult = {
3099
- id: preset.id,
3100
- expectations: plan.validateExpectations.map((expectation) => {
3101
- const actual = report.sections.find((entry) => entry.id === expectation.sectionId)?.status ?? "missing";
3102
- return {
3103
- sectionId: expectation.sectionId,
3104
- expected: expectation.mustBe,
3105
- actual,
3106
- met: actual === expectation.mustBe,
3107
- };
3108
- }),
3109
- };
3110
- if (args.json === true) {
3111
- process.stdout.write(`${JSON.stringify({
3112
- ok: report.ok,
3113
- sections: report.sections,
3114
- preset: presetResult,
3115
- })}\n`);
3116
- return report.ok ? 0 : 1;
3117
- }
3118
- for (const section of report.sections) {
3119
- process.stdout.write(formatSection(section));
3120
- }
3121
- process.stdout.write(renderPlanCompleteness(plan.validateExpectations, `Preset: ${preset.id}`, report));
3122
- }
3123
- else if (args.json === true) {
3124
- process.stdout.write(`${JSON.stringify({ ok: report.ok, sections: report.sections })}\n`);
3125
- return report.ok ? 0 : 1;
3126
- }
3127
- else {
3128
- for (const section of report.sections) {
3129
- process.stdout.write(formatSection(section));
3130
- }
3131
- }
3132
- const hasWaitingSections = report.sections.some((section) => section.status === "waiting");
3133
- process.stdout.write(report.ok
3134
- ? hasWaitingSections
3135
- ? `\n${ui.style.yellow("⚠ Config is structurally valid, but needs attention before start.")}\n${ui.style.dim("Review the waiting sections above, then re-run mono-agent validate.")}\n`
3136
- : `\n${ui.style.green("✓ Config is ready to start.")}\n${ui.style.dim("Run `mono-agent config` for the full field-by-field view.")}\n`
3137
- : `\n${ui.hint("Fix the errors above, then re-run mono-agent validate.")}`);
3138
- process.stdout.write(ui.style.dim("Core sections activate by presence; channels need `enabled: true` — see docs/config (How sections activate).\n"));
3139
- return report.ok ? 0 : 1;
3140
- }
3141
- /**
3142
- * Resolve the preset to check `validate` against: `--preset` wins, `--recipe` is a
3143
- * deprecated alias. Returns the preset, `undefined` (no capability check), or
3144
- * `"unknown"` after emitting the error/hint.
3145
- */
3146
- function resolveValidatePreset(args) {
3147
- if (args.preset !== undefined) {
3148
- const preset = findPreset(args.preset);
3149
- if (preset === undefined) {
3150
- process.stderr.write(ui.errorLine(`Unknown preset \`${args.preset}\`.`));
3151
- process.stderr.write(ui.hint(`Available presets: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
3152
- return "unknown";
3153
- }
3154
- return preset;
3155
- }
3156
- if (args.recipe !== undefined) {
3157
- const preset = RECIPE_TO_PRESET.get(args.recipe);
3158
- if (preset === undefined) {
3159
- process.stderr.write(ui.errorLine(`Recipe \`${args.recipe}\` was retired; validate against a preset instead.`));
3160
- process.stderr.write(ui.hint(`Available presets: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
3161
- return "unknown";
3162
- }
3163
- process.stderr.write(ui.hint(`--recipe is deprecated; using preset ${preset.id}. See \`mono-agent presets list\`.`));
3164
- return preset;
3165
- }
3166
- return undefined;
3167
- }
3168
- /**
3169
- * Capability-aware completeness check: for each capability a preset (or module
3170
- * set) promises, report whether the matching doctor section reached the expected
3171
- * status. `waiting` stays non-fatal (it never changes the validate exit code) but
3172
- * is surfaced as "incomplete" so the operator knows what is left to wire up.
3173
- */
3174
- function renderPlanCompleteness(expectations, label, report) {
3175
- let out = "\n" + ui.heading(label);
3176
- let incomplete = 0;
3177
- for (const expectation of expectations) {
3178
- const section = report.sections.find((entry) => entry.id === expectation.sectionId);
3179
- const status = section?.status ?? "missing";
3180
- const met = status === expectation.mustBe;
3181
- if (!met) {
3182
- incomplete += 1;
3183
- }
3184
- const badge = met ? ui.badge("ok") : ui.badge(status === "error" ? "error" : "waiting");
3185
- out += `${badge}${ui.style.bold(expectation.sectionId)} ${ui.style.dim(`(${status}, expected ${expectation.mustBe})`)}\n`;
3186
- if (!met && expectation.note !== undefined) {
3187
- out += ` ${ui.style.dim(expectation.note)}\n`;
3188
- }
3189
- }
3190
- out += incomplete === 0
3191
- ? `${ui.style.green(`✓ ${label} is fully configured.`)}\n`
3192
- : ui.style.yellow(`⚠ ${label} incomplete: ${incomplete} capability(ies) not yet live.\n`);
3193
- return out;
3194
- }
3195
- function riskColor(risk) {
3196
- if (risk === "high") {
3197
- return ui.style.red(risk);
3198
- }
3199
- if (risk === "medium") {
3200
- return ui.style.yellow(risk);
3201
- }
3202
- return ui.style.green(risk);
3203
- }
3204
- /** `mono-agent presets list` — one block per preset. */
3205
- export function renderPresetList() {
3206
- let out = ui.banner("mono-agent", "presets") + "\n";
3207
- for (const preset of PRESET_CATALOG) {
3208
- out += `${ui.style.bold(ui.style.cyan(preset.id))} ${ui.style.dim(`[${riskColor(preset.riskLevel)}]`)}\n`;
3209
- out += ` ${preset.title}\n`;
3210
- out += ` ${ui.style.dim(preset.description)}\n`;
3211
- }
3212
- out += "\n" + ui.style.dim("Scaffold one with: mono-agent init --preset <id>\n");
3213
- out += ui.style.dim("Build interactively with: mono-agent init\n");
3214
- return out;
3215
- }
3216
- /** `mono-agent presets show <id>` — description, composed JSON, env example, checklist. */
3217
- export function renderPresetShow(preset) {
3218
- const plan = composeWizardPlan(presetAnswers(preset), { dirBasename: "your-agent", skillsRootExists: false });
3219
- let out = ui.banner("mono-agent", `preset: ${preset.id}`) + "\n";
3220
- out += `${ui.style.bold(preset.title)} ${ui.style.dim(`(risk: ${riskColor(preset.riskLevel)})`)}\n`;
3221
- out += `${preset.description}\n`;
3222
- if (preset.playbook !== undefined) {
3223
- out += ui.style.dim(`Playbook: docs/playbooks/${preset.playbook}\n`);
3224
- }
3225
- out += "\n" + ui.heading("Generated mono-agent.config.json");
3226
- out += JSON.stringify(plan.configJson, null, 2) + "\n";
3227
- const envExample = plan.envExample;
3228
- if (envExample !== undefined && envExample.trim().length > 0) {
3229
- out += "\n" + ui.heading(".env.example");
3230
- out += envExample.endsWith("\n") ? envExample : envExample + "\n";
3231
- }
3232
- if (plan.files.length > 0) {
3233
- out += "\n" + ui.heading("Scaffolded files");
3234
- for (const file of plan.files) {
3235
- out += ` ${ui.style.cyan(file.path)}\n`;
3236
- }
3237
- }
3238
- if (plan.validateExpectations.length > 0) {
3239
- out += "\n" + ui.heading("Follow-up checklist");
3240
- for (const expectation of plan.validateExpectations) {
3241
- const note = expectation.note === undefined ? "" : ` — ${expectation.note}`;
3242
- out += ` ${ui.style.gray("•")} ${expectation.sectionId} ${ui.style.dim(`must be ${expectation.mustBe}`)}${ui.style.dim(note)}\n`;
3243
- }
3244
- }
3245
- return out;
3246
- }
3247
- /** Dispatch `mono-agent presets list|show <id>`. */
3248
- function runPresets(args) {
3249
- const [sub, id] = args.positionals;
3250
- if (sub === undefined || sub === "list") {
3251
- process.stdout.write(renderPresetList());
3252
- return 0;
3253
- }
3254
- if (sub === "show") {
3255
- if (id === undefined) {
3256
- process.stderr.write(ui.errorLine("Usage: mono-agent presets show <id>."));
3257
- process.stderr.write(ui.hint(`Available: ${presetIds().join(", ")}.`));
3258
- return 2;
3259
- }
3260
- const preset = findPreset(id);
3261
- if (preset === undefined) {
3262
- process.stderr.write(ui.errorLine(`Unknown preset \`${id}\`.`));
3263
- process.stderr.write(ui.hint(`Available: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
3264
- return 1;
3265
- }
3266
- process.stdout.write(renderPresetShow(preset));
3267
- return 0;
3268
- }
3269
- process.stderr.write(ui.errorLine(`Unknown presets subcommand \`${sub}\`. Expected list or show.`));
3270
- return 2;
3271
- }
3272
- const SOURCE_TAG = {
3273
- env: ui.style.green("[env]"),
3274
- json: ui.style.cyan("[json]"),
3275
- default: ui.style.dim("[default]"),
3276
- };
3277
- /**
3278
- * Render the complete, source-annotated config view: every core section and
3279
- * field with its resolved value and whether it came from an env var, the JSON
3280
- * file, or the built-in default. This is the single discovery surface that
3281
- * replaced the old partial config panes.
3282
- */
3283
- export function renderConfigView(sections) {
3284
- let out = "";
3285
- for (const section of sections) {
3286
- const badgeStatus = section.status === "active" ? "ok" : "disabled";
3287
- out += `${ui.badge(badgeStatus)}${ui.style.bold(section.label)}\n`;
3288
- const width = section.fields.reduce((max, field) => Math.max(max, field.label.length), 0);
3289
- for (const field of section.fields) {
3290
- const tag = SOURCE_TAG[field.source];
3291
- const lock = field.redacted === true ? ` ${ui.style.dim("(secret)")}` : "";
3292
- const defaultRestatement = field.restatesDefault === true ? ` ${ui.style.dim("(same as default)")}` : "";
3293
- out += ` ${ui.style.gray(field.label.padEnd(width))} ${field.value}${lock}${defaultRestatement} ${tag}\n`;
3294
- }
3295
- }
3296
- return out;
3297
- }
3298
- /**
3299
- * `mono-agent config`: print the resolved configuration field-by-field with the
3300
- * source (env / json / default) of every value, then the channel summary. Read
3301
- * only — edits go in mono-agent.config.json and take effect on the next restart.
3302
- */
3303
- async function runConfig(args) {
3304
- const cwd = process.cwd();
3305
- const configPath = resolve(cwd, args.configPath ?? "mono-agent.config.json");
3306
- const env = process.env;
3307
- const jsonResult = await readMonoAgentConfigJson(configPath);
3308
- let config;
3309
- try {
3310
- config = await loadAppCoreConfig({ env, cwd, configPath });
3311
- }
3312
- catch (error) {
3313
- if (isAppCoreConfigError(error)) {
3314
- process.stderr.write(ui.errorLine(error.message));
3315
- if (jsonResult.missing) {
3316
- process.stderr.write(ui.hint(`No mono-agent config found at ${configPath}. Run \`mono-agent init\` to scaffold one.`));
3317
- }
3318
- else {
3319
- process.stderr.write(ui.hint("Fix the config above, then re-run `mono-agent config`."));
3320
- }
3321
- return 1;
3322
- }
3323
- throw error;
3324
- }
3325
- const sections = buildMonoAgentConfigView({
3326
- redacted: redactMonoAgentConfig(config),
3327
- json: jsonResult.json,
3328
- env,
3329
- });
3330
- const drivers = await resolveChannelDrivers({ env, cwd, configPath });
3331
- const channelViews = await collectChannelConfigViews(drivers, { env, cwd, configPath });
3332
- process.stdout.write(ui.banner("mono-agent", "resolved config") + "\n");
3333
- process.stdout.write(renderConfigView(sections));
3334
- if (channelViews.length > 0) {
3335
- process.stdout.write("\n" + ui.heading("Channels"));
3336
- process.stdout.write(renderConfigView(channelViews));
3337
- }
3338
- for (const warning of [
3339
- ...findUnknownAppConfigWarnings(jsonResult.json),
3340
- ...findJsonSecretConfigWarnings([...sections, ...channelViews]),
3341
- ...findRemovedConfigWarnings({ json: jsonResult.json, env }),
3342
- ]) {
3343
- process.stdout.write(`${ui.style.yellow(warning)}\n`);
3344
- }
3345
- const report = await validateMonoAgentFolder({ env, cwd, configPath, liveness: false, drivers });
3346
- const channels = report.sections.filter((section) => section.id.startsWith("channel:"));
3347
- if (channels.length > 0) {
3348
- process.stdout.write("\n" + ui.heading("Channel status"));
3349
- for (const section of channels) {
3350
- process.stdout.write(formatSection(section));
3351
- }
3352
- }
3353
- process.stdout.write("\n" + ui.style.dim("Source precedence: [env] > [json] > [default]. Edit mono-agent.config.json and run `mono-agent restart` to apply.") + "\n");
3354
- return 0;
3355
- }
3356
- /** Render one validation section: a status badge, a bold label, and its details. */
3357
- function formatSection(section) {
3358
- let out = `${ui.badge(section.status)}${ui.style.bold(section.label)}\n`;
3359
- for (const detail of section.details) {
3360
- out += ` ${colorDetail(section.status, detail)}\n`;
3361
- }
3362
- return out;
3363
- }
3364
- function colorDetail(status, detail) {
3365
- if (status === "error") {
3366
- return ui.style.red(detail);
3367
- }
3368
- if (detail.startsWith("[WARN]") || detail.startsWith("WARNING:")) {
3369
- return ui.style.yellow(detail);
3370
- }
3371
- return ui.style.dim(detail);
3372
- }
3373
- /**
3374
- * Gate for `start`/`restart`: refuse unless the directory has a present, valid
3375
- * config. First the config FILE must exist (env vars alone are not enough — a
3376
- * folder without a config is not a configured agent). Then run the structural
3377
- * validation with `liveness:false` (network probes only yield `waiting`, never
3378
- * `error`, so skipping them keeps the verdict but avoids ~6s of timeouts) and
3379
- * refuse on any `error` section. `waiting` (e.g. Ollama/Phoenix not up yet) is
3380
- * runtime-soft and never blocks.
3381
- */
3382
- export async function ensureStartable(args, env = process.env, options = {}) {
3383
- const cwd = options.cwd ?? process.cwd();
3384
- const configPath = options.configPath ?? resolve(cwd, args.configPath ?? "mono-agent.config.json");
3385
- if (!(await pathExists(configPath))) {
3386
- return { ok: false, code: 2, kind: "missing-config", configPath };
3387
- }
3388
- const report = await validateMonoAgentFolder({
3389
- env,
3390
- cwd,
3391
- configPath,
3392
- liveness: false,
3393
- ...(options.preferAppPluginInstall === true ? { preferAppPluginInstall: true } : {}),
3394
- });
3395
- if (!report.ok) {
3396
- return { ok: false, code: 1, kind: "validation", report };
3397
- }
3398
- return { ok: true };
3399
- }
3400
- function printPreflightFailure(result) {
3401
- if (result.kind === "missing-config") {
3402
- process.stderr.write(ui.errorLine(`No mono-agent config found at ${result.configPath}.`));
3403
- process.stderr.write(ui.hint("Run `mono-agent init` to scaffold one, or pass --config <path>."));
3404
- return;
3405
- }
3406
- process.stderr.write(ui.heading("Cannot start: config has errors"));
3407
- for (const section of result.report.sections) {
3408
- if (section.status === "error") {
3409
- process.stderr.write(formatSection(section));
3410
- }
3411
- }
3412
- process.stderr.write(ui.hint("Run `mono-agent validate` for the full report, fix the errors, then retry."));
3413
- }
3414
- async function pathExists(path) {
3415
- try {
3416
- await stat(path);
3417
- return true;
3418
- }
3419
- catch {
3420
- return false;
3421
- }
3422
- }
3423
- async function runStart(args, env, managedBackgroundWorker = false) {
3424
- if (args.foreground) {
3425
- return await runForeground(args, env, managedBackgroundWorker);
3426
- }
3427
- return await runBackgroundCommand(args, "start", env);
3428
- }
3429
- /**
3430
- * The blocking worker: builds the responder, starts every configured channel
3431
- * plus traceability, and stays alive until a signal. This is what launchd
3432
- * invokes (via `start --foreground`) and what users get with `--foreground`/`-f`.
3433
- */
3434
- async function runForeground(args, env = process.env, managedBackgroundWorker = false) {
3435
- const cwd = process.cwd();
3436
- const configPath = await canonicalBackgroundConfigPath(cwd, args.configPath);
3437
- const startupEnvironment = { ...env };
3438
- let lease;
3439
- try {
3440
- lease = await acquireBackgroundWorkerLease(configPath);
3441
- }
3442
- catch (error) {
3443
- process.stderr.write(ui.errorLine(`Could not acquire the worker singleton lease: ${error instanceof Error ? error.message : String(error)}`));
3444
- return 1;
3445
- }
3446
- if (lease === undefined) {
3447
- process.stderr.write(ui.errorLine(`Another foreground or managed background worker already owns ${configPath}; refusing to start a duplicate.`));
3448
- return 1;
3449
- }
3450
- let runtimeInputs;
3451
- let app;
3452
- try {
3453
- let backgroundSnapshot;
3454
- if (managedBackgroundWorker) {
3455
- try {
3456
- backgroundSnapshot = decodeBackgroundSnapshot(args.expectedBackgroundSnapshot ?? "");
3457
- const dotenvPath = resolve(cwd, args.envFile ?? ".env");
3458
- if (backgroundSnapshot.configPath !== configPath || backgroundSnapshot.dotenvPath !== dotenvPath) {
3459
- throw new Error("The approved snapshot paths do not match the managed worker arguments.");
3460
- }
3461
- runtimeInputs = await materializeBackgroundRuntimeInputs({
3462
- snapshot: backgroundSnapshot,
3463
- cwd,
3464
- env: startupEnvironment,
3465
- });
3466
- }
3467
- catch (error) {
3468
- process.stderr.write(ui.errorLine(`Managed worker could not freeze its startup snapshot: ${error instanceof Error ? error.message : String(error)}`));
3469
- // KeepAlive restarts only unsuccessful exits. This refusal cannot heal
3470
- // without a new approved snapshot, so exit successfully and let the
3471
- // controller unload/recreate the job instead of retrying forever.
3472
- return 0;
3473
- }
3474
- }
3475
- const preflightEnvironment = runtimeInputs?.environment ?? startupEnvironment;
3476
- const pre = await ensureStartable(args, preflightEnvironment, runtimeInputs === undefined
3477
- ? {}
3478
- : {
3479
- cwd,
3480
- configPath: runtimeInputs.configPath,
3481
- preferAppPluginInstall: true,
3482
- });
3483
- if (!pre.ok) {
3484
- printPreflightFailure(pre);
3485
- return managedBackgroundWorker ? 0 : pre.code;
3486
- }
3487
- try {
3488
- await readCliConfigSnapshot(runtimeInputs?.configPath ?? configPath);
3489
- }
3490
- catch (error) {
3491
- process.stderr.write(ui.errorLine(`Cannot establish the foreground config identity: ${error instanceof Error ? error.message : String(error)}`));
3492
- return managedBackgroundWorker ? 0 : 1;
3493
- }
3494
- app = await startMonoAgentApp({
3495
- cwd,
3496
- configPath,
3497
- ...(runtimeInputs === undefined ? {} : { configReadPath: runtimeInputs.configPath }),
3498
- env: runtimeInputs?.environment ?? startupEnvironment,
3499
- logger: consoleLogger(),
3500
- ...(backgroundSnapshot === undefined ? {} : { backgroundSnapshot }),
3501
- });
3502
- await printAppStatus(app);
3503
- // Block until a shutdown signal. Returning here (the old behavior) let the
3504
- // process exit immediately whenever no channel owned a live handle — e.g. a
3505
- // traceability-only config, now that the operator console is retired and the
3506
- // trace heartbeat timer is unref'd.
3507
- return await waitForShutdownSignal(app);
3508
- }
3509
- finally {
3510
- await app?.stop().catch(() => undefined);
3511
- await runtimeInputs?.dispose().catch(() => undefined);
3512
- await lease.release().catch((error) => {
3513
- process.stderr.write(ui.style.yellow(`⚠ Could not cleanly release worker singleton lease ${lease.path}: ${error instanceof Error ? error.message : String(error)}`) + "\n");
3514
- });
3515
- }
3516
- }
3517
- async function runBackgroundCommand(args, command, env = process.env) {
3518
- const guard = requireDarwin(command);
3519
- if (guard !== undefined) {
3520
- return guard;
3521
- }
3522
- let controllerEnvironment;
3523
- try {
3524
- controllerEnvironment = await loadDurableBackgroundEnvironment({
3525
- cwd: process.cwd(),
3526
- ...(args.envFile === undefined ? {} : { envFile: args.envFile }),
3527
- operationalEnvironment: managedBackgroundEnvironment(env),
3528
- });
3529
- }
3530
- catch (error) {
3531
- if (command === "start" || command === "restart") {
3532
- process.stderr.write(ui.errorLine(`Cannot reconstruct the managed worker environment: ${error instanceof Error ? error.message : String(error)}`));
3533
- process.stderr.write(ui.hint("No LaunchAgent changes were made. Fix the dotenv path and retry."));
3534
- return 1;
3535
- }
3536
- controllerEnvironment = { ...env };
3537
- process.stderr.write(ui.style.yellow(`⚠ Could not reconstruct the managed worker environment; ${command} will use the current shell only: ${error instanceof Error ? error.message : String(error)}`) + "\n");
3538
- }
3539
- // Refuse to launch (or relaunch) an unconfigured/broken folder BEFORE writing
3540
- // the plist and bootstrapping launchctl — otherwise the worker would crash and
3541
- // launchd's KeepAlive would retry it forever. stop/status/logs stay ungated so
3542
- // a broken instance can still be inspected and torn down.
3543
- if (command === "start" || command === "restart") {
3544
- const pre = await ensureStartable(args, controllerEnvironment);
3545
- if (!pre.ok) {
3546
- printPreflightFailure(pre);
3547
- return pre.code;
3548
- }
3549
- }
3550
- let target = await resolveInstanceTarget({
3551
- args: {
3552
- ...(args.configPath === undefined ? {} : { configPath: args.configPath }),
3553
- ...(args.envFile === undefined ? {} : { envFile: args.envFile }),
3554
- },
3555
- env: controllerEnvironment,
3556
- cwd: process.cwd(),
3557
- cliPath: fileURLToPath(import.meta.url),
3558
- });
3559
- if (command === "start" || command === "restart") {
3560
- try {
3561
- const expectedSnapshot = await captureBackgroundSnapshot({
3562
- cwd: target.cwd,
3563
- configPath: target.configPath,
3564
- ...(target.envFile === undefined ? {} : { envFile: target.envFile }),
3565
- env: controllerEnvironment,
3566
- });
3567
- target = { ...target, expectedSnapshot };
3568
- }
3569
- catch (error) {
3570
- process.stderr.write(ui.errorLine(`Cannot prove the durable background snapshot: ${error instanceof Error ? error.message : String(error)}`));
3571
- process.stderr.write(ui.hint("No LaunchAgent changes were made. Fix the config/dotenv/Identity/Soul/MCP mismatch and retry."));
3572
- return 1;
3573
- }
3574
- }
3575
- const deps = defaultBackgroundDeps();
3576
- switch (command) {
3577
- case "start":
3578
- return await startBackground(target, deps);
3579
- case "restart":
3580
- return args.force
3581
- ? await runForceRestart(target, deps, controllerEnvironment)
3582
- : await restartBackground(target, deps);
3583
- case "stop":
3584
- return await stopBackground(target, deps);
3585
- case "status":
3586
- return await statusBackground(target, deps);
3587
- case "logs":
3588
- return await tailLogs(target, deps, { follow: args.follow, lines: args.lines ?? DEFAULT_LOG_LINES });
3589
- }
3590
- }
3591
- /**
3592
- * `restart --force`: stop the worker, purge its persisted pi-session store, then
3593
- * start fresh. Stopping first guarantees the worker is not writing sessions while
3594
- * they are deleted; the runtime recreates the store on the next session, and the
3595
- * agent's durable memory lives elsewhere, so only resumable transcripts are dropped.
3596
- */
3597
- async function runForceRestart(target, deps, environment) {
3598
- return await forceRestartBackground(target, deps, async () => {
3599
- const result = await purgeSessions({ env: environment, cwd: target.cwd, configPath: target.configPath });
3600
- if (result.removed) {
3601
- const count = result.files === 0 ? "" : ` (${result.files} session file${result.files === 1 ? "" : "s"})`;
3602
- process.stdout.write(`${ui.badge("ok")}${ui.style.bold("Cleared persisted sessions")}${count}.\n`);
3603
- }
3604
- else {
3605
- process.stdout.write(ui.style.dim("No persisted sessions to clear (in-memory or none on disk).") + "\n");
3606
- }
3607
- });
3608
- }
3609
- /**
3610
- * Background service mode is launchd-specific. On other platforms point the
3611
- * user at the still-supported blocking foreground path.
3612
- */
3613
- function requireDarwin(command) {
3614
- if (process.platform === "darwin") {
3615
- return undefined;
3616
- }
3617
- process.stderr.write(ui.errorLine(`Background service mode (mono-agent ${command}) requires macOS (launchd).`));
3618
- process.stderr.write(ui.hint("Run `mono-agent start --foreground` to run in the foreground on this platform."));
3619
- return 1;
3620
- }
3621
- export async function printAppStatus(app, options = {}) {
3622
- const trace = app.traceabilityStatus;
3623
- process.stdout.write(ui.rule("instance"));
3624
- process.stdout.write(ui.keyValue([
3625
- ["config", app.configPath],
3626
- [
3627
- "traceability",
3628
- trace.kind === "running" ? `running (source ${trace.sourceId})` : `${trace.kind}: ${trace.reason}`,
3629
- ],
3630
- ], 2));
3631
- const artifactDir = app.traceabilityStatus.kind === "running" ? app.traceabilityStatus.artifactDir : undefined;
3632
- process.stdout.write(ui.rule("sandbox"));
3633
- process.stdout.write(` ${describeSandboxStatus(app.sandboxStatus)}\n`);
3634
- process.stdout.write(ui.rule("observability"));
3635
- process.stdout.write(` ${describeExporter(app.exporterStatus, artifactDir)}\n`);
3636
- const channels = [...app.channelStatuses()];
3637
- if (channels.length > 0) {
3638
- process.stdout.write(ui.rule("channels"));
3639
- for (const [id, status] of channels) {
3640
- process.stdout.write(` ${ui.channelBadge(status.kind)}${ui.style.bold(id.padEnd(11))} ${describeChannelStatus(status)}\n`);
3641
- }
3642
- }
3643
- await writeAppRunsHealthDetail(app, options);
3644
- }
3645
- async function writeAppRunsHealthDetail(app, options) {
3646
- const artifactDir = app.traceabilityStatus.kind === "running" ? app.traceabilityStatus.artifactDir : undefined;
3647
- if (artifactDir === undefined || artifactDir.trim().length === 0) {
3648
- return;
3649
- }
3650
- const reader = options.listRecordedRuns ?? listRecordedRuns;
3651
- let result;
3652
- try {
3653
- result = await reader({ artifactDir, maxRuns: RUNS_HEALTH_MAX_RUNS, scope: "agent" });
3654
- }
3655
- catch (error) {
3656
- result = {
3657
- totalRuns: 0,
3658
- runs: [],
3659
- warnings: [`Unable to read run summaries: ${reasonOf(error)}`],
3660
- };
3661
- }
3662
- const display = buildRunsHealthDisplay({
3663
- artifactDir,
3664
- totalRuns: result.totalRuns,
3665
- runs: result.runs,
3666
- warnings: result.warnings,
3667
- includeSelectedSkills: true,
3668
- runOwnerAlive: true,
3669
- maxRuns: RUNS_HEALTH_MAX_RUNS,
3670
- ...(app.selectedSkills === undefined ? {} : { selectedSkills: app.selectedSkills }),
3671
- ...(options.nowMs === undefined ? {} : { nowMs: options.nowMs }),
3672
- });
3673
- process.stdout.write(ui.rule("runs health"));
3674
- for (const detail of display.details) {
3675
- process.stdout.write(` ${detail}\n`);
3676
- }
3677
- }
3678
- function reasonOf(error) {
3679
- return error instanceof Error ? error.message : String(error);
3680
- }
3681
- function describeExporter(status, artifactDir) {
3682
- if (status.kind !== "configured") {
3683
- return `${status.kind}: ${status.reason}`;
3684
- }
3685
- const parts = [`phoenix ${status.endpoint}`];
3686
- const appUrl = phoenixAppBaseUrl(status.endpoint);
3687
- if (appUrl !== undefined) {
3688
- parts.push(`app ${appUrl}`);
3689
- }
3690
- if (status.includeSensitiveData) {
3691
- parts.push(ui.style.yellow(describeSensitiveDataExportWarning(status.endpoint)));
3692
- }
3693
- if (status.lastWarning !== undefined) {
3694
- parts.push(`last warning: ${status.lastWarning}`);
3695
- }
3696
- if (status.lastError !== undefined) {
3697
- parts.push(`last error: ${status.lastError}`);
3698
- }
3699
- parts.push(artifactDir === undefined
3700
- ? "JSONL artifacts remain local"
3701
- : `JSONL artifacts remain local at ${artifactDir}`);
3702
- return parts.join("; ");
3703
- }
3704
- function describeSandboxStatus(status) {
3705
- const engineAvailability = status.engineAvailable === true
3706
- ? "present"
3707
- : status.engineAvailable === false
3708
- ? "absent"
3709
- : "not checked";
3710
- const parts = [
3711
- `effective: ${status.effective}`,
3712
- `engine: ${status.engine ?? "none"} (${engineAvailability})`,
3713
- ...(status.fallback === undefined ? [] : [`fallback: ${status.fallback}`]),
3714
- `fallback active: ${status.fallbackActive ? "yes" : "no"}`,
3715
- status.detail,
3716
- ];
3717
- const warning = status.warning ?? sandboxEffectiveStateWarning(status);
3718
- if (warning !== undefined) {
3719
- parts.push(ui.style.yellow(warning));
3720
- }
3721
- return parts.join("; ");
3722
- }
3723
- export function describeChannelStatus(status) {
3724
- if (status.kind === "running") {
3725
- const facts = Object.entries(status.summary)
3726
- .map(([key, value]) => `${key}=${formatChannelFactValue(value)}`)
3727
- .join(" ");
3728
- return facts.length === 0 ? "running" : `running (${facts})`;
3729
- }
3730
- return `${status.kind}: ${status.reason}`;
3731
- }
3732
- /**
3733
- * Block the foreground process until SIGINT/SIGTERM, then stop the app and
3734
- * resolve the exit code. A referenced no-op timer owns the event loop so the
3735
- * process stays alive even with no channel handle (signal listeners alone do
3736
- * NOT keep Node running, and the trace heartbeat is unref'd). Cleared on stop so
3737
- * the loop drains cleanly without a forceful `process.exit`. Exported for tests.
3738
- */
3739
- export function waitForShutdownSignal(app) {
3740
- return new Promise((resolve) => {
3741
- const keepAlive = setInterval(() => { }, KEEP_ALIVE_INTERVAL_MS);
3742
- let stopping = false;
3743
- const onSignal = (signal) => {
3744
- if (stopping) {
3745
- return;
3746
- }
3747
- stopping = true;
3748
- process.off("SIGINT", onSignal);
3749
- process.off("SIGTERM", onSignal);
3750
- clearInterval(keepAlive);
3751
- void (async () => {
3752
- process.stdout.write("\n" + ui.hint(`Received ${signal}; stopping mono agent app…`));
3753
- try {
3754
- await app.stop();
3755
- resolve(0);
3756
- }
3757
- catch (error) {
3758
- process.stderr.write(ui.errorLine(`Foreground shutdown failed: ${error instanceof Error ? error.message : String(error)}`));
3759
- // Resolve so runForeground's finally block can retry idempotent app
3760
- // cleanup and release the process-lifetime singleton lease.
3761
- resolve(1);
3762
- }
3763
- })();
3764
- };
3765
- process.on("SIGINT", onSignal);
3766
- process.on("SIGTERM", onSignal);
3767
- });
3768
- }
3769
- function consoleLogger() {
3770
- return {
3771
- info(message, metadata) {
3772
- process.stdout.write(`${message}${metadata === undefined ? "" : ` ${JSON.stringify(metadata)}`}\n`);
3773
- },
3774
- warn(message, metadata) {
3775
- process.stderr.write(`${message}${metadata === undefined ? "" : ` ${JSON.stringify(metadata)}`}\n`);
3776
- },
3777
- error(message, metadata) {
3778
- process.stderr.write(`${message}${metadata === undefined ? "" : ` ${JSON.stringify(metadata)}`}\n`);
3779
- },
3780
- };
3781
- }
3782
- const cliEntryName = process.argv[1] === undefined ? undefined : basename(process.argv[1]);
3783
- const isDirectCliInvocation = cliEntryName === "cli.js" || cliEntryName === "mono-agent";
3784
- if (isDirectCliInvocation) {
3785
- runCli(process.argv.slice(2))
3786
- .then((code) => {
3787
- if (code !== 0) {
3788
- process.exitCode = code;
3789
- }
3790
- })
3791
- .catch((error) => {
3792
- process.stderr.write(`${ui.style.red("✗")} ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
3793
- process.exitCode = 1;
3794
- });
3795
- }
9
+ export * from "./cli-commands.js";
3796
10
  //# sourceMappingURL=cli.js.map