@ouro.bot/cli 0.1.0-alpha.49 → 0.1.0-alpha.491

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 (365) hide show
  1. package/README.md +133 -19
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  4. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  5. package/changelog.json +3126 -0
  6. package/dist/arc/attention-types.js +8 -0
  7. package/dist/arc/cares.js +140 -0
  8. package/dist/arc/episodes.js +117 -0
  9. package/dist/arc/intentions.js +133 -0
  10. package/dist/arc/json-store.js +117 -0
  11. package/dist/arc/obligations.js +237 -0
  12. package/dist/arc/packets.js +193 -0
  13. package/dist/arc/presence.js +185 -0
  14. package/dist/arc/task-lifecycle.js +65 -0
  15. package/dist/heart/active-work.js +989 -0
  16. package/dist/heart/agent-entry.js +58 -3
  17. package/dist/heart/attachments/image-normalize.js +194 -0
  18. package/dist/heart/attachments/materialize.js +97 -0
  19. package/dist/heart/attachments/originals.js +88 -0
  20. package/dist/heart/attachments/render.js +29 -0
  21. package/dist/heart/attachments/sources/adapter.js +2 -0
  22. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  23. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  24. package/dist/heart/attachments/sources/index.js +16 -0
  25. package/dist/heart/attachments/store.js +103 -0
  26. package/dist/heart/attachments/types.js +93 -0
  27. package/dist/heart/auth/auth-flow.js +426 -0
  28. package/dist/heart/background-operations.js +281 -0
  29. package/dist/heart/bridges/manager.js +37 -0
  30. package/dist/heart/bridges/state-machine.js +20 -0
  31. package/dist/heart/bundle-state.js +168 -0
  32. package/dist/heart/commitments.js +111 -0
  33. package/dist/heart/config-registry.js +304 -0
  34. package/dist/heart/config.js +119 -129
  35. package/dist/heart/core.js +845 -229
  36. package/dist/heart/cross-chat-delivery.js +131 -0
  37. package/dist/heart/daemon/agent-config-check.js +490 -0
  38. package/dist/heart/daemon/agent-discovery.js +79 -3
  39. package/dist/heart/daemon/agent-service.js +360 -0
  40. package/dist/heart/daemon/agentic-repair.js +216 -0
  41. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  42. package/dist/heart/daemon/cadence.js +70 -0
  43. package/dist/heart/daemon/cli-defaults.js +640 -0
  44. package/dist/heart/daemon/cli-exec.js +7239 -0
  45. package/dist/heart/daemon/cli-help.js +493 -0
  46. package/dist/heart/daemon/cli-parse.js +1533 -0
  47. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  48. package/dist/heart/daemon/cli-render.js +561 -0
  49. package/dist/heart/daemon/cli-types.js +8 -0
  50. package/dist/heart/daemon/connect-bay.js +323 -0
  51. package/dist/heart/daemon/daemon-cli.js +29 -1616
  52. package/dist/heart/daemon/daemon-entry.js +345 -3
  53. package/dist/heart/daemon/daemon-health.js +141 -0
  54. package/dist/heart/daemon/daemon-runtime-sync.js +190 -12
  55. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  56. package/dist/heart/daemon/daemon.js +677 -58
  57. package/dist/heart/daemon/dns-workflow.js +394 -0
  58. package/dist/heart/daemon/doctor-types.js +8 -0
  59. package/dist/heart/daemon/doctor.js +486 -0
  60. package/dist/heart/daemon/health-monitor.js +92 -1
  61. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  62. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  63. package/dist/heart/daemon/http-health-probe.js +80 -0
  64. package/dist/heart/daemon/human-command-screens.js +234 -0
  65. package/dist/heart/daemon/human-readiness.js +114 -0
  66. package/dist/heart/daemon/inner-status.js +89 -0
  67. package/dist/heart/daemon/interactive-repair.js +394 -0
  68. package/dist/heart/daemon/launchd.js +25 -5
  69. package/dist/heart/daemon/log-tailer.js +82 -12
  70. package/dist/heart/daemon/logs-prune.js +110 -0
  71. package/dist/heart/daemon/message-router.js +2 -2
  72. package/dist/heart/daemon/os-cron-deps.js +134 -0
  73. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  74. package/dist/heart/daemon/ouro-entry.js +3 -1
  75. package/dist/heart/daemon/process-manager.js +214 -0
  76. package/dist/heart/daemon/provider-discovery.js +137 -0
  77. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  78. package/dist/heart/daemon/pulse.js +475 -0
  79. package/dist/heart/daemon/readiness-repair.js +365 -0
  80. package/dist/heart/daemon/run-hooks.js +2 -0
  81. package/dist/heart/daemon/runtime-logging.js +67 -16
  82. package/dist/heart/daemon/runtime-metadata.js +73 -0
  83. package/dist/heart/daemon/runtime-mode.js +67 -0
  84. package/dist/heart/daemon/safe-mode.js +161 -0
  85. package/dist/heart/daemon/sense-manager.js +178 -37
  86. package/dist/heart/daemon/session-id-resolver.js +131 -0
  87. package/dist/heart/daemon/skill-management-installer.js +94 -0
  88. package/dist/heart/daemon/socket-client.js +109 -4
  89. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  90. package/dist/heart/daemon/startup-tui.js +264 -0
  91. package/dist/heart/daemon/task-scheduler.js +3 -25
  92. package/dist/heart/daemon/terminal-ui.js +499 -0
  93. package/dist/heart/daemon/thoughts.js +162 -17
  94. package/dist/heart/daemon/up-progress.js +366 -0
  95. package/dist/heart/daemon/vault-items.js +56 -0
  96. package/dist/heart/delegation.js +62 -0
  97. package/dist/heart/habits/habit-migration.js +189 -0
  98. package/dist/heart/habits/habit-parser.js +140 -0
  99. package/dist/heart/habits/habit-runtime-state.js +100 -0
  100. package/dist/heart/habits/habit-scheduler.js +372 -0
  101. package/dist/heart/{daemon → hatch}/hatch-flow.js +52 -117
  102. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  103. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  104. package/dist/heart/{daemon → hatch}/specialist-tools.js +35 -12
  105. package/dist/heart/identity.js +201 -66
  106. package/dist/heart/kept-notes.js +357 -0
  107. package/dist/heart/kicks.js +1 -1
  108. package/dist/heart/machine-identity.js +161 -0
  109. package/dist/heart/mail-import-discovery.js +353 -0
  110. package/dist/heart/mcp/mcp-server.js +653 -0
  111. package/dist/heart/migrate-config.js +100 -0
  112. package/dist/heart/model-capabilities.js +59 -0
  113. package/dist/heart/outlook/outlook-http-hooks.js +66 -0
  114. package/dist/heart/outlook/outlook-http-response.js +7 -0
  115. package/dist/heart/outlook/outlook-http-routes.js +244 -0
  116. package/dist/heart/outlook/outlook-http-static.js +103 -0
  117. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  118. package/dist/heart/outlook/outlook-http.js +99 -0
  119. package/dist/heart/outlook/outlook-read.js +31 -0
  120. package/dist/heart/outlook/outlook-types.js +27 -0
  121. package/dist/heart/outlook/outlook-view.js +195 -0
  122. package/dist/heart/outlook/readers/agent-machine.js +382 -0
  123. package/dist/heart/outlook/readers/continuity-readers.js +336 -0
  124. package/dist/heart/outlook/readers/mail.js +362 -0
  125. package/dist/heart/outlook/readers/runtime-readers.js +644 -0
  126. package/dist/heart/outlook/readers/sessions.js +232 -0
  127. package/dist/heart/outlook/readers/shared.js +111 -0
  128. package/dist/heart/platform.js +81 -0
  129. package/dist/heart/progress-story.js +42 -0
  130. package/dist/heart/provider-attempt.js +134 -0
  131. package/dist/heart/provider-binding-resolver.js +255 -0
  132. package/dist/heart/provider-credentials.js +424 -0
  133. package/dist/heart/provider-failover.js +301 -0
  134. package/dist/heart/provider-models.js +81 -0
  135. package/dist/heart/provider-ping.js +262 -0
  136. package/dist/heart/provider-state.js +216 -0
  137. package/dist/heart/provider-visibility.js +188 -0
  138. package/dist/heart/providers/anthropic-token.js +131 -0
  139. package/dist/heart/providers/anthropic.js +193 -55
  140. package/dist/heart/providers/azure.js +104 -13
  141. package/dist/heart/providers/error-classification.js +63 -0
  142. package/dist/heart/providers/github-copilot.js +145 -0
  143. package/dist/heart/providers/minimax-vlm.js +189 -0
  144. package/dist/heart/providers/minimax.js +29 -7
  145. package/dist/heart/providers/openai-codex.js +63 -39
  146. package/dist/heart/runtime-capability-check.js +170 -0
  147. package/dist/heart/runtime-credentials.js +260 -0
  148. package/dist/heart/sense-truth.js +11 -4
  149. package/dist/heart/session-activity.js +190 -0
  150. package/dist/heart/session-events.js +981 -0
  151. package/dist/heart/session-transcript.js +167 -0
  152. package/dist/heart/start-of-turn-packet.js +345 -0
  153. package/dist/heart/streaming.js +48 -28
  154. package/dist/heart/sync.js +332 -0
  155. package/dist/heart/target-resolution.js +127 -0
  156. package/dist/heart/tempo.js +93 -0
  157. package/dist/heart/temporal-view.js +41 -0
  158. package/dist/heart/tool-activity-callbacks.js +36 -0
  159. package/dist/heart/tool-description.js +135 -0
  160. package/dist/heart/tool-friction.js +55 -0
  161. package/dist/heart/tool-loop.js +200 -0
  162. package/dist/heart/turn-context.js +372 -0
  163. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
  164. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  165. package/dist/heart/versioning/ouro-path-installer.js +425 -0
  166. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  167. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  168. package/dist/heart/{daemon → versioning}/update-checker.js +5 -1
  169. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  170. package/dist/mailroom/attention.js +167 -0
  171. package/dist/mailroom/autonomy.js +209 -0
  172. package/dist/mailroom/blob-store.js +600 -0
  173. package/dist/mailroom/core.js +658 -0
  174. package/dist/mailroom/entry.js +160 -0
  175. package/dist/mailroom/file-store.js +426 -0
  176. package/dist/mailroom/mbox-import.js +382 -0
  177. package/dist/mailroom/outbound.js +380 -0
  178. package/dist/mailroom/policy.js +263 -0
  179. package/dist/mailroom/reader.js +219 -0
  180. package/dist/mailroom/search-cache.js +182 -0
  181. package/dist/mailroom/search-relevance.js +319 -0
  182. package/dist/mailroom/smtp-ingress.js +176 -0
  183. package/dist/mailroom/source-state.js +176 -0
  184. package/dist/mailroom/travel-extract.js +89 -0
  185. package/dist/mind/bundle-manifest.js +7 -1
  186. package/dist/mind/context.js +164 -93
  187. package/dist/mind/diary-integrity.js +60 -0
  188. package/dist/mind/{memory.js → diary.js} +74 -93
  189. package/dist/mind/embedding-provider.js +60 -0
  190. package/dist/mind/file-state.js +179 -0
  191. package/dist/mind/friends/channel.js +30 -0
  192. package/dist/mind/friends/group-context.js +144 -0
  193. package/dist/mind/friends/resolver.js +54 -2
  194. package/dist/mind/friends/store-file.js +39 -3
  195. package/dist/mind/friends/trust-explanation.js +74 -0
  196. package/dist/mind/friends/types.js +2 -2
  197. package/dist/mind/journal-index.js +161 -0
  198. package/dist/mind/note-search.js +268 -0
  199. package/dist/mind/obligation-steering.js +221 -0
  200. package/dist/mind/pending.js +56 -8
  201. package/dist/mind/prompt-refresh.js +3 -2
  202. package/dist/mind/prompt.js +973 -168
  203. package/dist/mind/provenance-trust.js +26 -0
  204. package/dist/mind/scrutiny.js +173 -0
  205. package/dist/nerves/cli-logging.js +7 -1
  206. package/dist/nerves/coverage/audit-rules.js +15 -6
  207. package/dist/nerves/coverage/audit.js +28 -2
  208. package/dist/nerves/coverage/cli.js +1 -1
  209. package/dist/nerves/coverage/contract.js +5 -5
  210. package/dist/nerves/coverage/file-completeness.js +93 -5
  211. package/dist/nerves/coverage/run-artifacts.js +1 -1
  212. package/dist/nerves/event-buffer.js +111 -0
  213. package/dist/nerves/index.js +224 -4
  214. package/dist/nerves/observation.js +20 -0
  215. package/dist/nerves/redact.js +79 -0
  216. package/dist/nerves/runtime.js +5 -1
  217. package/dist/outlook-ui/assets/index-BPr5vNuM.css +1 -0
  218. package/dist/outlook-ui/assets/index-Cm51CY9W.js +61 -0
  219. package/dist/outlook-ui/index.html +15 -0
  220. package/dist/repertoire/ado-client.js +15 -56
  221. package/dist/repertoire/ado-semantic.js +11 -10
  222. package/dist/repertoire/api-client.js +97 -0
  223. package/dist/repertoire/bitwarden-store.js +774 -0
  224. package/dist/repertoire/bundle-templates.js +72 -0
  225. package/dist/repertoire/bw-installer.js +180 -0
  226. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  227. package/dist/repertoire/coding/context-pack.js +330 -0
  228. package/dist/repertoire/coding/feedback.js +197 -30
  229. package/dist/repertoire/coding/manager.js +158 -9
  230. package/dist/repertoire/coding/spawner.js +55 -9
  231. package/dist/repertoire/coding/tools.js +170 -7
  232. package/dist/repertoire/commerce-errors.js +109 -0
  233. package/dist/repertoire/commerce-self-test.js +156 -0
  234. package/dist/repertoire/credential-access.js +111 -0
  235. package/dist/repertoire/duffel-client.js +185 -0
  236. package/dist/repertoire/github-client.js +14 -55
  237. package/dist/repertoire/graph-client.js +11 -52
  238. package/dist/repertoire/guardrails.js +396 -0
  239. package/dist/repertoire/mcp-client.js +255 -0
  240. package/dist/repertoire/mcp-manager.js +305 -0
  241. package/dist/repertoire/mcp-tools.js +63 -0
  242. package/dist/repertoire/shell-sessions.js +133 -0
  243. package/dist/repertoire/skills.js +15 -24
  244. package/dist/repertoire/stripe-client.js +131 -0
  245. package/dist/repertoire/tasks/board.js +31 -5
  246. package/dist/repertoire/tasks/fix.js +182 -0
  247. package/dist/repertoire/tasks/index.js +16 -4
  248. package/dist/repertoire/tasks/lifecycle.js +2 -2
  249. package/dist/repertoire/tasks/parser.js +3 -2
  250. package/dist/repertoire/tasks/scanner.js +194 -37
  251. package/dist/repertoire/tasks/transitions.js +16 -78
  252. package/dist/repertoire/tool-results.js +29 -0
  253. package/dist/repertoire/tools-attachments.js +317 -0
  254. package/dist/repertoire/tools-base.js +46 -842
  255. package/dist/repertoire/tools-bluebubbles.js +1 -0
  256. package/dist/repertoire/tools-bridge.js +141 -0
  257. package/dist/repertoire/tools-bundle.js +984 -0
  258. package/dist/repertoire/tools-config.js +185 -0
  259. package/dist/repertoire/tools-continuity.js +248 -0
  260. package/dist/repertoire/tools-credential.js +381 -0
  261. package/dist/repertoire/tools-files.js +342 -0
  262. package/dist/repertoire/tools-flight.js +224 -0
  263. package/dist/repertoire/tools-flow.js +105 -0
  264. package/dist/repertoire/tools-github.js +1 -7
  265. package/dist/repertoire/tools-mail.js +1281 -0
  266. package/dist/repertoire/tools-notes.js +376 -0
  267. package/dist/repertoire/tools-session.js +749 -0
  268. package/dist/repertoire/tools-shell.js +120 -0
  269. package/dist/repertoire/tools-stripe.js +180 -0
  270. package/dist/repertoire/tools-surface.js +243 -0
  271. package/dist/repertoire/tools-teams.js +9 -39
  272. package/dist/repertoire/tools-travel.js +125 -0
  273. package/dist/repertoire/tools-trip.js +356 -0
  274. package/dist/repertoire/tools-user-profile.js +144 -0
  275. package/dist/repertoire/tools-vault.js +40 -0
  276. package/dist/repertoire/tools.js +144 -115
  277. package/dist/repertoire/travel-api-client.js +360 -0
  278. package/dist/repertoire/user-profile.js +131 -0
  279. package/dist/repertoire/vault-setup.js +246 -0
  280. package/dist/repertoire/vault-unlock.js +561 -0
  281. package/dist/scripts/claude-code-hook.js +41 -0
  282. package/dist/scripts/claude-code-stop-hook.js +47 -0
  283. package/dist/senses/attention-queue.js +116 -0
  284. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  285. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  286. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +219 -18
  287. package/dist/senses/bluebubbles/entry.js +73 -0
  288. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
  289. package/dist/senses/bluebubbles/index.js +1835 -0
  290. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  291. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  292. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
  293. package/dist/senses/bluebubbles/processed-log.js +111 -0
  294. package/dist/senses/bluebubbles/replay.js +129 -0
  295. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +2 -2
  296. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  297. package/dist/senses/cli/bracketed-paste.js +82 -0
  298. package/dist/senses/cli/image-paste.js +287 -0
  299. package/dist/senses/cli/image-ref-navigation.js +75 -0
  300. package/dist/senses/cli/ink-app.js +156 -0
  301. package/dist/senses/cli/inline-diff.js +64 -0
  302. package/dist/senses/cli/input-keys.js +174 -0
  303. package/dist/senses/cli/kill-ring.js +86 -0
  304. package/dist/senses/cli/message-list.js +51 -0
  305. package/dist/senses/cli/ouro-tui.js +605 -0
  306. package/dist/senses/cli/spinner-imperative.js +135 -0
  307. package/dist/senses/cli/spinner.js +101 -0
  308. package/dist/senses/cli/status-line.js +60 -0
  309. package/dist/senses/cli/streaming-markdown.js +526 -0
  310. package/dist/senses/cli/tool-display.js +83 -0
  311. package/dist/senses/cli/tool-render.js +85 -0
  312. package/dist/senses/cli/tui-store.js +240 -0
  313. package/dist/senses/cli/virtual-list.js +35 -0
  314. package/dist/senses/cli-entry.js +60 -8
  315. package/dist/senses/cli-layout.js +187 -0
  316. package/dist/senses/cli.js +515 -211
  317. package/dist/senses/commands.js +66 -3
  318. package/dist/senses/habit-turn-message.js +108 -0
  319. package/dist/senses/inner-dialog-worker.js +110 -20
  320. package/dist/senses/inner-dialog.js +408 -21
  321. package/dist/senses/mail-entry.js +66 -0
  322. package/dist/senses/mail.js +379 -0
  323. package/dist/senses/pipeline.js +588 -81
  324. package/dist/senses/proactive-content-guard.js +51 -0
  325. package/dist/senses/shared-turn.js +248 -0
  326. package/dist/senses/surface-tool.js +68 -0
  327. package/dist/senses/teams-entry.js +60 -8
  328. package/dist/senses/teams.js +412 -163
  329. package/dist/senses/trust-gate.js +100 -5
  330. package/dist/trips/core.js +138 -0
  331. package/dist/trips/store.js +146 -0
  332. package/package.json +37 -7
  333. package/skills/agent-commerce.md +106 -0
  334. package/skills/browser-navigation.md +117 -0
  335. package/skills/commerce-setup-guide.md +116 -0
  336. package/skills/commerce-setup.md +84 -0
  337. package/skills/configure-dev-tools.md +101 -0
  338. package/skills/travel-planning.md +138 -0
  339. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  340. package/dist/heart/daemon/subagent-installer.js +0 -166
  341. package/dist/heart/session-recall.js +0 -116
  342. package/dist/mind/associative-recall.js +0 -209
  343. package/dist/senses/bluebubbles-entry.js +0 -13
  344. package/dist/senses/bluebubbles.js +0 -1032
  345. package/dist/senses/debug-activity.js +0 -127
  346. package/subagents/README.md +0 -86
  347. package/subagents/work-doer.md +0 -237
  348. package/subagents/work-merger.md +0 -618
  349. package/subagents/work-planner.md +0 -390
  350. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  351. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  352. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  353. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  354. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  355. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  356. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  357. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  358. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  359. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  360. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  361. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  362. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  363. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  364. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  365. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pruneDaemonLogs = pruneDaemonLogs;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const crypto_1 = require("crypto");
