@ouro.bot/cli 0.1.0-alpha.40 → 0.1.0-alpha.400

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 (333) hide show
  1. package/README.md +109 -14
  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 +2455 -6
  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 +832 -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 +417 -0
  28. package/dist/heart/bridges/manager.js +358 -0
  29. package/dist/heart/bridges/state-machine.js +135 -0
  30. package/dist/heart/bridges/store.js +123 -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 +101 -128
  35. package/dist/heart/core.js +801 -217
  36. package/dist/heart/cross-chat-delivery.js +131 -0
  37. package/dist/heart/daemon/agent-config-check.js +397 -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 +213 -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 +599 -0
  44. package/dist/heart/daemon/cli-exec.js +3616 -0
  45. package/dist/heart/daemon/cli-help.js +396 -0
  46. package/dist/heart/daemon/cli-parse.js +1118 -0
  47. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  48. package/dist/heart/daemon/cli-render.js +560 -0
  49. package/dist/heart/daemon/cli-types.js +8 -0
  50. package/dist/heart/daemon/daemon-cli.js +28 -1582
  51. package/dist/heart/daemon/daemon-entry.js +356 -3
  52. package/dist/heart/daemon/daemon-health.js +141 -0
  53. package/dist/heart/daemon/daemon-runtime-sync.js +157 -12
  54. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  55. package/dist/heart/daemon/daemon.js +684 -58
  56. package/dist/heart/daemon/doctor-types.js +8 -0
  57. package/dist/heart/daemon/doctor.js +419 -0
  58. package/dist/heart/daemon/health-monitor.js +79 -1
  59. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  60. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  61. package/dist/heart/daemon/http-health-probe.js +80 -0
  62. package/dist/heart/daemon/inner-status.js +89 -0
  63. package/dist/heart/daemon/interactive-repair.js +209 -0
  64. package/dist/heart/daemon/launchd.js +46 -9
  65. package/dist/heart/daemon/log-tailer.js +82 -12
  66. package/dist/heart/daemon/logs-prune.js +105 -0
  67. package/dist/heart/daemon/message-router.js +17 -8
  68. package/dist/heart/daemon/os-cron-deps.js +134 -0
  69. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  70. package/dist/heart/daemon/ouro-entry.js +3 -1
  71. package/dist/heart/daemon/process-manager.js +201 -0
  72. package/dist/heart/daemon/provider-discovery.js +137 -0
  73. package/dist/heart/daemon/pulse.js +475 -0
  74. package/dist/heart/daemon/readiness-repair.js +216 -0
  75. package/dist/heart/daemon/run-hooks.js +2 -0
  76. package/dist/heart/daemon/runtime-logging.js +67 -16
  77. package/dist/heart/daemon/runtime-metadata.js +73 -0
  78. package/dist/heart/daemon/runtime-mode.js +67 -0
  79. package/dist/heart/daemon/safe-mode.js +161 -0
  80. package/dist/heart/daemon/sense-manager.js +119 -30
  81. package/dist/heart/daemon/session-id-resolver.js +131 -0
  82. package/dist/heart/daemon/skill-management-installer.js +94 -0
  83. package/dist/heart/daemon/socket-client.js +307 -0
  84. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  85. package/dist/heart/daemon/startup-tui.js +237 -0
  86. package/dist/heart/daemon/task-scheduler.js +3 -25
  87. package/dist/heart/daemon/thoughts.js +510 -0
  88. package/dist/heart/daemon/up-progress.js +135 -0
  89. package/dist/heart/delegation.js +62 -0
  90. package/dist/heart/habits/habit-migration.js +181 -0
  91. package/dist/heart/habits/habit-parser.js +140 -0
  92. package/dist/heart/habits/habit-scheduler.js +371 -0
  93. package/dist/heart/{daemon → hatch}/hatch-flow.js +53 -117
  94. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  95. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  96. package/dist/heart/{daemon → hatch}/specialist-tools.js +33 -12
  97. package/dist/heart/identity.js +161 -65
  98. package/dist/heart/kept-notes.js +357 -0
  99. package/dist/heart/kicks.js +1 -1
  100. package/dist/heart/machine-identity.js +161 -0
  101. package/dist/heart/mcp/mcp-server.js +653 -0
  102. package/dist/heart/migrate-config.js +100 -0
  103. package/dist/heart/model-capabilities.js +59 -0
  104. package/dist/heart/outlook/outlook-http-hooks.js +64 -0
  105. package/dist/heart/outlook/outlook-http-response.js +7 -0
  106. package/dist/heart/outlook/outlook-http-routes.js +232 -0
  107. package/dist/heart/outlook/outlook-http-static.js +99 -0
  108. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  109. package/dist/heart/outlook/outlook-http.js +99 -0
  110. package/dist/heart/outlook/outlook-read.js +28 -0
  111. package/dist/heart/outlook/outlook-types.js +27 -0
  112. package/dist/heart/outlook/outlook-view.js +195 -0
  113. package/dist/heart/outlook/readers/agent-machine.js +359 -0
  114. package/dist/heart/outlook/readers/continuity-readers.js +332 -0
  115. package/dist/heart/outlook/readers/runtime-readers.js +660 -0
  116. package/dist/heart/outlook/readers/sessions.js +232 -0
  117. package/dist/heart/outlook/readers/shared.js +111 -0
  118. package/dist/heart/platform.js +81 -0
  119. package/dist/heart/progress-story.js +42 -0
  120. package/dist/heart/provider-attempt.js +133 -0
  121. package/dist/heart/provider-binding-resolver.js +239 -0
  122. package/dist/heart/provider-credentials.js +383 -0
  123. package/dist/heart/provider-failover.js +266 -0
  124. package/dist/heart/provider-models.js +81 -0
  125. package/dist/heart/provider-ping.js +237 -0
  126. package/dist/heart/provider-state.js +216 -0
  127. package/dist/heart/provider-visibility.js +186 -0
  128. package/dist/heart/providers/anthropic-token.js +131 -0
  129. package/dist/heart/providers/anthropic.js +193 -55
  130. package/dist/heart/providers/azure.js +103 -12
  131. package/dist/heart/providers/error-classification.js +63 -0
  132. package/dist/heart/providers/github-copilot.js +145 -0
  133. package/dist/heart/providers/minimax-vlm.js +189 -0
  134. package/dist/heart/providers/minimax.js +29 -7
  135. package/dist/heart/providers/openai-codex.js +39 -29
  136. package/dist/heart/runtime-credentials.js +181 -0
  137. package/dist/heart/session-activity.js +190 -0
  138. package/dist/heart/session-events.js +855 -0
  139. package/dist/heart/session-transcript.js +167 -0
  140. package/dist/heart/start-of-turn-packet.js +345 -0
  141. package/dist/heart/streaming.js +36 -27
  142. package/dist/heart/sync.js +332 -0
  143. package/dist/heart/target-resolution.js +127 -0
  144. package/dist/heart/tempo.js +93 -0
  145. package/dist/heart/temporal-view.js +41 -0
  146. package/dist/heart/tool-activity-callbacks.js +36 -0
  147. package/dist/heart/tool-description.js +135 -0
  148. package/dist/heart/tool-friction.js +55 -0
  149. package/dist/heart/tool-loop.js +200 -0
  150. package/dist/heart/turn-context.js +351 -0
  151. package/dist/heart/turn-coordinator.js +28 -0
  152. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
  153. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  154. package/dist/heart/versioning/ouro-path-installer.js +301 -0
  155. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  156. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  157. package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
  158. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  159. package/dist/mind/bundle-manifest.js +7 -1
  160. package/dist/mind/context.js +134 -87
  161. package/dist/mind/diary-integrity.js +60 -0
  162. package/dist/mind/{memory.js → diary.js} +84 -96
  163. package/dist/mind/embedding-provider.js +60 -0
  164. package/dist/mind/file-state.js +179 -0
  165. package/dist/mind/first-impressions.js +14 -1
  166. package/dist/mind/friends/channel.js +21 -0
  167. package/dist/mind/friends/group-context.js +144 -0
  168. package/dist/mind/friends/resolver.js +38 -1
  169. package/dist/mind/friends/store-file.js +39 -3
  170. package/dist/mind/friends/trust-explanation.js +74 -0
  171. package/dist/mind/friends/types.js +1 -1
  172. package/dist/mind/journal-index.js +161 -0
  173. package/dist/mind/note-search.js +268 -0
  174. package/dist/mind/obligation-steering.js +221 -0
  175. package/dist/mind/pending.js +66 -7
  176. package/dist/mind/prompt-refresh.js +3 -2
  177. package/dist/mind/prompt.js +946 -167
  178. package/dist/mind/provenance-trust.js +26 -0
  179. package/dist/mind/scrutiny.js +173 -0
  180. package/dist/nerves/cli-logging.js +7 -1
  181. package/dist/nerves/coverage/audit-rules.js +15 -6
  182. package/dist/nerves/coverage/audit.js +28 -2
  183. package/dist/nerves/coverage/cli.js +1 -1
  184. package/dist/nerves/coverage/contract.js +5 -5
  185. package/dist/nerves/coverage/file-completeness.js +83 -5
  186. package/dist/nerves/coverage/run-artifacts.js +1 -1
  187. package/dist/nerves/event-buffer.js +111 -0
  188. package/dist/nerves/index.js +224 -4
  189. package/dist/nerves/observation.js +20 -0
  190. package/dist/nerves/redact.js +79 -0
  191. package/dist/nerves/runtime.js +5 -1
  192. package/dist/outlook-ui/assets/index-BAcU08c-.css +1 -0
  193. package/dist/outlook-ui/assets/index-D7l3l4vY.js +61 -0
  194. package/dist/outlook-ui/index.html +15 -0
  195. package/dist/repertoire/ado-client.js +15 -56
  196. package/dist/repertoire/ado-semantic.js +11 -10
  197. package/dist/repertoire/api-client.js +97 -0
  198. package/dist/repertoire/bitwarden-store.js +461 -0
  199. package/dist/repertoire/bundle-templates.js +72 -0
  200. package/dist/repertoire/bw-installer.js +79 -0
  201. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  202. package/dist/repertoire/coding/context-pack.js +330 -0
  203. package/dist/repertoire/coding/feedback.js +197 -30
  204. package/dist/repertoire/coding/manager.js +158 -9
  205. package/dist/repertoire/coding/spawner.js +55 -9
  206. package/dist/repertoire/coding/tools.js +170 -7
  207. package/dist/repertoire/commerce-errors.js +109 -0
  208. package/dist/repertoire/commerce-self-test.js +156 -0
  209. package/dist/repertoire/credential-access.js +107 -0
  210. package/dist/repertoire/duffel-client.js +185 -0
  211. package/dist/repertoire/github-client.js +14 -55
  212. package/dist/repertoire/graph-client.js +11 -52
  213. package/dist/repertoire/guardrails.js +371 -0
  214. package/dist/repertoire/mcp-client.js +255 -0
  215. package/dist/repertoire/mcp-manager.js +305 -0
  216. package/dist/repertoire/mcp-tools.js +63 -0
  217. package/dist/repertoire/shell-sessions.js +133 -0
  218. package/dist/repertoire/skills.js +15 -24
  219. package/dist/repertoire/stripe-client.js +131 -0
  220. package/dist/repertoire/tasks/board.js +43 -5
  221. package/dist/repertoire/tasks/fix.js +182 -0
  222. package/dist/repertoire/tasks/index.js +26 -1
  223. package/dist/repertoire/tasks/lifecycle.js +2 -2
  224. package/dist/repertoire/tasks/parser.js +3 -2
  225. package/dist/repertoire/tasks/scanner.js +194 -37
  226. package/dist/repertoire/tasks/transitions.js +16 -78
  227. package/dist/repertoire/tool-results.js +29 -0
  228. package/dist/repertoire/tools-attachments.js +317 -0
  229. package/dist/repertoire/tools-base.js +42 -687
  230. package/dist/repertoire/tools-bluebubbles.js +1 -0
  231. package/dist/repertoire/tools-bridge.js +141 -0
  232. package/dist/repertoire/tools-bundle.js +984 -0
  233. package/dist/repertoire/tools-config.js +185 -0
  234. package/dist/repertoire/tools-continuity.js +248 -0
  235. package/dist/repertoire/tools-credential.js +182 -0
  236. package/dist/repertoire/tools-files.js +342 -0
  237. package/dist/repertoire/tools-flight.js +224 -0
  238. package/dist/repertoire/tools-flow.js +105 -0
  239. package/dist/repertoire/tools-github.js +1 -7
  240. package/dist/repertoire/tools-notes.js +376 -0
  241. package/dist/repertoire/tools-session.js +739 -0
  242. package/dist/repertoire/tools-shell.js +120 -0
  243. package/dist/repertoire/tools-stripe.js +180 -0
  244. package/dist/repertoire/tools-surface.js +243 -0
  245. package/dist/repertoire/tools-teams.js +9 -39
  246. package/dist/repertoire/tools-travel.js +125 -0
  247. package/dist/repertoire/tools-user-profile.js +144 -0
  248. package/dist/repertoire/tools-vault.js +40 -0
  249. package/dist/repertoire/tools.js +144 -113
  250. package/dist/repertoire/travel-api-client.js +360 -0
  251. package/dist/repertoire/user-profile.js +118 -0
  252. package/dist/repertoire/vault-setup.js +246 -0
  253. package/dist/repertoire/vault-unlock.js +382 -0
  254. package/dist/scripts/claude-code-hook.js +41 -0
  255. package/dist/scripts/claude-code-stop-hook.js +47 -0
  256. package/dist/senses/attention-queue.js +116 -0
  257. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  258. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  259. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +260 -9
  260. package/dist/senses/bluebubbles/entry.js +70 -0
  261. package/dist/senses/bluebubbles/inbound-log.js +113 -0
  262. package/dist/senses/bluebubbles/index.js +1620 -0
  263. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  264. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  265. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +45 -3
  266. package/dist/senses/bluebubbles/replay.js +129 -0
  267. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  268. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  269. package/dist/senses/cli/bracketed-paste.js +82 -0
  270. package/dist/senses/cli/image-paste.js +287 -0
  271. package/dist/senses/cli/image-ref-navigation.js +75 -0
  272. package/dist/senses/cli/ink-app.js +156 -0
  273. package/dist/senses/cli/inline-diff.js +64 -0
  274. package/dist/senses/cli/input-keys.js +174 -0
  275. package/dist/senses/cli/kill-ring.js +86 -0
  276. package/dist/senses/cli/message-list.js +51 -0
  277. package/dist/senses/cli/ouro-tui.js +605 -0
  278. package/dist/senses/cli/spinner-imperative.js +135 -0
  279. package/dist/senses/cli/spinner.js +101 -0
  280. package/dist/senses/cli/status-line.js +60 -0
  281. package/dist/senses/cli/streaming-markdown.js +526 -0
  282. package/dist/senses/cli/tool-display.js +83 -0
  283. package/dist/senses/cli/tool-render.js +85 -0
  284. package/dist/senses/cli/tui-store.js +240 -0
  285. package/dist/senses/cli/virtual-list.js +35 -0
  286. package/dist/senses/cli-entry.js +60 -8
  287. package/dist/senses/cli-layout.js +187 -0
  288. package/dist/senses/cli.js +526 -211
  289. package/dist/senses/commands.js +66 -3
  290. package/dist/senses/continuity.js +94 -0
  291. package/dist/senses/habit-turn-message.js +108 -0
  292. package/dist/senses/inner-dialog-worker.js +112 -19
  293. package/dist/senses/inner-dialog.js +600 -95
  294. package/dist/senses/pipeline.js +539 -61
  295. package/dist/senses/proactive-content-guard.js +51 -0
  296. package/dist/senses/shared-turn.js +205 -0
  297. package/dist/senses/surface-tool.js +68 -0
  298. package/dist/senses/teams-entry.js +60 -8
  299. package/dist/senses/teams.js +569 -237
  300. package/dist/senses/trust-gate.js +5 -5
  301. package/package.json +28 -7
  302. package/skills/agent-commerce.md +106 -0
  303. package/skills/browser-navigation.md +110 -0
  304. package/skills/commerce-setup-guide.md +116 -0
  305. package/skills/commerce-setup.md +84 -0
  306. package/skills/configure-dev-tools.md +101 -0
  307. package/skills/travel-planning.md +134 -0
  308. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  309. package/dist/heart/daemon/subagent-installer.js +0 -134
  310. package/dist/mind/associative-recall.js +0 -197
  311. package/dist/senses/bluebubbles-entry.js +0 -11
  312. package/dist/senses/bluebubbles.js +0 -832
  313. package/dist/senses/debug-activity.js +0 -127
  314. package/subagents/README.md +0 -60
  315. package/subagents/work-doer.md +0 -235
  316. package/subagents/work-merger.md +0 -618
  317. package/subagents/work-planner.md +0 -382
  318. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  319. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  320. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  321. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  322. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  323. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  324. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  325. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  326. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  327. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  328. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  329. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  330. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  331. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  332. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  333. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -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,62 @@ 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.ok) {
162
+ state.snapshot.status = "crashed";
163
+ // Surface the error and fix to the snapshot so sibling agents can
164
+ // read it via the pulse. Without this, the diagnosis stayed
165
+ // trapped in the nerves event and stderr — visible to humans
166
+ // running `ouro status` or grepping logs, but invisible to
167
+ // peer agents trying to coordinate around the broken state.
168
+ state.snapshot.errorReason = result.error ?? "agent config validation failed";
169
+ state.snapshot.fixHint = result.fix ?? null;
170
+ (0, runtime_1.emitNervesEvent)({
171
+ level: "error",
172
+ component: "daemon",
173
+ event: "daemon.agent_config_invalid",
174
+ message: result.error ?? "agent config validation failed",
175
+ meta: { agent, fix: result.fix ?? null },
176
+ });
177
+ this.writeStatus(agent, `[daemon] ${agent}: ${result.error}\n` +
178
+ (result.fix ? ` Fix: ${result.fix}\n` : ""));
179
+ this.notifySnapshotChange(state.snapshot);
180
+ return;
181
+ }
182
+ // Config check passed — clear any prior error so the pulse stops
183
+ // reporting the broken state. This is the recovery path: the user
184
+ // fixed their secrets/config, the next startAgent attempt sees a
185
+ // valid config, and the pulse goes quiet.
186
+ state.snapshot.errorReason = null;
187
+ state.snapshot.fixHint = null;
188
+ }
97
189
  const runCwd = (0, identity_1.getRepoRoot)();
