@ouro.bot/cli 0.1.0-alpha.77 → 0.1.0-alpha.771

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 (608) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -23
  3. package/RepairGuide.ouro/agent.json +5 -0
  4. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  5. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  6. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  7. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  8. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  9. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  12. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  13. package/assets/bluebubbles-host +264 -0
  14. package/changelog.json +4936 -16
  15. package/deploy/unraid/Dockerfile +48 -0
  16. package/deploy/unraid/README.txt +2483 -0
  17. package/deploy/unraid/audit-container-spec.sh +4 -0
  18. package/deploy/unraid/container-runtime.json +1 -0
  19. package/deploy/unraid/migrate-sanctuary-bundle.mjs +21 -0
  20. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  21. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  22. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  23. package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
  24. package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
  25. package/deploy/unraid/sanctuary-acceptance-adapter.sh +37 -0
  26. package/deploy/unraid/sanctuary-acceptance-contract.json +201 -0
  27. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  28. package/deploy/unraid/sanctuary-deployment-target.mjs +709 -0
  29. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  30. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +1819 -0
  31. package/deploy/unraid/sanctuary-unit16-run.sh +544 -0
  32. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  33. package/deploy/unraid/sanctuary.ouro/agent.json +24 -0
  34. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  35. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  36. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  37. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
  38. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
  39. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
  40. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +9 -0
  41. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +9 -0
  42. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +10 -0
  43. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  44. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +23 -0
  45. package/deploy/unraid/sanctuary.xml +27 -0
  46. package/dist/a2a/card.js +57 -0
  47. package/dist/a2a/client.js +180 -0
  48. package/dist/a2a/config.js +50 -0
  49. package/dist/a2a/delegation-stores.js +56 -0
  50. package/dist/a2a/did-resolution.js +93 -0
  51. package/dist/a2a/identity.js +170 -0
  52. package/dist/a2a/inbound-share.js +107 -0
  53. package/dist/a2a/mission-result-wire.js +196 -0
  54. package/dist/a2a/onboarding.js +115 -0
  55. package/dist/a2a/pin-store.js +132 -0
  56. package/dist/a2a/seen-ledger.js +120 -0
  57. package/dist/a2a/server.js +604 -0
  58. package/dist/a2a/task-store.js +69 -0
  59. package/dist/a2a/transport.js +45 -0
  60. package/dist/a2a/types.js +3 -0
  61. package/dist/arc/attention-types.js +8 -0
  62. package/dist/arc/cares.js +360 -0
  63. package/dist/arc/episodes.js +118 -0
  64. package/dist/arc/evolution.js +490 -0
  65. package/dist/arc/flight-recorder.js +1034 -0
  66. package/dist/arc/intentions.js +134 -0
  67. package/dist/arc/json-store.js +117 -0
  68. package/dist/arc/obligations.js +386 -0
  69. package/dist/arc/packets.js +336 -0
  70. package/dist/arc/presence.js +185 -0
  71. package/dist/arc/task-lifecycle.js +57 -0
  72. package/dist/commerce/store.js +755 -0
  73. package/dist/commerce/types.js +3 -0
  74. package/dist/heart/active-work.js +820 -29
  75. package/dist/heart/agent-entry.js +190 -3
  76. package/dist/heart/approval-files.js +113 -0
  77. package/dist/heart/approval-store.js +950 -0
  78. package/dist/heart/attachments/image-normalize.js +194 -0
  79. package/dist/heart/attachments/materialize.js +97 -0
  80. package/dist/heart/attachments/originals.js +88 -0
  81. package/dist/heart/attachments/render.js +29 -0
  82. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  83. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  84. package/dist/heart/attachments/sources/index.js +18 -0
  85. package/dist/heart/attachments/sources/telegram.js +71 -0
  86. package/dist/heart/attachments/store.js +132 -0
  87. package/dist/heart/attachments/types.js +93 -0
  88. package/dist/heart/auth/auth-flow.js +495 -0
  89. package/dist/heart/autonomy-budget.js +478 -0
  90. package/dist/heart/awaiting/await-alert.js +148 -0
  91. package/dist/heart/awaiting/await-expiry.js +143 -0
  92. package/dist/heart/awaiting/await-loader.js +91 -0
  93. package/dist/heart/awaiting/await-parser.js +149 -0
  94. package/dist/heart/awaiting/await-runtime-state.js +100 -0
  95. package/dist/heart/awaiting/await-scheduler.js +382 -0
  96. package/dist/heart/background-operations.js +281 -0
  97. package/dist/heart/bridges/manager.js +137 -17
  98. package/dist/heart/bridges/store.js +14 -2
  99. package/dist/heart/bundle-state.js +168 -0
  100. package/dist/heart/commitments.js +64 -18
  101. package/dist/heart/config-registry.js +331 -0
  102. package/dist/heart/config.js +128 -129
  103. package/dist/heart/context-loss-gauntlet.js +383 -0
  104. package/dist/heart/context-loss-sentinel.js +1157 -0
  105. package/dist/heart/core.js +2014 -469
  106. package/dist/heart/cross-chat-delivery.js +9 -22
  107. package/dist/heart/daemon/agent-config-check.js +451 -0
  108. package/dist/heart/daemon/agent-discovery.js +185 -3
  109. package/dist/heart/daemon/agent-service.js +542 -0
  110. package/dist/heart/daemon/agentic-repair.js +547 -0
  111. package/dist/heart/daemon/await-private-wake.js +59 -0
  112. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  113. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  114. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  115. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  116. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  117. package/dist/heart/daemon/cadence.js +180 -0
  118. package/dist/heart/daemon/cli-defaults.js +832 -0
  119. package/dist/heart/daemon/cli-desk.js +322 -0
  120. package/dist/heart/daemon/cli-exec.js +8692 -0
  121. package/dist/heart/daemon/cli-help.js +710 -0
  122. package/dist/heart/daemon/cli-parse.js +2714 -0
  123. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  124. package/dist/heart/daemon/cli-render.js +777 -0
  125. package/dist/heart/daemon/cli-types.js +8 -0
  126. package/dist/heart/daemon/connect-bay.js +323 -0
  127. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  128. package/dist/heart/daemon/container-healthcheck.js +96 -0
  129. package/dist/heart/daemon/container-runtime.js +97 -0
  130. package/dist/heart/daemon/container-spec-auditor-main.js +160 -0
  131. package/dist/heart/daemon/container-spec-auditor.js +176 -0
  132. package/dist/heart/daemon/daemon-bootstrap-startup.js +41 -0
  133. package/dist/heart/daemon/daemon-cli.js +29 -2181
  134. package/dist/heart/daemon/daemon-entry.js +944 -10
  135. package/dist/heart/daemon/daemon-health.js +186 -0
  136. package/dist/heart/daemon/daemon-rollup.js +57 -0
  137. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  138. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  139. package/dist/heart/daemon/daemon.js +1877 -88
  140. package/dist/heart/daemon/dns-workflow.js +394 -0
  141. package/dist/heart/daemon/doctor-types.js +8 -0
  142. package/dist/heart/daemon/doctor.js +1755 -0
  143. package/dist/heart/daemon/freshness.js +345 -0
  144. package/dist/heart/daemon/habit-private-wake.js +61 -0
  145. package/dist/heart/daemon/health-monitor.js +142 -11
  146. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  147. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  148. package/dist/heart/daemon/http-health-probe.js +80 -0
  149. package/dist/heart/daemon/human-command-screens.js +234 -0
  150. package/dist/heart/daemon/human-readiness.js +114 -0
  151. package/dist/heart/daemon/inner-status.js +78 -0
  152. package/dist/heart/daemon/interactive-repair.js +394 -0
  153. package/dist/heart/daemon/launchd.js +37 -8
  154. package/dist/heart/daemon/log-tailer.js +79 -10
  155. package/dist/heart/daemon/logs-prune.js +132 -0
  156. package/dist/heart/daemon/mcp-canary.js +524 -0
  157. package/dist/heart/daemon/message-router.js +45 -5
  158. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  159. package/dist/heart/daemon/os-cron-deps.js +135 -0
  160. package/dist/heart/daemon/os-cron.js +22 -14
  161. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  162. package/dist/heart/daemon/ouro-entry.js +3 -1
  163. package/dist/heart/daemon/plugin-cli.js +432 -0
  164. package/dist/heart/daemon/process-manager.js +624 -61
  165. package/dist/heart/daemon/provider-discovery.js +137 -0
  166. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  167. package/dist/heart/daemon/prunable-bundle.js +144 -0
  168. package/dist/heart/daemon/pulse.js +511 -0
  169. package/dist/heart/daemon/readiness-repair.js +365 -0
  170. package/dist/heart/daemon/run-hooks.js +2 -0
  171. package/dist/heart/daemon/runtime-logging.js +47 -14
  172. package/dist/heart/daemon/runtime-metadata.js +2 -30
  173. package/dist/heart/daemon/runtime-mode.js +13 -2
  174. package/dist/heart/daemon/safe-mode.js +161 -0
  175. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2468 -0
  176. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1624 -0
  177. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  178. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +976 -0
  179. package/dist/heart/daemon/sanctuary-bundle-migration.js +398 -0
  180. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  181. package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
  182. package/dist/heart/daemon/sense-manager.js +689 -48
  183. package/dist/heart/daemon/session-id-resolver.js +131 -0
  184. package/dist/heart/daemon/skill-management-installer.js +1 -1
  185. package/dist/heart/daemon/socket-client.js +180 -12
  186. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  187. package/dist/heart/daemon/startup-tui.js +338 -0
  188. package/dist/heart/daemon/supercronic-supervisor.js +282 -0
  189. package/dist/heart/daemon/task-scheduler.js +117 -39
  190. package/dist/heart/daemon/terminal-ui.js +499 -0
  191. package/dist/heart/daemon/thoughts.js +138 -54
  192. package/dist/heart/daemon/up-progress.js +366 -0
  193. package/dist/heart/daemon/vault-items.js +56 -0
  194. package/dist/heart/delegation.js +1 -4
  195. package/dist/heart/external-events/router.js +1187 -0
  196. package/dist/heart/habits/habit-cancel.js +810 -0
  197. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  198. package/dist/heart/habits/habit-migration.js +194 -0
  199. package/dist/heart/habits/habit-parser.js +278 -0
  200. package/dist/heart/habits/habit-runtime-state.js +93 -0
  201. package/dist/heart/habits/habit-scheduler.js +653 -0
  202. package/dist/heart/habits/habit-session-summary.js +318 -0
  203. package/dist/heart/habits/habit-session.js +636 -0
  204. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  205. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  206. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  207. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  208. package/dist/heart/identity.js +184 -59
  209. package/dist/heart/kept-notes.js +289 -0
  210. package/dist/heart/kicks.js +1 -1
  211. package/dist/heart/machine-identity.js +161 -0
  212. package/dist/heart/mail-import-discovery.js +353 -0
  213. package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
  214. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  215. package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
  216. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  217. package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
  218. package/dist/heart/mailbox/mailbox-http.js +99 -0
  219. package/dist/heart/mailbox/mailbox-read.js +39 -0
  220. package/dist/heart/mailbox/mailbox-types.js +27 -0
  221. package/dist/heart/mailbox/mailbox-view.js +197 -0
  222. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  223. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  224. package/dist/heart/mailbox/readers/mail.js +375 -0
  225. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  226. package/dist/heart/mailbox/readers/sessions.js +232 -0
  227. package/dist/heart/mailbox/readers/shared.js +111 -0
  228. package/dist/heart/mcp/mcp-server.js +696 -0
  229. package/dist/heart/migrate-config.js +100 -0
  230. package/dist/heart/model-capabilities.js +11 -0
  231. package/dist/heart/orientation-frame.js +345 -0
  232. package/dist/heart/platform.js +81 -0
  233. package/dist/heart/primitives.js +1 -1
  234. package/dist/heart/private-runtime/decision-renderer.js +158 -0
  235. package/dist/heart/private-runtime/index.js +18 -0
  236. package/dist/heart/private-runtime/ledger.js +221 -0
  237. package/dist/heart/private-runtime/policy.js +228 -0
  238. package/dist/heart/private-runtime/types.js +2 -0
  239. package/dist/heart/progress-story.js +1 -1
  240. package/dist/heart/provider-attempt.js +137 -0
  241. package/dist/heart/provider-binding-resolver.js +273 -0
  242. package/dist/heart/provider-credentials.js +449 -0
  243. package/dist/heart/provider-failover.js +311 -0
  244. package/dist/heart/provider-models.js +96 -0
  245. package/dist/heart/provider-ping.js +265 -0
  246. package/dist/heart/provider-readiness-cache.js +119 -0
  247. package/dist/heart/provider-visibility.js +188 -0
  248. package/dist/heart/providers/anthropic-token.js +131 -0
  249. package/dist/heart/providers/anthropic.js +173 -60
  250. package/dist/heart/providers/azure.js +23 -11
  251. package/dist/heart/providers/error-classification.js +127 -0
  252. package/dist/heart/providers/github-copilot.js +34 -38
  253. package/dist/heart/providers/minimax-vlm.js +189 -0
  254. package/dist/heart/providers/minimax.js +26 -8
  255. package/dist/heart/providers/openai-codex-token.js +349 -0
  256. package/dist/heart/providers/openai-codex.js +55 -40
  257. package/dist/heart/providers/openai-compatible.js +177 -0
  258. package/dist/heart/run-ledger.js +247 -0
  259. package/dist/heart/runtime-capability-check.js +170 -0
  260. package/dist/heart/runtime-credentials.js +607 -0
  261. package/dist/heart/runtime-cwd.js +87 -0
  262. package/dist/heart/sense-truth.js +22 -4
  263. package/dist/heart/session-activity.js +90 -24
  264. package/dist/heart/session-events.js +1318 -0
  265. package/dist/heart/session-playback-cli-main.js +5 -0
  266. package/dist/heart/session-playback-cli.js +36 -0
  267. package/dist/heart/session-playback.js +231 -0
  268. package/dist/heart/session-stats-cli-main.js +5 -0
  269. package/dist/heart/session-stats.js +182 -0
  270. package/dist/heart/session-transcript.js +133 -0
  271. package/dist/heart/start-of-turn-packet.js +372 -0
  272. package/dist/heart/steward-policy.js +302 -0
  273. package/dist/heart/streaming.js +604 -207
  274. package/dist/heart/structured-output.js +196 -0
  275. package/dist/heart/sync-classification.js +176 -0
  276. package/dist/heart/sync.js +449 -0
  277. package/dist/heart/target-resolution.js +11 -7
  278. package/dist/heart/tempo.js +93 -0
  279. package/dist/heart/temporal-view.js +41 -0
  280. package/dist/heart/timeouts.js +101 -0
  281. package/dist/heart/tool-activity-callbacks.js +59 -0
  282. package/dist/heart/tool-approval.js +342 -0
  283. package/dist/heart/tool-description.js +155 -0
  284. package/dist/heart/tool-friction.js +55 -0
  285. package/dist/heart/tool-loop.js +200 -0
  286. package/dist/heart/turn-context.js +481 -0
  287. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  288. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  289. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  290. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  291. package/dist/heart/versioning/ouro-version-manager.js +409 -0
  292. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  293. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  294. package/dist/heart/versioning/update-hooks.js +154 -0
  295. package/dist/heart/versioning/version-intent.js +114 -0
  296. package/dist/heart/work-card.js +380 -0
  297. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
  298. package/dist/mailbox-ui/assets/index-J8zDwHmE.js +1 -0
  299. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  300. package/dist/mailbox-ui/index.html +16 -0
  301. package/dist/mailroom/attention.js +167 -0
  302. package/dist/mailroom/autonomy.js +209 -0
  303. package/dist/mailroom/blob-store.js +889 -0
  304. package/dist/mailroom/body-cache.js +61 -0
  305. package/dist/mailroom/cache-sync-cli.js +58 -0
  306. package/dist/mailroom/core.js +803 -0
  307. package/dist/mailroom/entry.js +160 -0
  308. package/dist/mailroom/file-store.js +568 -0
  309. package/dist/mailroom/hosted-cache-sync.js +384 -0
  310. package/dist/mailroom/mbox-import.js +393 -0
  311. package/dist/mailroom/migration.js +164 -0
  312. package/dist/mailroom/outbound.js +380 -0
  313. package/dist/mailroom/policy.js +263 -0
  314. package/dist/mailroom/reader.js +292 -0
  315. package/dist/mailroom/search-cache.js +513 -0
  316. package/dist/mailroom/search-relevance.js +319 -0
  317. package/dist/mailroom/smtp-ingress.js +176 -0
  318. package/dist/mailroom/source-state.js +176 -0
  319. package/dist/mailroom/thread.js +109 -0
  320. package/dist/mailroom/travel-extract.js +89 -0
  321. package/dist/mind/bundle-manifest.js +24 -2
  322. package/dist/mind/context.js +278 -111
  323. package/dist/mind/desk-section.js +362 -0
  324. package/dist/mind/diary-integrity.js +60 -0
  325. package/dist/mind/{memory.js → diary.js} +68 -77
  326. package/dist/mind/embedding-provider.js +60 -0
  327. package/dist/mind/file-state.js +179 -0
  328. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  329. package/dist/mind/obligation-steering.js +227 -0
  330. package/dist/mind/pending.js +34 -8
  331. package/dist/mind/prompt-budget.js +479 -0
  332. package/dist/mind/prompt-refresh.js +3 -2
  333. package/dist/mind/prompt.js +1058 -187
  334. package/dist/mind/provenance-trust.js +26 -0
  335. package/dist/mind/record-paths.js +312 -0
  336. package/dist/mind/scrutiny.js +173 -0
  337. package/dist/mind/session-transaction.js +415 -0
  338. package/dist/nerves/cli-logging.js +7 -1
  339. package/dist/nerves/coverage/audit-rules.js +15 -6
  340. package/dist/nerves/coverage/audit.js +28 -2
  341. package/dist/nerves/coverage/cli.js +1 -1
  342. package/dist/nerves/coverage/contract.js +5 -5
  343. package/dist/nerves/coverage/file-completeness.js +139 -5
  344. package/dist/nerves/coverage/run-artifacts.js +55 -35
  345. package/dist/nerves/coverage/source-scanner.js +4 -4
  346. package/dist/nerves/event-buffer.js +111 -0
  347. package/dist/nerves/index.js +293 -9
  348. package/dist/nerves/observation.js +20 -0
  349. package/dist/nerves/redact.js +79 -0
  350. package/dist/nerves/review/cli-main.js +5 -0
  351. package/dist/nerves/review/cli.js +156 -0
  352. package/dist/nerves/review/core.js +159 -0
  353. package/dist/nerves/runtime.js +17 -1
  354. package/dist/repertoire/ado-client.js +15 -56
  355. package/dist/repertoire/ado-semantic.js +16 -10
  356. package/dist/repertoire/api-client.js +97 -0
  357. package/dist/repertoire/bitwarden-store.js +1045 -0
  358. package/dist/repertoire/bundle-templates.js +71 -0
  359. package/dist/repertoire/bw-installer.js +180 -0
  360. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  361. package/dist/repertoire/coding/context-pack.js +331 -0
  362. package/dist/repertoire/coding/feedback.js +218 -30
  363. package/dist/repertoire/coding/index.js +15 -3
  364. package/dist/repertoire/coding/manager.js +166 -10
  365. package/dist/repertoire/coding/spawner.js +58 -11
  366. package/dist/repertoire/coding/tools.js +242 -14
  367. package/dist/repertoire/coding/workbench-client.js +272 -0
  368. package/dist/repertoire/coding/workbench-manager.js +346 -0
  369. package/dist/repertoire/commerce-errors.js +109 -0
  370. package/dist/repertoire/commerce-self-test.js +156 -0
  371. package/dist/repertoire/credential-access.js +178 -0
  372. package/dist/repertoire/desk/classifier.js +362 -0
  373. package/dist/repertoire/duffel-client.js +185 -0
  374. package/dist/repertoire/github-client.js +14 -55
  375. package/dist/repertoire/graph-client.js +11 -52
  376. package/dist/repertoire/guardrails.js +813 -18
  377. package/dist/repertoire/mcp-client.js +44 -3
  378. package/dist/repertoire/mcp-manager.js +259 -20
  379. package/dist/repertoire/mcp-tools.js +83 -0
  380. package/dist/repertoire/plugin-mcp.js +175 -0
  381. package/dist/repertoire/plugins.js +253 -0
  382. package/dist/repertoire/relationship-authorization.js +194 -0
  383. package/dist/repertoire/shell-sessions.js +133 -0
  384. package/dist/repertoire/skills.js +48 -4
  385. package/dist/repertoire/stripe-client.js +131 -0
  386. package/dist/repertoire/tool-arguments.js +143 -0
  387. package/dist/repertoire/tool-results.js +29 -0
  388. package/dist/repertoire/tools-a2a.js +699 -0
  389. package/dist/repertoire/tools-attachments.js +322 -0
  390. package/dist/repertoire/tools-awaiting.js +575 -0
  391. package/dist/repertoire/tools-base.js +82 -1139
  392. package/dist/repertoire/tools-bluebubbles.js +2 -0
  393. package/dist/repertoire/tools-bridge.js +144 -0
  394. package/dist/repertoire/tools-bundle.js +993 -0
  395. package/dist/repertoire/tools-commerce.js +253 -0
  396. package/dist/repertoire/tools-config.js +186 -0
  397. package/dist/repertoire/tools-continuity.js +521 -0
  398. package/dist/repertoire/tools-credential.js +383 -0
  399. package/dist/repertoire/tools-evolution.js +527 -0
  400. package/dist/repertoire/tools-files.js +344 -0
  401. package/dist/repertoire/tools-flight.js +290 -0
  402. package/dist/repertoire/tools-flow.js +119 -0
  403. package/dist/repertoire/tools-github.js +3 -8
  404. package/dist/repertoire/tools-habits.js +103 -0
  405. package/dist/repertoire/tools-mail.js +1828 -0
  406. package/dist/repertoire/tools-notes.js +485 -0
  407. package/dist/repertoire/tools-obligations.js +182 -0
  408. package/dist/repertoire/tools-orientation.js +31 -0
  409. package/dist/repertoire/tools-record.js +482 -0
  410. package/dist/repertoire/tools-rsvp.js +139 -0
  411. package/dist/repertoire/tools-runtime.js +150 -0
  412. package/dist/repertoire/tools-session.js +1096 -0
  413. package/dist/repertoire/tools-shell.js +133 -0
  414. package/dist/repertoire/tools-steward-policy.js +95 -0
  415. package/dist/repertoire/tools-stripe.js +224 -0
  416. package/dist/repertoire/tools-surface.js +355 -0
  417. package/dist/repertoire/tools-teams.js +12 -39
  418. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  419. package/dist/repertoire/tools-travel.js +125 -0
  420. package/dist/repertoire/tools-trip.js +982 -0
  421. package/dist/repertoire/tools-unraid.js +459 -0
  422. package/dist/repertoire/tools-user-profile.js +146 -0
  423. package/dist/repertoire/tools-vault.js +40 -0
  424. package/dist/repertoire/tools-voice.js +145 -0
  425. package/dist/repertoire/tools.js +377 -80
  426. package/dist/repertoire/travel-api-client.js +360 -0
  427. package/dist/repertoire/unraid-client.js +155 -0
  428. package/dist/repertoire/unraid-restart.js +212 -0
  429. package/dist/repertoire/user-profile.js +131 -0
  430. package/dist/repertoire/vault-setup.js +246 -0
  431. package/dist/repertoire/vault-unlock.js +628 -0
  432. package/dist/rsvp/aisleplanner-client.js +354 -0
  433. package/dist/rsvp/cli.js +926 -0
  434. package/dist/rsvp/config.js +327 -0
  435. package/dist/rsvp/cutover.js +433 -0
  436. package/dist/rsvp/diagnostics.js +232 -0
  437. package/dist/rsvp/diff-renderer.js +176 -0
  438. package/dist/rsvp/habit-policy.js +110 -0
  439. package/dist/rsvp/habit-stage.js +123 -0
  440. package/dist/rsvp/incident-bundle.js +97 -0
  441. package/dist/rsvp/migration.js +204 -0
  442. package/dist/rsvp/native-habit-runner.js +630 -0
  443. package/dist/rsvp/outbound-state.js +628 -0
  444. package/dist/rsvp/query.js +118 -0
  445. package/dist/rsvp/replay.js +233 -0
  446. package/dist/rsvp/snapshot.js +232 -0
  447. package/dist/rsvp/spend-ledger.js +175 -0
  448. package/dist/scripts/claude-code-hook.js +41 -0
  449. package/dist/scripts/claude-code-stop-hook.js +47 -0
  450. package/dist/senses/a2a-entry.js +115 -0
  451. package/dist/senses/attention-queue.js +186 -0
  452. package/dist/senses/await-turn-message.js +58 -0
  453. package/dist/senses/bluebubbles/active-turns.js +216 -0
  454. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  455. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  456. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +355 -40
  457. package/dist/senses/bluebubbles/context-packet.js +207 -0
  458. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  459. package/dist/senses/bluebubbles/entry.js +90 -0
  460. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  461. package/dist/senses/bluebubbles/index.js +3839 -0
  462. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  463. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  464. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  465. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  466. package/dist/senses/bluebubbles/outbound-state.js +308 -0
  467. package/dist/senses/bluebubbles/processed-log.js +133 -0
  468. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  469. package/dist/senses/bluebubbles/replay.js +284 -0
  470. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  471. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  472. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  473. package/dist/senses/bluebubbles/webhook-registration.js +228 -0
  474. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  475. package/dist/senses/cli/bracketed-paste.js +82 -0
  476. package/dist/senses/cli/image-paste.js +287 -0
  477. package/dist/senses/cli/image-ref-navigation.js +75 -0
  478. package/dist/senses/cli/ink-app.js +156 -0
  479. package/dist/senses/cli/inline-diff.js +64 -0
  480. package/dist/senses/cli/input-keys.js +174 -0
  481. package/dist/senses/cli/kill-ring.js +86 -0
  482. package/dist/senses/cli/message-list.js +51 -0
  483. package/dist/senses/cli/ouro-tui.js +607 -0
  484. package/dist/senses/cli/spinner-imperative.js +135 -0
  485. package/dist/senses/cli/spinner.js +101 -0
  486. package/dist/senses/cli/status-line.js +60 -0
  487. package/dist/senses/cli/streaming-markdown.js +526 -0
  488. package/dist/senses/cli/tool-display.js +85 -0
  489. package/dist/senses/cli/tool-render.js +85 -0
  490. package/dist/senses/cli/tui-store.js +241 -0
  491. package/dist/senses/cli/virtual-list.js +35 -0
  492. package/dist/senses/cli-entry.js +60 -8
  493. package/dist/senses/cli.js +600 -317
  494. package/dist/senses/commands.js +66 -3
  495. package/dist/senses/context-packet-ledger.js +371 -0
  496. package/dist/senses/context-packets.js +154 -0
  497. package/dist/senses/habit-lifecycle-guard.js +24 -0
  498. package/dist/senses/habit-turn-message.js +160 -0
  499. package/dist/senses/ingress-evidence.js +2 -0
  500. package/dist/senses/mail-entry.js +66 -0
  501. package/dist/senses/mail.js +524 -0
  502. package/dist/senses/pipeline.js +1081 -204
  503. package/dist/senses/private-runtime-worker.js +780 -0
  504. package/dist/senses/private-runtime.js +1491 -0
  505. package/dist/senses/proactive-content-guard.js +51 -0
  506. package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
  507. package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
  508. package/dist/senses/sanctuary-grounding.js +118 -0
  509. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  510. package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
  511. package/dist/senses/sanctuary-health-runner.js +101 -0
  512. package/dist/senses/sanctuary-health.js +408 -0
  513. package/dist/senses/sanctuary-interactive-control.js +306 -0
  514. package/dist/senses/sanctuary-media-optimization.js +436 -0
  515. package/dist/senses/sanctuary-runtime.js +287 -0
  516. package/dist/senses/sanctuary-sab.js +85 -0
  517. package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
  518. package/dist/senses/shared-turn.js +488 -0
  519. package/dist/senses/surface-tool.js +109 -0
  520. package/dist/senses/teams-entry.js +60 -8
  521. package/dist/senses/teams.js +515 -217
  522. package/dist/senses/telegram-admission.js +711 -0
  523. package/dist/senses/telegram-approval-runtime.js +566 -0
  524. package/dist/senses/telegram-attachments.js +182 -0
  525. package/dist/senses/telegram-audit-ledger.js +220 -0
  526. package/dist/senses/telegram-client.js +1676 -0
  527. package/dist/senses/telegram-effect-adapter.js +796 -0
  528. package/dist/senses/telegram-entry.js +83 -0
  529. package/dist/senses/telegram.js +2018 -0
  530. package/dist/senses/trust-gate.js +104 -9
  531. package/dist/senses/voice/audio-playback.js +237 -0
  532. package/dist/senses/voice/audio-routing.js +119 -0
  533. package/dist/senses/voice/elevenlabs.js +202 -0
  534. package/dist/senses/voice/floor-control.js +431 -0
  535. package/dist/senses/voice/floor-controller.js +115 -0
  536. package/dist/senses/voice/golden-path.js +116 -0
  537. package/dist/senses/voice/index.js +29 -0
  538. package/dist/senses/voice/meeting.js +113 -0
  539. package/dist/senses/voice/outbound.js +190 -0
  540. package/dist/senses/voice/phone.js +33 -0
  541. package/dist/senses/voice/playback.js +139 -0
  542. package/dist/senses/voice/realtime-eval.js +496 -0
  543. package/dist/senses/voice/realtime-trace.js +531 -0
  544. package/dist/senses/voice/transcript.js +70 -0
  545. package/dist/senses/voice/turn.js +191 -0
  546. package/dist/senses/voice/twilio-phone-runtime.js +807 -0
  547. package/dist/senses/voice/twilio-phone.js +5081 -0
  548. package/dist/senses/voice/types.js +2 -0
  549. package/dist/senses/voice/whisper.js +161 -0
  550. package/dist/senses/voice-entry.js +81 -0
  551. package/dist/senses/voice-realtime-eval-command.js +99 -0
  552. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  553. package/dist/senses/voice-twilio-entry.js +87 -0
  554. package/dist/trips/core.js +138 -0
  555. package/dist/trips/store.js +265 -0
  556. package/dist/util/frontmatter.js +69 -0
  557. package/npm-shrinkwrap.json +8129 -0
  558. package/package.json +71 -12
  559. package/skills/agent-commerce.md +113 -0
  560. package/skills/browser-navigation.md +117 -0
  561. package/skills/commerce-setup-guide.md +116 -0
  562. package/skills/commerce-setup.md +84 -0
  563. package/skills/configure-dev-tools.md +99 -0
  564. package/skills/travel-planning.md +138 -0
  565. package/dist/heart/daemon/auth-flow.js +0 -430
  566. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  567. package/dist/heart/daemon/update-hooks.js +0 -138
  568. package/dist/heart/obligations.js +0 -141
  569. package/dist/heart/safe-workspace.js +0 -228
  570. package/dist/heart/session-recall.js +0 -116
  571. package/dist/mind/friends/channel.js +0 -84
  572. package/dist/mind/friends/group-context.js +0 -144
  573. package/dist/mind/friends/resolver.js +0 -84
  574. package/dist/mind/friends/store-file.js +0 -190
  575. package/dist/mind/friends/store.js +0 -4
  576. package/dist/mind/friends/tokens.js +0 -26
  577. package/dist/mind/friends/trust-explanation.js +0 -74
  578. package/dist/mind/friends/types.js +0 -29
  579. package/dist/repertoire/tasks/board.js +0 -134
  580. package/dist/repertoire/tasks/index.js +0 -224
  581. package/dist/repertoire/tasks/lifecycle.js +0 -80
  582. package/dist/repertoire/tasks/middleware.js +0 -65
  583. package/dist/repertoire/tasks/parser.js +0 -173
  584. package/dist/repertoire/tasks/scanner.js +0 -132
  585. package/dist/repertoire/tasks/transitions.js +0 -144
  586. package/dist/senses/bluebubbles-entry.js +0 -13
  587. package/dist/senses/bluebubbles.js +0 -1180
  588. package/dist/senses/debug-activity.js +0 -148
  589. package/dist/senses/inner-dialog-worker.js +0 -90
  590. package/dist/senses/inner-dialog.js +0 -525
  591. package/subagents/README.md +0 -7
  592. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  593. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  594. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  595. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  596. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  597. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  598. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  599. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  600. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  601. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  602. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  603. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  604. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  605. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  606. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  607. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  608. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.DaemonProcessManager = void 0;