7
+ const nerves_1 = require("../../nerves");
8
+ const runtime_1 = require("../../nerves/runtime");
9
+ const identity_1 = require("../identity");
10
+ function isActiveLogStream(name) {
11
+ if (name.endsWith(".ndjson")) {
12
+ return !/\.\d+\.ndjson$/.test(name);
13
+ }
14
+ if (name.endsWith(".log")) {
15
+ return !/\.\d+\.log$/.test(name);
16
+ }
17
+ return false;
18
+ }
19
+ function pruneDaemonLogs(options = {}) {
20
+ /* v8 ignore next -- defensive: tests always pass logsDir to avoid prod paths @preserve */
21
+ const logsDir = options.logsDir ?? (0, identity_1.getAgentDaemonLogsDir)(options.agentName);
22
+ const maxSizeBytes = options.maxSizeBytes ?? nerves_1.DEFAULT_MAX_LOG_SIZE_BYTES;
23
+ const maxGenerations = options.maxGenerations ?? nerves_1.DEFAULT_MAX_GENERATIONS;
24
+ const traceId = (0, crypto_1.randomUUID)();
25
+ (0, runtime_1.emitNervesEvent)({
26
+ component: "nerves",
27
+ event: "nerves.logs_prune_start",
28
+ trace_id: traceId,
29
+ message: "pruning daemon logs",
30
+ meta: { logsDir, maxSizeBytes, maxGenerations },
31
+ });
32
+ let completed = false;
33
+ try {
34
+ if (!(0, fs_1.existsSync)(logsDir)) {
35
+ completed = true;
36
+ (0, runtime_1.emitNervesEvent)({
37
+ component: "nerves",
38
+ event: "nerves.logs_prune_end",
39
+ trace_id: traceId,
40
+ message: "daemon logs dir does not exist",
41
+ meta: { logsDir, filesCompacted: 0, bytesFreed: 0 },
42
+ });
43
+ return { filesCompacted: 0, bytesFreed: 0 };
44
+ }
45
+ let filesCompacted = 0;
46
+ let bytesFreed = 0;
47
+ // Enumerate and rotate each active structured stream plus legacy launchd
48
+ // .log streams. We explicitly skip .gz and other files — only the active
49
+ // stream can be a rotation candidate. Legacy generation files are handled
50
+ // inside rotateIfNeeded's generation-shift step, so we skip them here to
51
+ // avoid double-rotating.
52
+ for (const name of (0, fs_1.readdirSync)(logsDir)) {
53
+ if (!isActiveLogStream(name))
54
+ continue;
55
+ const filePath = (0, path_1.join)(logsDir, name);
56
+ let sizeBefore;
57
+ try {
58
+ sizeBefore = (0, fs_1.statSync)(filePath).size;
59
+ /* v8 ignore start -- defensive: file disappears between readdir and stat @preserve */
60
+ }
61
+ catch {
62
+ continue;
63
+ }
64
+ /* v8 ignore stop */
65
+ if (sizeBefore < maxSizeBytes)
66
+ continue;
67
+ // rotateIfNeeded returns true because we pre-checked sizeBefore >=
68
+ // maxSizeBytes above. The false branch of `if (rotated)` is defensive
69
+ // and unreachable under normal flow; we skip it from coverage so a
70
+ // future refactor that weakens the pre-check still reports correct
71
+ // counts without needing a contrived test.
72
+ const rotated = (0, nerves_1.rotateIfNeeded)(filePath, {
73
+ maxSizeBytes,
74
+ maxGenerations,
75
+ compress: true,
76
+ });
77
+ /* v8 ignore next 3 -- defensive: pre-check guarantees rotated=true @preserve */
78
+ if (!rotated) {
79
+ continue;
80
+ }
81
+ filesCompacted += 1;
82
+ bytesFreed += sizeBefore;
83
+ }
84
+ completed = true;
85
+ (0, runtime_1.emitNervesEvent)({
86
+ component: "nerves",
87
+ event: "nerves.logs_prune_end",
88
+ trace_id: traceId,
89
+ message: "daemon logs pruned",
90
+ meta: { logsDir, filesCompacted, bytesFreed },
91
+ });
92
+ return { filesCompacted, bytesFreed };
93
+ }
94
+ catch (err) {
95
+ /* v8 ignore next -- defensive: completed=true only reached after try returns @preserve */
96
+ if (!completed) {
97
+ /* v8 ignore next -- defensive: rotation always throws real Errors @preserve */
98
+ const reason = err instanceof Error ? err.message : String(err);
99
+ (0, runtime_1.emitNervesEvent)({
100
+ component: "nerves",
101
+ event: "nerves.logs_prune_error",
102
+ trace_id: traceId,
103
+ level: "error",
104
+ message: "daemon logs prune failed",
105
+ meta: { logsDir, error: reason },
106
+ });
107
+ }
108
+ throw err;
109
+ }
110
+ }
@@ -35,9 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.FileMessageRouter = void 0;
37
37
  const fs = __importStar(require("fs"));
