@ouro.bot/cli 0.1.0-alpha.65 → 0.1.0-alpha.651

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 (435) hide show
  1. package/README.md +127 -23
  2. package/RepairGuide.ouro/agent.json +5 -0
  3. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  4. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  5. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  6. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  7. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  8. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  9. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  12. package/changelog.json +4157 -14
  13. package/dist/arc/attention-types.js +8 -0
  14. package/dist/arc/cares.js +144 -0
  15. package/dist/arc/episodes.js +118 -0
  16. package/dist/arc/evolution.js +487 -0
  17. package/dist/arc/intentions.js +134 -0
  18. package/dist/arc/json-store.js +117 -0
  19. package/dist/arc/obligations.js +270 -0
  20. package/dist/arc/packets.js +288 -0
  21. package/dist/arc/presence.js +185 -0
  22. package/dist/arc/task-lifecycle.js +57 -0
  23. package/dist/heart/active-work.js +860 -43
  24. package/dist/heart/agent-entry.js +69 -3
  25. package/dist/heart/attachments/image-normalize.js +194 -0
  26. package/dist/heart/attachments/materialize.js +97 -0
  27. package/dist/heart/attachments/originals.js +88 -0
  28. package/dist/heart/attachments/render.js +29 -0
  29. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  30. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  31. package/dist/heart/attachments/sources/index.js +16 -0
  32. package/dist/heart/attachments/store.js +103 -0
  33. package/dist/heart/attachments/types.js +93 -0
  34. package/dist/heart/auth/auth-flow.js +479 -0
  35. package/dist/heart/awaiting/await-alert.js +146 -0
  36. package/dist/heart/awaiting/await-expiry.js +108 -0
  37. package/dist/heart/awaiting/await-loader.js +91 -0
  38. package/dist/heart/awaiting/await-parser.js +141 -0
  39. package/dist/heart/awaiting/await-runtime-state.js +100 -0
  40. package/dist/heart/awaiting/await-scheduler.js +377 -0
  41. package/dist/heart/background-operations.js +281 -0
  42. package/dist/heart/bridges/manager.js +137 -17
  43. package/dist/heart/bridges/store.js +14 -2
  44. package/dist/heart/bundle-state.js +168 -0
  45. package/dist/heart/commitments.js +135 -0
  46. package/dist/heart/config-registry.js +322 -0
  47. package/dist/heart/config.js +118 -119
  48. package/dist/heart/core.js +1123 -247
  49. package/dist/heart/cross-chat-delivery.js +3 -18
  50. package/dist/heart/daemon/agent-config-check.js +419 -0
  51. package/dist/heart/daemon/agent-discovery.js +102 -3
  52. package/dist/heart/daemon/agent-service.js +522 -0
  53. package/dist/heart/daemon/agentic-repair.js +547 -0
  54. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  55. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  56. package/dist/heart/daemon/cadence.js +70 -0
  57. package/dist/heart/daemon/cli-defaults.js +780 -0
  58. package/dist/heart/daemon/cli-desk.js +322 -0
  59. package/dist/heart/daemon/cli-exec.js +7480 -0
  60. package/dist/heart/daemon/cli-help.js +505 -0
  61. package/dist/heart/daemon/cli-parse.js +1554 -0
  62. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  63. package/dist/heart/daemon/cli-render.js +763 -0
  64. package/dist/heart/daemon/cli-types.js +8 -0
  65. package/dist/heart/daemon/connect-bay.js +323 -0
  66. package/dist/heart/daemon/daemon-cli.js +29 -1750
  67. package/dist/heart/daemon/daemon-entry.js +485 -2
  68. package/dist/heart/daemon/daemon-health.js +176 -0
  69. package/dist/heart/daemon/daemon-rollup.js +57 -0
  70. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  71. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  72. package/dist/heart/daemon/daemon.js +932 -74
  73. package/dist/heart/daemon/dns-workflow.js +394 -0
  74. package/dist/heart/daemon/doctor-types.js +8 -0
  75. package/dist/heart/daemon/doctor.js +873 -0
  76. package/dist/heart/daemon/health-monitor.js +122 -1
  77. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  78. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  79. package/dist/heart/daemon/http-health-probe.js +80 -0
  80. package/dist/heart/daemon/human-command-screens.js +234 -0
  81. package/dist/heart/daemon/human-readiness.js +114 -0
  82. package/dist/heart/daemon/inner-status.js +89 -0
  83. package/dist/heart/daemon/interactive-repair.js +394 -0
  84. package/dist/heart/daemon/launchd.js +37 -8
  85. package/dist/heart/daemon/log-tailer.js +79 -10
  86. package/dist/heart/daemon/logs-prune.js +110 -0
  87. package/dist/heart/daemon/mcp-canary.js +297 -0
  88. package/dist/heart/daemon/message-router.js +6 -2
  89. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  90. package/dist/heart/daemon/os-cron-deps.js +135 -0
  91. package/dist/heart/daemon/os-cron.js +14 -12
  92. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  93. package/dist/heart/daemon/ouro-entry.js +3 -1
  94. package/dist/heart/daemon/plugin-cli.js +432 -0
  95. package/dist/heart/daemon/process-manager.js +510 -40
  96. package/dist/heart/daemon/provider-discovery.js +137 -0
  97. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  98. package/dist/heart/daemon/pulse.js +475 -0
  99. package/dist/heart/daemon/readiness-repair.js +365 -0
  100. package/dist/heart/daemon/run-hooks.js +2 -0
  101. package/dist/heart/daemon/runtime-logging.js +35 -14
  102. package/dist/heart/daemon/runtime-metadata.js +2 -30
  103. package/dist/heart/daemon/safe-mode.js +161 -0
  104. package/dist/heart/daemon/sense-manager.js +493 -38
  105. package/dist/heart/daemon/session-id-resolver.js +131 -0
  106. package/dist/heart/daemon/skill-management-installer.js +1 -1
  107. package/dist/heart/daemon/socket-client.js +158 -11
  108. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  109. package/dist/heart/daemon/startup-tui.js +330 -0
  110. package/dist/heart/daemon/task-scheduler.js +117 -39
  111. package/dist/heart/daemon/terminal-ui.js +499 -0
  112. package/dist/heart/daemon/thoughts.js +229 -17
  113. package/dist/heart/daemon/up-progress.js +366 -0
  114. package/dist/heart/daemon/vault-items.js +56 -0
  115. package/dist/heart/delegation.js +1 -4
  116. package/dist/heart/habits/habit-migration.js +189 -0
  117. package/dist/heart/habits/habit-parser.js +140 -0
  118. package/dist/heart/habits/habit-runtime-state.js +100 -0
  119. package/dist/heart/habits/habit-scheduler.js +372 -0
  120. package/dist/heart/{daemon → hatch}/hatch-flow.js +32 -56
  121. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  122. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  123. package/dist/heart/{daemon → hatch}/specialist-tools.js +37 -14
  124. package/dist/heart/identity.js +168 -57
  125. package/dist/heart/kept-notes.js +357 -0
  126. package/dist/heart/kicks.js +1 -1
  127. package/dist/heart/machine-identity.js +161 -0
  128. package/dist/heart/mail-import-discovery.js +353 -0
  129. package/dist/heart/mailbox/mailbox-http-hooks.js +66 -0
  130. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  131. package/dist/heart/mailbox/mailbox-http-routes.js +246 -0
  132. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  133. package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
  134. package/dist/heart/mailbox/mailbox-http.js +99 -0
  135. package/dist/heart/mailbox/mailbox-read.js +31 -0
  136. package/dist/heart/mailbox/mailbox-types.js +27 -0
  137. package/dist/heart/mailbox/mailbox-view.js +197 -0
  138. package/dist/heart/mailbox/readers/agent-machine.js +418 -0
  139. package/dist/heart/mailbox/readers/continuity-readers.js +319 -0
  140. package/dist/heart/mailbox/readers/mail.js +375 -0
  141. package/dist/heart/mailbox/readers/runtime-readers.js +756 -0
  142. package/dist/heart/mailbox/readers/sessions.js +232 -0
  143. package/dist/heart/mailbox/readers/shared.js +111 -0
  144. package/dist/heart/mcp/mcp-server.js +692 -0
  145. package/dist/heart/migrate-config.js +100 -0
  146. package/dist/heart/model-capabilities.js +19 -0
  147. package/dist/heart/orientation-frame.js +217 -0
  148. package/dist/heart/platform.js +81 -0
  149. package/dist/heart/provider-attempt.js +134 -0
  150. package/dist/heart/provider-binding-resolver.js +272 -0
  151. package/dist/heart/provider-credentials.js +425 -0
  152. package/dist/heart/provider-failover.js +311 -0
  153. package/dist/heart/provider-models.js +81 -0
  154. package/dist/heart/provider-ping.js +262 -0
  155. package/dist/heart/provider-readiness-cache.js +40 -0
  156. package/dist/heart/provider-visibility.js +188 -0
  157. package/dist/heart/providers/anthropic-token.js +131 -0
  158. package/dist/heart/providers/anthropic.js +139 -52
  159. package/dist/heart/providers/azure.js +23 -11
  160. package/dist/heart/providers/error-classification.js +127 -0
  161. package/dist/heart/providers/github-copilot.js +145 -0
  162. package/dist/heart/providers/minimax-vlm.js +189 -0
  163. package/dist/heart/providers/minimax.js +26 -8
  164. package/dist/heart/providers/openai-codex-token.js +349 -0
  165. package/dist/heart/providers/openai-codex.js +55 -40
  166. package/dist/heart/runtime-capability-check.js +170 -0
  167. package/dist/heart/runtime-credentials.js +367 -0
  168. package/dist/heart/runtime-cwd.js +87 -0
  169. package/dist/heart/sense-truth.js +13 -4
  170. package/dist/heart/session-activity.js +48 -24
  171. package/dist/heart/session-events.js +1163 -0
  172. package/dist/heart/session-playback-cli-main.js +5 -0
  173. package/dist/heart/session-playback-cli.js +36 -0
  174. package/dist/heart/session-playback.js +231 -0
  175. package/dist/heart/session-stats-cli-main.js +5 -0
  176. package/dist/heart/session-stats.js +182 -0
  177. package/dist/heart/session-transcript.js +133 -0
  178. package/dist/heart/start-of-turn-packet.js +345 -0
  179. package/dist/heart/streaming.js +44 -27
  180. package/dist/heart/structured-output.js +196 -0
  181. package/dist/heart/sync-classification.js +176 -0
  182. package/dist/heart/sync.js +449 -0
  183. package/dist/heart/target-resolution.js +9 -5
  184. package/dist/heart/tempo.js +93 -0
  185. package/dist/heart/temporal-view.js +41 -0
  186. package/dist/heart/timeouts.js +101 -0
  187. package/dist/heart/tool-activity-callbacks.js +59 -0
  188. package/dist/heart/tool-description.js +143 -0
  189. package/dist/heart/tool-friction.js +55 -0
  190. package/dist/heart/tool-loop.js +200 -0
  191. package/dist/heart/turn-context.js +389 -0
  192. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  193. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  194. package/dist/heart/versioning/ouro-path-installer.js +426 -0
  195. package/dist/heart/versioning/ouro-version-manager.js +409 -0
  196. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  197. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  198. package/dist/heart/versioning/update-hooks.js +154 -0
  199. package/dist/mailbox-ui/assets/index-9-AxCxuB.js +61 -0
  200. package/dist/mailbox-ui/assets/index-CWzt267f.css +1 -0
  201. package/dist/mailbox-ui/index.html +15 -0
  202. package/dist/mailroom/attention.js +167 -0
  203. package/dist/mailroom/autonomy.js +209 -0
  204. package/dist/mailroom/blob-store.js +715 -0
  205. package/dist/mailroom/body-cache.js +61 -0
  206. package/dist/mailroom/core.js +788 -0
  207. package/dist/mailroom/entry.js +160 -0
  208. package/dist/mailroom/file-store.js +568 -0
  209. package/dist/mailroom/mbox-import.js +393 -0
  210. package/dist/mailroom/migration.js +164 -0
  211. package/dist/mailroom/outbound.js +380 -0
  212. package/dist/mailroom/policy.js +263 -0
  213. package/dist/mailroom/reader.js +233 -0
  214. package/dist/mailroom/search-cache.js +334 -0
  215. package/dist/mailroom/search-relevance.js +319 -0
  216. package/dist/mailroom/smtp-ingress.js +176 -0
  217. package/dist/mailroom/source-state.js +176 -0
  218. package/dist/mailroom/thread.js +109 -0
  219. package/dist/mailroom/travel-extract.js +89 -0
  220. package/dist/mind/bundle-manifest.js +14 -1
  221. package/dist/mind/context.js +251 -101
  222. package/dist/mind/desk-section.js +310 -0
  223. package/dist/mind/diary-integrity.js +60 -0
  224. package/dist/mind/{memory.js → diary.js} +68 -76
  225. package/dist/mind/embedding-provider.js +60 -0
  226. package/dist/mind/file-state.js +179 -0
  227. package/dist/mind/friends/channel.js +39 -0
  228. package/dist/mind/friends/resolver.js +54 -2
  229. package/dist/mind/friends/store-file.js +48 -4
  230. package/dist/mind/friends/types.js +2 -2
  231. package/dist/mind/journal-index.js +162 -0
  232. package/dist/mind/note-search.js +268 -0
  233. package/dist/mind/obligation-steering.js +221 -0
  234. package/dist/mind/pending.js +6 -1
  235. package/dist/mind/prompt-refresh.js +3 -2
  236. package/dist/mind/prompt.js +1051 -138
  237. package/dist/mind/provenance-trust.js +26 -0
  238. package/dist/mind/scrutiny.js +173 -0
  239. package/dist/nerves/cli-logging.js +7 -1
  240. package/dist/nerves/coverage/audit-rules.js +15 -6
  241. package/dist/nerves/coverage/audit.js +28 -2
  242. package/dist/nerves/coverage/cli.js +1 -1
  243. package/dist/nerves/coverage/contract.js +5 -5
  244. package/dist/nerves/coverage/file-completeness.js +139 -5
  245. package/dist/nerves/event-buffer.js +111 -0
  246. package/dist/nerves/index.js +224 -4
  247. package/dist/nerves/observation.js +20 -0
  248. package/dist/nerves/redact.js +79 -0
  249. package/dist/nerves/review/cli-main.js +5 -0
  250. package/dist/nerves/review/cli.js +156 -0
  251. package/dist/nerves/review/core.js +152 -0
  252. package/dist/nerves/runtime.js +5 -1
  253. package/dist/repertoire/ado-client.js +15 -56
  254. package/dist/repertoire/ado-semantic.js +16 -10
  255. package/dist/repertoire/api-client.js +97 -0
  256. package/dist/repertoire/bitwarden-store.js +1041 -0
  257. package/dist/repertoire/bundle-templates.js +72 -0
  258. package/dist/repertoire/bw-installer.js +180 -0
  259. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  260. package/dist/repertoire/coding/context-pack.js +331 -0
  261. package/dist/repertoire/coding/feedback.js +197 -30
  262. package/dist/repertoire/coding/manager.js +166 -10
  263. package/dist/repertoire/coding/spawner.js +55 -9
  264. package/dist/repertoire/coding/tools.js +219 -7
  265. package/dist/repertoire/commerce-errors.js +109 -0
  266. package/dist/repertoire/commerce-self-test.js +156 -0
  267. package/dist/repertoire/credential-access.js +178 -0
  268. package/dist/repertoire/desk/classifier.js +362 -0
  269. package/dist/repertoire/duffel-client.js +185 -0
  270. package/dist/repertoire/github-client.js +14 -55
  271. package/dist/repertoire/graph-client.js +11 -52
  272. package/dist/repertoire/guardrails.js +136 -25
  273. package/dist/repertoire/mcp-client.js +295 -0
  274. package/dist/repertoire/mcp-manager.js +403 -0
  275. package/dist/repertoire/mcp-tools.js +83 -0
  276. package/dist/repertoire/plugin-mcp.js +175 -0
  277. package/dist/repertoire/plugins.js +253 -0
  278. package/dist/repertoire/shell-sessions.js +133 -0
  279. package/dist/repertoire/skills.js +48 -4
  280. package/dist/repertoire/stripe-client.js +131 -0
  281. package/dist/repertoire/tool-results.js +29 -0
  282. package/dist/repertoire/tools-attachments.js +317 -0
  283. package/dist/repertoire/tools-awaiting.js +372 -0
  284. package/dist/repertoire/tools-base.js +59 -1082
  285. package/dist/repertoire/tools-bluebubbles.js +2 -0
  286. package/dist/repertoire/tools-bridge.js +144 -0
  287. package/dist/repertoire/tools-bundle.js +993 -0
  288. package/dist/repertoire/tools-config.js +186 -0
  289. package/dist/repertoire/tools-continuity.js +252 -0
  290. package/dist/repertoire/tools-credential.js +383 -0
  291. package/dist/repertoire/tools-evolution.js +527 -0
  292. package/dist/repertoire/tools-files.js +344 -0
  293. package/dist/repertoire/tools-flight.js +227 -0
  294. package/dist/repertoire/tools-flow.js +119 -0
  295. package/dist/repertoire/tools-github.js +3 -8
  296. package/dist/repertoire/tools-mail.js +1975 -0
  297. package/dist/repertoire/tools-notes.js +438 -0
  298. package/dist/repertoire/tools-obligations.js +143 -0
  299. package/dist/repertoire/tools-orientation.js +31 -0
  300. package/dist/repertoire/tools-record.js +464 -0
  301. package/dist/repertoire/tools-runtime.js +150 -0
  302. package/dist/repertoire/tools-session.js +766 -0
  303. package/dist/repertoire/tools-shell.js +120 -0
  304. package/dist/repertoire/tools-stripe.js +182 -0
  305. package/dist/repertoire/tools-surface.js +344 -0
  306. package/dist/repertoire/tools-teams.js +12 -39
  307. package/dist/repertoire/tools-travel.js +125 -0
  308. package/dist/repertoire/tools-trip.js +982 -0
  309. package/dist/repertoire/tools-user-profile.js +146 -0
  310. package/dist/repertoire/tools-vault.js +40 -0
  311. package/dist/repertoire/tools-voice.js +145 -0
  312. package/dist/repertoire/tools.js +193 -77
  313. package/dist/repertoire/travel-api-client.js +360 -0
  314. package/dist/repertoire/user-profile.js +131 -0
  315. package/dist/repertoire/vault-setup.js +246 -0
  316. package/dist/repertoire/vault-unlock.js +594 -0
  317. package/dist/scripts/claude-code-hook.js +41 -0
  318. package/dist/scripts/claude-code-stop-hook.js +47 -0
  319. package/dist/senses/attention-queue.js +186 -0
  320. package/dist/senses/await-turn-message.js +58 -0
  321. package/dist/senses/bluebubbles/active-turns.js +216 -0
  322. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  323. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  324. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +219 -18
  325. package/dist/senses/bluebubbles/entry.js +77 -0
  326. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
  327. package/dist/senses/bluebubbles/index.js +2737 -0
  328. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  329. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  330. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
  331. package/dist/senses/bluebubbles/processed-log.js +133 -0
  332. package/dist/senses/bluebubbles/replay.js +137 -0
  333. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  334. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  335. package/dist/senses/bluebubbles-meta-guard.js +40 -0
  336. package/dist/senses/cli/bracketed-paste.js +82 -0
  337. package/dist/senses/cli/image-paste.js +287 -0
  338. package/dist/senses/cli/image-ref-navigation.js +75 -0
  339. package/dist/senses/cli/ink-app.js +156 -0
  340. package/dist/senses/cli/inline-diff.js +64 -0
  341. package/dist/senses/cli/input-keys.js +174 -0
  342. package/dist/senses/cli/kill-ring.js +86 -0
  343. package/dist/senses/cli/message-list.js +51 -0
  344. package/dist/senses/cli/ouro-tui.js +607 -0
  345. package/dist/senses/cli/spinner-imperative.js +135 -0
  346. package/dist/senses/cli/spinner.js +101 -0
  347. package/dist/senses/cli/status-line.js +60 -0
  348. package/dist/senses/cli/streaming-markdown.js +526 -0
  349. package/dist/senses/cli/tool-display.js +85 -0
  350. package/dist/senses/cli/tool-render.js +85 -0
  351. package/dist/senses/cli/tui-store.js +240 -0
  352. package/dist/senses/cli/virtual-list.js +35 -0
  353. package/dist/senses/cli-entry.js +60 -8
  354. package/dist/senses/cli-layout.js +100 -0
  355. package/dist/senses/cli.js +517 -204
  356. package/dist/senses/commands.js +66 -3
  357. package/dist/senses/habit-turn-message.js +108 -0
  358. package/dist/senses/inner-dialog-worker.js +254 -22
  359. package/dist/senses/inner-dialog.js +505 -40
  360. package/dist/senses/mail-entry.js +66 -0
  361. package/dist/senses/mail.js +379 -0
  362. package/dist/senses/pipeline.js +711 -181
  363. package/dist/senses/proactive-content-guard.js +51 -0
  364. package/dist/senses/shared-turn.js +393 -0
  365. package/dist/senses/surface-tool.js +108 -0
  366. package/dist/senses/teams-entry.js +60 -8
  367. package/dist/senses/teams.js +390 -98
  368. package/dist/senses/trust-gate.js +100 -5
  369. package/dist/senses/voice/audio-playback.js +237 -0
  370. package/dist/senses/voice/audio-routing.js +119 -0
  371. package/dist/senses/voice/elevenlabs.js +202 -0
  372. package/dist/senses/voice/floor-control.js +431 -0
  373. package/dist/senses/voice/floor-controller.js +115 -0
  374. package/dist/senses/voice/golden-path.js +116 -0
  375. package/dist/senses/voice/index.js +29 -0
  376. package/dist/senses/voice/meeting.js +113 -0
  377. package/dist/senses/voice/outbound.js +190 -0
  378. package/dist/senses/voice/phone.js +33 -0
  379. package/dist/senses/voice/playback.js +139 -0
  380. package/dist/senses/voice/realtime-eval.js +496 -0
  381. package/dist/senses/voice/realtime-trace.js +531 -0
  382. package/dist/senses/voice/transcript.js +70 -0
  383. package/dist/senses/voice/turn.js +191 -0
  384. package/dist/senses/voice/twilio-phone-runtime.js +807 -0
  385. package/dist/senses/voice/twilio-phone.js +5079 -0
  386. package/dist/senses/voice/types.js +2 -0
  387. package/dist/senses/voice/whisper.js +161 -0
  388. package/dist/senses/voice-entry.js +81 -0
  389. package/dist/senses/voice-realtime-eval-command.js +99 -0
  390. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  391. package/dist/senses/voice-twilio-entry.js +87 -0
  392. package/dist/trips/core.js +138 -0
  393. package/dist/trips/store.js +265 -0
  394. package/dist/util/frontmatter.js +53 -0
  395. package/package.json +53 -10
  396. package/skills/agent-commerce.md +106 -0
  397. package/skills/browser-navigation.md +117 -0
  398. package/skills/commerce-setup-guide.md +116 -0
  399. package/skills/commerce-setup.md +84 -0
  400. package/skills/configure-dev-tools.md +99 -0
  401. package/skills/travel-planning.md +138 -0
  402. package/dist/heart/daemon/auth-flow.js +0 -351
  403. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  404. package/dist/heart/daemon/update-hooks.js +0 -138
  405. package/dist/heart/safe-workspace.js +0 -228
  406. package/dist/heart/session-recall.js +0 -116
  407. package/dist/mind/associative-recall.js +0 -209
  408. package/dist/repertoire/tasks/board.js +0 -134
  409. package/dist/repertoire/tasks/index.js +0 -224
  410. package/dist/repertoire/tasks/lifecycle.js +0 -80
  411. package/dist/repertoire/tasks/middleware.js +0 -65
  412. package/dist/repertoire/tasks/parser.js +0 -173
  413. package/dist/repertoire/tasks/scanner.js +0 -132
  414. package/dist/repertoire/tasks/transitions.js +0 -144
  415. package/dist/senses/bluebubbles-entry.js +0 -13
  416. package/dist/senses/bluebubbles.js +0 -1177
  417. package/dist/senses/debug-activity.js +0 -148
  418. package/subagents/README.md +0 -7
  419. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  420. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  421. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  422. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  423. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  424. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  425. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  426. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  427. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  428. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  429. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  430. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  431. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  432. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  433. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  434. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  435. /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,6 +41,19 @@ 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();