36
+ exports.DaemonProcessManager = exports.RESPAWN_GUARD_WINDOW_MS = exports.RESPAWN_GUARD_MAX_RESTARTS = void 0;
37
37
  const child_process_1 = require("child_process");
38
38
  const path = __importStar(require("path"));
39
39
  const identity_1 = require("../identity");
@@ -41,8 +41,24 @@ const runtime_1 = require("../../nerves/runtime");
41
41
  function startOfHour(ms) {
42
42
  return ms - 60 * 60 * 1000;
43
43
  }
44
+ /**
45
+ * Respawn-loop guard: refuse `restartAgent` if we've already orchestrated
46
+ * RESPAWN_GUARD_MAX_RESTARTS in the past RESPAWN_GUARD_WINDOW_MS.
47
+ *
48
+ * Calibrated for the 2026-05-11 BB sense incident: a misconfigured probe
49
+ * was triggering `restartAgent` every ~60s for hours. Five restarts in
50
+ * 10 minutes is well above the rate of legitimate operational restarts
51
+ * (a single human-initiated `ouro down && ouro up` produces one) and well
52
+ * below the rate of a death spiral (60/hr ⇒ 10/10min).
53
+ */
54
+ exports.RESPAWN_GUARD_MAX_RESTARTS = 5;
55
+ exports.RESPAWN_GUARD_WINDOW_MS = 10 * 60_000;
56
+ const MAX_PENDING_IPC_MESSAGES = 20;
44
57
  class DaemonProcessManager {
45
58
  agents = new Map();
59
+ /** `stopAll()` is terminal for a manager instance. Once set, it fences
60
+ * detached restart/start continuations until the owning daemon exits. */
61
+ shutdownRequested = false;
46
62
  maxRestartsPerHour;
47
63
  stabilityThresholdMs;
48
64
  initialBackoffMs;
@@ -51,33 +67,128 @@ class DaemonProcessManager {
51
67
  now;
52
68
  setTimeoutFn;
53
69
  clearTimeoutFn;
70
+ cooldownRecoveryMs;
71
+ maxCooldownRetries;
72
+ startupStaleAfterMs;
73
+ stopTimeoutMs;
54
74
  existsSyncFn;
75
+ configCheckFn;
76
+ statusWriterFn;
77
+ onSnapshotChangeFn;
78
+ /**
79
+ * Notify the snapshot-change observer (if registered). Swallows any
80
+ * errors from the observer so process lifecycle code never fails
81
+ * because the observer threw.
82
+ */
83
+ notifySnapshotChange(snapshot) {
84
+ if (!this.onSnapshotChangeFn)
85
+ return;
86
+ try {
87
+ this.onSnapshotChangeFn(snapshot);
88
+ }
89
+ catch (error) {
90
+ (0, runtime_1.emitNervesEvent)({
91
+ level: "warn",
92
+ component: "daemon",
93
+ event: "daemon.snapshot_change_observer_error",
94
+ message: "snapshot-change observer threw",
95
+ meta: {
96
+ agent: snapshot.name,
97
+ error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error),
98
+ },
99
+ });
100
+ }
101
+ }
102
+ writeStatus(agent, text) {
103
+ try {
104
+ this.statusWriterFn(text);
105
+ }
106
+ catch (error) {
107
+ (0, runtime_1.emitNervesEvent)({
108
+ level: "warn",
109
+ component: "daemon",
110
+ event: "daemon.status_writer_error",
111
+ message: "daemon status writer threw",
112
+ meta: {
113
+ agent,
114
+ error: error instanceof Error ? error.message : String(error),
115
+ },
116
+ });
117
+ }
118
+ }
119
+ currentTimeMs() {
120
+ const value = this.now();
121
+ return Number.isFinite(value) ? value : Date.now();
122
+ }
123
+ isStartAttemptCurrent(state, attemptId) {
124
+ return state.startAttemptId === attemptId;
125
+ }
126
+ markConfigCheckFailed(state, errorReason, fixHint) {
127
+ const agent = state.config.name;
128
+ state.snapshot.status = "crashed";
129
+ state.snapshot.errorReason = errorReason;
130
+ state.snapshot.fixHint = fixHint;
131
+ (0, runtime_1.emitNervesEvent)({
132
+ level: "error",
133
+ component: "daemon",
134
+ event: "daemon.agent_config_invalid",
135
+ message: errorReason,
136
+ meta: { agent, fix: fixHint },
137
+ });
138
+ this.writeStatus(agent, `[daemon] ${agent}: ${errorReason}\n` +
139
+ (fixHint ? ` Fix: ${fixHint}\n` : ""));
140
+ this.notifySnapshotChange(state.snapshot);
141
+ }
55
142
  constructor(options) {
56
143
  this.maxRestartsPerHour = options.maxRestartsPerHour ?? 10;
57
144
  this.stabilityThresholdMs = options.stabilityThresholdMs ?? 60_000;
58
145
  this.initialBackoffMs = options.initialBackoffMs ?? 1_000;
59
146
  this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
147
+ this.cooldownRecoveryMs = options.cooldownRecoveryMs ?? 5 * 60 * 1_000;
148
+ this.maxCooldownRetries = options.maxCooldownRetries ?? 3;
149
+ this.startupStaleAfterMs = options.startupStaleAfterMs ?? 45_000;
150
+ this.stopTimeoutMs = options.stopTimeoutMs ?? 4_000;
60
151
  this.spawnFn = options.spawn ?? ((command, args, spawnOptions) => (0, child_process_1.spawn)(command, args, spawnOptions));
61
152
  this.now = options.now ?? (() => Date.now());
62
153
  this.setTimeoutFn = options.setTimeoutFn ?? ((cb, delay) => setTimeout(cb, delay));
63
154
  this.clearTimeoutFn = options.clearTimeoutFn ?? ((timer) => clearTimeout(timer));
64
155
  this.existsSyncFn = options.existsSync ?? null;
156
+ this.configCheckFn = options.configCheck ?? null;
157
+ this.statusWriterFn = options.statusWriter ?? ((text) => {
158
+ process.stderr.write(text);
159
+ });
160
+ this.onSnapshotChangeFn = options.onSnapshotChange ?? null;
65
161
  for (const agent of options.agents) {
66
162
  this.agents.set(agent.name, {
67
163
  config: agent,
68
164
  process: null,
165
+ startInFlight: false,
166
+ startAttemptedAtMs: null,
167
+ startAttemptId: 0,
168
+ pendingIpcMessages: [],
69
169
  restartTimer: null,
70
170
  crashTimestamps: [],
171
+ orchestratedRestartTimestamps: [],
172
+ respawnLoopTripped: false,
71
173
  stopRequested: false,
174
+ cooldownTimer: null,
175
+ cooldownRetryCount: 0,
176
+ fastCrashCount: 0,
177
+ stopFailureActive: false,
72
178
  snapshot: {
73
179
  name: agent.name,
74
180
  channel: agent.channel,
181
+ autoStart: agent.autoStart,
75
182
  status: "stopped",
76
183
  pid: null,
77
184
  restartCount: 0,
78
185
  startedAt: null,
79
186
  lastCrashAt: null,
80
187
  backoffMs: this.initialBackoffMs,
188
+ lastExitCode: null,
189
+ lastSignal: null,
190
+ errorReason: null,
191
+ fixHint: null,
81
192
  },
82
193
  });
83
194
  }
@@ -89,85 +200,444 @@ class DaemonProcessManager {
89
200
  }
90
201
  }
91
202
  }