98
190
  const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
191
+ if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
192
+ state.snapshot.status = "crashed";
193
+ (0, runtime_1.emitNervesEvent)({
194
+ level: "error",
195
+ component: "daemon",
196
+ event: "daemon.agent_entry_missing",
197
+ message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
198
+ meta: { agent, entryScript },
199
+ });
200
+ this.notifySnapshotChange(state.snapshot);
201
+ return;
202
+ }
99
203
  const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
100
204
  const child = this.spawnFn("node", args, {
101
205
  cwd: runCwd,
102
206
  env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
103
207
  stdio: ["ignore", "ignore", "ignore", "ipc"],
104
208
  });
209
+ /* v8 ignore next 7 -- defensive: spawn should always return a ChildProcess @preserve */
210
+ if (!child) {
211
+ state.snapshot.status = "crashed";
212
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.agent_spawn_failed", message: "spawn returned null", meta: { agent } });
213
+ return;
214
+ }
105
215
  state.process = child;
106
216
  state.snapshot.status = "running";
107
217
  state.snapshot.pid = child.pid ?? null;
@@ -112,6 +222,18 @@ class DaemonProcessManager {
112
222
  message: "daemon started managed agent process",
113
223
  meta: { agent, pid: child.pid ?? null, cwd: runCwd },
114
224
  });