46
59
  maxRestartsPerHour;
@@ -51,24 +64,111 @@ class DaemonProcessManager {
51
64
  now;
52
65
  setTimeoutFn;
53
66
  clearTimeoutFn;
67
+ cooldownRecoveryMs;
68
+ maxCooldownRetries;
69
+ startupStaleAfterMs;
54
70
  existsSyncFn;
71
+ configCheckFn;
72
+ statusWriterFn;
73
+ onSnapshotChangeFn;
74
+ /**
75
+ * Notify the snapshot-change observer (if registered). Swallows any
76
+ * errors from the observer so process lifecycle code never fails
77
+ * because the observer threw.
78
+ */
79
+ notifySnapshotChange(snapshot) {
80
+ if (!this.onSnapshotChangeFn)
81
+ return;
82
+ try {
83
+ this.onSnapshotChangeFn(snapshot);
84
+ }
85
+ catch (error) {
86
+ (0, runtime_1.emitNervesEvent)({
87
+ level: "warn",
88
+ component: "daemon",
89
+ event: "daemon.snapshot_change_observer_error",
90
+ message: "snapshot-change observer threw",
91
+ meta: {
92
+ agent: snapshot.name,
93
+ error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error),
94
+ },
95
+ });
96
+ }
97
+ }
98
+ writeStatus(agent, text) {
99
+ try {
100
+ this.statusWriterFn(text);
101
+ }
102
+ catch (error) {
103
+ (0, runtime_1.emitNervesEvent)({
104
+ level: "warn",
105
+ component: "daemon",
106
+ event: "daemon.status_writer_error",
107
+ message: "daemon status writer threw",
108
+ meta: {
109
+ agent,
110
+ error: error instanceof Error ? error.message : String(error),
111
+ },
112
+ });
113
+ }
114
+ }
115
+ currentTimeMs() {
116
+ const value = this.now();
117
+ return Number.isFinite(value) ? value : Date.now();
118
+ }
119
+ isStartAttemptCurrent(state, attemptId) {
120
+ return state.startAttemptId === attemptId;
121
+ }
122
+ markConfigCheckFailed(state, errorReason, fixHint) {
123
+ const agent = state.config.name;
124
+ state.snapshot.status = "crashed";
125
+ state.snapshot.errorReason = errorReason;
126
+ state.snapshot.fixHint = fixHint;
127
+ (0, runtime_1.emitNervesEvent)({
128
+ level: "error",
129
+ component: "daemon",
130
+ event: "daemon.agent_config_invalid",
131
+ message: errorReason,
132
+ meta: { agent, fix: fixHint },
133
+ });
134
+ this.writeStatus(agent, `[daemon] ${agent}: ${errorReason}\n` +
135
+ (fixHint ? ` Fix: ${fixHint}\n` : ""));
136
+ this.notifySnapshotChange(state.snapshot);
137
+ }
55
138
  constructor(options) {
56
139
  this.maxRestartsPerHour = options.maxRestartsPerHour ?? 10;
57
140
  this.stabilityThresholdMs = options.stabilityThresholdMs ?? 60_000;
58
141
  this.initialBackoffMs = options.initialBackoffMs ?? 1_000;
59
142
  this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
143
+ this.cooldownRecoveryMs = options.cooldownRecoveryMs ?? 5 * 60 * 1_000;
144
+ this.maxCooldownRetries = options.maxCooldownRetries ?? 3;
145
+ this.startupStaleAfterMs = options.startupStaleAfterMs ?? 45_000;
60
146
  this.spawnFn = options.spawn ?? ((command, args, spawnOptions) => (0, child_process_1.spawn)(command, args, spawnOptions));
61
147
  this.now = options.now ?? (() => Date.now());
62
148
  this.setTimeoutFn = options.setTimeoutFn ?? ((cb, delay) => setTimeout(cb, delay));
63
149
  this.clearTimeoutFn = options.clearTimeoutFn ?? ((timer) => clearTimeout(timer));
64
150
  this.existsSyncFn = options.existsSync ?? null;
151
+ this.configCheckFn = options.configCheck ?? null;
152
+ this.statusWriterFn = options.statusWriter ?? ((text) => {
153
+ process.stderr.write(text);
154
+ });
155
+ this.onSnapshotChangeFn = options.onSnapshotChange ?? null;
65
156
  for (const agent of options.agents) {
66
157
  this.agents.set(agent.name, {
67
158
  config: agent,
68
159
  process: null,
160
+ startInFlight: false,
161
+ startAttemptedAtMs: null,
162
+ startAttemptId: 0,
163
+ pendingIpcMessages: [],
69
164
  restartTimer: null,
70
165
  crashTimestamps: [],
166
+ orchestratedRestartTimestamps: [],
167
+ respawnLoopTripped: false,
71
168
  stopRequested: false,
169
+ cooldownTimer: null,
170
+ cooldownRetryCount: 0,
171
+ fastCrashCount: 0,
72
172
  snapshot: {
73
173
  name: agent.name,
74
174
  channel: agent.channel,
@@ -78,6 +178,10 @@ class DaemonProcessManager {
78
178
  startedAt: null,
79
179
  lastCrashAt: null,
80
180
  backoffMs: this.initialBackoffMs,
181
+ lastExitCode: null,
182
+ lastSignal: null,
183
+ errorReason: null,
184
+ fixHint: null,
81
185
  },
82
186
  });
83
187
  }
@@ -89,53 +193,234 @@ class DaemonProcessManager {
89
193
  }
90
194
  }
91
195
  }
