@ouro.bot/cli 0.1.0-alpha.28 → 0.1.0-alpha.280

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 (267) hide show
  1. package/README.md +187 -191
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
  4. package/changelog.json +1696 -0
  5. package/dist/arc/attention-types.js +8 -0
  6. package/dist/arc/cares.js +140 -0
  7. package/dist/arc/episodes.js +117 -0
  8. package/dist/arc/intentions.js +133 -0
  9. package/dist/arc/json-store.js +117 -0
  10. package/dist/arc/obligations.js +237 -0
  11. package/dist/arc/presence.js +185 -0
  12. package/dist/heart/active-work.js +832 -0
  13. package/dist/heart/agent-entry.js +37 -2
  14. package/dist/heart/auth/auth-flow.js +456 -0
  15. package/dist/heart/bridges/manager.js +358 -0
  16. package/dist/heart/bridges/state-machine.js +135 -0
  17. package/dist/heart/bridges/store.js +123 -0
  18. package/dist/heart/commitments.js +111 -0
  19. package/dist/heart/config-registry.js +304 -0
  20. package/dist/heart/config.js +63 -30
  21. package/dist/heart/core.js +644 -196
  22. package/dist/heart/cross-chat-delivery.js +131 -0
  23. package/dist/heart/daemon/agent-config-check.js +149 -0
  24. package/dist/heart/daemon/agent-discovery.js +79 -3
  25. package/dist/heart/daemon/agent-service.js +360 -0
  26. package/dist/heart/daemon/cadence.js +70 -0
  27. package/dist/heart/daemon/cli-defaults.js +574 -0
  28. package/dist/heart/daemon/cli-exec.js +1932 -0
  29. package/dist/heart/daemon/cli-parse.js +762 -0
  30. package/dist/heart/daemon/cli-render.js +503 -0
  31. package/dist/heart/daemon/cli-types.js +8 -0
  32. package/dist/heart/daemon/daemon-cli.js +29 -1155
  33. package/dist/heart/daemon/daemon-entry.js +234 -3
  34. package/dist/heart/daemon/daemon-health.js +136 -0
  35. package/dist/heart/daemon/daemon-runtime-sync.js +153 -12
  36. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  37. package/dist/heart/daemon/daemon.js +702 -57
  38. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  39. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  40. package/dist/heart/daemon/inner-status.js +89 -0
  41. package/dist/heart/daemon/launchd.js +46 -9
  42. package/dist/heart/daemon/log-tailer.js +82 -12
  43. package/dist/heart/daemon/logs-prune.js +105 -0
  44. package/dist/heart/daemon/message-router.js +17 -8
  45. package/dist/heart/daemon/os-cron-deps.js +134 -0
  46. package/dist/heart/daemon/ouro-bot-entry.js +1 -1
  47. package/dist/heart/daemon/process-manager.js +180 -0
  48. package/dist/heart/daemon/pulse.js +463 -0
  49. package/dist/heart/daemon/run-hooks.js +2 -0
  50. package/dist/heart/daemon/runtime-logging.js +67 -16
  51. package/dist/heart/daemon/runtime-metadata.js +101 -0
  52. package/dist/heart/daemon/runtime-mode.js +67 -0
  53. package/dist/heart/daemon/safe-mode.js +161 -0
  54. package/dist/heart/daemon/sense-manager.js +72 -3
  55. package/dist/heart/daemon/session-id-resolver.js +131 -0
  56. package/dist/heart/daemon/skill-management-installer.js +94 -0
  57. package/dist/heart/daemon/socket-client.js +307 -0
  58. package/dist/heart/daemon/task-scheduler.js +3 -25
  59. package/dist/heart/daemon/thoughts.js +510 -0
  60. package/dist/heart/delegation.js +62 -0
  61. package/dist/heart/habits/habit-migration.js +181 -0
  62. package/dist/heart/habits/habit-parser.js +119 -0
  63. package/dist/heart/habits/habit-scheduler.js +371 -0
  64. package/dist/heart/{daemon → hatch}/hatch-flow.js +30 -120
  65. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  66. package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
  67. package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
  68. package/dist/heart/identity.js +163 -60
  69. package/dist/heart/kicks.js +2 -20
  70. package/dist/heart/mcp/mcp-server.js +653 -0
  71. package/dist/heart/migrate-config.js +127 -0
  72. package/dist/heart/model-capabilities.js +59 -0
  73. package/dist/heart/outlook/outlook-http.js +439 -0
  74. package/dist/heart/outlook/outlook-read.js +1618 -0
  75. package/dist/heart/outlook/outlook-render.js +1032 -0
  76. package/dist/heart/outlook/outlook-types.js +7 -0
  77. package/dist/heart/outlook/outlook-view.js +194 -0
  78. package/dist/heart/progress-story.js +42 -0
  79. package/dist/heart/provider-failover.js +88 -0
  80. package/dist/heart/provider-ping.js +162 -0
  81. package/dist/heart/providers/anthropic-token.js +163 -0
  82. package/dist/heart/providers/anthropic.js +169 -46
  83. package/dist/heart/providers/azure.js +98 -11
  84. package/dist/heart/providers/error-classification.js +63 -0
  85. package/dist/heart/providers/github-copilot.js +136 -0
  86. package/dist/heart/providers/minimax-vlm.js +189 -0
  87. package/dist/heart/providers/minimax.js +23 -5
  88. package/dist/heart/providers/openai-codex.js +33 -22
  89. package/dist/heart/session-activity.js +172 -0
  90. package/dist/heart/session-recall.js +216 -0
  91. package/dist/heart/start-of-turn-packet.js +330 -0
  92. package/dist/heart/streaming.js +36 -27
  93. package/dist/heart/sync.js +277 -0
  94. package/dist/heart/target-resolution.js +127 -0
  95. package/dist/heart/tempo.js +93 -0
  96. package/dist/heart/temporal-view.js +41 -0
  97. package/dist/heart/tool-activity-callbacks.js +36 -0
  98. package/dist/heart/tool-description.js +135 -0
  99. package/dist/heart/tool-loop.js +200 -0
  100. package/dist/heart/turn-context.js +358 -0
  101. package/dist/heart/turn-coordinator.js +28 -0
  102. package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
  103. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  104. package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
  105. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  106. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  107. package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
  108. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  109. package/dist/mind/associative-recall.js +126 -66
  110. package/dist/mind/bundle-manifest.js +8 -1
  111. package/dist/mind/context.js +125 -14
  112. package/dist/mind/{memory.js → diary.js} +68 -96
  113. package/dist/mind/embedding-provider.js +60 -0
  114. package/dist/mind/file-state.js +179 -0
  115. package/dist/mind/first-impressions.js +14 -1
  116. package/dist/mind/friends/channel.js +56 -0
  117. package/dist/mind/friends/group-context.js +144 -0
  118. package/dist/mind/friends/resolver.js +37 -0
  119. package/dist/mind/friends/store-file.js +58 -3
  120. package/dist/mind/friends/trust-explanation.js +74 -0
  121. package/dist/mind/friends/types.js +8 -0
  122. package/dist/mind/journal-index.js +161 -0
  123. package/dist/mind/obligation-steering.js +221 -0
  124. package/dist/mind/pending.js +76 -9
  125. package/dist/mind/prompt.js +874 -109
  126. package/dist/mind/scrutiny.js +173 -0
  127. package/dist/mind/token-estimate.js +8 -12
  128. package/dist/nerves/cli-logging.js +7 -1
  129. package/dist/nerves/coverage/audit.js +1 -1
  130. package/dist/nerves/coverage/file-completeness.js +50 -5
  131. package/dist/nerves/coverage/run-artifacts.js +1 -1
  132. package/dist/nerves/event-buffer.js +111 -0
  133. package/dist/nerves/index.js +220 -4
  134. package/dist/nerves/observation.js +20 -0
  135. package/dist/nerves/runtime.js +5 -1
  136. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  137. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  138. package/dist/outlook-ui/index.html +15 -0
  139. package/dist/repertoire/ado-client.js +15 -56
  140. package/dist/repertoire/ado-semantic.js +11 -10
  141. package/dist/repertoire/api-client.js +97 -0
  142. package/dist/repertoire/bitwarden-store.js +319 -0
  143. package/dist/repertoire/bw-installer.js +79 -0
  144. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  145. package/dist/repertoire/coding/context-pack.js +330 -0
  146. package/dist/repertoire/coding/feedback.js +197 -30
  147. package/dist/repertoire/coding/manager.js +149 -2
  148. package/dist/repertoire/coding/spawner.js +55 -9
  149. package/dist/repertoire/coding/tools.js +170 -7
  150. package/dist/repertoire/commerce-errors.js +109 -0
  151. package/dist/repertoire/commerce-self-test.js +156 -0
  152. package/dist/repertoire/credential-access.js +515 -0
  153. package/dist/repertoire/duffel-client.js +185 -0
  154. package/dist/repertoire/github-client.js +14 -55
  155. package/dist/repertoire/graph-client.js +11 -52
  156. package/dist/repertoire/guardrails.js +369 -0
  157. package/dist/repertoire/mcp-client.js +255 -0
  158. package/dist/repertoire/mcp-manager.js +305 -0
  159. package/dist/repertoire/mcp-tools.js +63 -0
  160. package/dist/repertoire/shell-sessions.js +133 -0
  161. package/dist/repertoire/skills.js +14 -23
  162. package/dist/repertoire/stripe-client.js +131 -0
  163. package/dist/repertoire/tasks/board.js +43 -5
  164. package/dist/repertoire/tasks/fix.js +182 -0
  165. package/dist/repertoire/tasks/index.js +28 -10
  166. package/dist/repertoire/tasks/lifecycle.js +2 -2
  167. package/dist/repertoire/tasks/parser.js +3 -2
  168. package/dist/repertoire/tasks/scanner.js +194 -37
  169. package/dist/repertoire/tasks/transitions.js +11 -28
  170. package/dist/repertoire/tools-base.js +43 -771
  171. package/dist/repertoire/tools-bluebubbles.js +201 -0
  172. package/dist/repertoire/tools-bridge.js +141 -0
  173. package/dist/repertoire/tools-config.js +185 -0
  174. package/dist/repertoire/tools-continuity.js +248 -0
  175. package/dist/repertoire/tools-credential.js +182 -0
  176. package/dist/repertoire/tools-files.js +342 -0
  177. package/dist/repertoire/tools-flight.js +224 -0
  178. package/dist/repertoire/tools-flow.js +62 -0
  179. package/dist/repertoire/tools-github.js +1 -7
  180. package/dist/repertoire/tools-memory.js +347 -0
  181. package/dist/repertoire/tools-session.js +739 -0
  182. package/dist/repertoire/tools-shell.js +120 -0
  183. package/dist/repertoire/tools-stripe.js +180 -0
  184. package/dist/repertoire/tools-surface.js +243 -0
  185. package/dist/repertoire/tools-teams.js +12 -62
  186. package/dist/repertoire/tools-travel.js +125 -0
  187. package/dist/repertoire/tools-user-profile.js +144 -0
  188. package/dist/repertoire/tools-vault.js +110 -0
  189. package/dist/repertoire/tools.js +155 -130
  190. package/dist/repertoire/travel-api-client.js +360 -0
  191. package/dist/repertoire/user-profile.js +118 -0
  192. package/dist/repertoire/vault-setup.js +241 -0
  193. package/dist/scripts/claude-code-hook.js +41 -0
  194. package/dist/scripts/claude-code-stop-hook.js +47 -0
  195. package/dist/senses/attention-queue.js +97 -0
  196. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  197. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +143 -9
  198. package/dist/senses/bluebubbles/entry.js +13 -0
  199. package/dist/senses/bluebubbles/inbound-log.js +110 -0
  200. package/dist/senses/bluebubbles/index.js +1410 -0
  201. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +140 -23
  202. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +20 -5
  203. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  204. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  205. package/dist/senses/bluebubbles/session-cleanup.js +72 -0
  206. package/dist/senses/cli/bracketed-paste.js +82 -0
  207. package/dist/senses/cli/image-paste.js +211 -0
  208. package/dist/senses/cli/image-ref-navigation.js +75 -0
  209. package/dist/senses/cli/ink-app.js +156 -0
  210. package/dist/senses/cli/inline-diff.js +64 -0
  211. package/dist/senses/cli/input-keys.js +174 -0
  212. package/dist/senses/cli/kill-ring.js +86 -0
  213. package/dist/senses/cli/message-list.js +51 -0
  214. package/dist/senses/cli/ouro-tui.js +595 -0
  215. package/dist/senses/cli/spinner-imperative.js +135 -0
  216. package/dist/senses/cli/spinner.js +101 -0
  217. package/dist/senses/cli/status-line.js +60 -0
  218. package/dist/senses/cli/streaming-markdown.js +526 -0
  219. package/dist/senses/cli/tool-display.js +83 -0
  220. package/dist/senses/cli/tool-render.js +85 -0
  221. package/dist/senses/cli/tui-store.js +240 -0
  222. package/dist/senses/cli/virtual-list.js +35 -0
  223. package/dist/senses/cli-entry.js +1 -1
  224. package/dist/senses/cli-layout.js +187 -0
  225. package/dist/senses/cli.js +569 -246
  226. package/dist/senses/commands.js +65 -1
  227. package/dist/senses/continuity.js +94 -0
  228. package/dist/senses/habit-turn-message.js +108 -0
  229. package/dist/senses/inner-dialog-worker.js +112 -19
  230. package/dist/senses/inner-dialog.js +593 -85
  231. package/dist/senses/pipeline.js +552 -0
  232. package/dist/senses/shared-turn.js +199 -0
  233. package/dist/senses/surface-tool.js +68 -0
  234. package/dist/senses/teams.js +664 -165
  235. package/dist/senses/trust-gate.js +112 -2
  236. package/package.json +14 -7
  237. package/skills/agent-commerce.md +106 -0
  238. package/skills/browser-navigation.md +110 -0
  239. package/skills/commerce-setup-guide.md +116 -0
  240. package/skills/commerce-setup.md +84 -0
  241. package/skills/configure-dev-tools.md +81 -0
  242. package/skills/travel-planning.md +138 -0
  243. package/dist/heart/daemon/subagent-installer.js +0 -134
  244. package/dist/senses/bluebubbles-entry.js +0 -11
  245. package/dist/senses/bluebubbles.js +0 -421
  246. package/dist/senses/debug-activity.js +0 -108
  247. package/subagents/README.md +0 -73
  248. package/subagents/work-doer.md +0 -235
  249. package/subagents/work-merger.md +0 -618
  250. package/subagents/work-planner.md +0 -382
  251. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  252. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  253. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  254. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  255. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  256. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  257. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  258. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  259. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  260. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  261. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  262. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  263. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  264. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  265. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  266. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  267. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -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",