225
+ this.notifySnapshotChange(state.snapshot);
226
+ /* v8 ignore start — child process error handler; requires real spawn to trigger */
227
+ child.on("error", (err) => {
228
+ (0, runtime_1.emitNervesEvent)({
229
+ level: "warn",
230
+ component: "daemon",
231
+ event: "daemon.agent_process_error",
232
+ message: "managed agent process emitted error",
233
+ meta: { agent, error: err.message },
234
+ });
235
+ });
236
+ /* v8 ignore stop */
115
237
  child.once("exit", (code, signal) => {
116
238
  this.onExit(state, code, signal);
117
239
  });
@@ -119,10 +241,12 @@ class DaemonProcessManager {
119
241
  async stopAgent(agent) {
120
242
  const state = this.requireAgent(agent);
121
243
  this.clearRestartTimer(state);
244
+ this.clearCooldownTimer(state);
122
245
  state.stopRequested = true;
123
246
  if (!state.process) {
124
247
  state.snapshot.status = "stopped";
125
248
  state.snapshot.pid = null;
249
+ this.notifySnapshotChange(state.snapshot);
126
250
  return;
127
251
  }
128
252
  const child = state.process;
@@ -141,6 +265,7 @@ class DaemonProcessManager {
141
265
  meta: { agent },
142
266
  });
143
267
  }