92
- async startAgent(agent) {
203
+ triggerAutoStartAgents() {
204
+ for (const state of this.agents.values()) {
205
+ if (!state.config.autoStart)
206
+ continue;
207
+ void this.startAgent(state.config.name).catch((error) => {
208
+ const errorReason = error instanceof Error ? error.message : String(error);
209
+ this.markConfigCheckFailed(state, `agent startup threw before the worker could run: ${errorReason}`, "Run 'ouro doctor' for diagnostics, then retry 'ouro up'.");
210
+ });
211
+ }
212
+ }
213
+ async startAgent(agent, options = {}) {
93
214
  const state = this.requireAgent(agent);
94
- if (state.process)
215
+ if (this.shutdownRequested)
95
216
  return;
96
- this.clearRestartTimer(state);
97
- state.stopRequested = false;
98
- state.snapshot.status = "starting";
99
- const runCwd = (0, identity_1.getRepoRoot)();
100
- const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
101
- if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
102
- state.snapshot.status = "crashed";
217
+ if (state.process || state.startInFlight)
218
+ return;
219
+ const attemptId = state.startAttemptId + 1;
220
+ state.startAttemptId = attemptId;
221
+ state.startInFlight = true;
222
+ state.startAttemptedAtMs = this.currentTimeMs();
223
+ try {
224
+ this.clearRestartTimer(state);
225
+ state.stopRequested = false;
226
+ state.snapshot.status = "starting";
227
+ if (this.configCheckFn && options.skipConfigCheck !== true) {
228
+ let result;
229
+ try {
230
+ result = await this.configCheckFn(agent);
231
+ }
232
+ catch (error) {
233
+ const errorReason = error instanceof Error ? error.message : String(error);
234
+ this.markConfigCheckFailed(state, `agent config validation threw: ${errorReason}`, "Run 'ouro doctor' for diagnostics, then retry 'ouro up'.");
235
+ return;
236
+ }
237
+ if (!this.isStartAttemptCurrent(state, attemptId))
238
+ return;
239
+ if (state.stopRequested) {
240
+ state.snapshot.status = "stopped";
241
+ state.snapshot.pid = null;
242
+ this.notifySnapshotChange(state.snapshot);
243
+ return;
244
+ }
245
+ if (result.skip) {
246
+ state.snapshot.status = "stopped";
247
+ state.snapshot.errorReason = null;
248
+ state.snapshot.fixHint = null;
249
+ (0, runtime_1.emitNervesEvent)({
250
+ component: "daemon",
251
+ event: "daemon.agent_config_skipped",
252
+ message: result.error ?? "agent start skipped by config check",
253
+ meta: { agent, fix: result.fix ?? null },
254
+ });
255
+ this.notifySnapshotChange(state.snapshot);
256
+ return;
257
+ }
258
+ if (!result.ok) {
259
+ this.markConfigCheckFailed(state, result.error ?? "agent config validation failed", result.fix ?? null);
260
+ return;
261
+ }
262
+ /* v8 ignore next -- defensive duplicate stale-start guard before spawn preparation @preserve */
263
+ if (!this.isStartAttemptCurrent(state, attemptId))
264
+ return;
265
+ // Config check passed — clear any prior error so the pulse stops
266
+ // reporting the broken state. This is the recovery path: the user
267
+ // fixed their secrets/config, the next startAgent attempt sees a
268
+ // valid config, and the pulse goes quiet.
269
+ state.snapshot.errorReason = null;
270
+ state.snapshot.fixHint = null;
271
+ }
272
+ if (options.skipConfigCheck === true) {
273
+ state.snapshot.errorReason = null;
274
+ state.snapshot.fixHint = null;
275
+ }
276
+ const runCwd = (0, identity_1.getRepoRoot)();
277
+ const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
278
+ if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
279
+ state.snapshot.status = "crashed";
280
+ (0, runtime_1.emitNervesEvent)({
281
+ level: "error",
282
+ component: "daemon",
283
+ event: "daemon.agent_entry_missing",
284
+ message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
285
+ meta: { agent, entryScript },
286
+ });
287
+ this.notifySnapshotChange(state.snapshot);
288
+ return;
289
+ }
290
+ /* v8 ignore next -- defensive duplicate stale-start guard immediately before spawn @preserve */
291
+ if (!this.isStartAttemptCurrent(state, attemptId))
292
+ return;
293
+ if (state.stopRequested) {
294
+ state.snapshot.status = "stopped";
295
+ state.snapshot.pid = null;
296
+ this.notifySnapshotChange(state.snapshot);
297
+ return;
298
+ }
299
+ const agentArgs = state.config.getArgs?.() ?? state.config.args ?? [];
300
+ const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...agentArgs];
301
+ const child = this.spawnFn("node", args, {
302
+ cwd: runCwd,
303
+ env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
304
+ stdio: ["ignore", "ignore", "ignore", "ipc"],
305
+ });
306
+ /* v8 ignore next 7 -- defensive: spawn should always return a ChildProcess @preserve */
307
+ if (!child) {
308
+ state.snapshot.status = "crashed";
309
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.agent_spawn_failed", message: "spawn returned null", meta: { agent } });
310
+ return;
311
+ }
312
+ if (!this.isStartAttemptCurrent(state, attemptId)) {
313
+ try {
314
+ child.kill("SIGTERM");
315
+ }
316
+ catch {
317
+ (0, runtime_1.emitNervesEvent)({
318
+ level: "warn",
319
+ component: "daemon",
320
+ event: "daemon.agent_stop_error",
321
+ message: "failed to stop stale managed agent startup",
322
+ meta: { agent },
323
+ });
324
+ }
325
+ return;
326
+ }
327
+ state.process = child;
328
+ state.snapshot.status = "running";
329
+ state.snapshot.pid = child.pid ?? null;
330
+ state.snapshot.startedAt = new Date(this.currentTimeMs()).toISOString();
331
+ const bootstrap = state.config.getRuntimeCredentialBootstrap?.() ?? null;
332
+ if (bootstrap) {
333
+ const message = {
334
+ type: "ouro.runtimeCredentialBootstrap",
335
+ agentName: bootstrap.agentName,
336
+ };
337
+ if (bootstrap.runtimeConfig)
338
+ message.runtimeConfig = bootstrap.runtimeConfig;
339
+ if (bootstrap.machineRuntimeConfig)
340
+ message.machineRuntimeConfig = bootstrap.machineRuntimeConfig;
341
+ if (bootstrap.machineId)
342
+ message.machineId = bootstrap.machineId;
343
+ if (bootstrap.providerCredentialRecords)
344
+ message.providerCredentialRecords = bootstrap.providerCredentialRecords;
345
+ try {
346
+ child.send?.(message);
347
+ (0, runtime_1.emitNervesEvent)({
348
+ component: "daemon",
349
+ event: "daemon.agent_runtime_credentials_bootstrap_sent",
350
+ message: "sent runtime credential bootstrap to managed agent process",
351
+ meta: {
352
+ agent,
353
+ runtimeConfig: !!bootstrap.runtimeConfig,
354
+ machineRuntimeConfig: !!bootstrap.machineRuntimeConfig,
355
+ providerCredentialRecords: bootstrap.providerCredentialRecords?.length ?? 0,
356
+ },
357
+ });
358
+ }
359
+ catch (error) {
360
+ (0, runtime_1.emitNervesEvent)({
361
+ level: "warn",
362
+ component: "daemon",
363
+ event: "daemon.agent_ipc_send_error",
364
+ message: "failed to send runtime credential bootstrap to managed agent process",
365
+ meta: { agent, error: error instanceof Error ? error.message : String(error) },
366
+ });
367
+ }
368
+ }
369
+ const pendingIpcMessages = state.pendingIpcMessages.splice(0);
370
+ for (const message of pendingIpcMessages) {
371
+ try {
372
+ child.send?.(message);
373
+ (0, runtime_1.emitNervesEvent)({
374
+ component: "daemon",
375
+ event: "daemon.agent_pending_ipc_flushed",
376
+ message: "flushed pending IPC message to managed agent",
377
+ meta: { agent },
378
+ });
379
+ }
380
+ catch (error) {
381
+ (0, runtime_1.emitNervesEvent)({
382
+ level: "warn",
383
+ component: "daemon",
384
+ event: "daemon.agent_ipc_send_error",
385
+ message: "failed to flush pending IPC message to managed agent",
386
+ meta: { agent, error: error instanceof Error ? error.message : String(error) },
387
+ });
388
+ }
389
+ }
103
390
  (0, runtime_1.emitNervesEvent)({
104
- level: "error",
105
391
  component: "daemon",
106
- event: "daemon.agent_entry_missing",
107
- message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
108
- meta: { agent, entryScript },
392
+ event: "daemon.agent_started",
393
+ message: "daemon started managed agent process",
394
+ meta: { agent, pid: child.pid ?? null, cwd: runCwd },
395
+ });
396
+ this.notifySnapshotChange(state.snapshot);
397
+ /* v8 ignore start — child process error handler; requires real spawn to trigger */
398
+ child.on("error", (err) => {
399
+ (0, runtime_1.emitNervesEvent)({
400
+ level: "warn",
401
+ component: "daemon",
402
+ event: "daemon.agent_process_error",
403
+ message: "managed agent process emitted error",
404
+ meta: { agent, error: err.message },
405
+ });
406
+ });
407
+ /* v8 ignore stop */
408
+ child.once("exit", (code, signal) => {
409
+ this.onExit(state, child, code, signal);
109
410
  });
110
- return;
111
411
  }