38
- const os = __importStar(require("os"));
39
38
  const path = __importStar(require("path"));
40
39
  const runtime_1 = require("../../nerves/runtime");
40
+ const identity_1 = require("../identity");
41
41
  function messageId(nowIso) {
42
42
  return `msg-${nowIso.replace(/[^0-9]/g, "")}`;
43
43
  }
@@ -45,7 +45,7 @@ class FileMessageRouter {
45
45
  baseDir;
46
46
  now;
47
47
  constructor(options = {}) {
48
- this.baseDir = options.baseDir ?? path.join(os.homedir(), ".agentstate", "messages");
48
+ this.baseDir = options.baseDir ?? (0, identity_1.getAgentMessagesRoot)();
49
49
  this.now = options.now ?? (() => new Date().toISOString());
50
50
  fs.mkdirSync(this.baseDir, { recursive: true });
51
51
  }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createRealOsCronDeps = createRealOsCronDeps;
37
+ exports.createRealCrontabDeps = createRealCrontabDeps;
38
+ exports.resolveOuroBinaryPath = resolveOuroBinaryPath;
39
+ const child_process_1 = require("child_process");
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ const runtime_1 = require("../../nerves/runtime");
44
+ function createRealOsCronDeps() {
45
+ (0, runtime_1.emitNervesEvent)({
46
+ component: "daemon",
47
+ event: "daemon.os_cron_deps_created",
48
+ message: "created real OS cron deps",
49
+ meta: { platform: process.platform },
50
+ });
51
+ return {
52
+ exec: (cmd) => {
53
+ try {
54
+ (0, child_process_1.execSync)(cmd, { stdio: "ignore" });
55
+ }
56
+ catch {
57
+ /* best effort */
58
+ }
59
+ },
60
+ writeFile: (p, c) => fs.writeFileSync(p, c, "utf-8"),
61
+ removeFile: (p) => {
62
+ try {
63
+ fs.unlinkSync(p);
64
+ }
65
+ catch {
66
+ /* best effort — file may already be gone */
67
+ }
68
+ },
69
+ existsFile: (p) => fs.existsSync(p),
70
+ listDir: (dir) => {
71
+ try {
72
+ return fs.readdirSync(dir);
73
+ }
74
+ catch {
75
+ return [];
76
+ }
77
+ },
78
+ mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
79
+ homeDir: os.homedir(),
80
+ };
81
+ }
82
+ function createRealCrontabDeps() {
83
+ (0, runtime_1.emitNervesEvent)({
84
+ component: "daemon",
85
+ event: "daemon.crontab_deps_created",
86
+ message: "created real crontab deps",
87
+ meta: {},
88
+ });
89
+ /* v8 ignore start -- crontab exec closures: calling these in tests would modify the real system crontab @preserve */
90
+ return {
91
+ execOutput: (cmd) => (0, child_process_1.execSync)(cmd, { encoding: "utf-8" }),
92
+ execWrite: (cmd, stdin) => {
93
+ (0, child_process_1.execSync)(cmd, { input: stdin, stdio: ["pipe", "ignore", "ignore"] });
94
+ },
95
+ };
96
+ /* v8 ignore stop */
97
+ }
98
+ /* v8 ignore start -- ouro path resolution: probes process.argv, filesystem layout, and PATH; branches depend on install method and runtime environment @preserve */
99
+ function resolveOuroBinaryPath() {
100
+ // Try to resolve from process.argv[1] — the script being run
101
+ const scriptPath = process.argv[1];
102
+ if (scriptPath) {
103
+ // If running via node dist/heart/daemon/daemon-entry.js, resolve the ouro wrapper
104
+ // The ouro binary is typically at the package root's bin
105
+ const distDir = path.resolve(path.dirname(scriptPath));
106
+ const packageBin = path.resolve(distDir, "..", "..", "..", "node_modules", ".bin", "ouro");
107
+ if (fs.existsSync(packageBin)) {
108
+ return packageBin;
109
+ }
110
+ // Try the repo-local scripts/ouro.sh
111
+ const repoOuro = path.resolve(distDir, "..", "..", "..", "scripts", "ouro.sh");
112
+ if (fs.existsSync(repoOuro)) {
113
+ return repoOuro;
114
+ }
115
+ }
116
+ // Try which ouro
117
+ try {
118
+ const result = (0, child_process_1.execSync)("which ouro", { encoding: "utf-8" }).trim();
119
+ if (result.length > 0)
120
+ return result;
121
+ }
122
+ catch {
123
+ /* not on PATH */
124
+ }
125
+ // Fallback: use "ouro" and rely on PATH
126
+ (0, runtime_1.emitNervesEvent)({
127
+ component: "daemon",
128
+ event: "daemon.ouro_path_fallback",
129
+ message: "could not resolve full ouro binary path, falling back to 'ouro'",
130
+ meta: {},
131
+ });
132
+ return "ouro";
133
+ }
134
+ /* v8 ignore stop */
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const runtime_1 = require("../../nerves/runtime");
5
5
  const runtime_logging_1 = require("./runtime-logging");
6
- const ouro_bot_wrapper_1 = require("./ouro-bot-wrapper");
6
+ const ouro_bot_wrapper_1 = require("../versioning/ouro-bot-wrapper");
7
7
  (0, runtime_logging_1.configureDaemonRuntimeLogger)("ouro-bot");
8
8
  (0, runtime_1.emitNervesEvent)({
9
9
  component: "daemon",
@@ -12,12 +12,14 @@ const ouro_bot_wrapper_1 = require("./ouro-bot-wrapper");
12
12
  meta: { args: process.argv.slice(2) },
13
13
  });
14
14
  void (0, ouro_bot_wrapper_1.runOuroBotWrapper)(process.argv.slice(2)).catch((error) => {
15
+ const message = error instanceof Error ? error.message : String(error);
16
+ process.stderr.write(`${message}\n`);
15
17
  (0, runtime_1.emitNervesEvent)({
16
18
  level: "error",
17
19
  component: "daemon",
18
20
  event: "daemon.ouro_bot_entry_error",
19
21
  message: "ouro.bot wrapper entrypoint failed",
20
- meta: { error: error instanceof Error ? error.message : String(error) },
22
+ meta: { error: message },
21
23
  });
22
24
  process.exit(1);
23
25
  });
@@ -12,12 +12,14 @@ const runtime_logging_1 = require("./runtime-logging");
12
12
  meta: { args: process.argv.slice(2) },
13
13
  });
