@ouro.bot/cli 0.1.0-alpha.34 → 0.1.0-alpha.340

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 (314) hide show
  1. package/README.md +188 -187
  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 +2031 -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/packets.js +193 -0
  12. package/dist/arc/presence.js +185 -0
  13. package/dist/arc/task-lifecycle.js +65 -0
  14. package/dist/heart/active-work.js +832 -0
  15. package/dist/heart/agent-entry.js +37 -2
  16. package/dist/heart/attachments/image-normalize.js +194 -0
  17. package/dist/heart/attachments/materialize.js +97 -0
  18. package/dist/heart/attachments/originals.js +88 -0
  19. package/dist/heart/attachments/render.js +29 -0
  20. package/dist/heart/attachments/sources/adapter.js +2 -0
  21. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  22. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  23. package/dist/heart/attachments/sources/index.js +16 -0
  24. package/dist/heart/attachments/store.js +103 -0
  25. package/dist/heart/attachments/types.js +93 -0
  26. package/dist/heart/auth/auth-flow.js +463 -0
  27. package/dist/heart/bridges/manager.js +358 -0
  28. package/dist/heart/bridges/state-machine.js +135 -0
  29. package/dist/heart/bridges/store.js +123 -0
  30. package/dist/heart/bundle-state.js +168 -0
  31. package/dist/heart/commitments.js +111 -0
  32. package/dist/heart/config-registry.js +304 -0
  33. package/dist/heart/config.js +53 -21
  34. package/dist/heart/core.js +695 -195
  35. package/dist/heart/cross-chat-delivery.js +131 -0
  36. package/dist/heart/daemon/agent-config-check.js +292 -0
  37. package/dist/heart/daemon/agent-discovery.js +79 -3
  38. package/dist/heart/daemon/agent-service.js +360 -0
  39. package/dist/heart/daemon/agentic-repair.js +170 -0
  40. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  41. package/dist/heart/daemon/cadence.js +70 -0
  42. package/dist/heart/daemon/cli-defaults.js +591 -0
  43. package/dist/heart/daemon/cli-exec.js +2297 -0
  44. package/dist/heart/daemon/cli-help.js +306 -0
  45. package/dist/heart/daemon/cli-parse.js +824 -0
  46. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  47. package/dist/heart/daemon/cli-render.js +512 -0
  48. package/dist/heart/daemon/cli-types.js +8 -0
  49. package/dist/heart/daemon/daemon-cli.js +30 -1171
  50. package/dist/heart/daemon/daemon-entry.js +358 -3
  51. package/dist/heart/daemon/daemon-health.js +141 -0
  52. package/dist/heart/daemon/daemon-runtime-sync.js +157 -12
  53. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  54. package/dist/heart/daemon/daemon.js +751 -58
  55. package/dist/heart/daemon/doctor-types.js +8 -0
  56. package/dist/heart/daemon/doctor.js +401 -0
  57. package/dist/heart/daemon/health-monitor.js +79 -1
  58. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  59. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  60. package/dist/heart/daemon/http-health-probe.js +80 -0
  61. package/dist/heart/daemon/inner-status.js +89 -0
  62. package/dist/heart/daemon/interactive-repair.js +91 -0
  63. package/dist/heart/daemon/launchd.js +46 -9
  64. package/dist/heart/daemon/log-tailer.js +82 -12
  65. package/dist/heart/daemon/logs-prune.js +105 -0
  66. package/dist/heart/daemon/message-router.js +17 -8
  67. package/dist/heart/daemon/os-cron-deps.js +134 -0
  68. package/dist/heart/daemon/ouro-bot-entry.js +1 -1
  69. package/dist/heart/daemon/process-manager.js +201 -0
  70. package/dist/heart/daemon/provider-discovery.js +105 -0
  71. package/dist/heart/daemon/pulse.js +463 -0
  72. package/dist/heart/daemon/run-hooks.js +2 -0
  73. package/dist/heart/daemon/runtime-logging.js +67 -16
  74. package/dist/heart/daemon/runtime-metadata.js +101 -0
  75. package/dist/heart/daemon/runtime-mode.js +67 -0
  76. package/dist/heart/daemon/safe-mode.js +161 -0
  77. package/dist/heart/daemon/sense-manager.js +72 -3
  78. package/dist/heart/daemon/session-id-resolver.js +131 -0
  79. package/dist/heart/daemon/skill-management-installer.js +94 -0
  80. package/dist/heart/daemon/socket-client.js +307 -0
  81. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  82. package/dist/heart/daemon/startup-tui.js +237 -0
  83. package/dist/heart/daemon/task-scheduler.js +3 -25
  84. package/dist/heart/daemon/thoughts.js +510 -0
  85. package/dist/heart/daemon/up-progress.js +135 -0
  86. package/dist/heart/delegation.js +62 -0
  87. package/dist/heart/habits/habit-migration.js +181 -0
  88. package/dist/heart/habits/habit-parser.js +140 -0
  89. package/dist/heart/habits/habit-scheduler.js +371 -0
  90. package/dist/heart/{daemon → hatch}/hatch-flow.js +32 -120
  91. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  92. package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
  93. package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
  94. package/dist/heart/identity.js +154 -59
  95. package/dist/heart/kicks.js +2 -20
  96. package/dist/heart/mcp/mcp-server.js +653 -0
  97. package/dist/heart/migrate-config.js +127 -0
  98. package/dist/heart/model-capabilities.js +59 -0
  99. package/dist/heart/outlook/outlook-http-hooks.js +64 -0
  100. package/dist/heart/outlook/outlook-http-response.js +7 -0
  101. package/dist/heart/outlook/outlook-http-routes.js +232 -0
  102. package/dist/heart/outlook/outlook-http-static.js +99 -0
  103. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  104. package/dist/heart/outlook/outlook-http.js +99 -0
  105. package/dist/heart/outlook/outlook-read.js +28 -0
  106. package/dist/heart/outlook/outlook-types.js +27 -0
  107. package/dist/heart/outlook/outlook-view.js +194 -0
  108. package/dist/heart/outlook/readers/agent-machine.js +355 -0
  109. package/dist/heart/outlook/readers/continuity-readers.js +332 -0
  110. package/dist/heart/outlook/readers/runtime-readers.js +660 -0
  111. package/dist/heart/outlook/readers/sessions.js +231 -0
  112. package/dist/heart/outlook/readers/shared.js +111 -0
  113. package/dist/heart/progress-story.js +42 -0
  114. package/dist/heart/provider-failover.js +135 -0
  115. package/dist/heart/provider-models.js +81 -0
  116. package/dist/heart/provider-ping.js +234 -0
  117. package/dist/heart/providers/anthropic-token.js +163 -0
  118. package/dist/heart/providers/anthropic.js +171 -50
  119. package/dist/heart/providers/azure.js +97 -11
  120. package/dist/heart/providers/error-classification.js +63 -0
  121. package/dist/heart/providers/github-copilot.js +135 -0
  122. package/dist/heart/providers/minimax-vlm.js +189 -0
  123. package/dist/heart/providers/minimax.js +23 -6
  124. package/dist/heart/providers/openai-codex.js +33 -23
  125. package/dist/heart/session-activity.js +190 -0
  126. package/dist/heart/session-events.js +726 -0
  127. package/dist/heart/session-recall.js +162 -0
  128. package/dist/heart/start-of-turn-packet.js +341 -0
  129. package/dist/heart/streaming.js +36 -27
  130. package/dist/heart/sync.js +332 -0
  131. package/dist/heart/target-resolution.js +127 -0
  132. package/dist/heart/tempo.js +93 -0
  133. package/dist/heart/temporal-view.js +41 -0
  134. package/dist/heart/tool-activity-callbacks.js +36 -0
  135. package/dist/heart/tool-description.js +135 -0
  136. package/dist/heart/tool-friction.js +55 -0
  137. package/dist/heart/tool-loop.js +200 -0
  138. package/dist/heart/turn-context.js +358 -0
  139. package/dist/heart/turn-coordinator.js +28 -0
  140. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
  141. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  142. package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
  143. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  144. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  145. package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
  146. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  147. package/dist/mind/associative-recall.js +137 -66
  148. package/dist/mind/bundle-manifest.js +7 -1
  149. package/dist/mind/context.js +89 -93
  150. package/dist/mind/diary-integrity.js +60 -0
  151. package/dist/mind/{memory.js → diary.js} +84 -96
  152. package/dist/mind/embedding-provider.js +60 -0
  153. package/dist/mind/file-state.js +179 -0
  154. package/dist/mind/first-impressions.js +14 -1
  155. package/dist/mind/friends/channel.js +56 -0
  156. package/dist/mind/friends/group-context.js +144 -0
  157. package/dist/mind/friends/resolver.js +37 -0
  158. package/dist/mind/friends/store-file.js +58 -3
  159. package/dist/mind/friends/trust-explanation.js +74 -0
  160. package/dist/mind/friends/types.js +8 -0
  161. package/dist/mind/journal-index.js +161 -0
  162. package/dist/mind/obligation-steering.js +221 -0
  163. package/dist/mind/pending.js +74 -7
  164. package/dist/mind/prompt.js +999 -111
  165. package/dist/mind/provenance-trust.js +26 -0
  166. package/dist/mind/scrutiny.js +173 -0
  167. package/dist/mind/token-estimate.js +8 -12
  168. package/dist/nerves/cli-logging.js +7 -1
  169. package/dist/nerves/coverage/audit.js +1 -1
  170. package/dist/nerves/coverage/file-completeness.js +83 -5
  171. package/dist/nerves/coverage/run-artifacts.js +1 -1
  172. package/dist/nerves/event-buffer.js +111 -0
  173. package/dist/nerves/index.js +224 -4
  174. package/dist/nerves/observation.js +20 -0
  175. package/dist/nerves/redact.js +79 -0
  176. package/dist/nerves/runtime.js +5 -1
  177. package/dist/outlook-ui/assets/index-DC7sZefn.js +61 -0
  178. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  179. package/dist/outlook-ui/index.html +15 -0
  180. package/dist/repertoire/ado-client.js +15 -56
  181. package/dist/repertoire/ado-semantic.js +11 -10
  182. package/dist/repertoire/api-client.js +97 -0
  183. package/dist/repertoire/bitwarden-store.js +319 -0
  184. package/dist/repertoire/bundle-templates.js +72 -0
  185. package/dist/repertoire/bw-installer.js +79 -0
  186. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  187. package/dist/repertoire/coding/context-pack.js +330 -0
  188. package/dist/repertoire/coding/feedback.js +197 -30
  189. package/dist/repertoire/coding/manager.js +158 -9
  190. package/dist/repertoire/coding/spawner.js +55 -9
  191. package/dist/repertoire/coding/tools.js +170 -7
  192. package/dist/repertoire/commerce-errors.js +109 -0
  193. package/dist/repertoire/commerce-self-test.js +156 -0
  194. package/dist/repertoire/credential-access.js +527 -0
  195. package/dist/repertoire/duffel-client.js +185 -0
  196. package/dist/repertoire/github-client.js +14 -55
  197. package/dist/repertoire/graph-client.js +11 -52
  198. package/dist/repertoire/guardrails.js +375 -0
  199. package/dist/repertoire/mcp-client.js +255 -0
  200. package/dist/repertoire/mcp-manager.js +305 -0
  201. package/dist/repertoire/mcp-tools.js +63 -0
  202. package/dist/repertoire/shell-sessions.js +133 -0
  203. package/dist/repertoire/skills.js +14 -23
  204. package/dist/repertoire/stripe-client.js +131 -0
  205. package/dist/repertoire/tasks/board.js +43 -5
  206. package/dist/repertoire/tasks/fix.js +182 -0
  207. package/dist/repertoire/tasks/index.js +28 -10
  208. package/dist/repertoire/tasks/lifecycle.js +2 -2
  209. package/dist/repertoire/tasks/parser.js +3 -2
  210. package/dist/repertoire/tasks/scanner.js +194 -37
  211. package/dist/repertoire/tasks/transitions.js +16 -79
  212. package/dist/repertoire/tool-results.js +29 -0
  213. package/dist/repertoire/tools-attachments.js +316 -0
  214. package/dist/repertoire/tools-base.js +45 -771
  215. package/dist/repertoire/tools-bluebubbles.js +1 -0
  216. package/dist/repertoire/tools-bridge.js +141 -0
  217. package/dist/repertoire/tools-bundle.js +984 -0
  218. package/dist/repertoire/tools-config.js +185 -0
  219. package/dist/repertoire/tools-continuity.js +248 -0
  220. package/dist/repertoire/tools-credential.js +182 -0
  221. package/dist/repertoire/tools-files.js +342 -0
  222. package/dist/repertoire/tools-flight.js +224 -0
  223. package/dist/repertoire/tools-flow.js +105 -0
  224. package/dist/repertoire/tools-github.js +1 -7
  225. package/dist/repertoire/tools-memory.js +376 -0
  226. package/dist/repertoire/tools-session.js +739 -0
  227. package/dist/repertoire/tools-shell.js +120 -0
  228. package/dist/repertoire/tools-stripe.js +180 -0
  229. package/dist/repertoire/tools-surface.js +243 -0
  230. package/dist/repertoire/tools-teams.js +12 -62
  231. package/dist/repertoire/tools-travel.js +125 -0
  232. package/dist/repertoire/tools-user-profile.js +144 -0
  233. package/dist/repertoire/tools-vault.js +110 -0
  234. package/dist/repertoire/tools.js +144 -138
  235. package/dist/repertoire/travel-api-client.js +360 -0
  236. package/dist/repertoire/user-profile.js +118 -0
  237. package/dist/repertoire/vault-setup.js +241 -0
  238. package/dist/scripts/claude-code-hook.js +41 -0
  239. package/dist/scripts/claude-code-stop-hook.js +47 -0
  240. package/dist/senses/attention-queue.js +116 -0
  241. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  242. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  243. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +225 -9
  244. package/dist/senses/bluebubbles/entry.js +13 -0
  245. package/dist/senses/bluebubbles/inbound-log.js +113 -0
  246. package/dist/senses/bluebubbles/index.js +1590 -0
  247. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  248. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +43 -12
  249. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  250. package/dist/senses/bluebubbles/replay.js +129 -0
  251. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  252. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  253. package/dist/senses/cli/bracketed-paste.js +82 -0
  254. package/dist/senses/cli/image-paste.js +287 -0
  255. package/dist/senses/cli/image-ref-navigation.js +75 -0
  256. package/dist/senses/cli/ink-app.js +156 -0
  257. package/dist/senses/cli/inline-diff.js +64 -0
  258. package/dist/senses/cli/input-keys.js +174 -0
  259. package/dist/senses/cli/kill-ring.js +86 -0
  260. package/dist/senses/cli/message-list.js +51 -0
  261. package/dist/senses/cli/ouro-tui.js +605 -0
  262. package/dist/senses/cli/spinner-imperative.js +135 -0
  263. package/dist/senses/cli/spinner.js +101 -0
  264. package/dist/senses/cli/status-line.js +60 -0
  265. package/dist/senses/cli/streaming-markdown.js +526 -0
  266. package/dist/senses/cli/tool-display.js +83 -0
  267. package/dist/senses/cli/tool-render.js +85 -0
  268. package/dist/senses/cli/tui-store.js +240 -0
  269. package/dist/senses/cli/virtual-list.js +35 -0
  270. package/dist/senses/cli-entry.js +1 -1
  271. package/dist/senses/cli-layout.js +187 -0
  272. package/dist/senses/cli.js +595 -246
  273. package/dist/senses/commands.js +65 -1
  274. package/dist/senses/continuity.js +94 -0
  275. package/dist/senses/habit-turn-message.js +108 -0
  276. package/dist/senses/inner-dialog-worker.js +112 -19
  277. package/dist/senses/inner-dialog.js +633 -86
  278. package/dist/senses/pipeline.js +567 -0
  279. package/dist/senses/shared-turn.js +199 -0
  280. package/dist/senses/surface-tool.js +68 -0
  281. package/dist/senses/teams.js +665 -160
  282. package/dist/senses/trust-gate.js +112 -2
  283. package/package.json +29 -7
  284. package/skills/agent-commerce.md +106 -0
  285. package/skills/browser-navigation.md +110 -0
  286. package/skills/commerce-setup-guide.md +116 -0
  287. package/skills/commerce-setup.md +84 -0
  288. package/skills/configure-dev-tools.md +81 -0
  289. package/skills/travel-planning.md +138 -0
  290. package/dist/heart/daemon/subagent-installer.js +0 -134
  291. package/dist/senses/bluebubbles-entry.js +0 -11
  292. package/dist/senses/bluebubbles.js +0 -547
  293. package/dist/senses/debug-activity.js +0 -124
  294. package/subagents/README.md +0 -73
  295. package/subagents/work-doer.md +0 -235
  296. package/subagents/work-merger.md +0 -618
  297. package/subagents/work-planner.md +0 -382
  298. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  299. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  300. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  301. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  302. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  303. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  304. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  305. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  306. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  307. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  308. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  309. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  310. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  311. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  312. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  313. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  314. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -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,91 @@
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.runInteractiveRepair = runInteractiveRepair;
10
+ const runtime_1 = require("../../nerves/runtime");
11
+ const identity_1 = require("../identity");
12
+ function isCredentialIssue(degraded) {
13
+ const reason = degraded.errorReason.toLowerCase();
14
+ const hint = degraded.fixHint.toLowerCase();
15
+ return reason.includes("credentials") || hint.includes("ouro auth");
16
+ }
17
+ function isConfigError(degraded) {
18
+ return degraded.fixHint.length > 0 && !isCredentialIssue(degraded);
19
+ }
20
+ function isAgentProvider(value) {
21
+ return Object.prototype.hasOwnProperty.call(identity_1.PROVIDER_CREDENTIALS, value);
22
+ }
23
+ function extractProviderFromFixHint(fixHint) {
24
+ const provider = fixHint.match(/--provider\s+([a-z0-9-]+)/)?.[1]
25
+ ?? fixHint.match(/providers\.([a-z0-9-]+)/)?.[1];
26
+ if (!provider || !isAgentProvider(provider))
27
+ return undefined;
28
+ return provider;
29
+ }
30
+ function authCommandFor(degraded) {
31
+ const command = degraded.fixHint.match(/ouro auth[^\n.]+/)?.[0]?.trim();
32
+ return command && command.length > 0 ? command : `ouro auth --agent ${degraded.agent}`;
33
+ }
34
+ async function runInteractiveRepair(degraded, deps) {
35
+ (0, runtime_1.emitNervesEvent)({
36
+ level: "info",
37
+ component: "daemon",
38
+ event: "daemon.interactive_repair_start",
39
+ message: "interactive repair flow started",
40
+ meta: { degradedCount: degraded.length },
41
+ });
42
+ if (degraded.length === 0) {
43
+ return { repairsAttempted: false };
44
+ }
45
+ let repairsAttempted = false;
46
+ for (const entry of degraded) {
47
+ if (isCredentialIssue(entry)) {
48
+ const provider = extractProviderFromFixHint(entry.fixHint);
49
+ const authCommand = authCommandFor(entry);
50
+ const answer = await deps.promptInput(`run \`${authCommand}\` now? [y/n] `);
51
+ if (answer.toLowerCase() === "y") {
52
+ try {
53
+ if (provider) {
54
+ await deps.runAuthFlow(entry.agent, provider);
55
+ }
56
+ else {
57
+ await deps.runAuthFlow(entry.agent);
58
+ }
59
+ repairsAttempted = true;
60
+ }
61
+ catch (error) {
62
+ const msg = error instanceof Error ? error.message : String(error);
63
+ deps.writeStdout(`auth flow error for ${entry.agent}: ${msg}`);
64
+ repairsAttempted = true;
65
+ (0, runtime_1.emitNervesEvent)({
66
+ level: "error",
67
+ component: "daemon",
68
+ event: "daemon.interactive_repair_auth_error",
69
+ message: `auth flow failed for ${entry.agent}`,
70
+ meta: { agent: entry.agent, error: msg },
71
+ });
72
+ }
73
+ }
74
+ }
75
+ else if (isConfigError(entry)) {
76
+ deps.writeStdout(`fix hint for ${entry.agent}: ${entry.fixHint}`);
77
+ }
78
+ else {
79
+ // Unknown error with no actionable fix hint
80
+ deps.writeStdout(`${entry.agent}: ${entry.errorReason}`);
81
+ }
82
+ }
83
+ (0, runtime_1.emitNervesEvent)({
84
+ level: "info",
85
+ component: "daemon",
86
+ event: "daemon.interactive_repair_end",
87
+ message: "interactive repair flow completed",
88
+ meta: { repairsAttempted },
89
+ });
90
+ return { repairsAttempted };
91
+ }
@@ -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);
@@ -37,39 +37,103 @@ exports.discoverLogFiles = discoverLogFiles;
37
37
  exports.readLastLines = readLastLines;