92
- async startAgent(agent) {
196
+ triggerAutoStartAgents() {
197
+ for (const state of this.agents.values()) {
198
+ if (!state.config.autoStart)
199
+ continue;
200
+ void this.startAgent(state.config.name).catch((error) => {
201
+ const errorReason = error instanceof Error ? error.message : String(error);
202
+ this.markConfigCheckFailed(state, `agent startup threw before the worker could run: ${errorReason}`, "Run 'ouro doctor' for diagnostics, then retry 'ouro up'.");
203
+ });
204
+ }
205
+ }
206
+ async startAgent(agent, options = {}) {
93
207
  const state = this.requireAgent(agent);
94
- if (state.process)
208
+ if (state.process || state.startInFlight)
95
209
  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";
210
+ const attemptId = state.startAttemptId + 1;
211
+ state.startAttemptId = attemptId;
212
+ state.startInFlight = true;
213
+ state.startAttemptedAtMs = this.currentTimeMs();
214
+ try {
215
+ this.clearRestartTimer(state);
216
+ state.stopRequested = false;
217
+ state.snapshot.status = "starting";
218
+ if (this.configCheckFn && options.skipConfigCheck !== true) {
219
+ let result;
220
+ try {
221
+ result = await this.configCheckFn(agent);
222
+ }
223
+ catch (error) {
224
+ const errorReason = error instanceof Error ? error.message : String(error);
225
+ this.markConfigCheckFailed(state, `agent config validation threw: ${errorReason}`, "Run 'ouro doctor' for diagnostics, then retry 'ouro up'.");
226
+ return;
227
+ }
228
+ if (!this.isStartAttemptCurrent(state, attemptId))
229
+ return;
230
+ if (state.stopRequested) {
231
+ state.snapshot.status = "stopped";
232
+ state.snapshot.pid = null;
233
+ this.notifySnapshotChange(state.snapshot);
234
+ return;
235
+ }
236
+ if (result.skip) {
237
+ state.snapshot.status = "stopped";
238
+ state.snapshot.errorReason = null;
239
+ state.snapshot.fixHint = null;
240
+ (0, runtime_1.emitNervesEvent)({
241
+ component: "daemon",
242
+ event: "daemon.agent_config_skipped",
243
+ message: result.error ?? "agent start skipped by config check",
244
+ meta: { agent, fix: result.fix ?? null },
245
+ });
246
+ this.notifySnapshotChange(state.snapshot);
247
+ return;
248
+ }
249
+ if (!result.ok) {
250
+ this.markConfigCheckFailed(state, result.error ?? "agent config validation failed", result.fix ?? null);
251
+ return;
252
+ }
253
+ /* v8 ignore next -- defensive duplicate stale-start guard before spawn preparation @preserve */
254
+ if (!this.isStartAttemptCurrent(state, attemptId))
255
+ return;
256
+ // Config check passed — clear any prior error so the pulse stops
257
+ // reporting the broken state. This is the recovery path: the user
258
+ // fixed their secrets/config, the next startAgent attempt sees a
259
+ // valid config, and the pulse goes quiet.
260
+ state.snapshot.errorReason = null;
261
+ state.snapshot.fixHint = null;
262
+ }
263
+ if (options.skipConfigCheck === true) {
264
+ state.snapshot.errorReason = null;
265
+ state.snapshot.fixHint = null;
266
+ }
267
+ const runCwd = (0, identity_1.getRepoRoot)();
268
+ const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
269
+ if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
270
+ state.snapshot.status = "crashed";
271
+ (0, runtime_1.emitNervesEvent)({
272
+ level: "error",
273
+ component: "daemon",
274
+ event: "daemon.agent_entry_missing",
275
+ message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
276
+ meta: { agent, entryScript },
277
+ });
278
+ this.notifySnapshotChange(state.snapshot);
279
+ return;
280
+ }
281
+ /* v8 ignore next -- defensive duplicate stale-start guard immediately before spawn @preserve */
282
+ if (!this.isStartAttemptCurrent(state, attemptId))
283
+ return;
284
+ if (state.stopRequested) {
285
+ state.snapshot.status = "stopped";
286
+ state.snapshot.pid = null;
287
+ this.notifySnapshotChange(state.snapshot);
288
+ return;
289
+ }
290
+ const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
291
+ const child = this.spawnFn("node", args, {
292
+ cwd: runCwd,
293
+ env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
294
+ stdio: ["ignore", "ignore", "ignore", "ipc"],
295
+ });
296
+ /* v8 ignore next 7 -- defensive: spawn should always return a ChildProcess @preserve */
297
+ if (!child) {
298
+ state.snapshot.status = "crashed";
299
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.agent_spawn_failed", message: "spawn returned null", meta: { agent } });
300
+ return;
301
+ }
302
+ if (!this.isStartAttemptCurrent(state, attemptId)) {
303
+ try {
304
+ child.kill("SIGTERM");
305
+ }
306
+ catch {
307
+ (0, runtime_1.emitNervesEvent)({
308
+ level: "warn",
309
+ component: "daemon",
310
+ event: "daemon.agent_stop_error",
311
+ message: "failed to stop stale managed agent startup",
312
+ meta: { agent },
313
+ });
314
+ }
315
+ return;
316
+ }
317
+ state.process = child;
318
+ state.snapshot.status = "running";
319
+ state.snapshot.pid = child.pid ?? null;
320
+ state.snapshot.startedAt = new Date(this.currentTimeMs()).toISOString();
321
+ const bootstrap = state.config.getRuntimeCredentialBootstrap?.() ?? null;
322
+ if (bootstrap) {
323
+ const message = {
324
+ type: "ouro.runtimeCredentialBootstrap",
325
+ agentName: bootstrap.agentName,
326
+ };
327
+ if (bootstrap.runtimeConfig)
328
+ message.runtimeConfig = bootstrap.runtimeConfig;
329
+ if (bootstrap.machineRuntimeConfig)
330
+ message.machineRuntimeConfig = bootstrap.machineRuntimeConfig;
331
+ if (bootstrap.machineId)
332
+ message.machineId = bootstrap.machineId;
333
+ if (bootstrap.providerCredentialRecords)
334
+ message.providerCredentialRecords = bootstrap.providerCredentialRecords;
335
+ try {
336
+ child.send?.(message);
337
+ (0, runtime_1.emitNervesEvent)({
338
+ component: "daemon",
339
+ event: "daemon.agent_runtime_credentials_bootstrap_sent",
340
+ message: "sent runtime credential bootstrap to managed agent process",
341
+ meta: {
342
+ agent,
343
+ runtimeConfig: !!bootstrap.runtimeConfig,
344
+ machineRuntimeConfig: !!bootstrap.machineRuntimeConfig,
345
+ providerCredentialRecords: bootstrap.providerCredentialRecords?.length ?? 0,
346
+ },
347
+ });
348
+ }
349
+ catch (error) {
350
+ (0, runtime_1.emitNervesEvent)({
351
+ level: "warn",
352
+ component: "daemon",
353
+ event: "daemon.agent_ipc_send_error",
354
+ message: "failed to send runtime credential bootstrap to managed agent process",
355
+ meta: { agent, error: error instanceof Error ? error.message : String(error) },
356
+ });
357
+ }
358
+ }
359
+ const pendingIpcMessages = state.pendingIpcMessages.splice(0);
360
+ for (const message of pendingIpcMessages) {
361
+ try {
362
+ child.send?.(message);
363
+ (0, runtime_1.emitNervesEvent)({
364
+ component: "daemon",
365
+ event: "daemon.agent_pending_ipc_flushed",
366
+ message: "flushed pending IPC message to managed agent",
367
+ meta: { agent },
368
+ });
369
+ }
370
+ catch (error) {
371
+ (0, runtime_1.emitNervesEvent)({
372
+ level: "warn",
373
+ component: "daemon",
374
+ event: "daemon.agent_ipc_send_error",
375
+ message: "failed to flush pending IPC message to managed agent",
376
+ meta: { agent, error: error instanceof Error ? error.message : String(error) },
377
+ });
378
+ }
379
+ }
103
380
  (0, runtime_1.emitNervesEvent)({
104
- level: "error",
105
381
  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 },
382
+ event: "daemon.agent_started",
383
+ message: "daemon started managed agent process",
384
+ meta: { agent, pid: child.pid ?? null, cwd: runCwd },
385
+ });
386
+ this.notifySnapshotChange(state.snapshot);
387
+ /* v8 ignore start — child process error handler; requires real spawn to trigger */
388
+ child.on("error", (err) => {
389
+ (0, runtime_1.emitNervesEvent)({
390
+ level: "warn",
391
+ component: "daemon",
392
+ event: "daemon.agent_process_error",
393
+ message: "managed agent process emitted error",
394
+ meta: { agent, error: err.message },
395
+ });
396
+ });
397
+ /* v8 ignore stop */
398
+ child.once("exit", (code, signal) => {
399
+ this.onExit(state, child, code, signal);
109
400
  });
110
- return;
111
401
  }
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
- });
402
+ finally {
403
+ if (this.isStartAttemptCurrent(state, attemptId)) {
404
+ state.startInFlight = false;
405
+ state.startAttemptedAtMs = null;
406
+ }
407
+ }
131
408
  }
