@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
@@ -0,0 +1,1910 @@
1
+ import { createHash } from "node:crypto";
2
+ import { stat } from "node:fs/promises";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ import process from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { readMonoAgentConfigJson } from "@mono-agent/config";
7
+ import { parseMonoRuntimeModelReference } from "@mono-agent/runtime-adapter";
8
+ import { canonicalBackgroundConfigPath, defaultBackgroundDeps, ensureBackgroundReady, managedBackgroundEnvironment, resolveInstanceTarget, } from "./background.js";
9
+ import { captureDurableBackgroundInputs } from "./background-snapshot.js";
10
+ import { attachScopedKeypress, isAbortLike } from "./cli-cancellation.js";
11
+ import { formatSection, renderPlanCompleteness, riskColor, } from "./cli-validate-config-command.js";
12
+ import { validateMonoAgentFolder } from "./doctor.js";
13
+ import { effectiveFirstRunEnvironment, evaluateFirstRunConfigurationReadiness, evaluateFirstRunReadiness, hasSensitivePersistedEnvironmentValue, piAuthPathBackgroundConflict, readCliConfigSnapshot, readCliDotenvSnapshot, resolveEffectivePiAuthPath, selectedSecretEnvironmentConflicts, selectedSecretValues, unexpectedPersistedMonoAgentOverrides, validateWizardPlanInStaging, withExactProcessEnvironment, } from "./first-run-readiness.js";
14
+ import { initMonoAgentFolder, secretEnvConcurrentModificationCause, verifySecretEnvPersistenceGuard, } from "./init.js";
15
+ import { deriveLaunchdLabel, launchdPathsFor } from "./launchd.js";
16
+ import { detectProviderCredentialStates, executeProviderSetupPlan, isProviderSetupPiApiKeyAction, planProviderSetup, providerSetupActionCommandLine, } from "./provider-setup.js";
17
+ import { readinessProbeTimeoutMs, runAllRouteReadinessProbe } from "./readiness-probe.js";
18
+ import { setupManagedSrt } from "./sandbox-manager.js";
19
+ import { composeWizardPlan, referencedSetupModelRefs } from "./wizard/answers.js";
20
+ import { answersFromCli, isWithChannel } from "./wizard/from-flags.js";
21
+ import { findPreset, presetIds, RECIPE_TO_PRESET } from "./wizard/presets.js";
22
+ import { runInitWizard, runSetupRepairWizard } from "./wizard/run.js";
23
+ import * as p from "@clack/prompts";
24
+ import * as ui from "./ui.js";
25
+ export async function runInit(args, environment) {
26
+ const cwd = process.cwd();
27
+ // On an interactive TTY with no overriding flags, walk the step-by-step wizard;
28
+ // any flag (or a piped/non-TTY invocation) takes the silent default/preset path.
29
+ const wantsWizard = shouldRunInitWizard(args, process.stdin.isTTY === true, process.stdout.isTTY === true);
30
+ if (wantsWizard) {
31
+ // Existing-config pre-check — don't walk the wizard into a guaranteed no-op.
32
+ if (await pathExists(resolve(cwd, "mono-agent.config.json"))) {
33
+ 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"));
34
+ return 0;
35
+ }
36
+ let resolvedPiAuthPath = resolveEffectivePiAuthPath({
37
+ cwd,
38
+ ...(nonEmptyEnv(environment.dotenvEnv.MONO_AGENT_PI_AUTH_PATH)
39
+ ? { envPath: environment.dotenvEnv.MONO_AGENT_PI_AUTH_PATH }
40
+ : {}),
41
+ });
42
+ const initial = await runInitWizard({
43
+ cwd,
44
+ piAuthPath: resolvedPiAuthPath,
45
+ persistedEnv: environment.dotenvEnv,
46
+ });
47
+ if (initial.status === "cancelled")
48
+ return 1;
49
+ let answers = initial.answers;
50
+ let moduleSecrets = { ...initial.moduleSecrets };
51
+ let providerEnvironmentSecrets = { ...initial.providerEnvironmentSecrets };
52
+ let providerSetupSecrets = { ...initial.providerSetupSecrets };
53
+ let piApiKeyPersistenceByProvider = { ...initial.piApiKeyPersistenceByProvider };
54
+ let credentialStates = { ...initial.credentialStates };
55
+ let pendingProviderSetup = initial.runProviderSetup;
56
+ let selectedCodexAuthMode = "browser";
57
+ let readinessProgress;
58
+ let sandboxMutationCompleted = false;
59
+ let deferredFailure;
60
+ firstRun: for (;;) {
61
+ let dotenvSnapshot = { env: {}, fingerprint: "unreadable" };
62
+ let failure = deferredFailure;
63
+ let configurationRecoveryStep;
64
+ let invalidPlanStage;
65
+ deferredFailure = undefined;
66
+ try {
67
+ dotenvSnapshot = await readCliDotenvSnapshot(environment.dotenvPath);
68
+ }
69
+ catch {
70
+ failure ??= dotenvReadinessFailure("The persisted .env could not be read safely. Fix it before retrying setup.");
71
+ }
72
+ const plan = composeWizardPlan(answers, {
73
+ dirBasename: basename(cwd),
74
+ skillsRootExists: await pathExists(resolve(cwd, "skills")),
75
+ });
76
+ resolvedPiAuthPath = resolveEffectivePiAuthPath({
77
+ cwd,
78
+ ...(nonEmptyEnv(dotenvSnapshot.env.MONO_AGENT_PI_AUTH_PATH)
79
+ ? { envPath: dotenvSnapshot.env.MONO_AGENT_PI_AUTH_PATH }
80
+ : {}),
81
+ ...(nonEmptyEnv(plan.configJson.providers?.piAuthPath)
82
+ ? { configPath: plan.configJson.providers.piAuthPath }
83
+ : {}),
84
+ });
85
+ const effectiveEnv = effectiveFirstRunEnvironment({
86
+ shellEnv: environment.shellEnv,
87
+ dotenvEnv: dotenvSnapshot.env,
88
+ enteredSecrets: { ...moduleSecrets, ...providerEnvironmentSecrets },
89
+ resolvedPiAuthPath,
90
+ });
91
+ // Re-submit every selected durable value, not only values typed during this
92
+ // wizard session. That lets the secure merge tighten an existing .env to
93
+ // 0600 while preserving its non-empty operator-owned values verbatim.
94
+ const selectedSecrets = {
95
+ ...selectedSecretValues(plan, effectiveEnv),
96
+ ...providerEnvironmentSecrets,
97
+ };
98
+ const secureExistingDotenv = hasSensitivePersistedEnvironmentValue(dotenvSnapshot.env);
99
+ const conflicts = selectedSecretEnvironmentConflicts(plan, environment.shellEnv, dotenvSnapshot.env, moduleSecrets);
100
+ const persistedOverrides = unexpectedPersistedMonoAgentOverrides(plan, dotenvSnapshot.env);
101
+ if (failure === undefined && persistedOverrides.length > 0) {
102
+ failure = {
103
+ ok: false,
104
+ kind: "invalid_plan",
105
+ message: `Persisted .env contains mono-agent config override${persistedOverrides.length === 1 ? "" : "s"}: ` +
106
+ `${persistedOverrides.join(", ")}. Remove ${persistedOverrides.length === 1 ? "it" : "them"} so the ` +
107
+ "generated config is the exact config validated and started.",
108
+ };
109
+ }
110
+ if (failure === undefined && conflicts.length > 0) {
111
+ failure = {
112
+ ok: false,
113
+ kind: "invalid_plan",
114
+ message: `Selected secret${conflicts.length === 1 ? "" : "s"} ${conflicts.join(", ")} ` +
115
+ "differ between the exported shell, persisted .env, or newly entered value. " +
116
+ "Unset the shell value or make every source match, then retry.",
117
+ };
118
+ }
119
+ if (failure === undefined && piAuthPathBackgroundConflict({
120
+ cwd,
121
+ shellPath: environment.shellEnv.MONO_AGENT_PI_AUTH_PATH,
122
+ dotenvPath: dotenvSnapshot.env.MONO_AGENT_PI_AUTH_PATH,
123
+ ...(nonEmptyEnv(plan.configJson.providers?.piAuthPath)
124
+ ? { configPath: plan.configJson.providers.piAuthPath }
125
+ : {}),
126
+ })) {
127
+ failure = {
128
+ ok: false,
129
+ kind: "invalid_plan",
130
+ message: "The exported MONO_AGENT_PI_AUTH_PATH selects a different credential store than a background start. " +
131
+ "Persist the same path in .env or providers.piAuthPath, or unset the shell override, then retry.",
132
+ };
133
+ }
134
+ if (failure === undefined && pendingProviderSetup) {
135
+ pendingProviderSetup = false;
136
+ const modelRefs = referencedSetupModelRefs(plan);
137
+ const credentialObservation = await withScopedPreflightCancellation(async (abortSignal) => ({
138
+ states: await detectProviderCredentialStates({
139
+ modelRefs,
140
+ cwd,
141
+ piAuthPath: resolvedPiAuthPath,
142
+ persistedEnv: dotenvSnapshot.env,
143
+ abortSignal,
144
+ }),
145
+ interrupted: abortSignal.aborted,
146
+ }));
147
+ if (credentialObservation.interrupted) {
148
+ pendingProviderSetup = true;
149
+ deferredFailure = {
150
+ ok: false,
151
+ kind: "cancelled",
152
+ message: "Provider status detection was interrupted. No agent files were written.",
153
+ interrupted: true,
154
+ };
155
+ continue firstRun;
156
+ }
157
+ credentialStates = credentialObservation.states;
158
+ const plannedSetup = planProviderSetup({
159
+ modelRefs,
160
+ cwd,
161
+ piAuthPath: resolvedPiAuthPath,
162
+ credentialStates,
163
+ piApiKeyPersistenceByProvider,
164
+ });
165
+ if (plannedSetup.actions.some((action) => action.id === "codex-login")) {
166
+ const selected = await selectCodexAuthMode(selectedCodexAuthMode);
167
+ if (selected === undefined)
168
+ return 1;
169
+ selectedCodexAuthMode = selected;
170
+ }
171
+ const environmentApiKeys = environmentProviderApiKeys(plannedSetup, effectiveEnv);
172
+ const missingEnvironmentKeys = plannedSetup.actions
173
+ .filter(isProviderSetupPiApiKeyAction)
174
+ .filter((action) => action.persistence === "environment" && environmentApiKeys[action.id] === undefined)
175
+ .map((action) => action.envVar);
176
+ if (missingEnvironmentKeys.length > 0) {
177
+ for (const envVar of missingEnvironmentKeys) {
178
+ const answer = await p.password({
179
+ message: `Enter ${envVar} for the agent's owner-only .env`,
180
+ validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
181
+ clearOnError: true,
182
+ });
183
+ if (p.isCancel(answer))
184
+ return 1;
185
+ providerEnvironmentSecrets[envVar] = answer;
186
+ }
187
+ pendingProviderSetup = true;
188
+ continue firstRun;
189
+ }
190
+ const setup = await withScopedPreflightCancellation((abortSignal) => withExactProcessEnvironment(effectiveEnv, () => runProviderSetupBeforeInit({
191
+ modelRefs,
192
+ cwd,
193
+ auth: true,
194
+ dryRun: false,
195
+ piAuthPath: resolvedPiAuthPath,
196
+ apiKeys: { ...providerSetupSecrets, ...environmentApiKeys },
197
+ codexAuthMode: selectedCodexAuthMode,
198
+ credentialStates,
199
+ persistedEnv: dotenvSnapshot.env,
200
+ piApiKeyPersistenceByProvider,
201
+ abortSignal,
202
+ })), { keypress: false });
203
+ if (setup === "fatal")
204
+ return 130;
205
+ if (setup === "interrupted") {
206
+ pendingProviderSetup = true;
207
+ failure = {
208
+ ok: false,
209
+ kind: "cancelled",
210
+ message: "Provider setup was interrupted. No agent files were written.",
211
+ interrupted: true,
212
+ };
213
+ }
214
+ else if (setup === "failed") {
215
+ // A plain retry must revisit provider setup rather than falling
216
+ // through to a guaranteed-failing model turn.
217
+ pendingProviderSetup = true;
218
+ failure = {
219
+ ok: false,
220
+ kind: "provider_failed",
221
+ message: "Provider setup did not complete. No agent files were written.",
222
+ };
223
+ }
224
+ }
225
+ if (failure === undefined) {
226
+ if (answers.sandbox) {
227
+ const sandboxPreflight = await runGuidedSandboxPreflight(sandboxMutationCompleted);
228
+ sandboxMutationCompleted = sandboxMutationCompleted || sandboxPreflight.ok;
229
+ if (!sandboxPreflight.ok)
230
+ failure = sandboxPreflight;
231
+ }
232
+ }
233
+ if (failure === undefined) {
234
+ const configurationGate = await runConfigurationPreflightWithSpinner({
235
+ cwd,
236
+ answers,
237
+ plan,
238
+ env: effectiveEnv,
239
+ secretValues: selectedSecrets,
240
+ secureExistingDotenv,
241
+ });
242
+ if (configurationGate.interrupted === true) {
243
+ failure = {
244
+ ok: false,
245
+ kind: "cancelled",
246
+ message: "Configuration preflight was interrupted. No agent files were written.",
247
+ interrupted: true,
248
+ };
249
+ }
250
+ else if (!configurationGate.ready) {
251
+ configurationRecoveryStep = focusedConfigurationRepairStep(configurationGate.failedSectionIds);
252
+ invalidPlanStage = "configuration";
253
+ failure = {
254
+ ok: false,
255
+ kind: "invalid_plan",
256
+ message: `Configuration preflight did not pass: ${configurationGate.reasons.join(" ")}`,
257
+ };
258
+ }
259
+ }
260
+ if (failure === undefined) {
261
+ const readiness = await runReadinessProbeWithSpinner({
262
+ plan,
263
+ effectiveEnv,
264
+ resolvedPiAuthPath,
265
+ ...(readinessProgress === undefined ? {} : {
266
+ resume: {
267
+ planFingerprint: readinessProgress.planFingerprint,
268
+ successfulRouteKeys: readinessProgress.successfulRouteKeys,
269
+ },
270
+ }),
271
+ });
272
+ readinessProgress = mergeReadinessProgress(readinessProgress, readiness, plan);
273
+ failure = readiness;
274
+ }
275
+ readyAttempt: if (failure.ok) {
276
+ const stagedGate = await runFinalReadinessValidationWithSpinner({
277
+ cwd,
278
+ answers,
279
+ plan,
280
+ env: effectiveEnv,
281
+ secretValues: selectedSecrets,
282
+ secureExistingDotenv,
283
+ verifiedCredentialModelRefs: readinessProgress?.verifiedModelRefs ?? [],
284
+ });
285
+ if (stagedGate.interrupted === true) {
286
+ failure = {
287
+ ok: false,
288
+ kind: "cancelled",
289
+ message: "Final readiness validation was interrupted. No agent files were written.",
290
+ interrupted: true,
291
+ };
292
+ break readyAttempt;
293
+ }
294
+ if (stagedGate.ready) {
295
+ const drift = await firstRunDotenvDrift(environment.dotenvPath, dotenvSnapshot);
296
+ if (drift !== undefined) {
297
+ failure = drift;
298
+ break readyAttempt;
299
+ }
300
+ let result;
301
+ try {
302
+ result = await initMonoAgentFolder({
303
+ dir: cwd,
304
+ answers,
305
+ env: effectiveEnv,
306
+ secretValues: selectedSecrets,
307
+ secureExistingDotenv,
308
+ requireConfigCreation: true,
309
+ });
310
+ }
311
+ catch (error) {
312
+ const recovery = secretPersistenceRecoveryMessage(error);
313
+ process.stderr.write(ui.errorLine(`The validated scaffold could not be committed safely. The agent was not started; inspect the destination before retrying.${recovery}`));
314
+ return 1;
315
+ }
316
+ let committedConfigSnapshot;
317
+ try {
318
+ committedConfigSnapshot = await readCliConfigSnapshot(result.configPath);
319
+ }
320
+ catch {
321
+ printIncompleteSetup(["The committed config could not be read back as the regular file setup created."], result.configPath);
322
+ return 1;
323
+ }
324
+ if (committedConfigSnapshot.contents !== `${JSON.stringify(result.plan.configJson, null, 2)}\n`) {
325
+ printIncompleteSetup(["The committed config does not match the exact plan setup validated."], result.configPath);
326
+ return 1;
327
+ }
328
+ let committedDotenvSnapshot;
329
+ try {
330
+ committedDotenvSnapshot = await readCliDotenvSnapshot(environment.dotenvPath);
331
+ }
332
+ catch {
333
+ printIncompleteSetup(["The committed .env could not be read back safely; no readiness claim is safe."], result.configPath);
334
+ return 1;
335
+ }
336
+ // A missing default .env is a valid, durable empty environment. Keep
337
+ // proving its absence in the approved snapshot, but do not turn that
338
+ // default into an explicit --env-file argument: the managed worker
339
+ // treats an explicitly named missing file as an operator error.
340
+ const backgroundEnvFile = args.envFile === undefined
341
+ && committedDotenvSnapshot.fingerprint === "missing"
342
+ ? undefined
343
+ : environment.dotenvPath;
344
+ const postWriteConflicts = selectedSecretEnvironmentConflicts(result.plan, environment.shellEnv, committedDotenvSnapshot.env, moduleSecrets);
345
+ const postWriteOverrides = unexpectedPersistedMonoAgentOverrides(result.plan, committedDotenvSnapshot.env);
346
+ const postWritePiAuthConflict = piAuthPathBackgroundConflict({
347
+ cwd,
348
+ shellPath: environment.shellEnv.MONO_AGENT_PI_AUTH_PATH,
349
+ dotenvPath: committedDotenvSnapshot.env.MONO_AGENT_PI_AUTH_PATH,
350
+ ...(nonEmptyEnv(result.plan.configJson.providers?.piAuthPath)
351
+ ? { configPath: result.plan.configJson.providers.piAuthPath }
352
+ : {}),
353
+ });
354
+ if (postWriteConflicts.length > 0 || postWriteOverrides.length > 0 || postWritePiAuthConflict) {
355
+ printIncompleteSetup(["The committed .env no longer matches the values and generated config that setup approved."], result.configPath);
356
+ return 1;
357
+ }
358
+ const postWriteEnv = effectiveFirstRunEnvironment({
359
+ shellEnv: environment.shellEnv,
360
+ dotenvEnv: committedDotenvSnapshot.env,
361
+ resolvedPiAuthPath,
362
+ });
363
+ if (!sameConcreteEnvironment(effectiveEnv, postWriteEnv)) {
364
+ printIncompleteSetup(["The durable environment changed after the primary-model check. Retry setup before claiming readiness."], result.configPath);
365
+ return 1;
366
+ }
367
+ let committedBackgroundSnapshot;
368
+ let committedBackgroundEnvironment;
369
+ let committedBackgroundConfigSourceFingerprint;
370
+ try {
371
+ const durableInputs = await captureDurableBackgroundInputs({
372
+ cwd,
373
+ configPath: result.configPath,
374
+ envFile: environment.dotenvPath,
375
+ operationalEnvironment: managedBackgroundEnvironment(postWriteEnv),
376
+ });
377
+ committedBackgroundSnapshot = durableInputs.snapshot;
378
+ committedBackgroundEnvironment = durableInputs.environment;
379
+ committedBackgroundConfigSourceFingerprint = durableInputs.configSourceFingerprint;
380
+ }
381
+ catch {
382
+ printIncompleteSetup(["The complete committed config, dotenv, Identity, Soul, and MCP snapshot could not be proven safely."], result.configPath);
383
+ return 1;
384
+ }
385
+ if (committedBackgroundConfigSourceFingerprint !== committedConfigSnapshot.fingerprint) {
386
+ printIncompleteSetup(["The committed config or durable environment changed before the background snapshot was captured."], result.configPath);
387
+ return 1;
388
+ }
389
+ printInitResult(result);
390
+ let report;
391
+ try {
392
+ report = await validateMonoAgentFolder({
393
+ env: postWriteEnv,
394
+ cwd,
395
+ configPath: result.configPath,
396
+ liveness: true,
397
+ verifiedCredentialModelRefs: readinessProgress?.verifiedModelRefs ?? [],
398
+ });
399
+ }
400
+ catch {
401
+ printIncompleteSetup(["Post-write validation could not complete; no readiness claim is safe."], result.configPath);
402
+ return 1;
403
+ }
404
+ process.stdout.write("\n" + ui.heading("Validation"));
405
+ for (const section of report.sections)
406
+ process.stdout.write(formatSection(section));
407
+ process.stdout.write(renderPlanCompleteness(result.plan.validateExpectations, "Selected capabilities", report));
408
+ const configuredSecrets = configuredSecretNames(result, postWriteEnv);
409
+ printSecretsChecklist(result.plan.secrets, configuredSecrets);
410
+ const finalGate = evaluateFirstRunReadiness({
411
+ plan: result.plan,
412
+ report,
413
+ secretPersistence: result.secretPersistence,
414
+ verifiedCredentialModelRefs: readinessProgress?.verifiedModelRefs ?? [],
415
+ });
416
+ if (!finalGate.ready) {
417
+ printIncompleteSetup(finalGate.reasons, result.configPath);
418
+ return 1;
419
+ }
420
+ const postValidationConfigDrift = await firstRunConfigDrift(result.configPath, committedConfigSnapshot);
421
+ if (postValidationConfigDrift !== undefined) {
422
+ printIncompleteSetup([postValidationConfigDrift.message], result.configPath);
423
+ return 1;
424
+ }
425
+ const postValidationDrift = await firstRunDotenvDrift(environment.dotenvPath, committedDotenvSnapshot);
426
+ if (postValidationDrift !== undefined) {
427
+ printIncompleteSetup([postValidationDrift.message], result.configPath);
428
+ return 1;
429
+ }
430
+ const postValidationSecretGuard = await firstRunSecretEnvGuardFailure(environment.dotenvPath, result.secretPersistence.status === "persisted");
431
+ if (postValidationSecretGuard !== undefined) {
432
+ printIncompleteSetup([postValidationSecretGuard.message], result.configPath);
433
+ return 1;
434
+ }
435
+ process.stdout.write(ui.badge("ok") + ui.style.green("All runtime route checks passed — every selected model produced a real no-tool response.\n") +
436
+ ui.badge("ok") + ui.style.green("Agent configuration validated — every selected capability passed full validation.\n"));
437
+ const preTuiConfigDrift = await firstRunConfigDrift(result.configPath, committedConfigSnapshot);
438
+ if (preTuiConfigDrift !== undefined) {
439
+ printIncompleteSetup([preTuiConfigDrift.message], result.configPath);
440
+ return 1;
441
+ }
442
+ const preTuiDotenvDrift = await firstRunDotenvDrift(environment.dotenvPath, committedDotenvSnapshot);
443
+ if (preTuiDotenvDrift !== undefined) {
444
+ printIncompleteSetup([preTuiDotenvDrift.message], result.configPath);
445
+ return 1;
446
+ }
447
+ const preTuiSecretGuard = await firstRunSecretEnvGuardFailure(environment.dotenvPath, result.secretPersistence.status === "persisted");
448
+ if (preTuiSecretGuard !== undefined) {
449
+ printIncompleteSetup([preTuiSecretGuard.message], result.configPath);
450
+ return 1;
451
+ }
452
+ if (process.platform !== "darwin") {
453
+ printUnsupportedGuidedInitHandoff(result.configPath, backgroundEnvFile);
454
+ return 0;
455
+ }
456
+ process.stdout.write(ui.hint("Starting the authoritative background agent before configuration chat…"));
457
+ let background;
458
+ try {
459
+ const resolvedBackgroundTarget = await resolveInstanceTarget({
460
+ args: {
461
+ configPath: result.configPath,
462
+ ...(backgroundEnvFile === undefined ? {} : { envFile: backgroundEnvFile }),
463
+ },
464
+ env: { ...committedBackgroundEnvironment },
465
+ cwd,
466
+ cliPath: fileURLToPath(new URL("./cli.js", import.meta.url)),
467
+ requireTui: true,
468
+ });
469
+ const backgroundTarget = {
470
+ ...resolvedBackgroundTarget,
471
+ expectedSnapshot: committedBackgroundSnapshot,
472
+ };
473
+ background = await ensureBackgroundReady(backgroundTarget, defaultBackgroundDeps());
474
+ }
475
+ catch (error) {
476
+ printUnexpectedGuidedBackgroundFailure(result.configPath, backgroundEnvFile, error);
477
+ return 1;
478
+ }
479
+ if (!background.ok) {
480
+ process.stderr.write(ui.hint("The validated agent files were preserved, but configuration chat was not opened because the background agent is not ready."));
481
+ return 1;
482
+ }
483
+ process.stdout.write(ui.badge("ok") + ui.style.green("Agent ready — the background process reported startup complete.\n") +
484
+ ui.badge("ok") + ui.style.green("Opening configuration chat on the authoritative background agent.\n"));
485
+ const { runTui } = await import("./tui-command.js");
486
+ return await withExactProcessEnvironment(committedBackgroundEnvironment, () => runTui({
487
+ configPath: result.configPath,
488
+ cwd,
489
+ env: committedBackgroundEnvironment,
490
+ ...(backgroundEnvFile === undefined ? {} : { envFile: backgroundEnvFile }),
491
+ agent: background.source.sourceId,
492
+ configure: true,
493
+ }));
494
+ }
495
+ configurationRecoveryStep = focusedConfigurationRepairStep(stagedGate.failedSectionIds);
496
+ invalidPlanStage = "final_readiness";
497
+ failure = {
498
+ ok: false,
499
+ kind: "invalid_plan",
500
+ message: `Runtime route checks passed, but the complete agent is not ready: ${stagedGate.reasons.join(" ")}`,
501
+ };
502
+ }
503
+ if (failure.ok)
504
+ throw new Error("First-run recovery reached without a failure.");
505
+ if (failure.interrupted === true || failure.kind === "cancelled") {
506
+ interruptedRecoveryMenu: for (;;) {
507
+ const interruptedRecovery = await selectInterruptedFirstRunRecovery();
508
+ if (interruptedRecovery === "cancel")
509
+ return 1;
510
+ if (interruptedRecovery === "restart") {
511
+ readinessProgress = undefined;
512
+ break interruptedRecoveryMenu;
513
+ }
514
+ if (interruptedRecovery === "edit") {
515
+ const repaired = await runSetupRepairWizard({
516
+ cwd,
517
+ answers,
518
+ piAuthPath: resolvedPiAuthPath,
519
+ persistedEnv: dotenvSnapshot.env,
520
+ moduleSecrets,
521
+ providerSetupSecrets,
522
+ providerEnvironmentSecrets,
523
+ piApiKeyPersistenceByProvider,
524
+ credentialStates,
525
+ runProviderSetup: pendingProviderSetup,
526
+ });
527
+ if (repaired.status === "cancelled")
528
+ continue interruptedRecoveryMenu;
529
+ answers = repaired.answers;
530
+ moduleSecrets = { ...repaired.moduleSecrets };
531
+ providerSetupSecrets = { ...repaired.providerSetupSecrets };
532
+ providerEnvironmentSecrets = { ...repaired.providerEnvironmentSecrets };
533
+ piApiKeyPersistenceByProvider = { ...repaired.piApiKeyPersistenceByProvider };
534
+ credentialStates = { ...repaired.credentialStates };
535
+ pendingProviderSetup = repaired.runProviderSetup;
536
+ if (pendingProviderSetup)
537
+ readinessProgress = undefined;
538
+ }
539
+ break interruptedRecoveryMenu;
540
+ }
541
+ continue firstRun;
542
+ }
543
+ if (failure.message.startsWith("[sandbox_preflight_failed]")) {
544
+ sandboxRecoveryMenu: for (;;) {
545
+ const recovery = await selectSandboxPreflightRecovery();
546
+ if (recovery === "cancel")
547
+ return 1;
548
+ if (recovery === "edit") {
549
+ const edited = await runSetupRepairWizard({
550
+ cwd,
551
+ answers,
552
+ piAuthPath: resolvedPiAuthPath,
553
+ persistedEnv: dotenvSnapshot.env,
554
+ moduleSecrets,
555
+ providerSetupSecrets,
556
+ providerEnvironmentSecrets,
557
+ piApiKeyPersistenceByProvider,
558
+ credentialStates,
559
+ runProviderSetup: pendingProviderSetup,
560
+ });
561
+ if (edited.status === "cancelled")
562
+ continue sandboxRecoveryMenu;
563
+ answers = edited.answers;
564
+ moduleSecrets = { ...edited.moduleSecrets };
565
+ providerSetupSecrets = { ...edited.providerSetupSecrets };
566
+ providerEnvironmentSecrets = { ...edited.providerEnvironmentSecrets };
567
+ piApiKeyPersistenceByProvider = { ...edited.piApiKeyPersistenceByProvider };
568
+ credentialStates = { ...edited.credentialStates };
569
+ pendingProviderSetup = edited.runProviderSetup;
570
+ if (pendingProviderSetup)
571
+ readinessProgress = undefined;
572
+ }
573
+ break sandboxRecoveryMenu;
574
+ }
575
+ continue firstRun;
576
+ }
577
+ let recoveryFailure = failure;
578
+ recoveryMenu: for (;;) {
579
+ p.log.error(`[${recoveryFailure.kind}] ${recoveryFailure.message}`);
580
+ const recovery = await selectFirstRunRecovery(recoveryFailure, configurationRecoveryStep, invalidPlanStage);
581
+ if (recovery === "cancel")
582
+ return 1;
583
+ if (recovery === "save") {
584
+ let saved;
585
+ try {
586
+ saved = await initMonoAgentFolder({
587
+ dir: cwd,
588
+ answers,
589
+ env: effectiveEnv,
590
+ secretValues: selectedSecrets,
591
+ secureExistingDotenv,
592
+ requireConfigCreation: true,
593
+ });
594
+ }
595
+ catch (error) {
596
+ const recovery = secretPersistenceRecoveryMessage(error);
597
+ process.stderr.write(ui.errorLine(`The incomplete scaffold could not be committed safely; inspect the destination and retry.${recovery}`));
598
+ return 1;
599
+ }
600
+ printInitResult(saved);
601
+ let durableSavedEnv = {};
602
+ if (saved.secretPersistence.status === "persisted") {
603
+ try {
604
+ durableSavedEnv = (await readCliDotenvSnapshot(environment.dotenvPath)).env;
605
+ }
606
+ catch {
607
+ durableSavedEnv = {};
608
+ }
609
+ }
610
+ printSecretsChecklist(saved.plan.secrets, configuredSecretNames(saved, durableSavedEnv));
611
+ printIncompleteSetup([recoveryFailure.message], saved.configPath);
612
+ return 1;
613
+ }
614
+ if (recovery === "edit") {
615
+ const repaired = await runSetupRepairWizard({
616
+ cwd,
617
+ answers,
618
+ piAuthPath: resolvedPiAuthPath,
619
+ persistedEnv: dotenvSnapshot.env,
620
+ ...(configurationRecoveryStep === undefined ? {} : { initialStep: configurationRecoveryStep }),
621
+ moduleSecrets,
622
+ providerSetupSecrets,
623
+ providerEnvironmentSecrets,
624
+ piApiKeyPersistenceByProvider,
625
+ credentialStates,
626
+ runProviderSetup: pendingProviderSetup,
627
+ });
628
+ if (repaired.status === "cancelled")
629
+ continue recoveryMenu;
630
+ answers = repaired.answers;
631
+ moduleSecrets = { ...repaired.moduleSecrets };
632
+ providerSetupSecrets = { ...repaired.providerSetupSecrets };
633
+ providerEnvironmentSecrets = { ...repaired.providerEnvironmentSecrets };
634
+ piApiKeyPersistenceByProvider = { ...repaired.piApiKeyPersistenceByProvider };
635
+ credentialStates = { ...repaired.credentialStates };
636
+ pendingProviderSetup = repaired.runProviderSetup;
637
+ if (pendingProviderSetup)
638
+ readinessProgress = undefined;
639
+ continue firstRun;
640
+ }
641
+ if (recovery === "model") {
642
+ const repaired = await runSetupRepairWizard({
643
+ cwd,
644
+ answers,
645
+ piAuthPath: resolvedPiAuthPath,
646
+ persistedEnv: dotenvSnapshot.env,
647
+ initialStep: 1,
648
+ moduleSecrets,
649
+ providerSetupSecrets,
650
+ providerEnvironmentSecrets,
651
+ piApiKeyPersistenceByProvider,
652
+ credentialStates,
653
+ runProviderSetup: pendingProviderSetup,
654
+ });
655
+ if (repaired.status === "cancelled")
656
+ continue recoveryMenu;
657
+ answers = repaired.answers;
658
+ moduleSecrets = { ...repaired.moduleSecrets };
659
+ providerSetupSecrets = { ...repaired.providerSetupSecrets };
660
+ providerEnvironmentSecrets = { ...repaired.providerEnvironmentSecrets };
661
+ piApiKeyPersistenceByProvider = { ...repaired.piApiKeyPersistenceByProvider };
662
+ credentialStates = { ...repaired.credentialStates };
663
+ pendingProviderSetup = repaired.runProviderSetup;
664
+ if (pendingProviderSetup)
665
+ readinessProgress = undefined;
666
+ continue firstRun;
667
+ }
668
+ if (recovery === "auth") {
669
+ // Authentication can replace credential bytes without changing the
670
+ // route/config fingerprint. Every route must be proven again.
671
+ readinessProgress = undefined;
672
+ if (referencedSetupModelRefs(plan).some((ref) => ref.startsWith("codex:"))) {
673
+ const selected = await selectCodexAuthMode(selectedCodexAuthMode);
674
+ if (selected === undefined)
675
+ return 1;
676
+ selectedCodexAuthMode = selected;
677
+ }
678
+ const setupPlan = planProviderSetup({
679
+ modelRefs: referencedSetupModelRefs(plan),
680
+ cwd,
681
+ piAuthPath: resolvedPiAuthPath,
682
+ codexAuthMode: selectedCodexAuthMode,
683
+ forceAuthentication: true,
684
+ });
685
+ const prompted = await promptProviderSetupSecrets(setupPlan, providerSetupSecrets, piApiKeyPersistenceByProvider, providerEnvironmentSecrets);
686
+ if (prompted === undefined)
687
+ return 1;
688
+ providerSetupSecrets = prompted.apiKeys;
689
+ piApiKeyPersistenceByProvider = prompted.persistenceByProvider;
690
+ providerEnvironmentSecrets = prompted.environmentSecrets;
691
+ readinessProgress = undefined;
692
+ const selectedSetupPlan = planProviderSetup({
693
+ modelRefs: referencedSetupModelRefs(plan),
694
+ cwd,
695
+ piAuthPath: resolvedPiAuthPath,
696
+ codexAuthMode: selectedCodexAuthMode,
697
+ forceAuthentication: true,
698
+ piApiKeyPersistenceByProvider,
699
+ });
700
+ const environmentApiKeys = environmentProviderApiKeys(selectedSetupPlan, { ...effectiveEnv, ...providerEnvironmentSecrets });
701
+ const missingEnvironmentKeys = selectedSetupPlan.actions
702
+ .filter(isProviderSetupPiApiKeyAction)
703
+ .filter((action) => action.persistence === "environment" && environmentApiKeys[action.id] === undefined)
704
+ .map((action) => action.envVar);
705
+ if (missingEnvironmentKeys.length > 0) {
706
+ recoveryFailure = {
707
+ ok: false,
708
+ kind: "provider_failed",
709
+ message: `Add ${missingEnvironmentKeys.join(", ")} to the durable owner-only .env, then retry authentication. No agent files were written.`,
710
+ };
711
+ continue recoveryMenu;
712
+ }
713
+ const setup = await withScopedPreflightCancellation((abortSignal) => withExactProcessEnvironment(effectiveEnv, () => runProviderSetupBeforeInit({
714
+ modelRefs: referencedSetupModelRefs(plan),
715
+ cwd,
716
+ auth: true,
717
+ dryRun: false,
718
+ piAuthPath: resolvedPiAuthPath,
719
+ apiKeys: { ...providerSetupSecrets, ...environmentApiKeys },
720
+ codexAuthMode: selectedCodexAuthMode,
721
+ forceAuthentication: true,
722
+ piApiKeyPersistenceByProvider,
723
+ abortSignal,
724
+ })), { keypress: false });
725
+ if (setup === "fatal")
726
+ return 130;
727
+ if (setup === "interrupted") {
728
+ pendingProviderSetup = true;
729
+ deferredFailure = {
730
+ ok: false,
731
+ kind: "cancelled",
732
+ message: "Provider setup was interrupted. No agent files were written.",
733
+ interrupted: true,
734
+ };
735
+ continue firstRun;
736
+ }
737
+ if (setup === "failed") {
738
+ pendingProviderSetup = true;
739
+ recoveryFailure = {
740
+ ok: false,
741
+ kind: "provider_failed",
742
+ message: "Provider setup still needs attention. No agent files were written.",
743
+ };
744
+ continue recoveryMenu;
745
+ }
746
+ pendingProviderSetup = false;
747
+ }
748
+ // "retry" deliberately reruns only the live checks. Provider setup is a
749
+ // separate explicit recovery action and is never repeated automatically.
750
+ continue firstRun;
751
+ }
752
+ }
753
+ }
754
+ const presetId = resolveInitPresetId(args);
755
+ if (presetId === "unknown") {
756
+ return 1;
757
+ }
758
+ const withChannels = resolveWithChannels(args);
759
+ if (withChannels === "invalid") {
760
+ return 1;
761
+ }
762
+ const answers = answersFromCli({
763
+ ...(args.model === undefined ? {} : { model: args.model }),
764
+ ...(args.name === undefined ? {} : { name: args.name }),
765
+ ...(args.fallbackModels === undefined ? {} : { fallbackModels: args.fallbackModels }),
766
+ ...(args.fallbacks === undefined ? {} : { fallbacks: args.fallbacks }),
767
+ ...(args.routeSafety === undefined ? {} : { routeSafety: args.routeSafety }),
768
+ ...(args.effort === undefined ? {} : { effort: args.effort }),
769
+ ...(args.memory === undefined ? {} : { memory: args.memory }),
770
+ ...(withChannels === undefined ? {} : { withChannels }),
771
+ ...(presetId === undefined ? {} : { presetId }),
772
+ });
773
+ const previewPlan = composeWizardPlan(answers, {
774
+ dirBasename: basename(process.cwd()),
775
+ skillsRootExists: await pathExists(resolve(process.cwd(), "skills")),
776
+ });
777
+ const nonInteractivePiAuthPath = resolveEffectivePiAuthPath({
778
+ cwd,
779
+ ...(nonEmptyEnv(environment.shellEnv.MONO_AGENT_PI_AUTH_PATH)
780
+ ? { envPath: environment.shellEnv.MONO_AGENT_PI_AUTH_PATH }
781
+ : nonEmptyEnv(environment.dotenvEnv.MONO_AGENT_PI_AUTH_PATH)
782
+ ? { envPath: environment.dotenvEnv.MONO_AGENT_PI_AUTH_PATH }
783
+ : {}),
784
+ ...(nonEmptyEnv(previewPlan.configJson.providers?.piAuthPath)
785
+ ? { configPath: previewPlan.configJson.providers.piAuthPath }
786
+ : {}),
787
+ });
788
+ const nonInteractiveEnvironment = effectiveFirstRunEnvironment({
789
+ shellEnv: environment.shellEnv,
790
+ dotenvEnv: environment.dotenvEnv,
791
+ resolvedPiAuthPath: nonInteractivePiAuthPath,
792
+ });
793
+ const setup = await withScopedPreflightCancellation((abortSignal) => withExactProcessEnvironment(nonInteractiveEnvironment, () => runProviderSetupBeforeInit({
794
+ modelRefs: referencedSetupModelRefs(previewPlan),
795
+ cwd,
796
+ auth: args.auth === true,
797
+ dryRun: args.dryRun,
798
+ persistedEnv: environment.dotenvEnv,
799
+ piAuthPath: nonInteractivePiAuthPath,
800
+ ...(args.codexAuthMode === undefined ? {} : { codexAuthMode: args.codexAuthMode }),
801
+ abortSignal,
802
+ })), { keypress: false });
803
+ if (setup === "interrupted" || setup === "fatal") {
804
+ return 130;
805
+ }
806
+ if (setup === "failed") {
807
+ return 1;
808
+ }
809
+ const result = await initMonoAgentFolder({
810
+ dir: cwd,
811
+ answers,
812
+ dryRun: args.dryRun,
813
+ // Scaffold-only init has no immediate launchd-minimal worker proof. Reject
814
+ // both shell and persisted memory-identity overrides instead of creating a
815
+ // generation at a different path/tier than a follow-up validate would use.
816
+ env: { ...environment.shellEnv, ...environment.dotenvEnv },
817
+ });
818
+ printInitResult(result);
819
+ printSecretsChecklist(result.plan.secrets, new Set());
820
+ printNextSteps(result.configPath);
821
+ return 0;
822
+ }
823
+ const FIRST_RUN_STAGING_FAILURE_MAX_LENGTH = 500;
824
+ const FIRST_RUN_SENSITIVE_ENV_NAME = /(api.?key|credential|password|secret|token)/iu;
825
+ function throwIfFirstRunPreflightAborted(signal) {
826
+ if (signal?.aborted !== true)
827
+ return;
828
+ const error = new Error("Preflight was interrupted.");
829
+ error.name = "AbortError";
830
+ throw error;
831
+ }
832
+ function firstRunStagingFailureDetail(error, sensitiveValues = []) {
833
+ let message = reasonOf(error);
834
+ for (const value of [...new Set(sensitiveValues)].filter((candidate) => candidate.length >= 4).sort((left, right) => right.length - left.length)) {
835
+ message = message.replaceAll(value, "[secret-redacted]");
836
+ }
837
+ const normalized = message
838
+ .replace(/\bBearer\s+[^\s,;]+/giu, "Bearer [secret-redacted]")
839
+ .replace(/\b(api[ _-]?key|access[ _-]?token|auth[ _-]?token|password|secret)(\s*[=:]\s*)([^\s,;]+)/giu, (_match, label, separator) => `${label}${separator}[secret-redacted]`)
840
+ .replace(/\s+/gu, " ")
841
+ .trim();
842
+ if (normalized.length === 0)
843
+ return "Unknown staging failure.";
844
+ return normalized.length <= FIRST_RUN_STAGING_FAILURE_MAX_LENGTH
845
+ ? normalized
846
+ : `${normalized.slice(0, FIRST_RUN_STAGING_FAILURE_MAX_LENGTH - 1).trimEnd()}…`;
847
+ }
848
+ function firstRunStagingSensitiveValues(env, explicit) {
849
+ return [
850
+ ...Object.entries(env)
851
+ .filter((entry) => FIRST_RUN_SENSITIVE_ENV_NAME.test(entry[0])
852
+ && typeof entry[1] === "string"
853
+ && entry[1].length > 0)
854
+ .map(([, value]) => value),
855
+ ...Object.values(explicit),
856
+ ];
857
+ }
858
+ async function runConfigurationPreflightWithSpinner(options) {
859
+ process.stdout.write("\n" + ui.heading("Configuration preflight"));
860
+ const spinner = p.spinner();
861
+ try {
862
+ return await withScopedPreflightCancellation(async (abortSignal) => {
863
+ spinner.start("Validating generated files and selected capabilities before runtime calls");
864
+ const gate = await assessPrewriteFirstRunConfigurationReadiness({ ...options, abortSignal });
865
+ if (abortSignal.aborted || spinner.isCancelled) {
866
+ spinner.cancel("Configuration preflight interrupted");
867
+ return interruptedConfigurationAssessment();
868
+ }
869
+ if (gate.ready) {
870
+ spinner.stop("Selected capabilities are ready for runtime checks");
871
+ }
872
+ else {
873
+ spinner.error("Configuration preflight needs attention");
874
+ }
875
+ return gate;
876
+ });
877
+ }
878
+ catch (error) {
879
+ if (!isAbortLike(error))
880
+ throw error;
881
+ spinner.cancel("Configuration preflight interrupted");
882
+ return interruptedConfigurationAssessment();
883
+ }
884
+ }
885
+ function interruptedConfigurationAssessment() {
886
+ return {
887
+ ready: false,
888
+ reasons: ["Configuration preflight was interrupted."],
889
+ failedSectionIds: [],
890
+ interrupted: true,
891
+ };
892
+ }
893
+ async function runFinalReadinessValidationWithSpinner(options) {
894
+ process.stdout.write("\n" + ui.heading("Final readiness validation"));
895
+ const spinner = p.spinner();
896
+ try {
897
+ return await withScopedPreflightCancellation(async (abortSignal) => {
898
+ spinner.start("Revalidating the effective files after runtime route checks");
899
+ const gate = await assessPrewriteFirstRunReadiness({ ...options, abortSignal });
900
+ if (abortSignal.aborted || spinner.isCancelled) {
901
+ spinner.cancel("Final readiness validation interrupted");
902
+ return interruptedFinalReadinessAssessment();
903
+ }
904
+ if (gate.ready)
905
+ spinner.stop("Effective files and runtime routes are ready");
906
+ else
907
+ spinner.error("Final readiness validation needs attention");
908
+ return gate;
909
+ });
910
+ }
911
+ catch (error) {
912
+ if (!isAbortLike(error))
913
+ throw error;
914
+ spinner.cancel("Final readiness validation interrupted");
915
+ return interruptedFinalReadinessAssessment();
916
+ }
917
+ }
918
+ function interruptedFinalReadinessAssessment() {
919
+ return {
920
+ ready: false,
921
+ reasons: ["Final readiness validation was interrupted."],
922
+ failedSectionIds: [],
923
+ interrupted: true,
924
+ };
925
+ }
926
+ async function assessPrewriteFirstRunConfigurationReadiness(options) {
927
+ try {
928
+ throwIfFirstRunPreflightAborted(options.abortSignal);
929
+ const preview = await initMonoAgentFolder({
930
+ dir: options.cwd,
931
+ answers: options.answers,
932
+ env: options.env,
933
+ secretValues: options.secretValues,
934
+ secureExistingDotenv: options.secureExistingDotenv,
935
+ dryRun: true,
936
+ });
937
+ throwIfFirstRunPreflightAborted(options.abortSignal);
938
+ const report = await validateWizardPlanInStaging({
939
+ plan: options.plan,
940
+ sourceCwd: options.cwd,
941
+ env: options.env,
942
+ verifiedCredentialModelRefs: [],
943
+ ...(options.abortSignal === undefined ? {} : { abortSignal: options.abortSignal }),
944
+ });
945
+ const gate = evaluateFirstRunConfigurationReadiness({
946
+ plan: options.plan,
947
+ report,
948
+ secretPersistence: preview.secretPersistence,
949
+ });
950
+ return {
951
+ ...gate,
952
+ failedSectionIds: configurationFailureSectionIds(options.plan, report, true),
953
+ };
954
+ }
955
+ catch (error) {
956
+ throwIfFirstRunPreflightAborted(options.abortSignal);
957
+ if (error instanceof Error && error.name === "AbortError")
958
+ throw error;
959
+ return {
960
+ ready: false,
961
+ reasons: [
962
+ `The complete generated configuration could not be validated safely in staging: ${firstRunStagingFailureDetail(error, firstRunStagingSensitiveValues(options.env, options.secretValues))}`,
963
+ ],
964
+ failedSectionIds: [],
965
+ };
966
+ }
967
+ }
968
+ async function assessPrewriteFirstRunReadiness(options) {
969
+ try {
970
+ throwIfFirstRunPreflightAborted(options.abortSignal);
971
+ const preview = await initMonoAgentFolder({
972
+ dir: options.cwd,
973
+ answers: options.answers,
974
+ env: options.env,
975
+ secretValues: options.secretValues,
976
+ secureExistingDotenv: options.secureExistingDotenv,
977
+ dryRun: true,
978
+ });
979
+ throwIfFirstRunPreflightAborted(options.abortSignal);
980
+ const report = await validateWizardPlanInStaging({
981
+ plan: options.plan,
982
+ sourceCwd: options.cwd,
983
+ env: options.env,
984
+ verifiedCredentialModelRefs: options.verifiedCredentialModelRefs,
985
+ ...(options.abortSignal === undefined ? {} : { abortSignal: options.abortSignal }),
986
+ });
987
+ const gate = evaluateFirstRunReadiness({
988
+ plan: options.plan,
989
+ report,
990
+ secretPersistence: preview.secretPersistence,
991
+ verifiedCredentialModelRefs: options.verifiedCredentialModelRefs,
992
+ });
993
+ return {
994
+ ...gate,
995
+ failedSectionIds: configurationFailureSectionIds(options.plan, report, false),
996
+ };
997
+ }
998
+ catch (error) {
999
+ throwIfFirstRunPreflightAborted(options.abortSignal);
1000
+ if (error instanceof Error && error.name === "AbortError")
1001
+ throw error;
1002
+ return {
1003
+ ready: false,
1004
+ reasons: [
1005
+ `The complete generated plan could not be validated safely in staging: ${firstRunStagingFailureDetail(error, firstRunStagingSensitiveValues(options.env, options.secretValues))}`,
1006
+ ],
1007
+ failedSectionIds: [],
1008
+ };
1009
+ }
1010
+ }
1011
+ function configurationFailureSectionIds(plan, report, deferWaitingCredentials) {
1012
+ const byId = new Map(report.sections.map((section) => [section.id, section]));
1013
+ const ids = new Set();
1014
+ for (const expectation of plan.validateExpectations) {
1015
+ const actual = byId.get(expectation.sectionId)?.status;
1016
+ if (actual === expectation.mustBe
1017
+ || (deferWaitingCredentials && expectation.sectionId === "credentials" && actual === "waiting"))
1018
+ continue;
1019
+ ids.add(expectation.sectionId);
1020
+ }
1021
+ if (!report.ok) {
1022
+ for (const section of report.sections) {
1023
+ if (section.status === "error")
1024
+ ids.add(section.id);
1025
+ }
1026
+ }
1027
+ return [...ids];
1028
+ }
1029
+ function nonEmptyEnv(value) {
1030
+ return typeof value === "string" && value.trim().length > 0;
1031
+ }
1032
+ function sameConcreteEnvironment(left, right) {
1033
+ const concreteEntries = (env) => Object.entries(env)
1034
+ .filter((entry) => typeof entry[1] === "string")
1035
+ .sort(([leftName], [rightName]) => leftName.localeCompare(rightName));
1036
+ const leftEntries = concreteEntries(left);
1037
+ const rightEntries = concreteEntries(right);
1038
+ return leftEntries.length === rightEntries.length && leftEntries.every(([name, value], index) => rightEntries[index]?.[0] === name && rightEntries[index]?.[1] === value);
1039
+ }
1040
+ function dotenvReadinessFailure(message) {
1041
+ return { ok: false, kind: "invalid_plan", message };
1042
+ }
1043
+ async function firstRunDotenvDrift(path, expected) {
1044
+ let current;
1045
+ try {
1046
+ current = await readCliDotenvSnapshot(path);
1047
+ }
1048
+ catch {
1049
+ return dotenvReadinessFailure("The persisted .env became unreadable during setup. Readiness cannot be claimed.");
1050
+ }
1051
+ if (current.fingerprint === expected.fingerprint)
1052
+ return undefined;
1053
+ 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.");
1054
+ }
1055
+ async function firstRunConfigDrift(path, expected) {
1056
+ let current;
1057
+ try {
1058
+ current = await readCliConfigSnapshot(path);
1059
+ }
1060
+ catch {
1061
+ return dotenvReadinessFailure("The committed config became unreadable or unsafe during setup. Readiness cannot be claimed.");
1062
+ }
1063
+ if (current.fingerprint === expected.fingerprint)
1064
+ return undefined;
1065
+ return dotenvReadinessFailure("The committed config changed while setup was validating the agent. Review the change, then retry so the exact plan can be checked.");
1066
+ }
1067
+ async function firstRunSecretEnvGuardFailure(path, required) {
1068
+ if (!required)
1069
+ return undefined;
1070
+ try {
1071
+ if (await verifySecretEnvPersistenceGuard(path))
1072
+ return undefined;
1073
+ }
1074
+ catch {
1075
+ // Fall through to one stable, non-secret-bearing operator message.
1076
+ }
1077
+ return dotenvReadinessFailure("The committed .env is no longer owner-only, safely ignored, and untracked. Readiness cannot be claimed.");
1078
+ }
1079
+ function secretPersistenceRecoveryMessage(error) {
1080
+ const cause = secretEnvConcurrentModificationCause(error);
1081
+ return cause === undefined ? "" : ` ${cause.message}`;
1082
+ }
1083
+ function readinessPlanIdentity(plan) {
1084
+ const displayed = readinessRoutesForDisplay(plan);
1085
+ const immutable = displayed.map((route, index) => ({
1086
+ index,
1087
+ model: route.model,
1088
+ effort: route.effort ?? null,
1089
+ }));
1090
+ return {
1091
+ fingerprint: createHash("sha256")
1092
+ .update(JSON.stringify({ version: 1, routes: immutable }))
1093
+ .digest("hex"),
1094
+ routes: displayed.map((route, index) => ({
1095
+ index,
1096
+ ...route,
1097
+ key: createHash("sha256")
1098
+ .update(JSON.stringify({ version: 1, index, model: route.model, effort: route.effort ?? null }))
1099
+ .digest("hex"),
1100
+ })),
1101
+ };
1102
+ }
1103
+ function mergeReadinessProgress(previous, result, plan) {
1104
+ const identity = readinessPlanIdentity(plan);
1105
+ const fingerprint = result.planFingerprint ?? identity.fingerprint;
1106
+ const successfulKeys = new Set(previous?.planFingerprint === fingerprint ? previous.successfulRouteKeys : []);
1107
+ const verifiedRefs = new Set(previous?.planFingerprint === fingerprint ? previous.verifiedModelRefs : []);
1108
+ const reported = result.routes ?? (result.ok
1109
+ ? identity.routes.map((route) => ({ ...route, status: "verified" }))
1110
+ : []);
1111
+ for (const route of reported) {
1112
+ if (route.status === "verified" || route.status === "skipped_verified") {
1113
+ successfulKeys.add(route.key);
1114
+ verifiedRefs.add(route.model);
1115
+ }
1116
+ }
1117
+ const currentRefs = new Set(identity.routes.map((route) => route.model));
1118
+ return {
1119
+ planFingerprint: fingerprint,
1120
+ successfulRouteKeys: [...successfulKeys],
1121
+ verifiedModelRefs: [...verifiedRefs].filter((ref) => currentRefs.has(ref)),
1122
+ };
1123
+ }
1124
+ async function runGuidedSandboxPreflight(installedEarlier) {
1125
+ process.stdout.write("\n" + ui.heading("Sandbox preflight"));
1126
+ return await withScopedPreflightCancellation(async (signal) => {
1127
+ try {
1128
+ process.stdout.write(ui.style.dim(installedEarlier
1129
+ ? "Rechecking the pinned managed SRT copy and its functional enforcement postcondition.\n"
1130
+ : "Installing the pinned managed SRT copy in the private user cache, then running the functional enforcement check.\n"));
1131
+ const setup = await setupManagedSrt({ signal, verify: true });
1132
+ if (setup.status.source !== "managed" || setup.status.state !== "ready" || setup.check === undefined) {
1133
+ throw new Error("Managed SRT setup did not return a ready managed functional-check result.");
1134
+ }
1135
+ process.stdout.write(`${ui.badge("ok")}Managed SRT ${setup.repaired ? "repaired" : setup.installed ? "installed" : "verified"}; functional postcondition passed.\n`);
1136
+ return { ok: true };
1137
+ }
1138
+ catch (error) {
1139
+ if (signal.aborted || isAbortLike(error)) {
1140
+ process.stderr.write(ui.errorLine("Preflight was interrupted."));
1141
+ return {
1142
+ ok: false,
1143
+ kind: "cancelled",
1144
+ message: "Sandbox preflight was interrupted. No agent files were written.",
1145
+ interrupted: true,
1146
+ };
1147
+ }
1148
+ return {
1149
+ ok: false,
1150
+ kind: "provider_failed",
1151
+ message: `[sandbox_preflight_failed] ${reasonOf(error)} No agent files were written; retry setup or edit the sandbox choice.`,
1152
+ };
1153
+ }
1154
+ });
1155
+ }
1156
+ async function runReadinessProbeWithSpinner(options) {
1157
+ const routes = readinessRoutesForDisplay(options.plan);
1158
+ process.stdout.write("\n" + ui.heading("Runtime readiness"));
1159
+ routes.forEach((route, index) => {
1160
+ const timeoutMs = readinessProbeTimeoutMs(parseMonoRuntimeModelReference(route.model));
1161
+ process.stdout.write(` Route ${index + 1}/${routes.length}: ${route.model} ` +
1162
+ ui.style.dim(`(effort: ${route.effort ?? "provider-default"}; up to ${Math.ceil(timeoutMs / 1_000)}s)`) +
1163
+ "\n");
1164
+ });
1165
+ process.stdout.write(ui.style.dim("Running real no-tool checks sequentially. Press Esc or Ctrl-C once to interrupt safely.\n"));
1166
+ return await withScopedPreflightCancellation(async (signal) => {
1167
+ try {
1168
+ const result = await runAllRouteReadinessProbe({
1169
+ plan: options.plan,
1170
+ hostEnv: options.effectiveEnv,
1171
+ secretValues: selectedSecretValues(options.plan, options.effectiveEnv),
1172
+ resolvedPiAuthPath: options.resolvedPiAuthPath,
1173
+ abortSignal: signal,
1174
+ ...(options.resume === undefined ? {} : { resume: options.resume }),
1175
+ onRouteStart: (route) => {
1176
+ process.stdout.write(` Checking route ${route.index + 1}/${route.total}: ${route.model} ` +
1177
+ ui.style.dim(`(effort: ${route.effort ?? "provider-default"})`) +
1178
+ "\n");
1179
+ },
1180
+ onRouteComplete: (route) => {
1181
+ const ok = route.status === "verified" || route.status === "skipped_verified";
1182
+ process.stdout.write(`${ok ? ui.badge("ok") : route.status === "interrupted" ? ui.badge("waiting") : ui.badge("error")}` +
1183
+ `Route ${route.index + 1}/${routes.length} ${route.status.replaceAll("_", " ")}\n`);
1184
+ },
1185
+ });
1186
+ process.stdout.write(ui.heading("Readiness summary"));
1187
+ printReadinessRouteSummary(result, routes);
1188
+ return result;
1189
+ }
1190
+ catch (error) {
1191
+ if (signal.aborted || isAbortLike(error)) {
1192
+ process.stderr.write(ui.errorLine("Preflight was interrupted."));
1193
+ return {
1194
+ ok: false,
1195
+ kind: "cancelled",
1196
+ message: "Preflight was interrupted before the current route completed.",
1197
+ interrupted: true,
1198
+ };
1199
+ }
1200
+ process.stderr.write(ui.errorLine("[readiness_probe_failed] Runtime readiness could not run."));
1201
+ return {
1202
+ ok: false,
1203
+ kind: "probe_failed",
1204
+ message: "Runtime readiness could not run. Review provider authentication and retry.",
1205
+ };
1206
+ }
1207
+ });
1208
+ }
1209
+ function readinessRoutesForDisplay(plan) {
1210
+ const runtime = (plan.configJson.runtime ?? {});
1211
+ const primaryEffort = typeof runtime.effort === "string" ? runtime.effort : undefined;
1212
+ const routes = [];
1213
+ if (typeof runtime.model === "string") {
1214
+ routes.push({ model: runtime.model, ...(primaryEffort === undefined ? {} : { effort: primaryEffort }) });
1215
+ }
1216
+ if (Array.isArray(runtime.fallbacks) && runtime.fallbacks.length > 0) {
1217
+ for (const raw of runtime.fallbacks) {
1218
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
1219
+ continue;
1220
+ const entry = raw;
1221
+ if (typeof entry.model !== "string")
1222
+ continue;
1223
+ routes.push({
1224
+ model: entry.model,
1225
+ ...(typeof entry.effort === "string" ? { effort: entry.effort } : {}),
1226
+ });
1227
+ }
1228
+ }
1229
+ else if (Array.isArray(runtime.fallbackModels)) {
1230
+ for (const model of runtime.fallbackModels) {
1231
+ if (typeof model === "string") {
1232
+ routes.push({ model, ...(primaryEffort === undefined ? {} : { effort: primaryEffort }) });
1233
+ }
1234
+ }
1235
+ }
1236
+ return routes;
1237
+ }
1238
+ function printReadinessRouteSummary(result, planned) {
1239
+ const reported = result.routes ?? planned.map((route, index) => ({
1240
+ key: `${index}:${route.model}`,
1241
+ index,
1242
+ ...route,
1243
+ status: result.ok ? "verified" : result.kind === "cancelled" ? "interrupted" : "failed",
1244
+ ...(!result.ok ? { kind: result.kind, message: result.message } : {}),
1245
+ }));
1246
+ for (const route of reported) {
1247
+ const badge = route.status === "verified" || route.status === "skipped_verified"
1248
+ ? ui.badge("ok")
1249
+ : route.status === "interrupted"
1250
+ ? ui.badge("waiting")
1251
+ : ui.badge("error");
1252
+ const state = route.status === "skipped_verified" ? "verified earlier" : route.status.replaceAll("_", " ");
1253
+ process.stdout.write(`${badge}Route ${route.index + 1}/${planned.length}: ${route.model} ` +
1254
+ ui.style.dim(`(effort: ${route.effort ?? "provider-default"})`) +
1255
+ ` — ${state}${route.message === undefined ? "" : `: ${route.message}`}\n`);
1256
+ }
1257
+ if (!result.ok && result.interrupted === true) {
1258
+ process.stderr.write(ui.errorLine("Preflight was interrupted."));
1259
+ }
1260
+ }
1261
+ async function withScopedPreflightCancellation(task, options = {}) {
1262
+ const controller = new AbortController();
1263
+ let interruptCount = 0;
1264
+ const interrupt = () => {
1265
+ interruptCount += 1;
1266
+ controller.abort();
1267
+ if (interruptCount > 1)
1268
+ process.exitCode = 130;
1269
+ };
1270
+ const onKeypress = (_value, key) => {
1271
+ if (key?.name === "escape" || (key?.ctrl === true && key.name === "c"))
1272
+ interrupt();
1273
+ };
1274
+ process.on("SIGINT", interrupt);
1275
+ const restoreKeypress = options.keypress === false
1276
+ ? () => undefined
1277
+ : attachScopedKeypress(onKeypress);
1278
+ try {
1279
+ return await task(controller.signal);
1280
+ }
1281
+ finally {
1282
+ process.off("SIGINT", interrupt);
1283
+ restoreKeypress();
1284
+ }
1285
+ }
1286
+ function focusedConfigurationRepairStep(sectionIds) {
1287
+ const mapped = new Set();
1288
+ for (const id of sectionIds) {
1289
+ if (id === "agent")
1290
+ mapped.add(0);
1291
+ else if (id === "runtime" || id === "credentials")
1292
+ mapped.add(1);
1293
+ else if (id === "memory" || id.startsWith("memory:"))
1294
+ mapped.add(3);
1295
+ else if (id === "context" || id.startsWith("channel:"))
1296
+ mapped.add(4);
1297
+ else if (id === "tools")
1298
+ mapped.add(5);
1299
+ else if (id === "sandbox")
1300
+ mapped.add(6);
1301
+ else if (id === "observability")
1302
+ mapped.add(7);
1303
+ }
1304
+ return mapped.size === 1 ? [...mapped][0] : undefined;
1305
+ }
1306
+ function configurationRecoveryEditLabel(step) {
1307
+ switch (step) {
1308
+ case 0: return "Edit agent name";
1309
+ case 1: return "Edit model routes";
1310
+ case 3: return "Edit memory";
1311
+ case 4: return "Edit capability details";
1312
+ case 5: return "Edit tools";
1313
+ case 6: return "Edit route safety and sandbox";
1314
+ case 7: return "Edit observability";
1315
+ default: return "Edit setup choices";
1316
+ }
1317
+ }
1318
+ async function selectSandboxPreflightRecovery() {
1319
+ const recovery = await p.select({
1320
+ message: "Sandbox preflight did not pass. How would you like to recover?",
1321
+ initialValue: "retry",
1322
+ options: [
1323
+ { value: "retry", label: "Retry sandbox setup and check" },
1324
+ { value: "edit", label: "Change safety or other choices" },
1325
+ { value: "cancel", label: "Cancel without writing" },
1326
+ ],
1327
+ });
1328
+ return p.isCancel(recovery) ? "cancel" : recovery;
1329
+ }
1330
+ async function selectInterruptedFirstRunRecovery() {
1331
+ const recovery = await p.select({
1332
+ message: "Preflight was interrupted. What would you like to do?",
1333
+ initialValue: "resume",
1334
+ options: [
1335
+ { value: "resume", label: "Resume preflight", hint: "keeps successful auth, SRT setup, and route checks" },
1336
+ { value: "restart", label: "Restart all checks", hint: "keeps successful auth and SRT installation" },
1337
+ { value: "edit", label: "Edit setup choices" },
1338
+ { value: "cancel", label: "Cancel without writing" },
1339
+ ],
1340
+ });
1341
+ return p.isCancel(recovery) ? "cancel" : recovery;
1342
+ }
1343
+ async function selectFirstRunRecovery(failure, configurationRepairStep, invalidPlanStage) {
1344
+ const sharedTail = [
1345
+ { value: "save", label: "Save incomplete", hint: "does not call the agent ready or start it" },
1346
+ { value: "cancel", label: "Cancel without writing" },
1347
+ ];
1348
+ const providerSetupFailed = failure.kind === "provider_failed"
1349
+ && /^Provider setup (?:did not complete|still needs attention)\./u.test(failure.message);
1350
+ const message = failure.kind === "invalid_plan"
1351
+ ? invalidPlanStage === "final_readiness"
1352
+ ? "Final readiness validation did not pass. What would you like to do?"
1353
+ : "Configuration preflight did not pass. What would you like to do?"
1354
+ : providerSetupFailed
1355
+ ? "Provider setup did not pass. What would you like to do?"
1356
+ : "Runtime readiness did not pass. What would you like to do?";
1357
+ const options = failure.kind === "invalid_plan"
1358
+ ? [
1359
+ { value: "edit", label: configurationRecoveryEditLabel(configurationRepairStep) },
1360
+ {
1361
+ value: "retry",
1362
+ label: invalidPlanStage === "final_readiness"
1363
+ ? "Retry final readiness validation"
1364
+ : "Retry configuration preflight",
1365
+ },
1366
+ ...sharedTail,
1367
+ ]
1368
+ : providerSetupFailed
1369
+ ? [
1370
+ { value: "auth", label: "Repair authentication" },
1371
+ { value: "retry", label: "Retry provider setup" },
1372
+ { value: "model", label: "Edit model routes" },
1373
+ ...sharedTail,
1374
+ ]
1375
+ : failure.kind === "provider_failed"
1376
+ ? [
1377
+ { value: "retry", label: "Retry failed route" },
1378
+ { value: "auth", label: "Repair authentication" },
1379
+ { value: "model", label: "Edit model routes" },
1380
+ ...sharedTail,
1381
+ ]
1382
+ : failure.kind === "unsupported_guided_probe"
1383
+ ? [
1384
+ { value: "model", label: "Edit model routes" },
1385
+ ...sharedTail,
1386
+ ]
1387
+ : [
1388
+ { value: "retry", label: "Retry runtime checks" },
1389
+ { value: "model", label: "Edit model routes" },
1390
+ ...sharedTail,
1391
+ ];
1392
+ const recovery = await p.select({
1393
+ message,
1394
+ initialValue: options[0]?.value ?? "cancel",
1395
+ options: [...options],
1396
+ });
1397
+ return p.isCancel(recovery) ? "cancel" : recovery;
1398
+ }
1399
+ async function promptProviderSetupSecrets(plan, existing, existingPersistence = {}, existingEnvironmentSecrets = {}) {
1400
+ const values = { ...existing };
1401
+ const persistenceByProvider = { ...existingPersistence };
1402
+ const environmentSecrets = { ...existingEnvironmentSecrets };
1403
+ for (const action of plan.actions) {
1404
+ if (!isProviderSetupPiApiKeyAction(action))
1405
+ continue;
1406
+ const reviewedPersistence = existingPersistence[action.provider];
1407
+ const persistence = reviewedPersistence ?? await p.select({
1408
+ message: `How should ${action.label} receive ${action.envVar}?`,
1409
+ initialValue: "secure-store",
1410
+ options: [
1411
+ { value: "secure-store", label: "Store securely in Pi auth.json", hint: "owner-only credential store" },
1412
+ { value: "environment", label: `Use environment variable ${action.envVar}`, hint: "save it to the agent's owner-only .env" },
1413
+ ],
1414
+ });
1415
+ if (p.isCancel(persistence))
1416
+ return undefined;
1417
+ if (persistence === "environment") {
1418
+ delete values[action.id];
1419
+ persistenceByProvider[action.provider] = "environment";
1420
+ const answer = await p.password({
1421
+ message: `Enter ${action.envVar} for the agent's owner-only .env`,
1422
+ validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
1423
+ clearOnError: true,
1424
+ });
1425
+ if (p.isCancel(answer))
1426
+ return undefined;
1427
+ environmentSecrets[action.envVar] = answer;
1428
+ continue;
1429
+ }
1430
+ persistenceByProvider[action.provider] = "secure-store";
1431
+ delete environmentSecrets[action.envVar];
1432
+ const answer = await p.password({
1433
+ message: `Enter ${action.label} (${action.envVar})`,
1434
+ validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
1435
+ clearOnError: true,
1436
+ });
1437
+ if (p.isCancel(answer))
1438
+ return undefined;
1439
+ values[action.id] = answer;
1440
+ }
1441
+ return { apiKeys: values, persistenceByProvider, environmentSecrets };
1442
+ }
1443
+ function environmentProviderApiKeys(plan, env) {
1444
+ const values = {};
1445
+ for (const action of plan.actions) {
1446
+ if (!isProviderSetupPiApiKeyAction(action) || action.persistence !== "environment")
1447
+ continue;
1448
+ const value = env[action.envVar];
1449
+ if (nonEmptyEnv(value))
1450
+ values[action.id] = value;
1451
+ }
1452
+ return values;
1453
+ }
1454
+ async function selectCodexAuthMode(initialValue) {
1455
+ const selected = await p.select({
1456
+ message: "How should Codex authenticate on this machine?",
1457
+ initialValue,
1458
+ options: [
1459
+ { value: "browser", label: "Browser login", hint: "opens a localhost callback server" },
1460
+ { value: "device", label: "Device-code login", hint: "recommended for remote or headless machines" },
1461
+ ],
1462
+ });
1463
+ return p.isCancel(selected) ? undefined : selected;
1464
+ }
1465
+ function configuredSecretNames(result, effectiveEnv) {
1466
+ const names = new Set();
1467
+ for (const secret of result.plan.secrets) {
1468
+ if (nonEmptyEnv(effectiveEnv[secret.envVar]))
1469
+ names.add(secret.envVar);
1470
+ }
1471
+ return names;
1472
+ }
1473
+ function printIncompleteSetup(reasons, configPath) {
1474
+ process.stderr.write(ui.hint("INCOMPLETE SETUP: no readiness claim was made and the agent was not started.\n"));
1475
+ for (const reason of reasons)
1476
+ process.stderr.write(ui.style.yellow(` - ${reason}\n`));
1477
+ process.stderr.write(ui.hint(`Review ${configPath}, run \`mono-agent validate\`, then retry the first turn.\n`));
1478
+ }
1479
+ export function shouldRunInitWizard(args, stdinIsTty, stdoutIsTty) {
1480
+ if (!stdinIsTty || !stdoutIsTty || args.command !== "init" || args.positionals.length > 0) {
1481
+ return false;
1482
+ }
1483
+ if (args.force || args.foreground || args.follow || args.all || args.dryRun || args.includeMemory) {
1484
+ return false;
1485
+ }
1486
+ // A bare parsed init has only these required/default keys. Treat every
1487
+ // optional key—current or future—as an overriding flag so the documented
1488
+ // "any flag is scaffold-only" contract cannot silently drift again.
1489
+ const bareKeys = new Set([
1490
+ "command",
1491
+ "positionals",
1492
+ "force",
1493
+ "foreground",
1494
+ "follow",
1495
+ "all",
1496
+ "dryRun",
1497
+ "includeMemory",
1498
+ ]);
1499
+ return Object.keys(args).every((key) => bareKeys.has(key));
1500
+ }
1501
+ export async function runProviderSetupBeforeInit(options) {
1502
+ const credentialStates = options.credentialStates !== undefined
1503
+ ? options.credentialStates
1504
+ : options.forceAuthentication === true || !options.auth || options.dryRun
1505
+ ? undefined
1506
+ : await detectProviderCredentialStates({
1507
+ modelRefs: options.modelRefs,
1508
+ cwd: options.cwd,
1509
+ ...(options.piAuthPath === undefined ? {} : { piAuthPath: options.piAuthPath }),
1510
+ ...(options.persistedEnv === undefined ? {} : { persistedEnv: options.persistedEnv }),
1511
+ ...(options.abortSignal === undefined ? {} : { abortSignal: options.abortSignal }),
1512
+ });
1513
+ const plan = planProviderSetup({
1514
+ ...options,
1515
+ ...(credentialStates === undefined ? {} : { credentialStates }),
1516
+ ...(options.codexAuthMode === undefined ? {} : { codexAuthMode: options.codexAuthMode }),
1517
+ ...(options.forceAuthentication === undefined ? {} : { forceAuthentication: options.forceAuthentication }),
1518
+ });
1519
+ if (plan.actions.length === 0) {
1520
+ return "skipped";
1521
+ }
1522
+ if (options.dryRun) {
1523
+ process.stdout.write("\n" + ui.heading("Provider setup"));
1524
+ process.stdout.write(ui.style.dim("Dry run - provider auth/preflight commands were not launched.\n"));
1525
+ printProviderSetupPlan(plan);
1526
+ return "skipped";
1527
+ }
1528
+ if (!options.auth) {
1529
+ return "skipped";
1530
+ }
1531
+ process.stdout.write("\n" + ui.heading("Provider setup"));
1532
+ printProviderSetupPlan(plan);
1533
+ if (options.abortSignal !== undefined) {
1534
+ process.stdout.write(ui.style.dim("Press Ctrl-C once to interrupt authentication safely.\n"));
1535
+ }
1536
+ const results = await (options.execute ?? ((setupPlan) => executeProviderSetupPlan(setupPlan, {
1537
+ ...(options.apiKeys === undefined ? {} : { apiKeys: options.apiKeys }),
1538
+ ...(options.abortSignal === undefined ? {} : { abortSignal: options.abortSignal }),
1539
+ })))(plan);
1540
+ const interrupted = options.abortSignal?.aborted === true;
1541
+ for (const result of results) {
1542
+ const badge = interrupted && result.status === "failed"
1543
+ ? ui.badge("waiting")
1544
+ : result.status === "ok"
1545
+ ? ui.badge("ok")
1546
+ : result.status === "skipped"
1547
+ ? ui.style.dim("- ")
1548
+ : ui.badge("error");
1549
+ process.stdout.write(`${badge}${result.action.label}: ${result.detail}\n`);
1550
+ }
1551
+ if (results.some((result) => result.failureKind !== undefined)) {
1552
+ 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."));
1553
+ return "fatal";
1554
+ }
1555
+ if (interrupted) {
1556
+ process.stderr.write(ui.errorLine("Provider setup was interrupted."));
1557
+ return "interrupted";
1558
+ }
1559
+ if (results.some((result) => result.status === "failed")) {
1560
+ process.stderr.write(ui.errorLine("Provider setup failed; init stopped before writing files."));
1561
+ return "failed";
1562
+ }
1563
+ return "ok";
1564
+ }
1565
+ export async function runAuth(args) {
1566
+ const [subcommand, provider, ...extra] = args.positionals;
1567
+ if (subcommand !== "login" || provider === undefined || extra.length > 0) {
1568
+ 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>]."));
1569
+ return 2;
1570
+ }
1571
+ const cwd = process.cwd();
1572
+ const configPath = await canonicalBackgroundConfigPath(cwd, args.configPath);
1573
+ const directCodex = provider === "codex";
1574
+ if (directCodex && args.piAuthPath !== undefined) {
1575
+ process.stderr.write(ui.errorLine("--pi-auth-path does not apply to direct Codex login."));
1576
+ return 2;
1577
+ }
1578
+ let configuredPiAuthPath;
1579
+ try {
1580
+ configuredPiAuthPath = directCodex ? resolve(cwd, ".pi", "auth.json") : await resolvePiAuthPathForLogin({
1581
+ configPath,
1582
+ cwd,
1583
+ ...(process.env.MONO_AGENT_PI_AUTH_PATH === undefined
1584
+ ? {}
1585
+ : { envPath: process.env.MONO_AGENT_PI_AUTH_PATH }),
1586
+ ...(args.piAuthPath === undefined ? {} : { piAuthPath: args.piAuthPath }),
1587
+ });
1588
+ }
1589
+ catch (error) {
1590
+ process.stderr.write(ui.errorLine(`Cannot resolve the Pi auth path from ${configPath}: ${error instanceof Error ? error.message : String(error)}`));
1591
+ return 1;
1592
+ }
1593
+ const plan = planProviderSetup({
1594
+ modelRefs: [directCodex ? "codex:gpt-5.6-terra" : `pi:${provider}:credential-setup`],
1595
+ cwd,
1596
+ piAuthPath: configuredPiAuthPath,
1597
+ forceAuthentication: true,
1598
+ ...(args.codexAuthMode === undefined ? {} : { codexAuthMode: args.codexAuthMode }),
1599
+ });
1600
+ if (plan.actions.length === 0) {
1601
+ process.stderr.write(ui.errorLine(`Provider \`${provider}\` has no interactive auth method in the bundled ${directCodex ? "Codex" : "Pi"} provider catalog.`));
1602
+ return 2;
1603
+ }
1604
+ const apiKeyActions = plan.actions.filter(isProviderSetupPiApiKeyAction);
1605
+ if (args.apiKeyStdin === true && apiKeyActions.length !== 1) {
1606
+ process.stderr.write(ui.errorLine("--api-key-stdin is only supported when the selected provider has one bundled API-key login action."));
1607
+ return 2;
1608
+ }
1609
+ process.stdout.write("\n" + ui.heading(directCodex ? "Codex authentication" : "Pi authentication"));
1610
+ printProviderSetupPlan(plan);
1611
+ let apiKeys;
1612
+ const apiKeyAction = apiKeyActions[0];
1613
+ if (apiKeyAction !== undefined) {
1614
+ let apiKey;
1615
+ if (args.apiKeyStdin === true) {
1616
+ if (process.stdin.isTTY === true) {
1617
+ process.stderr.write(ui.errorLine("--api-key-stdin requires redirected standard input. Omit the flag to enter the key in a masked prompt."));
1618
+ return 2;
1619
+ }
1620
+ try {
1621
+ apiKey = await readApiKeyFromStdin(process.stdin);
1622
+ }
1623
+ catch {
1624
+ process.stderr.write(ui.errorLine(`Could not read a valid ${apiKeyAction.envVar} value from standard input; no credentials were written.`));
1625
+ return 1;
1626
+ }
1627
+ }
1628
+ else {
1629
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
1630
+ process.stderr.write(ui.errorLine(`Cannot securely prompt for ${apiKeyAction.envVar} without an interactive TTY. ` +
1631
+ `Run this command in a terminal, or pipe the value explicitly with --api-key-stdin; no credentials were written.`));
1632
+ return 1;
1633
+ }
1634
+ const answer = await p.password({
1635
+ message: `Enter ${apiKeyAction.label} (${apiKeyAction.envVar})`,
1636
+ validate: (value) => apiKeyInputProblem(value ?? ""),
1637
+ clearOnError: true,
1638
+ });
1639
+ if (p.isCancel(answer)) {
1640
+ process.stderr.write(ui.errorLine("Authentication was cancelled; no credentials were written."));
1641
+ return 130;
1642
+ }
1643
+ apiKey = answer.trim();
1644
+ }
1645
+ apiKeys = { [apiKeyAction.id]: apiKey };
1646
+ }
1647
+ process.stdout.write(ui.style.dim("Press Ctrl-C once to interrupt authentication safely.\n"));
1648
+ const execution = await withScopedPreflightCancellation(async (abortSignal) => ({
1649
+ results: await executeProviderSetupPlan(plan, {
1650
+ ...(apiKeys === undefined ? {} : { apiKeys }),
1651
+ abortSignal,
1652
+ }),
1653
+ interrupted: abortSignal.aborted,
1654
+ }), { keypress: false });
1655
+ const { results } = execution;
1656
+ for (const result of results) {
1657
+ const badge = execution.interrupted && result.status === "failed"
1658
+ ? ui.badge("waiting")
1659
+ : result.status === "ok"
1660
+ ? ui.badge("ok")
1661
+ : ui.badge("error");
1662
+ process.stdout.write(`${badge}${result.action.label}: ${result.detail}\n`);
1663
+ }
1664
+ if (results.some((result) => result.failureKind !== undefined)) {
1665
+ 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."));
1666
+ return 130;
1667
+ }
1668
+ if (execution.interrupted) {
1669
+ process.stderr.write(ui.errorLine("Authentication was interrupted; temporary credentials were cleaned up."));
1670
+ return 130;
1671
+ }
1672
+ return results.every((result) => result.status === "ok") ? 0 : 1;
1673
+ }
1674
+ const MAX_STANDALONE_API_KEY_BYTES = 65_536;
1675
+ function apiKeyInputProblem(value) {
1676
+ const normalized = value.trim();
1677
+ if (normalized.length === 0)
1678
+ return "API key is required.";
1679
+ if (normalized.includes("\0") || /[\r\n]/u.test(normalized))
1680
+ return "API key must be a single non-empty line.";
1681
+ if (Buffer.byteLength(normalized, "utf8") > MAX_STANDALONE_API_KEY_BYTES)
1682
+ return "API key is too large.";
1683
+ return undefined;
1684
+ }
1685
+ /**
1686
+ * Read one explicitly redirected API key without consulting ambient provider
1687
+ * environment variables. A single trailing line ending from `echo` is accepted;
1688
+ * embedded newlines, NUL bytes, empty input, and unbounded input fail closed.
1689
+ */
1690
+ export async function readApiKeyFromStdin(input) {
1691
+ const chunks = [];
1692
+ let size = 0;
1693
+ for await (const chunk of input) {
1694
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
1695
+ size += bytes.byteLength;
1696
+ if (size > MAX_STANDALONE_API_KEY_BYTES + 2)
1697
+ throw new Error("API key input is too large.");
1698
+ chunks.push(bytes);
1699
+ }
1700
+ const value = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/u, "");
1701
+ const problem = apiKeyInputProblem(value);
1702
+ if (problem !== undefined)
1703
+ throw new Error(problem);
1704
+ return value.trim();
1705
+ }
1706
+ export async function resolvePiAuthPathForLogin(options) {
1707
+ // A missing config is represented by readMonoAgentConfigJson as `missing`; a
1708
+ // malformed or unreadable config throws and must remain visible to operators.
1709
+ const result = await readMonoAgentConfigJson(options.configPath);
1710
+ const configured = result.missing ? undefined : result.json.providers?.piAuthPath;
1711
+ return resolveEffectivePiAuthPath({
1712
+ cwd: options.cwd ?? dirname(resolve(options.configPath)),
1713
+ ...(nonEmptyEnv(options.piAuthPath) ? { explicitPath: options.piAuthPath } : {}),
1714
+ ...(nonEmptyEnv(options.envPath) ? { envPath: options.envPath } : {}),
1715
+ ...(nonEmptyEnv(configured) ? { configPath: configured } : {}),
1716
+ });
1717
+ }
1718
+ function printProviderSetupPlan(plan) {
1719
+ for (const action of plan.actions) {
1720
+ process.stdout.write(` ${action.label}: ${providerSetupActionCommandLine(action)} ${ui.style.dim(`(cwd: ${action.cwd})`)}\n`);
1721
+ }
1722
+ }
1723
+ /**
1724
+ * Resolve the preset id for `init`: `--preset` wins, `--recipe` is a deprecated
1725
+ * alias removed in v2.0.0 and mapped to the preset that replaced it. Returns
1726
+ * the preset id to compose from, `undefined` for the default scaffold, or
1727
+ * `"unknown"` after emitting the
1728
+ * error/hint (an unknown preset, or a retired recipe with no replacement).
1729
+ */
1730
+ function resolveInitPresetId(args) {
1731
+ if (args.preset !== undefined) {
1732
+ const preset = findPreset(args.preset);
1733
+ if (preset === undefined) {
1734
+ process.stderr.write(ui.errorLine(`Unknown preset \`${args.preset}\`.`));
1735
+ process.stderr.write(ui.hint(`Available presets: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
1736
+ return "unknown";
1737
+ }
1738
+ return preset.id;
1739
+ }
1740
+ if (args.recipe !== undefined) {
1741
+ const preset = RECIPE_TO_PRESET.get(args.recipe);
1742
+ if (preset === undefined) {
1743
+ process.stderr.write(ui.errorLine(`Recipe \`${args.recipe}\` was retired; the wizard composes capabilities directly.`));
1744
+ process.stderr.write(ui.hint(`Use \`mono-agent init --preset <id>\` (${presetIds().join(", ")}), or \`mono-agent init\` for the step-by-step wizard.`));
1745
+ process.stderr.write(ui.hint("See the mono-agent-composer skill and docs/playbooks for capability recipes."));
1746
+ return "unknown";
1747
+ }
1748
+ process.stderr.write(ui.hint(`Using replacement preset ${preset.id}. See \`mono-agent presets list\`.`));
1749
+ return preset.id;
1750
+ }
1751
+ return undefined;
1752
+ }
1753
+ function resolveWithChannels(args) {
1754
+ if (args.withChannels === undefined) {
1755
+ return undefined;
1756
+ }
1757
+ const invalid = args.withChannels.filter((channel) => !isWithChannel(channel));
1758
+ if (invalid.length > 0) {
1759
+ process.stderr.write(ui.errorLine(`Unknown --with channel(s): ${invalid.join(", ")}.`));
1760
+ process.stderr.write(ui.hint("Valid channels: telegram, slack, webhook, openaiApi, cron."));
1761
+ return "invalid";
1762
+ }
1763
+ return args.withChannels.filter(isWithChannel);
1764
+ }
1765
+ /** Safe reporting rows: paths and outcomes only, never secret contents. */
1766
+ export function initChangeDisplayRows(result) {
1767
+ const labels = {
1768
+ created: "created",
1769
+ updated: "updated",
1770
+ unchanged: "kept",
1771
+ "planned-create": "would create",
1772
+ "planned-update": "would update",
1773
+ };
1774
+ return result.changes.map((change) => ({
1775
+ label: labels[change.kind],
1776
+ path: change.path,
1777
+ unchanged: change.kind === "unchanged",
1778
+ }));
1779
+ }
1780
+ /** Human-readable, value-free proof of where the wizard Role did (or did not) land. */
1781
+ export function identityRoleDisplayLine(identityRole) {
1782
+ switch (identityRole.status) {
1783
+ case "created":
1784
+ return `Role saved to ${identityRole.path} → ${identityRole.section}. ` +
1785
+ `Edit ${identityRole.path} → ${identityRole.section} later to change it.`;
1786
+ case "preserved":
1787
+ return `${identityRole.path} already existed and was preserved; the entered Role was not written. ` +
1788
+ `Add or edit ${identityRole.path} → ${identityRole.section} to set it.`;
1789
+ case "planned-create":
1790
+ return `Dry run: Role would be saved to ${identityRole.path} → ${identityRole.section}.`;
1791
+ }
1792
+ }
1793
+ export function secretChecklistDisplayRows(secrets, configured) {
1794
+ return secrets.map((secret) => ({
1795
+ envVar: secret.envVar,
1796
+ label: secret.label,
1797
+ description: secret.description,
1798
+ status: configured.has(secret.envVar) ? "configured" : secret.required ? "missing" : "optional",
1799
+ }));
1800
+ }
1801
+ function printInitResult(result) {
1802
+ if (result.dryRun) {
1803
+ process.stdout.write(ui.style.dim("Dry run — nothing was written.\n"));
1804
+ }
1805
+ for (const row of initChangeDisplayRows(result)) {
1806
+ const prefix = row.unchanged ? " " : ui.badge("ok");
1807
+ const rendered = row.unchanged
1808
+ ? ui.style.dim(row.label.padEnd(12))
1809
+ : ui.style.green(row.label.padEnd(12));
1810
+ // Sensitive files are safe to identify by path; their contents are never
1811
+ // included in this result or printed here.
1812
+ process.stdout.write(`${prefix}${rendered} ${row.path}\n`);
1813
+ }
1814
+ process.stdout.write(`\n${identityRoleDisplayLine(result.identityRole)}\n`);
1815
+ if (result.knowledgeFiles.length > 0) {
1816
+ process.stdout.write(`\nIdentity references existing knowledge: ${ui.style.cyan(result.knowledgeFiles.join(", "))}\n`);
1817
+ }
1818
+ // Internal `provider:*` modules are auto-added for local models; they are an
1819
+ // implementation detail, not a user-facing capability, so exclude them here.
1820
+ const capabilities = result.plan.selectedModules.filter((module) => module.kind !== "provider");
1821
+ if (capabilities.length > 0) {
1822
+ process.stdout.write("\n" + ui.heading("Capabilities"));
1823
+ for (const module of capabilities) {
1824
+ process.stdout.write(` ${ui.style.cyan(module.title)} ${ui.style.dim(`(risk: ${riskColor(module.riskLevel)})`)}\n`);
1825
+ }
1826
+ }
1827
+ if (result.secretPersistence.status === "persisted") {
1828
+ process.stdout.write("\n" + ui.style.dim(result.secretPersistence.changed
1829
+ ? "Required secrets were securely merged into .env (mode 0600).\n"
1830
+ : "Required secrets were already securely configured in .env.\n"));
1831
+ }
1832
+ else if (result.secretPersistence.status === "planned") {
1833
+ process.stdout.write("\n" + ui.style.dim("Dry run: required secrets would be securely merged into .env.\n"));
1834
+ }
1835
+ else if (result.secretPersistence.status === "refused") {
1836
+ process.stderr.write(ui.hint(`Automatic secret persistence was refused${result.secretPersistence.reason === undefined ? "" : ` (${result.secretPersistence.reason})`}. No secret value was written.\n` +
1837
+ (result.secretPersistence.detail === undefined ? "" : `${result.secretPersistence.detail}\n`)));
1838
+ }
1839
+ else if (result.plan.envExample !== undefined) {
1840
+ 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"));
1841
+ }
1842
+ }
1843
+ function printSecretsChecklist(secrets, configured = new Set()) {
1844
+ process.stdout.write("\n" + ui.heading("Secrets checklist"));
1845
+ if (secrets.length === 0) {
1846
+ process.stdout.write(ui.style.dim("No secrets required by the selected capabilities.\n"));
1847
+ return;
1848
+ }
1849
+ process.stdout.write(ui.style.dim("Secret values are never written to config JSON and are never printed.\n"));
1850
+ for (const secret of secretChecklistDisplayRows(secrets, configured)) {
1851
+ const status = secret.status === "configured"
1852
+ ? ui.style.green(secret.status)
1853
+ : ui.style.yellow(secret.status);
1854
+ process.stdout.write(` ${ui.style.bold(secret.envVar)} ${ui.style.dim(`- ${secret.label}: ${secret.description}`)} ${status}\n`);
1855
+ }
1856
+ }
1857
+ function printNextSteps(configPath) {
1858
+ const startCommand = process.platform === "darwin" ? "mono-agent start" : "mono-agent start --foreground";
1859
+ const tuiCommand = process.platform === "darwin"
1860
+ ? `mono-agent tui --configure ${ui.style.dim("(after the agent reports ready)")}`
1861
+ : `mono-agent tui ${ui.style.dim("(ordinary chat after foreground startup; edit config files manually)")}`;
1862
+ process.stdout.write("\n" +
1863
+ ui.heading("Next steps") +
1864
+ ` ${ui.style.bold("1.")} Edit ${configPath} ${ui.style.dim("(model, channels, skills, memory, sandbox)")}\n` +
1865
+ ` ${ui.style.bold("2.")} mono-agent validate\n` +
1866
+ ` ${ui.style.bold("3.")} ${startCommand}\n` +
1867
+ ` ${ui.style.bold("4.")} ${tuiCommand}\n`);
1868
+ }
1869
+ function printUnsupportedGuidedInitHandoff(configPath, envFile) {
1870
+ const flags = guidedHandoffFlags(configPath, envFile);
1871
+ process.stdout.write("\n" +
1872
+ ui.heading("Manual start required") +
1873
+ ui.style.yellow("Automatic background start and configuration chat require macOS launchd.\n") +
1874
+ ui.style.dim("The validated agent files were preserved, but no agent process was started and readiness is not claimed.\n") +
1875
+ ` ${ui.style.bold("Configure manually:")} edit ${configPath} and IDENTITY.md, then run mono-agent validate${flags}\n` +
1876
+ ` ${ui.style.bold("Terminal 1:")} mono-agent start --foreground${flags}\n` +
1877
+ ` ${ui.style.bold("Terminal 2:")} mono-agent tui${flags} ${ui.style.dim("(ordinary chat after startup completes)")}\n` +
1878
+ ui.style.dim("Conversational configuration requires the managed macOS background lifecycle.\n"));
1879
+ }
1880
+ function printUnexpectedGuidedBackgroundFailure(configPath, envFile, error) {
1881
+ const flags = guidedHandoffFlags(configPath, envFile);
1882
+ const paths = launchdPathsFor(deriveLaunchdLabel(configPath));
1883
+ process.stderr.write(ui.errorLine(`The background lifecycle failed unexpectedly: ${error instanceof Error ? error.message : String(error)}`));
1884
+ process.stderr.write(ui.style.dim("The validated agent files were preserved, but configuration chat was not opened. Retry or inspect with:\n"));
1885
+ process.stderr.write(` ${ui.style.gray("logs: ")} ${paths.stderrPath}\n` +
1886
+ ` ${paths.stdoutPath}\n` +
1887
+ ` ${ui.style.gray("retry: ")} mono-agent start${flags}\n` +
1888
+ ` ${ui.style.gray("status:")} mono-agent status${flags}\n` +
1889
+ ` ${ui.style.gray("follow:")} mono-agent logs${flags} --follow\n`);
1890
+ }
1891
+ function guidedHandoffFlags(configPath, envFile) {
1892
+ return ` --config ${shellCommandArgument(configPath)}`
1893
+ + (envFile === undefined ? "" : ` --env-file ${shellCommandArgument(envFile)}`);
1894
+ }
1895
+ function shellCommandArgument(value) {
1896
+ return /^[a-zA-Z0-9_./:@%+=,-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
1897
+ }
1898
+ async function pathExists(path) {
1899
+ try {
1900
+ await stat(path);
1901
+ return true;
1902
+ }
1903
+ catch {
1904
+ return false;
1905
+ }
1906
+ }
1907
+ function reasonOf(error) {
1908
+ return error instanceof Error ? error.message : String(error);
1909
+ }
1910
+ //# sourceMappingURL=cli-init-command.js.map