112
- const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
113
- const child = this.spawnFn("node", args, {
114
- cwd: runCwd,
115
- env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
116
- stdio: ["ignore", "ignore", "ignore", "ipc"],
117
- });
118
- state.process = child;
119
- state.snapshot.status = "running";
120
- state.snapshot.pid = child.pid ?? null;
121
- state.snapshot.startedAt = new Date(this.now()).toISOString();
122
- (0, runtime_1.emitNervesEvent)({
123
- component: "daemon",
124
- event: "daemon.agent_started",
125
- message: "daemon started managed agent process",
126
- meta: { agent, pid: child.pid ?? null, cwd: runCwd },
127
- });
128
- child.once("exit", (code, signal) => {
129
- this.onExit(state, code, signal);
130
- });
412
+ finally {
413
+ if (this.isStartAttemptCurrent(state, attemptId)) {
414
+ state.startInFlight = false;
415
+ state.startAttemptedAtMs = null;
416
+ }
417
+ }
131
418
  }
132
419
  async stopAgent(agent) {
133
420
  const state = this.requireAgent(agent);
134
421
  this.clearRestartTimer(state);
422
+ this.clearCooldownTimer(state);
135
423
  state.stopRequested = true;
424
+ state.pendingIpcMessages = [];
425
+ // NOTE: do not touch state.respawnLoopTripped / orchestratedRestartTimestamps
426
+ // here. restartAgent calls stopAgent internally; clearing the guard here
427
+ // would reset the window every cycle and defeat the loop-detection. The
428
+ // guard self-clears when timestamps age out of the window (handled inside
429
+ // restartAgent at the prune step).
136
430
  if (!state.process) {
137
431
  state.snapshot.status = "stopped";
138
432
  state.snapshot.pid = null;
433
+ this.notifySnapshotChange(state.snapshot);
139
434
  return;
140
435
  }
141
436
  const child = state.process;
142
- state.process = null;
143
- state.snapshot.status = "stopped";
144
- state.snapshot.pid = null;
145
- try {
146
- child.kill("SIGTERM");
437
+ const childPid = child.pid ?? null;
438
+ const childPidLabel = childPid === null ? "unknown" : String(childPid);
439
+ await new Promise((resolve, reject) => {
440
+ let settled = false;
441
+ let timeout = null;
442
+ const finish = (error) => {
443
+ /* v8 ignore next -- defensive idempotence if an injected timer fires after cancellation @preserve */
444
+ if (settled)
445
+ return;
446
+ settled = true;
447
+ child.removeListener("exit", onExit);
448
+ if (timeout !== null)
449
+ this.clearTimeoutFn(timeout);
450
+ if (error)
451
+ reject(error);
452
+ else
453
+ resolve();
454
+ };
455
+ const onExit = () => finish();
456
+ child.once("exit", onExit);
457
+ let signalAccepted = false;
458
+ try {
459
+ signalAccepted = child.kill("SIGTERM");
460
+ }
461
+ catch {
462
+ const errorReason = "failed to send SIGTERM to managed agent; worker remains tracked";
463
+ state.stopFailureActive = true;
464
+ state.snapshot.errorReason = errorReason;
465
+ state.snapshot.fixHint = `Inspect managed worker PID ${childPidLabel} before retrying the stop or restart.`;
466
+ (0, runtime_1.emitNervesEvent)({
467
+ level: "error",
468
+ component: "daemon",
469
+ event: "daemon.agent_stop_error",
470
+ message: errorReason,
471
+ meta: { agent, pid: childPid },
472
+ });
473
+ this.notifySnapshotChange(state.snapshot);
474
+ finish(new Error(errorReason));
475
+ return;
476
+ }
477
+ if (!signalAccepted) {
478
+ const errorReason = "managed agent rejected SIGTERM; worker remains tracked";
479
+ state.stopFailureActive = true;
480
+ state.snapshot.errorReason = errorReason;
481
+ state.snapshot.fixHint = `Inspect managed worker PID ${childPidLabel} before retrying the stop or restart.`;
482
+ (0, runtime_1.emitNervesEvent)({
483
+ level: "error",
484
+ component: "daemon",
485
+ event: "daemon.agent_stop_error",
486
+ message: errorReason,
487
+ meta: { agent, pid: childPid },
488
+ });
489
+ this.notifySnapshotChange(state.snapshot);
490
+ finish(new Error(errorReason));
491
+ return;
492
+ }
493
+ if (settled)
494
+ return;
495
+ timeout = this.setTimeoutFn(() => {
496
+ if (settled)
497
+ return;
498
+ const errorReason = `managed agent did not exit within ${this.stopTimeoutMs}ms; replacement was not started`;
499
+ state.stopFailureActive = true;
500
+ state.snapshot.errorReason = errorReason;
501
+ state.snapshot.fixHint = `Inspect managed worker PID ${childPidLabel}; retry only after that worker has exited.`;
502
+ (0, runtime_1.emitNervesEvent)({
503
+ level: "error",
504
+ component: "daemon",
505
+ event: "daemon.agent_stop_timeout",
506
+ message: errorReason,
507
+ meta: { agent, pid: childPid, timeoutMs: this.stopTimeoutMs },
508
+ });
509
+ this.notifySnapshotChange(state.snapshot);
510
+ finish(new Error(errorReason));
511
+ }, this.stopTimeoutMs);
512
+ });
513
+ }
514
+ async restartAgent(agent, options = {}) {
515
+ const state = this.requireAgent(agent);
516
+ if (this.shutdownRequested)
517
+ return;
518
+ // Respawn-loop guard: prune timestamps outside the window, then check
519
+ // whether we've already restarted this agent too many times in it.
520
+ const now = this.now();
521
+ const windowStart = now - exports.RESPAWN_GUARD_WINDOW_MS;
522
+ state.orchestratedRestartTimestamps = state.orchestratedRestartTimestamps.filter((ts) => ts >= windowStart);
523
+ // If the window is now empty, the trip naturally self-clears. That means
524
+ // after RESPAWN_GUARD_WINDOW_MS of no restart attempts, the daemon is
525
+ // willing to try again (e.g. for a fresh health probe failure that has
526
+ // nothing to do with the original loop).
527
+ if (state.respawnLoopTripped && state.orchestratedRestartTimestamps.length === 0) {
528
+ state.respawnLoopTripped = false;
529
+ state.snapshot.errorReason = null;
530
+ state.snapshot.fixHint = null;
531
+ (0, runtime_1.emitNervesEvent)({
532
+ component: "daemon",
533
+ event: "daemon.agent_respawn_loop_cleared",
534
+ message: "respawn-loop guard cleared by window-aging",
535
+ meta: { agent, windowMs: exports.RESPAWN_GUARD_WINDOW_MS },
536
+ });
537
+ this.notifySnapshotChange(state.snapshot);
147
538
  }
148
- catch {
539
+ if (state.respawnLoopTripped) {
540
+ (0, runtime_1.emitNervesEvent)({
541
+ level: "error",
542
+ component: "daemon",
543
+ event: "daemon.agent_respawn_loop_blocked",
544
+ message: "refused agent restart — respawn-loop guard tripped; manual intervention required",
545
+ meta: {
546
+ agent,
547
+ recentRestartCount: state.orchestratedRestartTimestamps.length,
548
+ windowMs: exports.RESPAWN_GUARD_WINDOW_MS,
549
+ },
550
+ });
551
+ return;
552
+ }
553
+ if (state.orchestratedRestartTimestamps.length >= exports.RESPAWN_GUARD_MAX_RESTARTS) {
554
+ state.respawnLoopTripped = true;
555
+ state.snapshot.errorReason = `respawn loop detected: ${exports.RESPAWN_GUARD_MAX_RESTARTS}+ restarts in ${Math.round(exports.RESPAWN_GUARD_WINDOW_MS / 60_000)}min — refusing further restarts`;
556
+ state.snapshot.fixHint = "investigate the root cause then run `ouro up` to resume";
557
+ (0, runtime_1.emitNervesEvent)({
558
+ level: "error",
559
+ component: "daemon",
560
+ event: "daemon.agent_respawn_loop_tripped",
561
+ message: "respawn-loop guard tripped; further restarts blocked",
562
+ meta: {
563
+ agent,
564
+ restartCount: state.orchestratedRestartTimestamps.length,
565
+ windowMs: exports.RESPAWN_GUARD_WINDOW_MS,
566
+ maxRestarts: exports.RESPAWN_GUARD_MAX_RESTARTS,
567
+ },
568
+ });
569
+ this.notifySnapshotChange(state.snapshot);
570
+ return;
571
+ }
572
+ state.orchestratedRestartTimestamps.push(now);
573
+ if (state.startInFlight && !state.process) {
574
+ const startedAt = state.startAttemptedAtMs;
575
+ /* v8 ignore next -- defensive: startInFlight always records a start timestamp @preserve */
576
+ const elapsedMs = startedAt === null ? 0 : this.currentTimeMs() - startedAt;
577
+ if (elapsedMs < this.startupStaleAfterMs) {
578
+ (0, runtime_1.emitNervesEvent)({
579
+ component: "daemon",
580
+ event: "daemon.agent_restart_deferred",
581
+ message: "managed agent restart skipped while startup is already in flight",
582
+ meta: { agent, elapsedMs, startupStaleAfterMs: this.startupStaleAfterMs },
583
+ });
584
+ return;
585
+ }
149
586
  (0, runtime_1.emitNervesEvent)({
150
587
  level: "warn",
151
588
  component: "daemon",
152
- event: "daemon.agent_stop_error",
153
- message: "failed to send SIGTERM to managed agent",
154
- meta: { agent },
589
+ event: "daemon.agent_startup_stale_recovered",
590
+ message: "managed agent startup was stale; clearing the pending attempt before restart",
591
+ meta: { agent, elapsedMs, startupStaleAfterMs: this.startupStaleAfterMs },
155
592
  });
593
+ state.startAttemptId += 1;
594
+ state.startInFlight = false;
595
+ state.startAttemptedAtMs = null;
156
596
  }
157
- }
158
- async restartAgent(agent) {
159
597
  await this.stopAgent(agent);
160
- await this.startAgent(agent);
598
+ await this.startAgent(agent, options);
161
599
  }
162
600
  async stopAll() {
163
- for (const state of this.agents.values()) {
164
- await this.stopAgent(state.config.name);
601
+ this.shutdownRequested = true;
602
+ const states = [...this.agents.values()];
603
+ const results = await Promise.allSettled(states.map((state) => this.stopAgent(state.config.name)));
604
+ const failures = results.flatMap((result, index) => result.status === "rejected"
605
+ ? [{ agent: states[index].config.name, reason: result.reason }]
606
+ : []);
607
+ if (failures.length > 0) {
608
+ throw new AggregateError(failures.map((failure) => failure.reason), `failed to stop ${failures.length} managed worker(s): ${failures.map((failure) => failure.agent).join(", ")}`);
165
609
  }
166
610
  }
611
+ resetAgentFailureState(agent) {
612
+ const state = this.requireAgent(agent);
613
+ this.clearRestartTimer(state);
614
+ this.clearCooldownTimer(state);
615
+ state.cooldownRetryCount = 0;
616
+ state.crashTimestamps = [];
617
+ state.fastCrashCount = 0;
618
+ state.respawnLoopTripped = false;
619
+ state.orchestratedRestartTimestamps = [];
620
+ state.snapshot.errorReason = null;
621
+ state.snapshot.fixHint = null;
622
+ this.notifySnapshotChange(state.snapshot);
623
+ }
167
624
  sendToAgent(agent, message) {
168
625
  const state = this.requireAgent(agent);
169
- if (!state.process)
626
+ if (!state.process) {
627
+ if (state.startInFlight) {
628
+ if (state.pendingIpcMessages.length >= MAX_PENDING_IPC_MESSAGES) {
629
+ state.pendingIpcMessages.shift();
630
+ }
631
+ state.pendingIpcMessages.push(message);
632
+ (0, runtime_1.emitNervesEvent)({
633
+ component: "daemon",
634
+ event: "daemon.agent_ipc_queued_during_startup",
635
+ message: "queued IPC message while managed agent startup is in flight",
636
+ meta: { agent, queuedCount: state.pendingIpcMessages.length },
637
+ });
638
+ }
170
639
  return;
640
+ }
171
641
  try {
172
642
  state.process.send(message);
173
643
  }
@@ -187,41 +657,94 @@ class DaemonProcessManager {
187
657
  listAgentSnapshots() {
188
658
  return [...this.agents.values()].map((state) => state.snapshot);
189
659
  }
190
- onExit(state, code, signal) {
191
- if (!state.process)
660
+ onExit(state, child, code, signal) {
661
+ /* v8 ignore next -- defensive: replacement cannot start before this child's one-shot exit listener drains @preserve */
662
+ if (state.process !== child)
192
663
  return;
193
664
  state.process = null;
665
+ state.startInFlight = false;
666
+ state.startAttemptedAtMs = null;
194
667
  state.snapshot.pid = null;
195
- const crashed = !state.stopRequested && code !== 0;
196
- const now = this.now();
668
+ state.snapshot.lastExitCode = code;
669
+ state.snapshot.lastSignal = signal;
670
+ if (state.stopFailureActive) {
671
+ state.stopFailureActive = false;
672
+ state.snapshot.errorReason = null;
673
+ state.snapshot.fixHint = null;
674
+ }
675
+ const unexpectedExit = !state.stopRequested;
676
+ const crashed = unexpectedExit && code !== 0;
677
+ const now = this.currentTimeMs();
197
678
  const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
198
679
  const runDuration = Math.max(0, now - startedAt);
199
680
  (0, runtime_1.emitNervesEvent)({
200
- level: crashed ? "warn" : "info",
681
+ level: unexpectedExit ? "warn" : "info",
201
682
  component: "daemon",
202
683
  event: "daemon.agent_exit",
203
684
  message: "managed agent process exited",
204
- meta: { agent: state.config.name, code, signal, crashed, runDurationMs: runDuration },
685
+ meta: { agent: state.config.name, code, signal, crashed, unexpectedExit, runDurationMs: runDuration },
205
686
  });
206
- if (!crashed) {
687
+ if (runDuration >= this.stabilityThresholdMs) {
688
+ state.snapshot.backoffMs = this.initialBackoffMs;
689
+ }
690
+ if (!unexpectedExit) {
207
691
  state.snapshot.status = "stopped";
208
- if (runDuration >= this.stabilityThresholdMs) {
209
- state.snapshot.backoffMs = this.initialBackoffMs;
210
- }
692
+ this.notifySnapshotChange(state.snapshot);
211
693
  return;
212
694
  }
213
- state.snapshot.lastCrashAt = new Date(now).toISOString();
695
+ if (crashed) {
696
+ state.snapshot.lastCrashAt = new Date(now).toISOString();
697
+ }
698
+ if (state.config.autoStart === false) {
699
+ state.snapshot.status = "stopped";
700
+ state.snapshot.errorReason = "non-autostart worker exited unexpectedly; not respawning";
701
+ state.snapshot.fixHint = "Start it explicitly after checking the exit cause.";
702
+ this.notifySnapshotChange(state.snapshot);
703
+ return;
704
+ }
705
+ // Fast-exit detection: repeated early exits, including clean exit-code-0
706
+ // terminations, indicate a startup/configuration or lifecycle fault rather
707
+ // than a stable long-lived worker. Bound them without claiming a crash.
708
+ const FAST_CRASH_THRESHOLD_MS = 5000;
709
+ const FAST_CRASH_MAX = 3;
710
+ if (runDuration < FAST_CRASH_THRESHOLD_MS) {
711
+ state.fastCrashCount = state.fastCrashCount + 1;
712
+ if (state.fastCrashCount >= FAST_CRASH_MAX) {
713
+ state.snapshot.status = "crashed";
714
+ // Capture the fast-exit diagnosis on the snapshot so it surfaces via
715
+ // the pulse with a prescriptive, cause-neutral repair path.
716
+ state.snapshot.errorReason = `agent exited unexpectedly ${FAST_CRASH_MAX} times within ${FAST_CRASH_THRESHOLD_MS}ms of startup — likely a startup/configuration or lifecycle issue.`;
717
+ state.snapshot.fixHint = `Inspect daemon logs and lifecycle/configuration, then run \`ouro up\` to restart.`;
718
+ (0, runtime_1.emitNervesEvent)({
719
+ level: "error",
720
+ component: "daemon",
721
+ event: "daemon.agent_config_failure",
722
+ message: `agent exited unexpectedly ${FAST_CRASH_MAX} times within ${FAST_CRASH_THRESHOLD_MS}ms of startup. Not retrying. Inspect lifecycle/configuration and run \`ouro up\` to restart.`,
723
+ meta: { agent: state.config.name, fastUnexpectedExitCount: state.fastCrashCount, avgRunDurationMs: runDuration },
724
+ });
725
+ this.notifySnapshotChange(state.snapshot);
726
+ return; // Don't schedule cooldown recovery — this needs human/agent intervention
727
+ }
728
+ }
729
+ else {
730
+ // Reset the fast unexpected-exit counter on a stable run.
731
+ state.fastCrashCount = 0;
732
+ }
214
733
  state.crashTimestamps = state.crashTimestamps.filter((crashTs) => crashTs >= startOfHour(now));
215
734
  state.crashTimestamps.push(now);
216
735
  if (state.crashTimestamps.length > this.maxRestartsPerHour) {
217
736
  state.snapshot.status = "crashed";
737
+ state.snapshot.errorReason = `agent exceeded unexpected-exit restart limit (${this.maxRestartsPerHour}/hr) — entering cooldown`;
738
+ state.snapshot.fixHint = "investigate why the agent keeps exiting unexpectedly; cooldown will retry shortly";
218
739
  (0, runtime_1.emitNervesEvent)({
219
740
  level: "error",
220
741
  component: "daemon",
221
742
  event: "daemon.agent_restart_exhausted",
222
- message: "managed agent exceeded restart limit and is marked crashed",
743
+ message: "managed agent exceeded unexpected-exit restart limit and is marked crashed",
223
744
  meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
224
745
  });
746
+ this.notifySnapshotChange(state.snapshot);
747
+ this.scheduleCooldownRecovery(state);
225
748
  return;
226
749
  }
227
750
  state.snapshot.status = "starting";
@@ -232,6 +755,7 @@ class DaemonProcessManager {
232
755
  state.restartTimer = this.setTimeoutFn(() => {
233
756
  void this.startAgent(state.config.name);
234
757
  }, waitMs);
758
+ this.notifySnapshotChange(state.snapshot);
235
759
  }
236
760
  clearRestartTimer(state) {
237
761
  if (state.restartTimer === null)
@@ -239,6 +763,45 @@ class DaemonProcessManager {
239
763
  this.clearTimeoutFn(state.restartTimer);
240
764
  state.restartTimer = null;
241
765
  }
766
+ scheduleCooldownRecovery(state) {
767
+ if (state.cooldownRetryCount >= this.maxCooldownRetries) {
768
+ (0, runtime_1.emitNervesEvent)({
769
+ level: "error",
770
+ component: "daemon",
771
+ event: "daemon.agent_permanent_failure",
772
+ message: "managed agent exhausted all cooldown retries — permanently stopped",
773
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
774
+ });
775
+ return;
776
+ }
777
+ this.clearCooldownTimer(state);
778
+ state.cooldownTimer = this.setTimeoutFn(() => {
779
+ state.cooldownRetryCount += 1;
780
+ state.crashTimestamps = [];
781
+ state.snapshot.backoffMs = this.initialBackoffMs;
782
+ state.snapshot.status = "starting";
783
+ state.snapshot.restartCount += 1;
784
+ (0, runtime_1.emitNervesEvent)({
785
+ component: "daemon",
786
+ event: "daemon.agent_cooldown_recovery",
787
+ message: "attempting cooldown recovery for managed agent",
788
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
789
+ });
790
+ void this.startAgent(state.config.name);
791
+ }, this.cooldownRecoveryMs);
792
+ (0, runtime_1.emitNervesEvent)({
793
+ component: "daemon",
794
+ event: "daemon.agent_cooldown_scheduled",
795
+ message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
796
+ meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
797
+ });
798
+ }
799
+ clearCooldownTimer(state) {
800
+ if (state.cooldownTimer === null)
801
+ return;
802
+ this.clearTimeoutFn(state.cooldownTimer);
803
+ state.cooldownTimer = null;
804
+ }
242
805
  requireAgent(agent) {
243
806
  const state = this.agents.get(agent);
244
807
  if (!state) {