132
409
  async stopAgent(agent) {
133
410
  const state = this.requireAgent(agent);
134
411
  this.clearRestartTimer(state);
412
+ this.clearCooldownTimer(state);
135
413
  state.stopRequested = true;
414
+ state.pendingIpcMessages = [];
415
+ // NOTE: do not touch state.respawnLoopTripped / orchestratedRestartTimestamps
416
+ // here. restartAgent calls stopAgent internally; clearing the guard here
417
+ // would reset the window every cycle and defeat the loop-detection. The
418
+ // guard self-clears when timestamps age out of the window (handled inside
419
+ // restartAgent at the prune step).
136
420
  if (!state.process) {
137
421
  state.snapshot.status = "stopped";
138
422
  state.snapshot.pid = null;
423
+ this.notifySnapshotChange(state.snapshot);
139
424
  return;
140
425
  }
141
426
  const child = state.process;
@@ -154,20 +439,127 @@ class DaemonProcessManager {
154
439
  meta: { agent },
155
440
  });
156
441
  }
442
+ this.notifySnapshotChange(state.snapshot);
157
443
  }
158
- async restartAgent(agent) {
444
+ async restartAgent(agent, options = {}) {
445
+ const state = this.requireAgent(agent);
446
+ // Respawn-loop guard: prune timestamps outside the window, then check
447
+ // whether we've already restarted this agent too many times in it.
448
+ const now = this.now();
449
+ const windowStart = now - exports.RESPAWN_GUARD_WINDOW_MS;
450
+ state.orchestratedRestartTimestamps = state.orchestratedRestartTimestamps.filter((ts) => ts >= windowStart);
451
+ // If the window is now empty, the trip naturally self-clears. That means
452
+ // after RESPAWN_GUARD_WINDOW_MS of no restart attempts, the daemon is
453
+ // willing to try again (e.g. for a fresh health probe failure that has
454
+ // nothing to do with the original loop).
455
+ if (state.respawnLoopTripped && state.orchestratedRestartTimestamps.length === 0) {
456
+ state.respawnLoopTripped = false;
457
+ state.snapshot.errorReason = null;
458
+ state.snapshot.fixHint = null;
459
+ (0, runtime_1.emitNervesEvent)({
460
+ component: "daemon",
461
+ event: "daemon.agent_respawn_loop_cleared",
462
+ message: "respawn-loop guard cleared by window-aging",
463
+ meta: { agent, windowMs: exports.RESPAWN_GUARD_WINDOW_MS },
464
+ });
465
+ this.notifySnapshotChange(state.snapshot);
466
+ }
467
+ if (state.respawnLoopTripped) {
468
+ (0, runtime_1.emitNervesEvent)({
469
+ level: "error",
470
+ component: "daemon",
471
+ event: "daemon.agent_respawn_loop_blocked",
472
+ message: "refused agent restart — respawn-loop guard tripped; manual intervention required",
473
+ meta: {
474
+ agent,
475
+ recentRestartCount: state.orchestratedRestartTimestamps.length,
476
+ windowMs: exports.RESPAWN_GUARD_WINDOW_MS,
477
+ },
478
+ });
479
+ return;
480
+ }
481
+ if (state.orchestratedRestartTimestamps.length >= exports.RESPAWN_GUARD_MAX_RESTARTS) {
482
+ state.respawnLoopTripped = true;
483
+ 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`;
484
+ state.snapshot.fixHint = "investigate the root cause then run `ouro up` to resume";
485
+ (0, runtime_1.emitNervesEvent)({
486
+ level: "error",
487
+ component: "daemon",
488
+ event: "daemon.agent_respawn_loop_tripped",
489
+ message: "respawn-loop guard tripped; further restarts blocked",
490
+ meta: {
491
+ agent,
492
+ restartCount: state.orchestratedRestartTimestamps.length,
493
+ windowMs: exports.RESPAWN_GUARD_WINDOW_MS,
494
+ maxRestarts: exports.RESPAWN_GUARD_MAX_RESTARTS,
495
+ },
496
+ });
497
+ this.notifySnapshotChange(state.snapshot);
498
+ return;
499
+ }
500
+ state.orchestratedRestartTimestamps.push(now);
501
+ if (state.startInFlight && !state.process) {
502
+ const startedAt = state.startAttemptedAtMs;
503
+ /* v8 ignore next -- defensive: startInFlight always records a start timestamp @preserve */
504
+ const elapsedMs = startedAt === null ? 0 : this.currentTimeMs() - startedAt;
505
+ if (elapsedMs < this.startupStaleAfterMs) {
506
+ (0, runtime_1.emitNervesEvent)({
507
+ component: "daemon",
508
+ event: "daemon.agent_restart_deferred",
509
+ message: "managed agent restart skipped while startup is already in flight",
510
+ meta: { agent, elapsedMs, startupStaleAfterMs: this.startupStaleAfterMs },
511
+ });
512
+ return;
513
+ }
514
+ (0, runtime_1.emitNervesEvent)({
515
+ level: "warn",
516
+ component: "daemon",
517
+ event: "daemon.agent_startup_stale_recovered",
518
+ message: "managed agent startup was stale; clearing the pending attempt before restart",
519
+ meta: { agent, elapsedMs, startupStaleAfterMs: this.startupStaleAfterMs },
520
+ });
521
+ state.startAttemptId += 1;
522
+ state.startInFlight = false;
523
+ state.startAttemptedAtMs = null;
524
+ }
159
525
  await this.stopAgent(agent);
160
- await this.startAgent(agent);
526
+ await this.startAgent(agent, options);
161
527
  }
162
528
  async stopAll() {
163
529
  for (const state of this.agents.values()) {
164
530
  await this.stopAgent(state.config.name);
165
531
  }
166
532
  }
533
+ resetAgentFailureState(agent) {
534
+ const state = this.requireAgent(agent);
535
+ this.clearRestartTimer(state);
536
+ this.clearCooldownTimer(state);
537
+ state.cooldownRetryCount = 0;
538
+ state.crashTimestamps = [];
539
+ state.fastCrashCount = 0;
540
+ state.respawnLoopTripped = false;
541
+ state.orchestratedRestartTimestamps = [];
542
+ state.snapshot.errorReason = null;
543
+ state.snapshot.fixHint = null;
544
+ this.notifySnapshotChange(state.snapshot);
545
+ }
167
546
  sendToAgent(agent, message) {
168
547
  const state = this.requireAgent(agent);
169
- if (!state.process)
548
+ if (!state.process) {
549
+ if (state.startInFlight) {
550
+ if (state.pendingIpcMessages.length >= MAX_PENDING_IPC_MESSAGES) {
551
+ state.pendingIpcMessages.shift();
552
+ }
553
+ state.pendingIpcMessages.push(message);
554
+ (0, runtime_1.emitNervesEvent)({
555
+ component: "daemon",
556
+ event: "daemon.agent_ipc_queued_during_startup",
557
+ message: "queued IPC message while managed agent startup is in flight",
558
+ meta: { agent, queuedCount: state.pendingIpcMessages.length },
559
+ });
560
+ }
170
561
  return;
562
+ }
171
563
  try {
172
564
  state.process.send(message);
173
565
  }
@@ -187,13 +579,17 @@ class DaemonProcessManager {
187
579
  listAgentSnapshots() {
188
580
  return [...this.agents.values()].map((state) => state.snapshot);
189
581
  }
190
- onExit(state, code, signal) {
191
- if (!state.process)
582
+ onExit(state, child, code, signal) {
583
+ if (state.process !== child)
192
584
  return;
193
585
  state.process = null;
586
+ state.startInFlight = false;
587
+ state.startAttemptedAtMs = null;
194
588
  state.snapshot.pid = null;
589
+ state.snapshot.lastExitCode = code;
590
+ state.snapshot.lastSignal = signal;
195
591
  const crashed = !state.stopRequested && code !== 0;
196
- const now = this.now();
592
+ const now = this.currentTimeMs();
197
593
  const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
198
594
  const runDuration = Math.max(0, now - startedAt);
199
595
  (0, runtime_1.emitNervesEvent)({
@@ -208,13 +604,45 @@ class DaemonProcessManager {
208
604
  if (runDuration >= this.stabilityThresholdMs) {
209
605
  state.snapshot.backoffMs = this.initialBackoffMs;
210
606
  }
607
+ this.notifySnapshotChange(state.snapshot);
211
608
  return;
212
609
  }
213
610
  state.snapshot.lastCrashAt = new Date(now).toISOString();
611
+ // Fast-crash detection: if the agent dies within 5 seconds of starting, it's likely
612
+ // a configuration issue (missing credentials, bad provider, etc.) not a transient failure.
613
+ // After 3 consecutive fast crashes, stop retrying and mark as config-failed.
614
+ const FAST_CRASH_THRESHOLD_MS = 5000;
615
+ const FAST_CRASH_MAX = 3;
616
+ if (runDuration < FAST_CRASH_THRESHOLD_MS) {
617
+ state.fastCrashCount = state.fastCrashCount + 1;
618
+ if (state.fastCrashCount >= FAST_CRASH_MAX) {
619
+ state.snapshot.status = "crashed";
620
+ // Capture the fast-crash diagnosis on the snapshot so it surfaces
621
+ // via the pulse. The error message is prescriptive: it tells the
622
+ // user (and their sibling agents) exactly what to do.
623
+ 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).`;
624
+ state.snapshot.fixHint = `Fix the config and run \`ouro up\` to restart, or check daemon logs for the underlying error.`;
625
+ (0, runtime_1.emitNervesEvent)({
626
+ level: "error",
627
+ component: "daemon",
628
+ event: "daemon.agent_config_failure",
629
+ 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.`,
630
+ meta: { agent: state.config.name, fastCrashCount: state.fastCrashCount, avgRunDurationMs: runDuration },
631
+ });
632
+ this.notifySnapshotChange(state.snapshot);
633
+ return; // Don't schedule cooldown recovery — this needs human/agent intervention
634
+ }
635
+ }
636
+ else {
637
+ // Reset fast-crash counter on a stable run
638
+ state.fastCrashCount = 0;
639
+ }
214
640
  state.crashTimestamps = state.crashTimestamps.filter((crashTs) => crashTs >= startOfHour(now));
215
641
  state.crashTimestamps.push(now);
216
642
  if (state.crashTimestamps.length > this.maxRestartsPerHour) {
217
643
  state.snapshot.status = "crashed";
644
+ state.snapshot.errorReason = `agent exceeded restart limit (${this.maxRestartsPerHour}/hr) — entering cooldown`;
645
+ state.snapshot.fixHint = "investigate why the agent keeps crashing; cooldown will retry shortly";
218
646
  (0, runtime_1.emitNervesEvent)({
219
647
  level: "error",
220
648
  component: "daemon",
@@ -222,6 +650,8 @@ class DaemonProcessManager {
222
650
  message: "managed agent exceeded restart limit and is marked crashed",
223
651
  meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
224
652
  });
653
+ this.notifySnapshotChange(state.snapshot);
654
+ this.scheduleCooldownRecovery(state);
225
655
  return;
226
656
  }
227
657
  state.snapshot.status = "starting";
@@ -232,6 +662,7 @@ class DaemonProcessManager {
232
662
  state.restartTimer = this.setTimeoutFn(() => {
233
663
  void this.startAgent(state.config.name);
234
664
  }, waitMs);
665
+ this.notifySnapshotChange(state.snapshot);
235
666
  }
236
667
  clearRestartTimer(state) {
237
668
  if (state.restartTimer === null)
@@ -239,6 +670,45 @@ class DaemonProcessManager {
239
670
  this.clearTimeoutFn(state.restartTimer);
240
671
  state.restartTimer = null;
241
672
  }
673
+ scheduleCooldownRecovery(state) {
674
+ if (state.cooldownRetryCount >= this.maxCooldownRetries) {
675
+ (0, runtime_1.emitNervesEvent)({
676
+ level: "error",
677
+ component: "daemon",
678
+ event: "daemon.agent_permanent_failure",
679
+ message: "managed agent exhausted all cooldown retries — permanently stopped",
680
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
681
+ });
682
+ return;
683
+ }
684
+ this.clearCooldownTimer(state);
685
+ state.cooldownTimer = this.setTimeoutFn(() => {
686
+ state.cooldownRetryCount += 1;
687
+ state.crashTimestamps = [];
688
+ state.snapshot.backoffMs = this.initialBackoffMs;
689
+ state.snapshot.status = "starting";
690
+ state.snapshot.restartCount += 1;
691
+ (0, runtime_1.emitNervesEvent)({
692
+ component: "daemon",
693
+ event: "daemon.agent_cooldown_recovery",
694
+ message: "attempting cooldown recovery for managed agent",
695
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
696
+ });
697
+ void this.startAgent(state.config.name);
698
+ }, this.cooldownRecoveryMs);
699
+ (0, runtime_1.emitNervesEvent)({
700
+ component: "daemon",
701
+ event: "daemon.agent_cooldown_scheduled",
702
+ message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
703
+ meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
704
+ });
705
+ }
706
+ clearCooldownTimer(state) {
707
+ if (state.cooldownTimer === null)
708
+ return;
709
+ this.clearTimeoutFn(state.cooldownTimer);
710
+ state.cooldownTimer = null;
711
+ }
242
712
  requireAgent(agent) {
243
713
  const state = this.agents.get(agent);
244
714
  if (!state) {