14
14
  void (0, daemon_cli_1.runOuroCli)(process.argv.slice(2)).catch((error) => {
15
+ const message = error instanceof Error ? error.message : String(error);
16
+ process.stderr.write(`${message}\n`);
15
17
  (0, runtime_1.emitNervesEvent)({
16
18
  level: "error",
17
19
  component: "daemon",
18
20
  event: "daemon.cli_entry_error",
19
21
  message: "ouro CLI entrypoint failed",
20
- meta: { error: error instanceof Error ? error.message : String(error) },
22
+ meta: { error: message },
21
23
  });
22
24
  process.exit(1);
23
25
  });
@@ -51,15 +51,70 @@ class DaemonProcessManager {
51
51
  now;
52
52
  setTimeoutFn;
53
53
  clearTimeoutFn;
54
+ cooldownRecoveryMs;
55
+ maxCooldownRetries;
56
+ existsSyncFn;
57
+ configCheckFn;
58
+ statusWriterFn;
59
+ onSnapshotChangeFn;
60
+ /**
61
+ * Notify the snapshot-change observer (if registered). Swallows any
62
+ * errors from the observer so process lifecycle code never fails
63
+ * because the observer threw.
64
+ */
65
+ notifySnapshotChange(snapshot) {
66
+ if (!this.onSnapshotChangeFn)
67
+ return;
68
+ try {
69
+ this.onSnapshotChangeFn(snapshot);
70
+ }
71
+ catch (error) {
72
+ (0, runtime_1.emitNervesEvent)({
73
+ level: "warn",
74
+ component: "daemon",
75
+ event: "daemon.snapshot_change_observer_error",
76
+ message: "snapshot-change observer threw",
77
+ meta: {
78
+ agent: snapshot.name,
79
+ error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error),
80
+ },
81
+ });
82
+ }
83
+ }
84
+ writeStatus(agent, text) {
85
+ try {
86
+ this.statusWriterFn(text);
87
+ }
88
+ catch (error) {
89
+ (0, runtime_1.emitNervesEvent)({
90
+ level: "warn",
91
+ component: "daemon",
92
+ event: "daemon.status_writer_error",
93
+ message: "daemon status writer threw",
94
+ meta: {
95
+ agent,
96
+ error: error instanceof Error ? error.message : String(error),
97
+ },
98
+ });
99
+ }
100
+ }
54
101
  constructor(options) {
55
102
  this.maxRestartsPerHour = options.maxRestartsPerHour ?? 10;
56
103
  this.stabilityThresholdMs = options.stabilityThresholdMs ?? 60_000;
57
104
  this.initialBackoffMs = options.initialBackoffMs ?? 1_000;
58
105
  this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
106
+ this.cooldownRecoveryMs = options.cooldownRecoveryMs ?? 5 * 60 * 1_000;
107
+ this.maxCooldownRetries = options.maxCooldownRetries ?? 3;
59
108
  this.spawnFn = options.spawn ?? ((command, args, spawnOptions) => (0, child_process_1.spawn)(command, args, spawnOptions));
60
109
  this.now = options.now ?? (() => Date.now());
61
110
  this.setTimeoutFn = options.setTimeoutFn ?? ((cb, delay) => setTimeout(cb, delay));
62
111
  this.clearTimeoutFn = options.clearTimeoutFn ?? ((timer) => clearTimeout(timer));
112
+ this.existsSyncFn = options.existsSync ?? null;
113
+ this.configCheckFn = options.configCheck ?? null;
114
+ this.statusWriterFn = options.statusWriter ?? ((text) => {
115
+ process.stderr.write(text);
116
+ });
117
+ this.onSnapshotChangeFn = options.onSnapshotChange ?? null;
63
118
  for (const agent of options.agents) {
64
119
  this.agents.set(agent.name, {
65
120
  config: agent,
@@ -67,6 +122,9 @@ class DaemonProcessManager {
67
122
  restartTimer: null,
68
123
  crashTimestamps: [],
69
124
  stopRequested: false,
125
+ cooldownTimer: null,
126
+ cooldownRetryCount: 0,
127
+ fastCrashCount: 0,
70
128
  snapshot: {
71
129
  name: agent.name,
72
130
  channel: agent.channel,
@@ -76,6 +134,10 @@ class DaemonProcessManager {
76
134
  startedAt: null,
77
135
  lastCrashAt: null,
78
136
  backoffMs: this.initialBackoffMs,
137
+ lastExitCode: null,
138
+ lastSignal: null,
139
+ errorReason: null,
140
+ fixHint: null,
79
141
  },
80
142
  });
81
143
  }
@@ -94,14 +156,75 @@ class DaemonProcessManager {
94
156
  this.clearRestartTimer(state);
95
157
  state.stopRequested = false;
96
158
  state.snapshot.status = "starting";
159
+ if (this.configCheckFn) {
160
+ const result = await this.configCheckFn(agent);
161
+ if (result.skip) {
162
+ state.snapshot.status = "stopped";
163
+ state.snapshot.errorReason = null;
164
+ state.snapshot.fixHint = null;
165
+ (0, runtime_1.emitNervesEvent)({
166
+ component: "daemon",
167
+ event: "daemon.agent_config_skipped",
168
+ message: result.error ?? "agent start skipped by config check",
169
+ meta: { agent, fix: result.fix ?? null },
170
+ });
171
+ this.notifySnapshotChange(state.snapshot);
172
+ return;
173
+ }
174
+ if (!result.ok) {
175
+ state.snapshot.status = "crashed";
176
+ // Surface the error and fix to the snapshot so sibling agents can
177
+ // read it via the pulse. Without this, the diagnosis stayed
178
+ // trapped in the nerves event and stderr — visible to humans
179
+ // running `ouro status` or grepping logs, but invisible to
180
+ // peer agents trying to coordinate around the broken state.
181
+ state.snapshot.errorReason = result.error ?? "agent config validation failed";
182
+ state.snapshot.fixHint = result.fix ?? null;
183
+ (0, runtime_1.emitNervesEvent)({
184
+ level: "error",
185
+ component: "daemon",
186
+ event: "daemon.agent_config_invalid",
187
+ message: result.error ?? "agent config validation failed",
188
+ meta: { agent, fix: result.fix ?? null },
189
+ });
190
+ this.writeStatus(agent, `[daemon] ${agent}: ${result.error}\n` +
191
+ (result.fix ? ` Fix: ${result.fix}\n` : ""));
192
+ this.notifySnapshotChange(state.snapshot);
193
+ return;
194
+ }
195
+ // Config check passed — clear any prior error so the pulse stops
196
+ // reporting the broken state. This is the recovery path: the user
197
+ // fixed their secrets/config, the next startAgent attempt sees a
198
+ // valid config, and the pulse goes quiet.
199
+ state.snapshot.errorReason = null;
200
+ state.snapshot.fixHint = null;
201
+ }
97
202
  const runCwd = (0, identity_1.getRepoRoot)();
98
203
  const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
204
+ if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
205
+ state.snapshot.status = "crashed";
206
+ (0, runtime_1.emitNervesEvent)({
207
+ level: "error",
208
+ component: "daemon",
209
+ event: "daemon.agent_entry_missing",
210
+ message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
211
+ meta: { agent, entryScript },
212
+ });
213
+ this.notifySnapshotChange(state.snapshot);
214
+ return;
215
+ }
99
216
  const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
100
217
  const child = this.spawnFn("node", args, {
101
218
  cwd: runCwd,
102
219
  env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
103
220
  stdio: ["ignore", "ignore", "ignore", "ipc"],
104
221
  });
222
+ /* v8 ignore next 7 -- defensive: spawn should always return a ChildProcess @preserve */
223
+ if (!child) {
224
+ state.snapshot.status = "crashed";
225
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.agent_spawn_failed", message: "spawn returned null", meta: { agent } });
226
+ return;
227
+ }
105
228
  state.process = child;
106
229
  state.snapshot.status = "running";
107
230
  state.snapshot.pid = child.pid ?? null;
@@ -112,6 +235,18 @@ class DaemonProcessManager {
112
235
  message: "daemon started managed agent process",
113
236
  meta: { agent, pid: child.pid ?? null, cwd: runCwd },
114
237
  });
238
+ this.notifySnapshotChange(state.snapshot);
239
+ /* v8 ignore start — child process error handler; requires real spawn to trigger */
240
+ child.on("error", (err) => {
241
+ (0, runtime_1.emitNervesEvent)({
242
+ level: "warn",
243
+ component: "daemon",
244
+ event: "daemon.agent_process_error",
245
+ message: "managed agent process emitted error",
246
+ meta: { agent, error: err.message },
247
+ });
248
+ });
249
+ /* v8 ignore stop */
115
250
  child.once("exit", (code, signal) => {
116
251
  this.onExit(state, code, signal);
117
252
  });
@@ -119,10 +254,12 @@ class DaemonProcessManager {
119
254
  async stopAgent(agent) {
120
255
  const state = this.requireAgent(agent);
121
256
  this.clearRestartTimer(state);
257
+ this.clearCooldownTimer(state);
122
258
  state.stopRequested = true;
123
259
  if (!state.process) {
124
260
  state.snapshot.status = "stopped";
125
261
  state.snapshot.pid = null;
262
+ this.notifySnapshotChange(state.snapshot);
126
263
  return;
127
264
  }
128
265
  const child = state.process;
@@ -141,6 +278,7 @@ class DaemonProcessManager {
141
278
  meta: { agent },
142
279
  });
143
280
  }
281
+ this.notifySnapshotChange(state.snapshot);
144
282
  }
145
283
  async restartAgent(agent) {
146
284
  await this.stopAgent(agent);
@@ -179,6 +317,8 @@ class DaemonProcessManager {
179
317
  return;
180
318
  state.process = null;
181
319
  state.snapshot.pid = null;
320
+ state.snapshot.lastExitCode = code;
321
+ state.snapshot.lastSignal = signal;
182
322
  const crashed = !state.stopRequested && code !== 0;
183
323
  const now = this.now();
184
324
  const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
@@ -195,13 +335,45 @@ class DaemonProcessManager {
195
335
  if (runDuration >= this.stabilityThresholdMs) {
196
336
  state.snapshot.backoffMs = this.initialBackoffMs;
197
337
  }
338
+ this.notifySnapshotChange(state.snapshot);
198
339
  return;
199
340
  }
200
341
  state.snapshot.lastCrashAt = new Date(now).toISOString();
342
+ // Fast-crash detection: if the agent dies within 5 seconds of starting, it's likely
343
+ // a configuration issue (missing credentials, bad provider, etc.) not a transient failure.
344
+ // After 3 consecutive fast crashes, stop retrying and mark as config-failed.
345
+ const FAST_CRASH_THRESHOLD_MS = 5000;
346
+ const FAST_CRASH_MAX = 3;
347
+ if (runDuration < FAST_CRASH_THRESHOLD_MS) {
348
+ state.fastCrashCount = state.fastCrashCount + 1;
349
+ if (state.fastCrashCount >= FAST_CRASH_MAX) {
350
+ state.snapshot.status = "crashed";
351
+ // Capture the fast-crash diagnosis on the snapshot so it surfaces
352
+ // via the pulse. The error message is prescriptive: it tells the
353
+ // user (and their sibling agents) exactly what to do.
354
+ state.snapshot.errorReason = `agent crashed ${FAST_CRASH_MAX} times within ${FAST_CRASH_THRESHOLD_MS}ms of startup — likely a configuration issue (missing credentials, bad provider).`;
355
+ state.snapshot.fixHint = `Fix the config and run \`ouro up\` to restart, or check daemon logs for the underlying error.`;
356
+ (0, runtime_1.emitNervesEvent)({
357
+ level: "error",
358
+ component: "daemon",
359
+ event: "daemon.agent_config_failure",
360
+ message: `agent crashed ${FAST_CRASH_MAX} times within ${FAST_CRASH_THRESHOLD_MS}ms of startup — likely a configuration issue (missing credentials, bad provider). Not retrying. Fix the config and run \`ouro up\` to restart.`,
361
+ meta: { agent: state.config.name, fastCrashCount: state.fastCrashCount, avgRunDurationMs: runDuration },
362
+ });
363
+ this.notifySnapshotChange(state.snapshot);
364
+ return; // Don't schedule cooldown recovery — this needs human/agent intervention
365
+ }
366
+ }
367
+ else {
368
+ // Reset fast-crash counter on a stable run
369
+ state.fastCrashCount = 0;
370
+ }
201
371
  state.crashTimestamps = state.crashTimestamps.filter((crashTs) => crashTs >= startOfHour(now));
202
372
  state.crashTimestamps.push(now);
203
373
  if (state.crashTimestamps.length > this.maxRestartsPerHour) {
204
374
  state.snapshot.status = "crashed";
375
+ state.snapshot.errorReason = `agent exceeded restart limit (${this.maxRestartsPerHour}/hr) — entering cooldown`;
376
+ state.snapshot.fixHint = "investigate why the agent keeps crashing; cooldown will retry shortly";
205
377
  (0, runtime_1.emitNervesEvent)({
206
378
  level: "error",
207
379
  component: "daemon",
@@ -209,6 +381,8 @@ class DaemonProcessManager {
209
381
  message: "managed agent exceeded restart limit and is marked crashed",
210
382
  meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
211
383
  });
384
+ this.notifySnapshotChange(state.snapshot);
385
+ this.scheduleCooldownRecovery(state);
212
386
  return;
213
387
  }
214
388
  state.snapshot.status = "starting";
@@ -219,6 +393,7 @@ class DaemonProcessManager {
219
393
  state.restartTimer = this.setTimeoutFn(() => {
220
394
  void this.startAgent(state.config.name);
221
395
  }, waitMs);
396
+ this.notifySnapshotChange(state.snapshot);
222
397
  }
223
398
  clearRestartTimer(state) {
224
399
  if (state.restartTimer === null)
@@ -226,6 +401,45 @@ class DaemonProcessManager {
226
401
  this.clearTimeoutFn(state.restartTimer);
227
402
  state.restartTimer = null;
228
403
  }
404
+ scheduleCooldownRecovery(state) {
405
+ if (state.cooldownRetryCount >= this.maxCooldownRetries) {
406
+ (0, runtime_1.emitNervesEvent)({
407
+ level: "error",
408
+ component: "daemon",
409
+ event: "daemon.agent_permanent_failure",
410
+ message: "managed agent exhausted all cooldown retries — permanently stopped",
411
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
412
+ });
413
+ return;
414
+ }
415
+ this.clearCooldownTimer(state);
416
+ state.cooldownTimer = this.setTimeoutFn(() => {
417
+ state.cooldownRetryCount += 1;
418
+ state.crashTimestamps = [];
419
+ state.snapshot.backoffMs = this.initialBackoffMs;
420
+ state.snapshot.status = "starting";
421
+ state.snapshot.restartCount += 1;
422
+ (0, runtime_1.emitNervesEvent)({
423
+ component: "daemon",
424
+ event: "daemon.agent_cooldown_recovery",
425
+ message: "attempting cooldown recovery for managed agent",
426
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
427
+ });
428
+ void this.startAgent(state.config.name);
429
+ }, this.cooldownRecoveryMs);
430
+ (0, runtime_1.emitNervesEvent)({
431
+ component: "daemon",
432
+ event: "daemon.agent_cooldown_scheduled",
433
+ message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
434
+ meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
435
+ });
436
+ }
437
+ clearCooldownTimer(state) {
438
+ if (state.cooldownTimer === null)
439
+ return;
440
+ this.clearTimeoutFn(state.cooldownTimer);
441
+ state.cooldownTimer = null;
442
+ }
229
443
  requireAgent(agent) {
230
444
  const state = this.agents.get(agent);
231
445
  if (!state) {