268
+ this.notifySnapshotChange(state.snapshot);
144
269
  }
145
270
  async restartAgent(agent) {
146
271
  await this.stopAgent(agent);
@@ -179,6 +304,8 @@ class DaemonProcessManager {
179
304
  return;
180
305
  state.process = null;
181
306
  state.snapshot.pid = null;
307
+ state.snapshot.lastExitCode = code;
308
+ state.snapshot.lastSignal = signal;
182
309
  const crashed = !state.stopRequested && code !== 0;
183
310
  const now = this.now();
184
311
  const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
@@ -195,13 +322,45 @@ class DaemonProcessManager {
195
322
  if (runDuration >= this.stabilityThresholdMs) {
196
323
  state.snapshot.backoffMs = this.initialBackoffMs;
197
324
  }
325
+ this.notifySnapshotChange(state.snapshot);
198
326
  return;
199
327
  }
200
328
  state.snapshot.lastCrashAt = new Date(now).toISOString();
329
+ // Fast-crash detection: if the agent dies within 5 seconds of starting, it's likely
330
+ // a configuration issue (missing credentials, bad provider, etc.) not a transient failure.
331
+ // After 3 consecutive fast crashes, stop retrying and mark as config-failed.
332
+ const FAST_CRASH_THRESHOLD_MS = 5000;
333
+ const FAST_CRASH_MAX = 3;
334
+ if (runDuration < FAST_CRASH_THRESHOLD_MS) {
335
+ state.fastCrashCount = state.fastCrashCount + 1;
336
+ if (state.fastCrashCount >= FAST_CRASH_MAX) {
337
+ state.snapshot.status = "crashed";
338
+ // Capture the fast-crash diagnosis on the snapshot so it surfaces
339
+ // via the pulse. The error message is prescriptive: it tells the
340
+ // user (and their sibling agents) exactly what to do.
341
+ 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).`;
342
+ state.snapshot.fixHint = `Fix the config and run \`ouro up\` to restart, or check daemon logs for the underlying error.`;
343
+ (0, runtime_1.emitNervesEvent)({
344
+ level: "error",
345
+ component: "daemon",
346
+ event: "daemon.agent_config_failure",
347
+ 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.`,
348
+ meta: { agent: state.config.name, fastCrashCount: state.fastCrashCount, avgRunDurationMs: runDuration },
349
+ });
350
+ this.notifySnapshotChange(state.snapshot);
351
+ return; // Don't schedule cooldown recovery — this needs human/agent intervention
352
+ }
353
+ }
354
+ else {
355
+ // Reset fast-crash counter on a stable run
356
+ state.fastCrashCount = 0;
357
+ }
201
358
  state.crashTimestamps = state.crashTimestamps.filter((crashTs) => crashTs >= startOfHour(now));
202
359
  state.crashTimestamps.push(now);
203
360
  if (state.crashTimestamps.length > this.maxRestartsPerHour) {
204
361
  state.snapshot.status = "crashed";
362
+ state.snapshot.errorReason = `agent exceeded restart limit (${this.maxRestartsPerHour}/hr) — entering cooldown`;
363
+ state.snapshot.fixHint = "investigate why the agent keeps crashing; cooldown will retry shortly";
205
364
  (0, runtime_1.emitNervesEvent)({
206
365
  level: "error",
207
366
  component: "daemon",
@@ -209,6 +368,8 @@ class DaemonProcessManager {
209
368
  message: "managed agent exceeded restart limit and is marked crashed",
210
369
  meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
211
370
  });
371
+ this.notifySnapshotChange(state.snapshot);
372
+ this.scheduleCooldownRecovery(state);
212
373
  return;
213
374
  }
214
375
  state.snapshot.status = "starting";
@@ -219,6 +380,7 @@ class DaemonProcessManager {
219
380
  state.restartTimer = this.setTimeoutFn(() => {
220
381
  void this.startAgent(state.config.name);
221
382
  }, waitMs);
383
+ this.notifySnapshotChange(state.snapshot);
222
384
  }
223
385
  clearRestartTimer(state) {
224
386
  if (state.restartTimer === null)
@@ -226,6 +388,45 @@ class DaemonProcessManager {
226
388
  this.clearTimeoutFn(state.restartTimer);
227
389
  state.restartTimer = null;
228
390
  }
391
+ scheduleCooldownRecovery(state) {
392
+ if (state.cooldownRetryCount >= this.maxCooldownRetries) {
393
+ (0, runtime_1.emitNervesEvent)({
394
+ level: "error",
395
+ component: "daemon",
396
+ event: "daemon.agent_permanent_failure",
397
+ message: "managed agent exhausted all cooldown retries — permanently stopped",
398
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
399
+ });
400
+ return;
401
+ }
402
+ this.clearCooldownTimer(state);
403
+ state.cooldownTimer = this.setTimeoutFn(() => {
404
+ state.cooldownRetryCount += 1;
405
+ state.crashTimestamps = [];
406
+ state.snapshot.backoffMs = this.initialBackoffMs;
407
+ state.snapshot.status = "starting";
408
+ state.snapshot.restartCount += 1;
409
+ (0, runtime_1.emitNervesEvent)({
410
+ component: "daemon",
411
+ event: "daemon.agent_cooldown_recovery",
412
+ message: "attempting cooldown recovery for managed agent",
413
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
414
+ });
415
+ void this.startAgent(state.config.name);
416
+ }, this.cooldownRecoveryMs);
417
+ (0, runtime_1.emitNervesEvent)({
418
+ component: "daemon",
419
+ event: "daemon.agent_cooldown_scheduled",
420
+ message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
421
+ meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
422
+ });
423
+ }
424
+ clearCooldownTimer(state) {
425
+ if (state.cooldownTimer === null)
426
+ return;
427
+ this.clearTimeoutFn(state.cooldownTimer);
428
+ state.cooldownTimer = null;
429
+ }
229
430
  requireAgent(agent) {
230
431
  const state = this.agents.get(agent);
231
432
  if (!state) {
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /**
3
+ * Shared provider discovery for repair.
4
+ *
5
+ * Runtime repair only trusts the agent vault. First-run conveniences may still
6
+ * inspect env vars before credentials are stored, but once an agent exists the
7
+ * vault is the source of truth.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.scanEnvVarCredentials = scanEnvVarCredentials;
11
+ exports.discoverInstalledAgentCredentials = discoverInstalledAgentCredentials;
12
+ exports.describeDiscoveredCredentialSource = describeDiscoveredCredentialSource;
13
+ exports.discoverWorkingProvider = discoverWorkingProvider;
14
+ const identity_1 = require("../identity");
15
+ const provider_credentials_1 = require("../provider-credentials");
16
+ const runtime_1 = require("../../nerves/runtime");
17
+ /**
18
+ * Scan environment variables for API keys during first-run bootstrap.
19
+ * This does not participate in runtime provider repair.
20
+ */
21
+ function scanEnvVarCredentials(env) {
22
+ const results = [];
23
+ for (const [provider, desc] of Object.entries(identity_1.PROVIDER_CREDENTIALS)) {
24
+ const cred = {};
25
+ for (const [envVar, credKey] of Object.entries(desc.envVars)) {
26
+ const value = env[envVar];
27
+ if (value) {
28
+ cred[credKey] = value;
29
+ }
30
+ }
31
+ const hasRequired = desc.required.some((key) => !!cred[key]);
32
+ if (hasRequired) {
33
+ results.push({
34
+ provider,
35
+ agentName: "env",
36
+ credentials: cred,
37
+ providerConfig: { ...cred },
38
+ });
39
+ }
40
+ }
41
+ return results;
42
+ }
43
+ function stringifyProviderFields(fields) {
44
+ const result = {};
45
+ for (const [key, value] of Object.entries(fields)) {
46
+ result[key] = String(value);
47
+ }
48
+ return result;
49
+ }
50
+ function discoveredFromVaultRecord(record, agentName = "vault") {
51
+ return {
52
+ provider: record.provider,
53
+ agentName,
54
+ credentials: stringifyProviderFields(record.credentials),
55
+ providerConfig: stringifyProviderFields(record.config),
56
+ };
57
+ }
58
+ async function discoverInstalledAgentCredentials(agentNames) {
59
+ const discovered = [];
60
+ for (const agentName of agentNames) {
61
+ if (agentName === "SerpentGuide")
62
+ continue;
63
+ const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, { preserveCachedOnFailure: true });
64
+ if (!poolResult.ok)
65
+ continue;
66
+ for (const record of Object.values(poolResult.pool.providers)) {
67
+ if (!record)
68
+ continue;
69
+ discovered.push(discoveredFromVaultRecord(record, agentName));
70
+ }
71
+ }
72
+ return discovered;
73
+ }
74
+ function describeDiscoveredCredentialSource(credential, envVar) {
75
+ if (credential.agentName === "env") {
76
+ return envVar ? `from env: $${envVar}` : "from env";
77
+ }
78
+ return `from ${credential.agentName}'s vault`;
79
+ }
80
+ async function discoverWorkingProvider(deps) {
81
+ const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(deps.agentName);
82
+ if (!poolResult.ok) {
83
+ (0, runtime_1.emitNervesEvent)({
84
+ level: "warn",
85
+ component: "daemon",
86
+ event: "daemon.provider_discovery_none",
87
+ message: "provider discovery could not read agent vault",
88
+ meta: { agentName: deps.agentName, reason: poolResult.reason },
89
+ });
90
+ return null;
91
+ }
92
+ const candidates = Object.entries(poolResult.pool.providers)
93
+ .map(([, record]) => discoveredFromVaultRecord(record));
94
+ if (candidates.length === 0) {
95
+ (0, runtime_1.emitNervesEvent)({
96
+ level: "info",
97
+ component: "daemon",
98
+ event: "daemon.provider_discovery_none",
99
+ message: "no provider credentials found in agent vault",
100
+ meta: { agentName: deps.agentName },
101
+ });
102
+ return null;
103
+ }
104
+ for (const candidate of candidates) {
105
+ const config = { ...candidate.providerConfig, ...candidate.credentials };
106
+ (0, runtime_1.emitNervesEvent)({
107
+ level: "info",
108
+ component: "daemon",
109
+ event: "daemon.provider_discovery_ping",
110
+ message: `pinging provider: ${candidate.provider}`,
111
+ meta: { agentName: deps.agentName, provider: candidate.provider, source: candidate.agentName },
112
+ });
113
+ const result = await deps.pingProvider(candidate.provider, config);
114
+ if (result.ok) {
115
+ (0, runtime_1.emitNervesEvent)({
116
+ level: "info",
117
+ component: "daemon",
118
+ event: "daemon.provider_discovery_ok",
119
+ message: `provider discovery succeeded: ${candidate.provider}`,
120
+ meta: { agentName: deps.agentName, provider: candidate.provider, source: candidate.agentName },
121
+ });
122
+ return {
123
+ provider: candidate.provider,
124
+ credentials: candidate.credentials,
125
+ providerConfig: candidate.providerConfig,
126
+ };
127
+ }
128
+ }
129
+ (0, runtime_1.emitNervesEvent)({
130
+ level: "warn",
131
+ component: "daemon",
132
+ event: "daemon.provider_discovery_all_failed",
133
+ message: "all vault provider candidates failed ping",
134
+ meta: { agentName: deps.agentName, candidateCount: candidates.length },
135
+ });
136
+ return null;
137
+ }