@@ -51,15 +51,49 @@ class DaemonProcessManager {
51
51
  now;
52
52
  setTimeoutFn;
53
53
  clearTimeoutFn;
54
+ cooldownRecoveryMs;
55
+ maxCooldownRetries;
56
+ existsSyncFn;
57
+ configCheckFn;
58
+ onSnapshotChangeFn;
59
+ /**
60
+ * Notify the snapshot-change observer (if registered). Swallows any
61
+ * errors from the observer so process lifecycle code never fails
62
+ * because the observer threw.
63
+ */
64
+ notifySnapshotChange(snapshot) {
65
+ if (!this.onSnapshotChangeFn)
66
+ return;
67
+ try {
68
+ this.onSnapshotChangeFn(snapshot);
69
+ }
70
+ catch (error) {
71
+ (0, runtime_1.emitNervesEvent)({
72
+ level: "warn",
73
+ component: "daemon",
74
+ event: "daemon.snapshot_change_observer_error",
75
+ message: "snapshot-change observer threw",
76
+ meta: {
77
+ agent: snapshot.name,
78
+ error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error),
79
+ },
80
+ });
81
+ }
82
+ }
54
83
  constructor(options) {
55
84
  this.maxRestartsPerHour = options.maxRestartsPerHour ?? 10;
56
85
  this.stabilityThresholdMs = options.stabilityThresholdMs ?? 60_000;
57
86
  this.initialBackoffMs = options.initialBackoffMs ?? 1_000;
58
87
  this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
88
+ this.cooldownRecoveryMs = options.cooldownRecoveryMs ?? 5 * 60 * 1_000;
89
+ this.maxCooldownRetries = options.maxCooldownRetries ?? 3;
59
90
  this.spawnFn = options.spawn ?? ((command, args, spawnOptions) => (0, child_process_1.spawn)(command, args, spawnOptions));
60
91
  this.now = options.now ?? (() => Date.now());
61
92
  this.setTimeoutFn = options.setTimeoutFn ?? ((cb, delay) => setTimeout(cb, delay));
62
93
  this.clearTimeoutFn = options.clearTimeoutFn ?? ((timer) => clearTimeout(timer));
94
+ this.existsSyncFn = options.existsSync ?? null;
95
+ this.configCheckFn = options.configCheck ?? null;
96
+ this.onSnapshotChangeFn = options.onSnapshotChange ?? null;
63
97
  for (const agent of options.agents) {
64
98
  this.agents.set(agent.name, {
65
99
  config: agent,
@@ -67,6 +101,9 @@ class DaemonProcessManager {
67
101
  restartTimer: null,
68
102
  crashTimestamps: [],
69
103
  stopRequested: false,
104
+ cooldownTimer: null,
105
+ cooldownRetryCount: 0,
106
+ fastCrashCount: 0,
70
107
  snapshot: {
71
108
  name: agent.name,
72
109
  channel: agent.channel,
@@ -76,6 +113,10 @@ class DaemonProcessManager {
76
113
  startedAt: null,
77
114
  lastCrashAt: null,
78
115
  backoffMs: this.initialBackoffMs,
116
+ lastExitCode: null,
117
+ lastSignal: null,
118
+ errorReason: null,
119
+ fixHint: null,
79
120
  },
80
121
  });
81
122
  }
@@ -94,14 +135,62 @@ class DaemonProcessManager {
94
135
  this.clearRestartTimer(state);
95
136
  state.stopRequested = false;
96
137
  state.snapshot.status = "starting";
138
+ if (this.configCheckFn) {
139
+ const result = this.configCheckFn(agent);
140
+ if (!result.ok) {
141
+ state.snapshot.status = "crashed";
142
+ // Surface the error and fix to the snapshot so sibling agents can
143
+ // read it via the pulse. Without this, the diagnosis stayed
144
+ // trapped in the nerves event and stderr — visible to humans
145
+ // running `ouro status` or grepping logs, but invisible to
146
+ // peer agents trying to coordinate around the broken state.
147
+ state.snapshot.errorReason = result.error ?? "agent config validation failed";
148
+ state.snapshot.fixHint = result.fix ?? null;
149
+ (0, runtime_1.emitNervesEvent)({
150
+ level: "error",
151
+ component: "daemon",
152
+ event: "daemon.agent_config_invalid",
153
+ message: result.error ?? "agent config validation failed",
154
+ meta: { agent, fix: result.fix ?? null },
155
+ });
156
+ process.stderr.write(`[daemon] ${agent}: ${result.error}\n` +
157
+ (result.fix ? ` Fix: ${result.fix}\n` : ""));
158
+ this.notifySnapshotChange(state.snapshot);
159
+ return;
160
+ }
161
+ // Config check passed — clear any prior error so the pulse stops
162
+ // reporting the broken state. This is the recovery path: the user
163
+ // fixed their secrets/config, the next startAgent attempt sees a
164
+ // valid config, and the pulse goes quiet.
165
+ state.snapshot.errorReason = null;
166
+ state.snapshot.fixHint = null;
167
+ }
97
168
  const runCwd = (0, identity_1.getRepoRoot)();
98
169
  const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
170
+ if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
171
+ state.snapshot.status = "crashed";
172
+ (0, runtime_1.emitNervesEvent)({
173
+ level: "error",
174
+ component: "daemon",
175
+ event: "daemon.agent_entry_missing",
176
+ message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
177
+ meta: { agent, entryScript },
178
+ });
179
+ this.notifySnapshotChange(state.snapshot);
180
+ return;
181
+ }
99
182
  const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
100
183
  const child = this.spawnFn("node", args, {
101
184
  cwd: runCwd,
102
185
  env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
103
186
  stdio: ["ignore", "ignore", "ignore", "ipc"],
104
187
  });
188
+ /* v8 ignore next 7 -- defensive: spawn should always return a ChildProcess @preserve */
189
+ if (!child) {
190
+ state.snapshot.status = "crashed";
191
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.agent_spawn_failed", message: "spawn returned null", meta: { agent } });
192
+ return;
193
+ }
105
194
  state.process = child;
106
195
  state.snapshot.status = "running";
107
196
  state.snapshot.pid = child.pid ?? null;
@@ -112,6 +201,18 @@ class DaemonProcessManager {
112
201
  message: "daemon started managed agent process",
113
202
  meta: { agent, pid: child.pid ?? null, cwd: runCwd },
114
203
  });
204
+ this.notifySnapshotChange(state.snapshot);
205
+ /* v8 ignore start — child process error handler; requires real spawn to trigger */
206
+ child.on("error", (err) => {
207
+ (0, runtime_1.emitNervesEvent)({
208
+ level: "warn",
209
+ component: "daemon",
210
+ event: "daemon.agent_process_error",
211
+ message: "managed agent process emitted error",
212
+ meta: { agent, error: err.message },
213
+ });
214
+ });
215
+ /* v8 ignore stop */
115
216
  child.once("exit", (code, signal) => {
116
217
  this.onExit(state, code, signal);
117
218
  });
@@ -119,10 +220,12 @@ class DaemonProcessManager {
119
220
  async stopAgent(agent) {
120
221
  const state = this.requireAgent(agent);
121
222
  this.clearRestartTimer(state);
223
+ this.clearCooldownTimer(state);
122
224
  state.stopRequested = true;
123
225
  if (!state.process) {
124
226
  state.snapshot.status = "stopped";
125
227
  state.snapshot.pid = null;
228
+ this.notifySnapshotChange(state.snapshot);
126
229
  return;
127
230
  }
128
231
  const child = state.process;
@@ -141,6 +244,7 @@ class DaemonProcessManager {
141
244
  meta: { agent },
142
245
  });
143
246
  }
247
+ this.notifySnapshotChange(state.snapshot);
144
248
  }
145
249
  async restartAgent(agent) {
146
250
  await this.stopAgent(agent);
@@ -179,6 +283,8 @@ class DaemonProcessManager {
179
283
  return;
180
284
  state.process = null;
181
285
  state.snapshot.pid = null;
286
+ state.snapshot.lastExitCode = code;
287
+ state.snapshot.lastSignal = signal;
182
288
  const crashed = !state.stopRequested && code !== 0;
183
289
  const now = this.now();
184
290
  const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
@@ -195,13 +301,45 @@ class DaemonProcessManager {
195
301
  if (runDuration >= this.stabilityThresholdMs) {
196
302
  state.snapshot.backoffMs = this.initialBackoffMs;
197
303
  }
304
+ this.notifySnapshotChange(state.snapshot);
198
305
  return;
199
306
  }
200
307
  state.snapshot.lastCrashAt = new Date(now).toISOString();
308
+ // Fast-crash detection: if the agent dies within 5 seconds of starting, it's likely
309
+ // a configuration issue (missing credentials, bad provider, etc.) not a transient failure.
310
+ // After 3 consecutive fast crashes, stop retrying and mark as config-failed.
311
+ const FAST_CRASH_THRESHOLD_MS = 5000;
312
+ const FAST_CRASH_MAX = 3;
313
+ if (runDuration < FAST_CRASH_THRESHOLD_MS) {
314
+ state.fastCrashCount = state.fastCrashCount + 1;
315
+ if (state.fastCrashCount >= FAST_CRASH_MAX) {
316
+ state.snapshot.status = "crashed";
317
+ // Capture the fast-crash diagnosis on the snapshot so it surfaces
318
+ // via the pulse. The error message is prescriptive: it tells the
319
+ // user (and their sibling agents) exactly what to do.
320
+ 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).`;
321
+ state.snapshot.fixHint = `Fix the config and run \`ouro up\` to restart, or check daemon logs for the underlying error.`;
322
+ (0, runtime_1.emitNervesEvent)({
323
+ level: "error",
324
+ component: "daemon",
325
+ event: "daemon.agent_config_failure",
326
+ 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.`,
327
+ meta: { agent: state.config.name, fastCrashCount: state.fastCrashCount, avgRunDurationMs: runDuration },
328
+ });
329
+ this.notifySnapshotChange(state.snapshot);
330
+ return; // Don't schedule cooldown recovery — this needs human/agent intervention
331
+ }
332
+ }
333
+ else {
334
+ // Reset fast-crash counter on a stable run
335
+ state.fastCrashCount = 0;
336
+ }
201
337
  state.crashTimestamps = state.crashTimestamps.filter((crashTs) => crashTs >= startOfHour(now));
202
338
  state.crashTimestamps.push(now);
203
339
  if (state.crashTimestamps.length > this.maxRestartsPerHour) {
204
340
  state.snapshot.status = "crashed";
341
+ state.snapshot.errorReason = `agent exceeded restart limit (${this.maxRestartsPerHour}/hr) — entering cooldown`;
342
+ state.snapshot.fixHint = "investigate why the agent keeps crashing; cooldown will retry shortly";
205
343
  (0, runtime_1.emitNervesEvent)({
206
344
  level: "error",
207
345
  component: "daemon",
@@ -209,6 +347,8 @@ class DaemonProcessManager {
209
347
  message: "managed agent exceeded restart limit and is marked crashed",
210
348
  meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
211
349
  });
350
+ this.notifySnapshotChange(state.snapshot);
351
+ this.scheduleCooldownRecovery(state);
212
352
  return;
213
353
  }
214
354
  state.snapshot.status = "starting";
@@ -219,6 +359,7 @@ class DaemonProcessManager {
219
359
  state.restartTimer = this.setTimeoutFn(() => {
220
360
  void this.startAgent(state.config.name);
221
361
  }, waitMs);
362
+ this.notifySnapshotChange(state.snapshot);
222
363
  }
223
364
  clearRestartTimer(state) {
224
365
  if (state.restartTimer === null)
@@ -226,6 +367,45 @@ class DaemonProcessManager {
226
367
  this.clearTimeoutFn(state.restartTimer);
227
368
  state.restartTimer = null;
228
369
  }
370
+ scheduleCooldownRecovery(state) {
371
+ if (state.cooldownRetryCount >= this.maxCooldownRetries) {
372
+ (0, runtime_1.emitNervesEvent)({
373
+ level: "error",
374
+ component: "daemon",
375
+ event: "daemon.agent_permanent_failure",
376
+ message: "managed agent exhausted all cooldown retries — permanently stopped",
377
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
378
+ });
379
+ return;
380
+ }
381
+ this.clearCooldownTimer(state);
382
+ state.cooldownTimer = this.setTimeoutFn(() => {
383
+ state.cooldownRetryCount += 1;
384
+ state.crashTimestamps = [];
385
+ state.snapshot.backoffMs = this.initialBackoffMs;
386
+ state.snapshot.status = "starting";
387
+ state.snapshot.restartCount += 1;
388
+ (0, runtime_1.emitNervesEvent)({
389
+ component: "daemon",
390
+ event: "daemon.agent_cooldown_recovery",
391
+ message: "attempting cooldown recovery for managed agent",
392
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
393
+ });
394
+ void this.startAgent(state.config.name);
395
+ }, this.cooldownRecoveryMs);
396
+ (0, runtime_1.emitNervesEvent)({
397
+ component: "daemon",
398
+ event: "daemon.agent_cooldown_scheduled",
399
+ message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
400
+ meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
401
+ });
402
+ }
403
+ clearCooldownTimer(state) {
404
+ if (state.cooldownTimer === null)
405
+ return;
406
+ this.clearTimeoutFn(state.cooldownTimer);
407
+ state.cooldownTimer = null;
408
+ }
229
409
  requireAgent(agent) {
230
410
  const state = this.agents.get(agent);
231
411
  if (!state) {