38
38
  exports.formatLogLine = formatLogLine;
39
39
  exports.tailLogs = tailLogs;
40
- const os = __importStar(require("os"));
41
40
  const path = __importStar(require("path"));
41
+ const zlib = __importStar(require("zlib"));
42
42
  const nerves_1 = require("../../nerves");
43
43
  const runtime_1 = require("../../nerves/runtime");
44
+ const identity_1 = require("../identity");
44
45
  const LEVEL_COLORS = {
45
46
  debug: "\x1b[2m",
46
47
  info: "\x1b[36m",
47
48
  warn: "\x1b[33m",
48
49
  error: "\x1b[31m",
49
50
  };
51
+ /**
52
+ * Parse a log filename into a (streamBase, generationRank) tuple.
53
+ *
54
+ * - `daemon.ndjson` → { streamBase: "daemon", rank: 0 } (active, newest)
55
+ * - `daemon.1.ndjson.gz` → { streamBase: "daemon", rank: 1 }
56
+ * - `daemon.5.ndjson.gz` → { streamBase: "daemon", rank: 5 } (oldest)
57
+ * - Anything else → null (not a log file).
58
+ *
59
+ * Higher rank = older. Used to sort so the oldest generation is read first
60
+ * and the active stream is read last, producing chronological output.
61
+ */
62
+ function parseLogFilename(name) {
63
+ if (name.endsWith(".ndjson.gz")) {
64
+ // e.g. daemon.1.ndjson.gz → base "daemon.1", strip ".gz" then ".ndjson" then ".<n>"
65
+ const withoutGz = name.slice(0, -".gz".length); // daemon.1.ndjson
66
+ const withoutNdjson = withoutGz.slice(0, -".ndjson".length); // daemon.1
67
+ const genMatch = withoutNdjson.match(/^(.+)\.(\d+)$/);
68
+ if (!genMatch)
69
+ return null;
70
+ return { streamBase: genMatch[1], rank: parseInt(genMatch[2], 10) };
71
+ }
72
+ if (name.endsWith(".ndjson")) {
73
+ const withoutNdjson = name.slice(0, -".ndjson".length);
74
+ // Active file: daemon.ndjson → base "daemon", rank 0
75
+ // Legacy numeric gen: daemon.1.ndjson → base "daemon", rank 1 (treat same as gzipped)
76
+ const legacyMatch = withoutNdjson.match(/^(.+)\.(\d+)$/);
77
+ if (legacyMatch) {
78
+ return { streamBase: legacyMatch[1], rank: parseInt(legacyMatch[2], 10) };
79
+ }
80
+ return { streamBase: withoutNdjson, rank: 0 };
81
+ }
82
+ return null;
83
+ }
50
84
  function discoverLogFiles(options) {
51
85
  /* v8 ignore start -- integration: default DI stubs for real OS @preserve */
52
- const homeDir = options.homeDir ?? os.homedir();
53
86
  const existsSync = options.existsSync ?? (() => false);
54
87
  const readdirSync = options.readdirSync ?? (() => []);
55
88
  /* v8 ignore stop */
56
- const logDir = path.join(homeDir, ".agentstate", "daemon", "logs");
57
- const files = [];
89
+ const logDir = options.homeDir
90
+ ? path.join(options.homeDir, "AgentBundles", `${options.agentName ?? "slugger"}.ouro`, "state", "daemon", "logs")
91
+ : (0, identity_1.getAgentDaemonLogsDir)(options.agentName);
92
+ const entries = [];
58
93
  if (existsSync(logDir)) {
59
94
  for (const name of readdirSync(logDir)) {
60
- if (!name.endsWith(".ndjson"))
95
+ const parsed = parseLogFilename(name);
96
+ if (!parsed)
61
97
  continue;
62
98
  if (options.agentFilter && !name.includes(options.agentFilter))
63
99
  continue;
64
- files.push(path.join(logDir, name));
100
+ entries.push({ name, parsed });
65
101
  }
66
102
  }
67
- return files.sort();
103
+ // Sort chronologically: for each stream, oldest generation first, active last.
104
+ // Across streams, sort alphabetically by streamBase so output is stable.
105
+ entries.sort((a, b) => {
106
+ if (a.parsed.streamBase !== b.parsed.streamBase) {
107
+ return a.parsed.streamBase < b.parsed.streamBase ? -1 : 1;
108
+ }
109
+ // Same stream: higher rank = older = read first.
110
+ return b.parsed.rank - a.parsed.rank;
111
+ });
112
+ return entries.map((e) => path.join(logDir, e.name));
113
+ }
114
+ /**
115
+ * Read a log file as a string, transparently handling gzipped rotations.
116
+ *
117
+ * For `.ndjson.gz` files we read the raw bytes via `fs.readFileSync` (ignoring
118
+ * any caller-supplied DI `readFileSync`, since that stub returns a `string`
119
+ * and would corrupt gzip bytes) and then `zlib.gunzipSync` to recover the
120
+ * original text. For plain `.ndjson` files we defer to the DI stub so tests
121
+ * can keep mocking fs.
122
+ */
123
+ function readNdjsonFileContents(filePath, readFileSync) {
124
+ if (filePath.endsWith(".gz")) {
125
+ // Binary path: tests can mock readFileSync to return a Buffer (typed as
126
+ // string) via `as unknown as string` — we accept both Buffer and Uint8Array.
127
+ const raw = readFileSync(filePath);
128
+ const buf = typeof raw === "string" ? Buffer.from(raw, "binary") : Buffer.from(raw);
129
+ return zlib.gunzipSync(buf).toString("utf-8");
130
+ }
131
+ return readFileSync(filePath, "utf-8");
68
132
  }
