@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
@@ -37,6 +37,115 @@ exports.bundleMetaHook = bundleMetaHook;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const runtime_1 = require("../../../nerves/runtime");
40
+ /**
41
+ * Migrate bundle from schema 1 to schema 2:
42
+ * - Move state/{episodes,obligations,cares,intentions}/* to arc/{name}/*
43
+ * - Move the old psyche note store to diary/
44
+ * Idempotent: skips missing sources; on collision, newer mtime wins.
45
+ */
46
+ function migrateToSchema2(agentRoot) {
47
+ (0, runtime_1.emitNervesEvent)({
48
+ component: "daemon",
49
+ event: "daemon.bundle_migration_start",
50
+ message: "migrating bundle to schema 2",
51
+ meta: { agentRoot },
52
+ });
53
+ // Migrate arc entities
54
+ for (const name of ["episodes", "obligations", "cares", "intentions"]) {
55
+ const src = path.join(agentRoot, "state", name);
56
+ const dest = path.join(agentRoot, "arc", name);
57
+ migrateDirectory(src, dest);
58
+ }
59
+ // Migrate diary from the old pre-diary bundle layout.
60
+ const legacyDiarySrc = path.join(agentRoot, "psyche", "mem" + "ory");
61
+ const diaryDest = path.join(agentRoot, "diary");
62
+ migrateDirectory(legacyDiarySrc, diaryDest);
63
+ // Update bundle .gitignore
64
+ updateBundleGitignore(agentRoot);
65
+ (0, runtime_1.emitNervesEvent)({
66
+ component: "daemon",
67
+ event: "daemon.bundle_migration_end",
68
+ message: "bundle migration to schema 2 complete",
69
+ meta: { agentRoot },
70
+ });
71
+ }
72
+ /**
73
+ * Ensure bundle .gitignore has state/ ignored and does NOT ignore arc/, diary/, journal/.
74
+ */
75
+ function updateBundleGitignore(agentRoot) {
76
+ const gitignorePath = path.join(agentRoot, ".gitignore");
77
+ let lines = [];
78
+ try {
79
+ if (fs.existsSync(gitignorePath)) {
80
+ lines = fs.readFileSync(gitignorePath, "utf-8").split("\n");
81
+ }
82
+ }
83
+ catch {
84
+ // If we can't read, start fresh
85
+ }
86
+ // Remove arc/, diary/, journal/ from ignore (they should be tracked)
87
+ const toRemove = new Set(["arc/", "diary/", "journal/"]);
88
+ lines = lines.filter((line) => !toRemove.has(line.trim()));
89
+ // Ensure state/ is in the ignore list
90
+ const hasState = lines.some((line) => line.trim() === "state/");
91
+ if (!hasState) {
92
+ lines.push("state/");
93
+ }
94
+ // Write back, trimming trailing empty lines and ensuring trailing newline
95
+ const content = lines.join("\n").replace(/\n+$/, "") + "\n";
96
+ try {
97
+ fs.writeFileSync(gitignorePath, content, "utf-8");
98
+ }
99
+ catch {
100
+ // Non-blocking: if we can't write .gitignore, migration still succeeds
101
+ }
102
+ }
103
+ /**
104
+ * Recursively copy files from src to dest.
105
+ * Creates destination directories as needed. Skips if source doesn't exist.
106
+ * When both source and destination exist, compares mtimes: newer file wins.
107
+ * Logs a warning either way when a collision is detected.
108
+ */
109
+ function migrateDirectory(src, dest) {
110
+ if (!fs.existsSync(src))
111
+ return;
112
+ fs.mkdirSync(dest, { recursive: true });
113
+ const entries = fs.readdirSync(src, { withFileTypes: true });
114
+ for (const entry of entries) {
115
+ const srcPath = path.join(src, entry.name);
116
+ const destPath = path.join(dest, entry.name);
117
+ if (entry.isDirectory()) {
118
+ migrateDirectory(srcPath, destPath);
119
+ }
120
+ else if (!fs.existsSync(destPath)) {
121
+ fs.copyFileSync(srcPath, destPath);
122
+ }
123
+ else {
124
+ // Collision: both source and destination exist — compare mtimes
125
+ const srcMtime = fs.statSync(srcPath).mtimeMs;
126
+ const destMtime = fs.statSync(destPath).mtimeMs;
127
+ if (srcMtime > destMtime) {
128
+ fs.copyFileSync(srcPath, destPath);
129
+ (0, runtime_1.emitNervesEvent)({
130
+ level: "warn",
131
+ component: "daemon",
132
+ event: "daemon.bundle_migration_collision",
133
+ message: `migration collision: source newer, overwriting destination`,
134
+ meta: { srcPath, destPath, srcMtime, destMtime },
135
+ });
136
+ }
137
+ else {
138
+ (0, runtime_1.emitNervesEvent)({
139
+ level: "warn",
140
+ component: "daemon",
141
+ event: "daemon.bundle_migration_collision",
142
+ message: `migration collision: destination newer or equal, keeping destination`,
143
+ meta: { srcPath, destPath, srcMtime, destMtime },
144
+ });
145
+ }
146
+ }
147
+ }
148
+ }
40
149
  async function bundleMetaHook(ctx) {
41
150
  (0, runtime_1.emitNervesEvent)({
42
151
  component: "daemon",
@@ -56,9 +165,14 @@ async function bundleMetaHook(ctx) {
56
165
  // Malformed JSON -- treat as missing, will overwrite with fresh
57
166
  existing = undefined;
58
167
  }
168
+ // Run schema-2 migration if needed
169
+ const currentSchema = existing?.bundleSchemaVersion ?? 1;
170
+ if (currentSchema < 2) {
171
+ migrateToSchema2(ctx.agentRoot);
172
+ }
59
173
  const updated = {
60
174
  runtimeVersion: ctx.currentVersion,
61
- bundleSchemaVersion: existing?.bundleSchemaVersion ?? 1,
175
+ bundleSchemaVersion: currentSchema < 2 ? 2 : currentSchema,
62
176
  lastUpdated: new Date().toISOString(),
63
177
  };
64
178
  // Save old runtimeVersion as previousRuntimeVersion (if there was one)
@@ -0,0 +1,80 @@
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.createHttpHealthProbe = createHttpHealthProbe;
37
+ const http = __importStar(require("node:http"));
38
+ function createHttpHealthProbe(name, port, timeoutMs = 5000) {
39
+ return {
40
+ name,
41
+ check: () => new Promise((resolve) => {
42
+ const req = http.get({
43
+ hostname: "127.0.0.1",
44
+ port,
45
+ path: "/health",
46
+ timeout: timeoutMs,
47
+ }, (res) => {
48
+ let body = "";
49
+ res.on("data", (chunk) => {
50
+ body += chunk.toString();
51
+ });
52
+ res.on("end", () => {
53
+ if (res.statusCode !== 200) {
54
+ resolve({ ok: false, detail: `HTTP ${res.statusCode}` });
55
+ return;
56
+ }
57
+ try {
58
+ const parsed = JSON.parse(body);
59
+ if (parsed.status === "ok") {
60
+ resolve({ ok: true });
61
+ }
62
+ else {
63
+ resolve({ ok: false, detail: `unexpected status: ${String(parsed.status)}` });
64
+ }
65
+ }
66
+ catch {
67
+ resolve({ ok: false, detail: "invalid JSON response" });
68
+ }
69
+ });
70
+ });
71
+ req.on("timeout", () => {
72
+ req.destroy();
73
+ resolve({ ok: false, detail: "timeout" });
74
+ });
75
+ req.on("error", (err) => {
76
+ resolve({ ok: false, detail: err.message });
77
+ });
78
+ }),
79
+ };
80
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildInnerStatusOutput = buildInnerStatusOutput;
4
+ const runtime_1 = require("../../nerves/runtime");
5
+ function formatRelativeTime(elapsedMs) {
6
+ const minutes = Math.floor(elapsedMs / (60 * 1000));
7
+ if (minutes < 1)
8
+ return "just now";
9
+ if (minutes === 1)
10
+ return "1 minute ago";
11
+ if (minutes < 60)
12
+ return `${minutes} minutes ago`;
13
+ const hours = Math.floor(minutes / 60);
14
+ if (hours === 1)
15
+ return "1 hour ago";
16
+ return `${hours} hours ago`;
17
+ }
18
+ function formatCadence(cadenceMs) {
19
+ const minutes = Math.round(cadenceMs / (60 * 1000));
20
+ if (minutes >= 60) {
21
+ const hours = Math.round(minutes / 60);
22
+ return `${hours}h`;
23
+ }
24
+ return `${minutes}m`;
25
+ }
26
+ function buildInnerStatusOutput(input) {
27
+ const { agentName, runtimeState, journalFiles, heartbeat, attentionCount, now } = input;
28
+ const lines = [];
29
+ lines.push(`inner dialog status: ${agentName}`);
30
+ // Last turn
31
+ if (runtimeState?.lastCompletedAt) {
32
+ const lastMs = new Date(runtimeState.lastCompletedAt).getTime();
33
+ const elapsed = now - lastMs;
34
+ const relativeTime = formatRelativeTime(elapsed);
35
+ const reasonSuffix = runtimeState.reason ? ` (${runtimeState.reason})` : "";
36
+ lines.push(` last turn: ${relativeTime}${reasonSuffix}`);
37
+ }
38
+ else {
39
+ lines.push(" last turn: unknown");
40
+ }
41
+ // Status
42
+ if (runtimeState) {
43
+ const reasonSuffix = runtimeState.status === "running" && runtimeState.reason ? ` (${runtimeState.reason})` : "";
44
+ lines.push(` status: ${runtimeState.status}${reasonSuffix}`);
45
+ }
46
+ else {
47
+ lines.push(" status: unknown");
48
+ }
49
+ // Heartbeat health
50
+ if (heartbeat && heartbeat.lastCompletedAt !== null) {
51
+ const elapsed = now - heartbeat.lastCompletedAt;
52
+ const threshold = heartbeat.cadenceMs * 1.5;
53
+ const health = elapsed < threshold ? "healthy" : "overdue";
54
+ const cadenceStr = formatCadence(heartbeat.cadenceMs);
55
+ const sinceStr = formatRelativeTime(elapsed);
56
+ lines.push(` heartbeat: ${health} (cadence ${cadenceStr}, ${sinceStr})`);
57
+ }
58
+ else {
59
+ lines.push(" heartbeat: unknown");
60
+ }
61
+ // Journal
62
+ if (journalFiles.length === 0) {
63
+ lines.push(" journal: (empty)");
64
+ }
65
+ else {
66
+ lines.push(" journal:");
67
+ const sorted = [...journalFiles].sort((a, b) => b.mtimeMs - a.mtimeMs);
68
+ for (const file of sorted) {
69
+ const elapsed = now - file.mtimeMs;
70
+ const relativeTime = formatRelativeTime(elapsed);
71
+ lines.push(` - ${file.name} (${relativeTime})`);
72
+ }
73
+ }
74
+ // Attention
75
+ const thoughtWord = attentionCount === 1 ? "thought" : "thoughts";
76
+ lines.push(` attention: ${attentionCount} held ${thoughtWord}`);
77
+ (0, runtime_1.emitNervesEvent)({
78
+ component: "daemon",
79
+ event: "daemon.inner_status_read",
80
+ message: "inner dialog status read",
81
+ meta: {
82
+ agentName,
83
+ status: runtimeState?.status ?? "unknown",
84
+ journalCount: journalFiles.length,
85
+ attentionCount,
86
+ },
87
+ });
88
+ return lines.join("\n");
89
+ }
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ /**
3
+ * Interactive repair flow for degraded agents detected during `ouro up`.
4
+ *
5
+ * Examines each degraded agent's errorReason and fixHint to detect common
6
+ * issue patterns and prompt the user for repair actions.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.hasRunnableInteractiveRepair = hasRunnableInteractiveRepair;
10
+ exports.isAffirmativeAnswer = isAffirmativeAnswer;
11
+ exports.runInteractiveRepair = runInteractiveRepair;
12
+ const runtime_1 = require("../../nerves/runtime");
13
+ const identity_1 = require("../identity");
14
+ function isCredentialIssue(degraded) {
15
+ const reason = degraded.errorReason.toLowerCase();
16
+ const hint = degraded.fixHint.toLowerCase();
17
+ return reason.includes("credentials") || hint.includes("ouro auth");
18
+ }
19
+ function isVaultUnlockIssue(degraded) {
20
+ const text = `${degraded.errorReason}\n${degraded.fixHint}`.toLowerCase();
21
+ return /ouro vault unlock|credential vault is locked|vault(?: is)? locked/.test(text);
22
+ }
23
+ function isConfigError(degraded) {
24
+ return degraded.fixHint.length > 0 && !isVaultUnlockIssue(degraded) && !isCredentialIssue(degraded);
25
+ }
26
+ function hasRunnableInteractiveRepair(degraded) {
27
+ if (degraded.issue?.actions.some((action) => typedActionToRunnable(degraded, action) !== undefined)) {
28
+ return true;
29
+ }
30
+ return isVaultUnlockIssue(degraded) || isCredentialIssue(degraded);
31
+ }
32
+ function isAgentProvider(value) {
33
+ return Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
34
+ }
35
+ function extractProviderFromFixHint(fixHint) {
36
+ const provider = fixHint.match(/--provider\s+([a-z0-9-]+)/)?.[1]
37
+ ?? fixHint.match(/providers\.([a-z0-9-]+)/)?.[1];
38
+ if (!provider || !isAgentProvider(provider))
39
+ return undefined;
40
+ return provider;
41
+ }
42
+ function escapeRegExp(value) {
43
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
44
+ }
45
+ function cleanExtractedCommand(command) {
46
+ const cleaned = command?.trim().replace(/[`'",;:.)]+$/g, "").trim();
47
+ return cleaned && cleaned.length > 0 ? cleaned : undefined;
48
+ }
49
+ function extractRepairCommand(fixHint, commandPrefix) {
50
+ const escapedPrefix = escapeRegExp(commandPrefix);
51
+ const commandBody = `${escapedPrefix}(?=\\s|$)[^\`'"]*`;
52
+ const quoted = fixHint.match(new RegExp(`[\`'"](${commandBody})[\`'"]`, "i"))?.[1];
53
+ const unquoted = fixHint.match(new RegExp(`(${escapedPrefix}(?=\\s|$)[^\\n,;.]+)`, "i"))?.[1];
54
+ return cleanExtractedCommand(quoted) ?? cleanExtractedCommand(unquoted);
55
+ }
56
+ function authCommandFor(degraded) {
57
+ const command = extractRepairCommand(degraded.fixHint, "ouro auth");
58
+ return command && command.length > 0 ? command : `ouro auth --agent ${degraded.agent}`;
59
+ }
60
+ function vaultUnlockCommandFor(degraded) {
61
+ const command = extractRepairCommand(degraded.fixHint, "ouro vault unlock");
62
+ return command && command.length > 0 ? command : `ouro vault unlock --agent ${degraded.agent}`;
63
+ }
64
+ function isAffirmativeAnswer(answer) {
65
+ return /^(y|yes)$/i.test(answer.trim());
66
+ }
67
+ function writeDeclinedRepair(degraded, command, deps) {
68
+ deps.writeStdout(`repair skipped for ${degraded.agent}; run \`${command}\` later.`);
69
+ if (degraded.fixHint.includes("ouro vault replace") || degraded.fixHint.includes("ouro vault recover")) {
70
+ deps.writeStdout(`repair options for ${degraded.agent}: ${degraded.fixHint}`);
71
+ }
72
+ }
73
+ function runnableRepairActionFor(degraded) {
74
+ const typedAction = degraded.issue?.actions
75
+ .map((action) => typedActionToRunnable(degraded, action))
76
+ .find((action) => action !== undefined);
77
+ if (typedAction)
78
+ return typedAction;
79
+ if (isVaultUnlockIssue(degraded)) {
80
+ return { kind: "vault-unlock", label: "vault unlock", command: vaultUnlockCommandFor(degraded) };
81
+ }
82
+ if (isCredentialIssue(degraded)) {
83
+ return {
84
+ kind: "provider-auth",
85
+ label: "provider auth",
86
+ command: authCommandFor(degraded),
87
+ provider: extractProviderFromFixHint(degraded.fixHint),
88
+ };
89
+ }
90
+ return undefined;
91
+ }
92
+ function typedActionToRunnable(degraded, action) {
93
+ if (action.executable === false || action.command.includes("<"))
94
+ return undefined;
95
+ if (action.kind === "vault-unlock") {
96
+ return { kind: "vault-unlock", label: "vault unlock", command: action.command };
97
+ }
98
+ if (action.kind === "provider-auth") {
99
+ return {
100
+ kind: "provider-auth",
101
+ label: "provider auth",
102
+ command: action.command || `ouro auth --agent ${degraded.agent}`,
103
+ provider: action.provider,
104
+ };
105
+ }
106
+ return undefined;
107
+ }
108
+ function writeRepairQueueSummary(degraded, deps) {
109
+ const repairable = degraded
110
+ .map((entry) => ({ entry, action: runnableRepairActionFor(entry) }))
111
+ .filter((item) => item.action !== undefined);
112
+ if (repairable.length < 2)
113
+ return;
114
+ const lines = [
115
+ "repair queue:",
116
+ ...repairable.map(({ entry, action }) => ` - ${entry.agent}: ${action.label}: \`${action.command}\``),
117
+ ];
118
+ deps.writeStdout(lines.join("\n"));
119
+ }
120
+ async function runInteractiveRepair(degraded, deps) {
121
+ (0, runtime_1.emitNervesEvent)({
122
+ level: "info",
123
+ component: "daemon",
124
+ event: "daemon.interactive_repair_start",
125
+ message: "interactive repair flow started",
126
+ meta: { degradedCount: degraded.length },
127
+ });
128
+ if (degraded.length === 0) {
129
+ return { repairsAttempted: false };
130
+ }
131
+ let repairsAttempted = false;
132
+ writeRepairQueueSummary(degraded, deps);
133
+ for (const entry of degraded) {
134
+ const action = runnableRepairActionFor(entry);
135
+ if (action?.kind === "vault-unlock") {
136
+ const answer = await deps.promptInput(`run \`${action.command}\` now? Only say yes if you have the saved unlock secret. [y/n] `);
137
+ if (isAffirmativeAnswer(answer)) {
138
+ try {
139
+ if (!deps.runVaultUnlock) {
140
+ deps.writeStdout(`fix hint for ${entry.agent}: ${entry.fixHint}`);
141
+ }
142
+ else {
143
+ await deps.runVaultUnlock(entry.agent);
144
+ repairsAttempted = true;
145
+ }
146
+ }
147
+ catch (error) {
148
+ const msg = error instanceof Error ? error.message : String(error);
149
+ deps.writeStdout(`vault unlock error for ${entry.agent}: ${msg}`);
150
+ repairsAttempted = true;
151
+ (0, runtime_1.emitNervesEvent)({
152
+ level: "error",
153
+ component: "daemon",
154
+ event: "daemon.interactive_repair_vault_unlock_error",
155
+ message: `vault unlock failed for ${entry.agent}`,
156
+ meta: { agent: entry.agent, error: msg },
157
+ });
158
+ }
159
+ }
160
+ else {
161
+ writeDeclinedRepair(entry, action.command, deps);
162
+ }
163
+ }
164
+ else if (action?.kind === "provider-auth") {
165
+ const answer = await deps.promptInput(`run \`${action.command}\` now? [y/n] `);
166
+ if (isAffirmativeAnswer(answer)) {
167
+ try {
168
+ if (action.provider) {
169
+ await deps.runAuthFlow(entry.agent, action.provider);
170
+ }
171
+ else {
172
+ await deps.runAuthFlow(entry.agent);
173
+ }
174
+ repairsAttempted = true;
175
+ }
176
+ catch (error) {
177
+ const msg = error instanceof Error ? error.message : String(error);
178
+ deps.writeStdout(`auth flow error for ${entry.agent}: ${msg}`);
179
+ repairsAttempted = true;
180
+ (0, runtime_1.emitNervesEvent)({
181
+ level: "error",
182
+ component: "daemon",
183
+ event: "daemon.interactive_repair_auth_error",
184
+ message: `auth flow failed for ${entry.agent}`,
185
+ meta: { agent: entry.agent, error: msg },
186
+ });
187
+ }
188
+ }
189
+ else {
190
+ writeDeclinedRepair(entry, action.command, deps);
191
+ }
192
+ }
193
+ else if (isConfigError(entry)) {
194
+ deps.writeStdout(`fix hint for ${entry.agent}: ${entry.fixHint}`);
195
+ }
196
+ else {
197
+ // Unknown error with no actionable fix hint
198
+ deps.writeStdout(`${entry.agent}: ${entry.errorReason}`);
199
+ }
200
+ }
201
+ (0, runtime_1.emitNervesEvent)({
202
+ level: "info",
203
+ component: "daemon",
204
+ event: "daemon.interactive_repair_end",
205
+ message: "interactive repair flow completed",
206
+ meta: { repairsAttempted },
207
+ });
208
+ return { repairsAttempted };
209
+ }
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.DAEMON_PLIST_LABEL = void 0;
37
37
  exports.generateDaemonPlist = generateDaemonPlist;
38
+ exports.writeLaunchAgentPlist = writeLaunchAgentPlist;
38
39
  exports.installLaunchAgent = installLaunchAgent;
39
40
  exports.uninstallLaunchAgent = uninstallLaunchAgent;
40
41
  exports.isDaemonInstalled = isDaemonInstalled;
@@ -44,6 +45,9 @@ exports.DAEMON_PLIST_LABEL = "bot.ouro.daemon";
44
45
  function plistFilePath(homeDir) {
45
46
  return path.join(homeDir, "Library", "LaunchAgents", `${exports.DAEMON_PLIST_LABEL}.plist`);
46
47
  }
48
+ function userLaunchDomain(userUid) {
49
+ return `gui/${userUid}`;
50
+ }
47
51
  function generateDaemonPlist(options) {
48
52
  (0, runtime_1.emitNervesEvent)({
49
53
  component: "daemon",
@@ -65,15 +69,43 @@ function generateDaemonPlist(options) {
65
69
  ` <string>--socket</string>`,
66
70
  ` <string>${options.socketPath}</string>`,
67
71
  ` </array>`,
72
+ ` <key>RunAtLoad</key>`,
73
+ ` <true/>`,
68
74
  ` <key>KeepAlive</key>`,
69
75
  ` <true/>`,
70
76
  ];
77
+ if (options.envPath) {
78
+ lines.push(` <key>EnvironmentVariables</key>`, ` <dict>`, ` <key>PATH</key>`, ` <string>${options.envPath}</string>`, ` </dict>`);
79
+ }
71
80
  if (options.logDir) {
72
- lines.push(` <key>StandardOutPath</key>`, ` <string>${path.join(options.logDir, "ouro-daemon-stdout.log")}</string>`, ` <key>StandardErrorPath</key>`, ` <string>${path.join(options.logDir, "ouro-daemon-stderr.log")}</string>`);
81
+ // PR 1 decision: we no longer emit `StandardErrorPath` for the daemon.
82
+ // The daemon's structured nerves ndjson pipeline (rotated + gzipped via
83
+ // createNdjsonFileSink) is the source of truth for diagnostics. Writing
84
+ // raw process stderr to an unrotated file grew to 366 MB in the wild;
85
+ // dropping the key lets launchd forward stray stderr to the system log
86
+ // where it gets rotated by the OS.
87
+ lines.push(` <key>StandardOutPath</key>`, ` <string>${path.join(options.logDir, "ouro-daemon-stdout.log")}</string>`);
73
88
  }
74
89
  lines.push(`</dict>`, `</plist>`, ``);
75
90
  return lines.join("\n");
76
91
  }
92
+ function writeLaunchAgentPlist(deps, options) {
93
+ const launchAgentsDir = path.join(deps.homeDir, "Library", "LaunchAgents");
94
+ deps.mkdirp(launchAgentsDir);
95
+ if (options.logDir) {
96
+ deps.mkdirp(options.logDir);
97
+ }
98
+ const fullPath = plistFilePath(deps.homeDir);
99
+ const xml = generateDaemonPlist(options);
100
+ deps.writeFile(fullPath, xml);
101
+ (0, runtime_1.emitNervesEvent)({
102
+ component: "daemon",
103
+ event: "daemon.launchd_plist_written",
104
+ message: "daemon launch agent plist written",
105
+ meta: { plistPath: fullPath, entryPath: options.entryPath, socketPath: options.socketPath },
106
+ });
107
+ return fullPath;
108
+ }
77
109
  function installLaunchAgent(deps, options) {
78
110
  (0, runtime_1.emitNervesEvent)({
79
111
  component: "daemon",
@@ -81,23 +113,27 @@ function installLaunchAgent(deps, options) {
81
113
  message: "installing launch agent",
82
114
  meta: { entryPath: options.entryPath, socketPath: options.socketPath },
83
115
  });
84
- const launchAgentsDir = path.join(deps.homeDir, "Library", "LaunchAgents");
85
- deps.mkdirp(launchAgentsDir);
86
116
  const fullPath = plistFilePath(deps.homeDir);
117
+ const domain = userLaunchDomain(deps.userUid);
87
118
  // Unload existing (best effort) for idempotent re-install
88
119
  if (deps.existsFile(fullPath)) {
89
120
  try {
90
- deps.exec(`launchctl unload "${fullPath}"`);
121
+ deps.exec(`launchctl bootout ${domain} "${fullPath}"`);
91
122
  }
92
123
  catch { /* best effort */ }
93
124
  }
94
- const xml = generateDaemonPlist(options);
95
- deps.writeFile(fullPath, xml);
96
- deps.exec(`launchctl load "${fullPath}"`);
125
+ writeLaunchAgentPlist(deps, options);
126
+ // Bootstrap the plist so launchd manages crash recovery via KeepAlive.
127
+ // This is safe because ouro up calls this AFTER the daemon is already running,
128
+ // so launchd sees the existing process and just registers for KeepAlive.
129
+ try {
130
+ deps.exec(`launchctl bootstrap ${domain} "${fullPath}"`);
131
+ }
132
+ catch { /* already loaded */ }
97
133
  (0, runtime_1.emitNervesEvent)({
98
134
  component: "daemon",
99
135
  event: "daemon.launchd_installed",
100
- message: "launch agent installed",
136
+ message: "launch agent installed with KeepAlive",
101
137
  meta: { plistPath: fullPath },
102
138
  });
103
139
  }
@@ -109,9 +145,10 @@ function uninstallLaunchAgent(deps) {
109
145
  meta: {},
110
146
  });
111
147
  const fullPath = plistFilePath(deps.homeDir);
148
+ const domain = userLaunchDomain(deps.userUid);
112
149
  if (deps.existsFile(fullPath)) {
113
150
  try {
114
- deps.exec(`launchctl unload "${fullPath}"`);
151
+ deps.exec(`launchctl bootout ${domain} "${fullPath}"`);
115
152
  }
116
153
  catch { /* best effort */ }
117
154
  deps.removeFile(fullPath);