69
133
  function readLastLines(filePath, count, readFileSync) {
70
134
  let content;
71
135
  try {
72
- content = readFileSync(filePath, "utf-8");
136
+ content = readNdjsonFileContents(filePath, readFileSync);
73
137
  }
74
138
  catch {
75
139
  return [];
@@ -99,12 +163,17 @@ function tailLogs(options = {}) {
99
163
  const files = discoverLogFiles(options);
100
164
  (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.log_tailer_started", message: "log tailer started", meta: { fileCount: files.length, follow: !!options.follow } });
101
165
  const fileSizes = new Map();
102
- // Read initial lines
166
+ // Read initial lines from each discovered file (oldest gz → newest gz → active).
167
+ // Gzipped files are historical and never tailed in follow mode.
103
168
  for (const file of files) {
104
169
  const lines = readLastLines(file, lineCount, readFileSync);
105
170
  for (const line of lines) {
106
171
  writer(`${formatLogLine(line)}\n`);
107
172
  }
173
+ if (file.endsWith(".gz")) {
174
+ // Historical rotation — skip size tracking, never followed.
175
+ continue;
176
+ }
108
177
  try {
109
178
  const content = readFileSync(file, "utf-8");
110
179
  fileSizes.set(file, content.length);
@@ -113,9 +182,10 @@ function tailLogs(options = {}) {
113
182
  fileSizes.set(file, 0);
114
183
  }
115
184
  }
116
- // Follow mode
185
+ // Follow mode: only the active (non-gzipped) stream is watched.
117
186
  if (options.follow && watchFile && unwatchFile) {
118
- for (const file of files) {
187
+ const activeFiles = files.filter((f) => !f.endsWith(".gz"));
188
+ for (const file of activeFiles) {
119
189
  watchFile(file, () => {
120
190
  let content;
121
191
  try {
@@ -137,7 +207,7 @@ function tailLogs(options = {}) {
137
207
  });
138
208
  }
139
209
  return () => {
140
- for (const file of files) {
210
+ for (const file of activeFiles) {
141
211
  unwatchFile(file);
142
212
  }
143
213
  };
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pruneDaemonLogs = pruneDaemonLogs;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
6
+ const crypto_1 = require("crypto");
7
+ const nerves_1 = require("../../nerves");
8
+ const runtime_1 = require("../../nerves/runtime");
9
+ const identity_1 = require("../identity");
10
+ function pruneDaemonLogs(options = {}) {
11
+ /* v8 ignore next -- defensive: tests always pass logsDir to avoid prod paths @preserve */
12
+ const logsDir = options.logsDir ?? (0, identity_1.getAgentDaemonLogsDir)(options.agentName);
13
+ const maxSizeBytes = options.maxSizeBytes ?? nerves_1.DEFAULT_MAX_LOG_SIZE_BYTES;
14
+ const maxGenerations = options.maxGenerations ?? nerves_1.DEFAULT_MAX_GENERATIONS;
15
+ const traceId = (0, crypto_1.randomUUID)();
16
+ (0, runtime_1.emitNervesEvent)({
17
+ component: "nerves",
18
+ event: "nerves.logs_prune_start",
19
+ trace_id: traceId,
20
+ message: "pruning daemon logs",
21
+ meta: { logsDir, maxSizeBytes, maxGenerations },
22
+ });
23
+ let completed = false;
24
+ try {
25
+ if (!(0, fs_1.existsSync)(logsDir)) {
26
+ completed = true;
27
+ (0, runtime_1.emitNervesEvent)({
28
+ component: "nerves",
29
+ event: "nerves.logs_prune_end",
30
+ trace_id: traceId,
31
+ message: "daemon logs dir does not exist",
32
+ meta: { logsDir, filesCompacted: 0, bytesFreed: 0 },
33
+ });
34
+ return { filesCompacted: 0, bytesFreed: 0 };
35
+ }
36
+ let filesCompacted = 0;
37
+ let bytesFreed = 0;
38
+ // Enumerate and rotate each active .ndjson stream. We explicitly skip
39
+ // .gz and other files — only the active stream can be a rotation
40
+ // candidate. Legacy .N.ndjson uncompressed files are handled inside
41
+ // rotateIfNeeded's generation-shift step, so we skip them here to
42
+ // avoid double-rotating.
43
+ for (const name of (0, fs_1.readdirSync)(logsDir)) {
44
+ if (!name.endsWith(".ndjson"))
45
+ continue;
46
+ // Skip legacy generation files like daemon.1.ndjson; rotateIfNeeded
47
+ // will migrate them on the next rotation of their parent stream.
48
+ if (/\.\d+\.ndjson$/.test(name))
49
+ continue;
50
+ const filePath = (0, path_1.join)(logsDir, name);
51
+ let sizeBefore;
52
+ try {
53
+ sizeBefore = (0, fs_1.statSync)(filePath).size;
54
+ /* v8 ignore start -- defensive: file disappears between readdir and stat @preserve */
55
+ }
56
+ catch {
57
+ continue;
58
+ }
59
+ /* v8 ignore stop */
60
+ if (sizeBefore < maxSizeBytes)
61
+ continue;
62
+ // rotateIfNeeded returns true because we pre-checked sizeBefore >=
63
+ // maxSizeBytes above. The false branch of `if (rotated)` is defensive
64
+ // and unreachable under normal flow; we skip it from coverage so a
65
+ // future refactor that weakens the pre-check still reports correct
66
+ // counts without needing a contrived test.
67
+ const rotated = (0, nerves_1.rotateIfNeeded)(filePath, {
68
+ maxSizeBytes,
69
+ maxGenerations,
70
+ compress: true,
71
+ });
72
+ /* v8 ignore next 3 -- defensive: pre-check guarantees rotated=true @preserve */
73
+ if (!rotated) {
74
+ continue;
75
+ }
76
+ filesCompacted += 1;
77
+ bytesFreed += sizeBefore;
78
+ }
79
+ completed = true;
80
+ (0, runtime_1.emitNervesEvent)({
81
+ component: "nerves",
82
+ event: "nerves.logs_prune_end",
83
+ trace_id: traceId,
84
+ message: "daemon logs pruned",
85
+ meta: { logsDir, filesCompacted, bytesFreed },
86
+ });
87
+ return { filesCompacted, bytesFreed };
88
+ }
89
+ catch (err) {
90
+ /* v8 ignore next -- defensive: completed=true only reached after try returns @preserve */
91
+ if (!completed) {
92
+ /* v8 ignore next -- defensive: rotation always throws real Errors @preserve */
93
+ const reason = err instanceof Error ? err.message : String(err);
94
+ (0, runtime_1.emitNervesEvent)({
95
+ component: "nerves",
96
+ event: "nerves.logs_prune_error",
97
+ trace_id: traceId,
98
+ level: "error",
99
+ message: "daemon logs prune failed",
100
+ meta: { logsDir, error: reason },
101
+ });
102
+ }
103
+ throw err;
104
+ }
105
+ }
@@ -35,9 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.FileMessageRouter = void 0;
37
37
  const fs = __importStar(require("fs"));
38
- const os = __importStar(require("os"));
39
38
  const path = __importStar(require("path"));
40
39
  const runtime_1 = require("../../nerves/runtime");
40
+ const identity_1 = require("../identity");
41
41
  function messageId(nowIso) {
42
42
  return `msg-${nowIso.replace(/[^0-9]/g, "")}`;
43
43
  }
@@ -45,7 +45,7 @@ class FileMessageRouter {
45
45
  baseDir;
46
46
  now;
47
47
  constructor(options = {}) {
48
- this.baseDir = options.baseDir ?? path.join(os.homedir(), ".agentstate", "messages");
48
+ this.baseDir = options.baseDir ?? (0, identity_1.getAgentMessagesRoot)();
49
49
  this.now = options.now ?? (() => new Date().toISOString());
50
50
  fs.mkdirSync(this.baseDir, { recursive: true });
51
51
  }
@@ -77,12 +77,21 @@ class FileMessageRouter {
77
77
  if (!fs.existsSync(inboxPath))
78
78
  return [];
79
79
  const raw = fs.readFileSync(inboxPath, "utf-8");
80
- fs.writeFileSync(inboxPath, "", "utf-8");
81
- const messages = raw
82
- .split("\n")
83
- .map((line) => line.trim())
84
- .filter((line) => line.length > 0)
85
- .map((line) => JSON.parse(line));
80
+ const messages = [];
81
+ const unparsed = [];
82
+ for (const line of raw.split("\n")) {
83
+ const trimmed = line.trim();
84
+ if (!trimmed)
85
+ continue;
86
+ try {
87
+ messages.push(JSON.parse(trimmed));
88
+ }
89
+ catch {
90
+ unparsed.push(trimmed);
91
+ }
92
+ }
93
+ // Only clear inbox after parsing; preserve lines that failed to parse.
94
+ fs.writeFileSync(inboxPath, unparsed.length > 0 ? unparsed.map((l) => `${l}\n`).join("") : "", "utf-8");
86
95
  (0, runtime_1.emitNervesEvent)({
87
96
  component: "daemon",
88
97
  event: "daemon.message_polled",