@ouro.bot/cli 0.1.0-alpha.4 → 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 (338) hide show
  1. package/README.md +208 -184
  2. package/SerpentGuide.ouro/agent.json +82 -0
  3. package/SerpentGuide.ouro/psyche/SOUL.md +25 -0
  4. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +2 -2
  5. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  6. package/assets/ouroboros.png +0 -0
  7. package/changelog.json +2610 -0
  8. package/dist/arc/attention-types.js +8 -0
  9. package/dist/arc/cares.js +140 -0
  10. package/dist/arc/episodes.js +117 -0
  11. package/dist/arc/intentions.js +133 -0
  12. package/dist/arc/json-store.js +117 -0
  13. package/dist/arc/obligations.js +237 -0
  14. package/dist/arc/packets.js +193 -0
  15. package/dist/arc/presence.js +185 -0
  16. package/dist/arc/task-lifecycle.js +65 -0
  17. package/dist/heart/active-work.js +832 -0
  18. package/dist/heart/agent-entry.js +58 -3
  19. package/dist/heart/attachments/image-normalize.js +194 -0
  20. package/dist/heart/attachments/materialize.js +97 -0
  21. package/dist/heart/attachments/originals.js +88 -0
  22. package/dist/heart/attachments/render.js +29 -0
  23. package/dist/heart/attachments/sources/adapter.js +2 -0
  24. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  25. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  26. package/dist/heart/attachments/sources/index.js +16 -0
  27. package/dist/heart/attachments/store.js +103 -0
  28. package/dist/heart/attachments/types.js +93 -0
  29. package/dist/heart/auth/auth-flow.js +417 -0
  30. package/dist/heart/bridges/manager.js +358 -0
  31. package/dist/heart/bridges/state-machine.js +135 -0
  32. package/dist/heart/bridges/store.js +123 -0
  33. package/dist/heart/bundle-state.js +168 -0
  34. package/dist/heart/commitments.js +111 -0
  35. package/dist/heart/config-registry.js +304 -0
  36. package/dist/heart/config.js +176 -130
  37. package/dist/heart/core.js +872 -255
  38. package/dist/heart/cross-chat-delivery.js +131 -0
  39. package/dist/heart/daemon/agent-config-check.js +397 -0
  40. package/dist/heart/daemon/agent-discovery.js +157 -0
  41. package/dist/heart/daemon/agent-service.js +360 -0
  42. package/dist/heart/daemon/agentic-repair.js +213 -0
  43. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  44. package/dist/heart/daemon/cadence.js +70 -0
  45. package/dist/heart/daemon/cli-defaults.js +599 -0
  46. package/dist/heart/daemon/cli-exec.js +3616 -0
  47. package/dist/heart/daemon/cli-help.js +396 -0
  48. package/dist/heart/daemon/cli-parse.js +1118 -0
  49. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  50. package/dist/heart/daemon/cli-render.js +560 -0
  51. package/dist/heart/daemon/cli-types.js +8 -0
  52. package/dist/heart/daemon/daemon-cli.js +29 -673
  53. package/dist/heart/daemon/daemon-entry.js +370 -8
  54. package/dist/heart/daemon/daemon-health.js +141 -0
  55. package/dist/heart/daemon/daemon-runtime-sync.js +235 -0
  56. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  57. package/dist/heart/daemon/daemon.js +813 -19
  58. package/dist/heart/daemon/doctor-types.js +8 -0
  59. package/dist/heart/daemon/doctor.js +419 -0
  60. package/dist/heart/daemon/health-monitor.js +79 -1
  61. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  62. package/dist/heart/daemon/hooks/bundle-meta.js +206 -0
  63. package/dist/heart/daemon/http-health-probe.js +80 -0
  64. package/dist/heart/daemon/inner-status.js +89 -0
  65. package/dist/heart/daemon/interactive-repair.js +209 -0
  66. package/dist/heart/daemon/launchd.js +171 -0
  67. package/dist/heart/daemon/log-tailer.js +82 -12
  68. package/dist/heart/daemon/logs-prune.js +105 -0
  69. package/dist/heart/daemon/message-router.js +17 -8
  70. package/dist/heart/daemon/os-cron-deps.js +134 -0
  71. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  72. package/dist/heart/daemon/ouro-entry.js +3 -1
  73. package/dist/heart/daemon/process-manager.js +202 -1
  74. package/dist/heart/daemon/provider-discovery.js +137 -0
  75. package/dist/heart/daemon/pulse.js +475 -0
  76. package/dist/heart/daemon/readiness-repair.js +216 -0
  77. package/dist/heart/daemon/run-hooks.js +39 -0
  78. package/dist/heart/daemon/runtime-logging.js +67 -16
  79. package/dist/heart/daemon/runtime-metadata.js +191 -0
  80. package/dist/heart/daemon/runtime-mode.js +67 -0
  81. package/dist/heart/daemon/safe-mode.js +161 -0
  82. package/dist/heart/daemon/sense-manager.js +355 -0
  83. package/dist/heart/daemon/session-id-resolver.js +131 -0
  84. package/dist/heart/daemon/skill-management-installer.js +94 -0
  85. package/dist/heart/daemon/socket-client.js +307 -0
  86. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  87. package/dist/heart/daemon/startup-tui.js +237 -0
  88. package/dist/heart/daemon/task-scheduler.js +3 -25
  89. package/dist/heart/daemon/thoughts.js +510 -0
  90. package/dist/heart/daemon/up-progress.js +135 -0
  91. package/dist/heart/delegation.js +62 -0
  92. package/dist/heart/habits/habit-migration.js +181 -0
  93. package/dist/heart/habits/habit-parser.js +140 -0
  94. package/dist/heart/habits/habit-scheduler.js +371 -0
  95. package/dist/heart/hatch/hatch-animation.js +35 -0
  96. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -135
  97. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  98. package/dist/heart/hatch/specialist-orchestrator.js +129 -0
  99. package/dist/heart/hatch/specialist-prompt.js +102 -0
  100. package/dist/heart/hatch/specialist-tools.js +304 -0
  101. package/dist/heart/identity.js +246 -58
  102. package/dist/heart/kept-notes.js +357 -0
  103. package/dist/heart/kicks.js +2 -20
  104. package/dist/heart/machine-identity.js +161 -0
  105. package/dist/heart/mcp/mcp-server.js +653 -0
  106. package/dist/heart/migrate-config.js +100 -0
  107. package/dist/heart/model-capabilities.js +59 -0
  108. package/dist/heart/outlook/outlook-http-hooks.js +64 -0
  109. package/dist/heart/outlook/outlook-http-response.js +7 -0
  110. package/dist/heart/outlook/outlook-http-routes.js +232 -0
  111. package/dist/heart/outlook/outlook-http-static.js +99 -0
  112. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  113. package/dist/heart/outlook/outlook-http.js +99 -0
  114. package/dist/heart/outlook/outlook-read.js +28 -0
  115. package/dist/heart/outlook/outlook-types.js +27 -0
  116. package/dist/heart/outlook/outlook-view.js +195 -0
  117. package/dist/heart/outlook/readers/agent-machine.js +359 -0
  118. package/dist/heart/outlook/readers/continuity-readers.js +332 -0
  119. package/dist/heart/outlook/readers/runtime-readers.js +660 -0
  120. package/dist/heart/outlook/readers/sessions.js +232 -0
  121. package/dist/heart/outlook/readers/shared.js +111 -0
  122. package/dist/heart/platform.js +81 -0
  123. package/dist/heart/progress-story.js +42 -0
  124. package/dist/heart/provider-attempt.js +133 -0
  125. package/dist/heart/provider-binding-resolver.js +239 -0
  126. package/dist/heart/provider-credentials.js +383 -0
  127. package/dist/heart/provider-failover.js +266 -0
  128. package/dist/heart/provider-models.js +81 -0
  129. package/dist/heart/provider-ping.js +237 -0
  130. package/dist/heart/provider-state.js +216 -0
  131. package/dist/heart/provider-visibility.js +186 -0
  132. package/dist/heart/providers/anthropic-token.js +131 -0
  133. package/dist/heart/providers/anthropic.js +202 -50
  134. package/dist/heart/providers/azure.js +103 -12
  135. package/dist/heart/providers/error-classification.js +63 -0
  136. package/dist/heart/providers/github-copilot.js +145 -0
  137. package/dist/heart/providers/minimax-vlm.js +189 -0
  138. package/dist/heart/providers/minimax.js +29 -7
  139. package/dist/heart/providers/openai-codex.js +39 -29
  140. package/dist/heart/runtime-credentials.js +181 -0
  141. package/dist/heart/sense-truth.js +61 -0
  142. package/dist/heart/session-activity.js +190 -0
  143. package/dist/heart/session-events.js +855 -0
  144. package/dist/heart/session-transcript.js +167 -0
  145. package/dist/heart/start-of-turn-packet.js +345 -0
  146. package/dist/heart/streaming.js +117 -33
  147. package/dist/heart/sync.js +332 -0
  148. package/dist/heart/target-resolution.js +127 -0
  149. package/dist/heart/tempo.js +93 -0
  150. package/dist/heart/temporal-view.js +41 -0
  151. package/dist/heart/tool-activity-callbacks.js +36 -0
  152. package/dist/heart/tool-description.js +135 -0
  153. package/dist/heart/tool-friction.js +55 -0
  154. package/dist/heart/tool-loop.js +200 -0
  155. package/dist/heart/turn-context.js +351 -0
  156. package/dist/heart/turn-coordinator.js +28 -0
  157. package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
  158. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  159. package/dist/heart/versioning/ouro-path-installer.js +301 -0
  160. package/dist/heart/{daemon → versioning}/ouro-uti.js +11 -2
  161. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  162. package/dist/heart/versioning/staged-restart.js +146 -0
  163. package/dist/heart/versioning/update-checker.js +113 -0
  164. package/dist/heart/versioning/update-hooks.js +142 -0
  165. package/dist/heart/versioning/wrapper-publish-guard.js +86 -0
  166. package/dist/mind/bundle-manifest.js +77 -1
  167. package/dist/mind/context.js +141 -94
  168. package/dist/mind/diary-integrity.js +60 -0
  169. package/dist/mind/{memory.js → diary.js} +84 -96
  170. package/dist/mind/embedding-provider.js +60 -0
  171. package/dist/mind/file-state.js +179 -0
  172. package/dist/mind/first-impressions.js +16 -2
  173. package/dist/mind/friends/channel.js +64 -0
  174. package/dist/mind/friends/group-context.js +144 -0
  175. package/dist/mind/friends/resolver.js +38 -1
  176. package/dist/mind/friends/store-file.js +58 -3
  177. package/dist/mind/friends/trust-explanation.js +74 -0
  178. package/dist/mind/friends/types.js +10 -2
  179. package/dist/mind/journal-index.js +161 -0
  180. package/dist/mind/note-search.js +268 -0
  181. package/dist/mind/obligation-steering.js +221 -0
  182. package/dist/mind/pending.js +76 -9
  183. package/dist/mind/phrases.js +1 -0
  184. package/dist/mind/prompt-refresh.js +3 -2
  185. package/dist/mind/prompt.js +1111 -117
  186. package/dist/mind/provenance-trust.js +26 -0
  187. package/dist/mind/scrutiny.js +173 -0
  188. package/dist/mind/token-estimate.js +8 -12
  189. package/dist/nerves/cli-logging.js +22 -3
  190. package/dist/nerves/coverage/audit-rules.js +15 -6
  191. package/dist/nerves/coverage/audit.js +28 -2
  192. package/dist/nerves/coverage/cli.js +1 -1
  193. package/dist/nerves/coverage/contract.js +5 -5
  194. package/dist/nerves/coverage/file-completeness.js +83 -5
  195. package/dist/nerves/coverage/run-artifacts.js +1 -1
  196. package/dist/nerves/event-buffer.js +111 -0
  197. package/dist/nerves/index.js +224 -4
  198. package/dist/nerves/observation.js +20 -0
  199. package/dist/nerves/redact.js +79 -0
  200. package/dist/nerves/runtime.js +5 -1
  201. package/dist/outlook-ui/assets/index-BAcU08c-.css +1 -0
  202. package/dist/outlook-ui/assets/index-D7l3l4vY.js +61 -0
  203. package/dist/outlook-ui/index.html +15 -0
  204. package/dist/repertoire/ado-client.js +17 -56
  205. package/dist/repertoire/ado-semantic.js +11 -10
  206. package/dist/repertoire/api-client.js +97 -0
  207. package/dist/repertoire/bitwarden-store.js +461 -0
  208. package/dist/repertoire/bundle-templates.js +72 -0
  209. package/dist/repertoire/bw-installer.js +79 -0
  210. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  211. package/dist/repertoire/coding/context-pack.js +330 -0
  212. package/dist/repertoire/coding/feedback.js +301 -0
  213. package/dist/repertoire/coding/index.js +4 -1
  214. package/dist/repertoire/coding/manager.js +220 -13
  215. package/dist/repertoire/coding/spawner.js +58 -12
  216. package/dist/repertoire/coding/tools.js +209 -7
  217. package/dist/repertoire/commerce-errors.js +109 -0
  218. package/dist/repertoire/commerce-self-test.js +156 -0
  219. package/dist/repertoire/credential-access.js +107 -0
  220. package/dist/repertoire/data/ado-endpoints.json +188 -0
  221. package/dist/repertoire/duffel-client.js +185 -0
  222. package/dist/repertoire/github-client.js +14 -55
  223. package/dist/repertoire/graph-client.js +11 -52
  224. package/dist/repertoire/guardrails.js +371 -0
  225. package/dist/repertoire/mcp-client.js +255 -0
  226. package/dist/repertoire/mcp-manager.js +305 -0
  227. package/dist/repertoire/mcp-tools.js +63 -0
  228. package/dist/repertoire/shell-sessions.js +133 -0
  229. package/dist/repertoire/skills.js +15 -24
  230. package/dist/repertoire/stripe-client.js +131 -0
  231. package/dist/repertoire/tasks/board.js +43 -5
  232. package/dist/repertoire/tasks/fix.js +182 -0
  233. package/dist/repertoire/tasks/index.js +28 -10
  234. package/dist/repertoire/tasks/lifecycle.js +2 -2
  235. package/dist/repertoire/tasks/parser.js +3 -2
  236. package/dist/repertoire/tasks/scanner.js +194 -37
  237. package/dist/repertoire/tasks/transitions.js +16 -79
  238. package/dist/repertoire/tool-results.js +29 -0
  239. package/dist/repertoire/tools-attachments.js +317 -0
  240. package/dist/repertoire/tools-base.js +45 -707
  241. package/dist/repertoire/tools-bluebubbles.js +94 -0
  242. package/dist/repertoire/tools-bridge.js +141 -0
  243. package/dist/repertoire/tools-bundle.js +984 -0
  244. package/dist/repertoire/tools-config.js +185 -0
  245. package/dist/repertoire/tools-continuity.js +248 -0
  246. package/dist/repertoire/tools-credential.js +182 -0
  247. package/dist/repertoire/tools-files.js +342 -0
  248. package/dist/repertoire/tools-flight.js +224 -0
  249. package/dist/repertoire/tools-flow.js +105 -0
  250. package/dist/repertoire/tools-github.js +1 -7
  251. package/dist/repertoire/tools-notes.js +376 -0
  252. package/dist/repertoire/tools-session.js +739 -0
  253. package/dist/repertoire/tools-shell.js +120 -0
  254. package/dist/repertoire/tools-stripe.js +180 -0
  255. package/dist/repertoire/tools-surface.js +243 -0
  256. package/dist/repertoire/tools-teams.js +64 -61
  257. package/dist/repertoire/tools-travel.js +125 -0
  258. package/dist/repertoire/tools-user-profile.js +144 -0
  259. package/dist/repertoire/tools-vault.js +40 -0
  260. package/dist/repertoire/tools.js +149 -98
  261. package/dist/repertoire/travel-api-client.js +360 -0
  262. package/dist/repertoire/user-profile.js +118 -0
  263. package/dist/repertoire/vault-setup.js +246 -0
  264. package/dist/repertoire/vault-unlock.js +382 -0
  265. package/dist/scripts/claude-code-hook.js +41 -0
  266. package/dist/scripts/claude-code-stop-hook.js +47 -0
  267. package/dist/senses/attention-queue.js +116 -0
  268. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  269. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  270. package/dist/senses/bluebubbles/client.js +685 -0
  271. package/dist/senses/bluebubbles/entry.js +70 -0
  272. package/dist/senses/bluebubbles/inbound-log.js +113 -0
  273. package/dist/senses/bluebubbles/index.js +1620 -0
  274. package/dist/senses/bluebubbles/media.js +389 -0
  275. package/dist/senses/bluebubbles/model.js +282 -0
  276. package/dist/senses/bluebubbles/mutation-log.js +116 -0
  277. package/dist/senses/bluebubbles/replay.js +129 -0
  278. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  279. package/dist/senses/bluebubbles/session-cleanup.js +72 -0
  280. package/dist/senses/cli/bracketed-paste.js +82 -0
  281. package/dist/senses/cli/image-paste.js +287 -0
  282. package/dist/senses/cli/image-ref-navigation.js +75 -0
  283. package/dist/senses/cli/ink-app.js +156 -0
  284. package/dist/senses/cli/inline-diff.js +64 -0
  285. package/dist/senses/cli/input-keys.js +174 -0
  286. package/dist/senses/cli/kill-ring.js +86 -0
  287. package/dist/senses/cli/message-list.js +51 -0
  288. package/dist/senses/cli/ouro-tui.js +605 -0
  289. package/dist/senses/cli/spinner-imperative.js +135 -0
  290. package/dist/senses/cli/spinner.js +101 -0
  291. package/dist/senses/cli/status-line.js +60 -0
  292. package/dist/senses/cli/streaming-markdown.js +526 -0
  293. package/dist/senses/cli/tool-display.js +83 -0
  294. package/dist/senses/cli/tool-render.js +85 -0
  295. package/dist/senses/cli/tui-store.js +240 -0
  296. package/dist/senses/cli/virtual-list.js +35 -0
  297. package/dist/senses/cli-entry.js +60 -8
  298. package/dist/senses/cli-layout.js +187 -0
  299. package/dist/senses/cli.js +768 -264
  300. package/dist/senses/commands.js +66 -3
  301. package/dist/senses/continuity.js +94 -0
  302. package/dist/senses/habit-turn-message.js +108 -0
  303. package/dist/senses/inner-dialog-worker.js +112 -19
  304. package/dist/senses/inner-dialog.js +636 -86
  305. package/dist/senses/pipeline.js +602 -0
  306. package/dist/senses/proactive-content-guard.js +51 -0
  307. package/dist/senses/shared-turn.js +205 -0
  308. package/dist/senses/surface-tool.js +68 -0
  309. package/dist/senses/teams-entry.js +60 -8
  310. package/dist/senses/teams.js +844 -197
  311. package/dist/senses/trust-gate.js +112 -2
  312. package/package.json +38 -6
  313. package/skills/agent-commerce.md +106 -0
  314. package/skills/browser-navigation.md +110 -0
  315. package/skills/commerce-setup-guide.md +116 -0
  316. package/skills/commerce-setup.md +84 -0
  317. package/skills/configure-dev-tools.md +101 -0
  318. package/skills/travel-planning.md +134 -0
  319. package/AdoptionSpecialist.ouro/agent.json +0 -20
  320. package/AdoptionSpecialist.ouro/psyche/SOUL.md +0 -22
  321. package/dist/heart/daemon/subagent-installer.js +0 -125
  322. package/dist/inner-worker-entry.js +0 -4
  323. package/dist/mind/associative-recall.js +0 -197
  324. package/subagents/README.md +0 -73
  325. package/subagents/work-doer.md +0 -233
  326. package/subagents/work-merger.md +0 -624
  327. package/subagents/work-planner.md +0 -373
  328. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  329. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  330. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  331. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  332. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  333. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  334. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  335. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  336. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  337. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  338. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runHooks = runHooks;
4
+ const runtime_1 = require("../../nerves/runtime");
5
+ const bundle_meta_1 = require("./hooks/bundle-meta");
6
+ const agent_config_v2_1 = require("./hooks/agent-config-v2");
7
+ async function runHooks(deps) {
8
+ (0, runtime_1.emitNervesEvent)({
9
+ component: "daemon",
10
+ event: "daemon.run_hooks_start",
11
+ message: "running update hooks",
12
+ meta: { bundlesRoot: deps.bundlesRoot },
13
+ });
14
+ try {
15
+ deps.registerUpdateHook(bundle_meta_1.bundleMetaHook);
16
+ deps.registerUpdateHook(agent_config_v2_1.agentConfigV2Hook);
17
+ const currentVersion = deps.getPackageVersion();
18
+ await deps.applyPendingUpdates(deps.bundlesRoot, currentVersion);
19
+ (0, runtime_1.emitNervesEvent)({
20
+ component: "daemon",
21
+ event: "daemon.run_hooks_success",
22
+ message: "update hooks completed successfully",
23
+ meta: { bundlesRoot: deps.bundlesRoot },
24
+ });
25
+ return 0;
26
+ }
27
+ catch (err) {
28
+ (0, runtime_1.emitNervesEvent)({
29
+ component: "daemon",
30
+ event: "daemon.run_hooks_error",
31
+ message: "update hooks failed",
32
+ meta: {
33
+ bundlesRoot: deps.bundlesRoot,
34
+ error: err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err),
35
+ },
36
+ });
37
+ return 1;
38
+ }
39
+ }
@@ -35,53 +35,104 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.configureDaemonRuntimeLogger = configureDaemonRuntimeLogger;
37
37
  const fs = __importStar(require("fs"));
38
- const os = __importStar(require("os"));
39
38
  const path = __importStar(require("path"));
40
39
  const nerves_1 = require("../../nerves");
41
40
  const runtime_1 = require("../../nerves/runtime");
42
- const DEFAULT_RUNTIME_LOGGING = {
41
+ const identity_1 = require("../identity");
42
+ const LEGACY_SHARED_RUNTIME_LOGGING = {
43
43
  level: "info",
44
44
  sinks: ["terminal", "ndjson"],
45
45
  };
46
- function defaultLevelForProcess(processName) {
47
- return processName === "daemon" ? "info" : "warn";
46
+ function defaultLoggingForProcess(processName) {
47
+ if (processName === "ouro" || processName === "ouro-bot") {
48
+ return {
49
+ level: "info",
50
+ sinks: ["ndjson"],
51
+ };
52
+ }
53
+ if (processName === "bluebubbles") {
54
+ return {
55
+ level: "warn",
56
+ sinks: ["terminal", "ndjson"],
57
+ };
58
+ }
59
+ return { ...LEGACY_SHARED_RUNTIME_LOGGING };
48
60
  }
49
61
  function isLogLevel(value) {
50
62
  return value === "debug" || value === "info" || value === "warn" || value === "error";
51
63
  }
64
+ function normalizeSinks(value, fallback) {
65
+ if (!Array.isArray(value)) {
66
+ return [...fallback];
67
+ }
68
+ const sinks = value.filter((entry) => entry === "terminal" || entry === "ndjson");
69
+ return sinks.length > 0 ? [...new Set(sinks)] : [...fallback];
70
+ }
71
+ function isLegacySharedDefaultConfig(candidate, normalizedLevel, normalizedSinks) {
72
+ return normalizedLevel === LEGACY_SHARED_RUNTIME_LOGGING.level
73
+ && normalizedSinks.length === LEGACY_SHARED_RUNTIME_LOGGING.sinks.length
74
+ && LEGACY_SHARED_RUNTIME_LOGGING.sinks.every((sink) => normalizedSinks.includes(sink))
75
+ && Object.keys(candidate).every((key) => key === "level" || key === "sinks");
76
+ }
52
77
  function resolveRuntimeLoggingConfig(configPath, processName) {
53
- const defaultLevel = defaultLevelForProcess(processName);
78
+ const processDefault = defaultLoggingForProcess(processName);
54
79
  let parsed = null;
55
80
  try {
56
81
  const raw = fs.readFileSync(configPath, "utf-8");
57
82
  parsed = JSON.parse(raw);
58
83
  }
59
84
  catch {
60
- return { ...DEFAULT_RUNTIME_LOGGING, level: defaultLevel };
85
+ return { ...processDefault };
61
86
  }
62
87
  if (!parsed || typeof parsed !== "object") {
63
- return { ...DEFAULT_RUNTIME_LOGGING, level: defaultLevel };
88
+ return { ...processDefault };
64
89
  }
65
90
  const candidate = parsed;
66
- const level = isLogLevel(candidate.level) ? candidate.level : defaultLevel;
67
- const sinks = Array.isArray(candidate.sinks)
68
- ? candidate.sinks.filter((entry) => entry === "terminal" || entry === "ndjson")
69
- : DEFAULT_RUNTIME_LOGGING.sinks;
91
+ const level = isLogLevel(candidate.level) ? candidate.level : processDefault.level;
92
+ const sinks = normalizeSinks(candidate.sinks, processDefault.sinks);
93
+ if ((processName === "ouro" || processName === "ouro-bot")
94
+ && isLegacySharedDefaultConfig(candidate, level, sinks)) {
95
+ return { ...processDefault };
96
+ }
70
97
  return {
71
98
  level,
72
- sinks: sinks.length > 0 ? [...new Set(sinks)] : [...DEFAULT_RUNTIME_LOGGING.sinks],
99
+ sinks,
73
100
  };
74
101
  }
102
+ function resolveAgentNameForPaths(explicit) {
103
+ if (explicit && explicit.trim().length > 0)
104
+ return explicit.trim();
105
+ try {
106
+ return (0, identity_1.getAgentName)();
107
+ }
108
+ catch {
109
+ return "slugger";
110
+ }
111
+ }
75
112
  function configureDaemonRuntimeLogger(processName, options = {}) {
76
- const homeDir = options.homeDir ?? os.homedir();
77
- const configPath = options.configPath ?? path.join(homeDir, ".agentstate", "daemon", "logging.json");
113
+ const agentName = resolveAgentNameForPaths(options.agentName);
114
+ const configPath = options.configPath
115
+ ?? (options.homeDir
116
+ ? path.join(options.homeDir, "AgentBundles", `${agentName}.ouro`, "state", "daemon", "logging.json")
117
+ : (0, identity_1.getAgentDaemonLoggingConfigPath)(agentName));
78
118
  const config = resolveRuntimeLoggingConfig(configPath, processName);
119
+ const logsDir = options.homeDir
120
+ ? path.join(options.homeDir, "AgentBundles", `${agentName}.ouro`, "state", "daemon", "logs")
121
+ : (0, identity_1.getAgentDaemonLogsDir)(agentName);
122
+ // Rotation policy per daemon ndjson stream (Unit 1c):
123
+ // 25 MB threshold x 5 gzipped generations = ~30 MB peak per stream.
124
+ // Call sites previously relied on the implicit 50 MB default; we now pass
125
+ // the options object explicitly so the policy is visible at the call site.
79
126
  const sinks = config.sinks.map((sinkName) => {
80
127
  if (sinkName === "terminal") {
81
128
  return (0, nerves_1.createTerminalSink)();
82
129
  }
83
- const ndjsonPath = path.join(homeDir, ".agentstate", "daemon", "logs", `${processName}.ndjson`);
84
- return (0, nerves_1.createNdjsonFileSink)(ndjsonPath);
130
+ const ndjsonPath = path.join(logsDir, `${processName}.ndjson`);
131
+ return (0, nerves_1.createNdjsonFileSink)(ndjsonPath, {
132
+ maxSizeBytes: 25 * 1024 * 1024,
133
+ maxGenerations: 5,
134
+ compress: true,
135
+ });
85
136
  });
86
137
  const logger = (0, nerves_1.createLogger)({
87
138
  level: config.level,
@@ -0,0 +1,191 @@
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.getRuntimeMetadata = getRuntimeMetadata;
37
+ const crypto_1 = require("crypto");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const childProcess = __importStar(require("child_process"));
41
+ const identity_1 = require("../identity");
42
+ const runtime_1 = require("../../nerves/runtime");
43
+ const UNKNOWN_METADATA = "unknown";
44
+ function optionalFunction(target, key) {
45
+ try {
46
+ const candidate = target[key];
47
+ return typeof candidate === "function" ? candidate : null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ function readVersion(packageJsonPath, readFileSyncImpl) {
54
+ try {
55
+ const parsed = JSON.parse(readFileSyncImpl(packageJsonPath, "utf-8"));
56
+ return typeof parsed.version === "string" && parsed.version.trim().length > 0
57
+ ? parsed.version.trim()
58
+ : UNKNOWN_METADATA;
59
+ }
60
+ catch {
61
+ return UNKNOWN_METADATA;
62
+ }
63
+ }
64
+ function readLastUpdated(repoRoot, packageJsonPath, statSyncImpl, execFileSyncImpl) {
65
+ try {
66
+ const raw = execFileSyncImpl("git", ["log", "-1", "--format=%cI"], {
67
+ cwd: repoRoot,
68
+ encoding: "utf-8",
69
+ stdio: ["ignore", "pipe", "ignore"],
70
+ }).trim();
71
+ if (raw.length > 0) {
72
+ return { value: raw, source: "git" };
73
+ }
74
+ }
75
+ catch {
76
+ // fall through to mtime fallback
77
+ }
78
+ try {
79
+ const stats = statSyncImpl(packageJsonPath);
80
+ return {
81
+ value: stats.mtime.toISOString(),
82
+ source: "package-json-mtime",
83
+ };
84
+ }
85
+ catch {
86
+ return { value: UNKNOWN_METADATA, source: "unknown" };
87
+ }
88
+ }
89
+ function listConfigTargets(bundlesRoot, daemonLoggingPath, readdirSyncImpl) {
90
+ if (!readdirSyncImpl)
91
+ return [];
92
+ const targets = new Set();
93
+ if (daemonLoggingPath) {
94
+ targets.add(daemonLoggingPath);
95
+ }
96
+ try {
97
+ const bundleEntries = readdirSyncImpl(bundlesRoot, { withFileTypes: true });
98
+ for (const entry of bundleEntries) {
99
+ if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
100
+ continue;
101
+ targets.add(path.join(bundlesRoot, entry.name, "agent.json"));
102
+ }
103
+ }
104
+ catch {
105
+ // ignore unreadable bundle roots
106
+ }
107
+ return [...targets].sort();
108
+ }
109
+ function readConfigFingerprint(targets, readFileSyncImpl, existsSyncImpl) {
110
+ if (!readFileSyncImpl || !existsSyncImpl) {
111
+ return {
112
+ value: UNKNOWN_METADATA,
113
+ source: "unknown",
114
+ trackedFiles: targets.length,
115
+ presentFiles: 0,
116
+ };
117
+ }
118
+ const hash = (0, crypto_1.createHash)("sha256");
119
+ let presentFiles = 0;
120
+ for (const target of targets) {
121
+ hash.update(target);
122
+ hash.update("\0");
123
+ if (!existsSyncImpl(target)) {
124
+ hash.update("missing");
125
+ hash.update("\0");
126
+ continue;
127
+ }
128
+ presentFiles += 1;
129
+ hash.update("present");
130
+ hash.update("\0");
131
+ try {
132
+ hash.update(readFileSyncImpl(target, "utf-8"));
133
+ }
134
+ catch {
135
+ hash.update("unreadable");
136
+ }
137
+ hash.update("\0");
138
+ }
139
+ return {
140
+ value: hash.digest("hex"),
141
+ source: "content-hash",
142
+ trackedFiles: targets.length,
143
+ presentFiles,
144
+ };
145
+ }
146
+ function getRuntimeMetadata(deps = {}) {
147
+ const repoRoot = deps.repoRoot ?? (0, identity_1.getRepoRoot)();
148
+ const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
149
+ const daemonLoggingPath = deps.daemonLoggingPath ?? (0, identity_1.getAgentDaemonLoggingConfigPath)();
150
+ const readFileSyncImpl = deps.readFileSync ?? optionalFunction(fs, "readFileSync")?.bind(fs) ?? null;
151
+ const statSyncImpl = deps.statSync ?? optionalFunction(fs, "statSync")?.bind(fs) ?? null;
152
+ const readdirSyncImpl = deps.readdirSync ?? optionalFunction(fs, "readdirSync")?.bind(fs) ?? null;
153
+ const existsSyncImpl = deps.existsSync ?? optionalFunction(fs, "existsSync")?.bind(fs) ?? null;
154
+ const execFileSyncImpl = deps.execFileSync
155
+ ?? optionalFunction(childProcess, "execFileSync")?.bind(childProcess)
156
+ ?? null;
157
+ const packageJsonPath = path.join(repoRoot, "package.json");
158
+ const version = readFileSyncImpl
159
+ ? readVersion(packageJsonPath, readFileSyncImpl)
160
+ : UNKNOWN_METADATA;
161
+ const lastUpdated = statSyncImpl
162
+ ? readLastUpdated(repoRoot, packageJsonPath, statSyncImpl, execFileSyncImpl ?? (() => {
163
+ throw new Error("git unavailable");
164
+ }))
165
+ : { value: UNKNOWN_METADATA, source: "unknown" };
166
+ const configTargets = listConfigTargets(bundlesRoot, daemonLoggingPath, readdirSyncImpl);
167
+ const configFingerprint = readConfigFingerprint(configTargets, readFileSyncImpl, existsSyncImpl);
168
+ (0, runtime_1.emitNervesEvent)({
169
+ component: "daemon",
170
+ event: "daemon.runtime_metadata_read",
171
+ message: "read runtime metadata",
172
+ meta: {
173
+ version,
174
+ lastUpdated: lastUpdated.value,
175
+ lastUpdatedSource: lastUpdated.source,
176
+ repoRoot,
177
+ configFingerprint: configFingerprint.value === UNKNOWN_METADATA
178
+ ? UNKNOWN_METADATA
179
+ : configFingerprint.value.slice(0, 12),
180
+ configFingerprintSource: configFingerprint.source,
181
+ configTrackedFiles: configFingerprint.trackedFiles,
182
+ configPresentFiles: configFingerprint.presentFiles,
183
+ },
184
+ });
185
+ return {
186
+ version,
187
+ lastUpdated: lastUpdated.value,
188
+ repoRoot,
189
+ configFingerprint: configFingerprint.value,
190
+ };
191
+ }
@@ -0,0 +1,67 @@
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.detectRuntimeMode = detectRuntimeMode;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const runtime_1 = require("../../nerves/runtime");
40
+ function detectRuntimeMode(rootPath, deps = {}) {
41
+ const checkExists = deps.existsSync ?? fs.existsSync;
42
+ // 1. Production: installed via npm
43
+ if (rootPath.includes("node_modules/@ouro.bot/cli") ||
44
+ rootPath.includes("node_modules/ouro.bot")) {
45
+ (0, runtime_1.emitNervesEvent)({
46
+ component: "daemon",
47
+ event: "daemon.runtime_mode_detected",
48
+ message: "detected runtime mode",
49
+ meta: { rootPath, mode: "production" },
50
+ });
51
+ return "production";
52
+ }
53
+ // 2-4. Everything else is dev: worktrees, git repos, unknown paths
54
+ // (conservative default: assume dev unless proven production)
55
+ const reason = rootPath.includes(".claude/worktrees/")
56
+ ? "worktree"
57
+ : checkExists(path.join(rootPath, ".git"))
58
+ ? "git-repo"
59
+ : "unknown";
60
+ (0, runtime_1.emitNervesEvent)({
61
+ component: "daemon",
62
+ event: "daemon.runtime_mode_detected",
63
+ message: "detected runtime mode",
64
+ meta: { rootPath, mode: "dev", reason },
65
+ });
66
+ return "dev";
67
+ }
@@ -0,0 +1,161 @@
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.SAFE_MODE_OVERRIDE_FILENAME = void 0;
37
+ exports.detectSafeMode = detectSafeMode;
38
+ exports.pruneOldCrashes = pruneOldCrashes;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const runtime_1 = require("../../nerves/runtime");
42
+ exports.SAFE_MODE_OVERRIDE_FILENAME = "safe-mode-override.json";
43
+ /** 3+ crashes within this window triggers safe mode */
44
+ const CRASH_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
45
+ const CRASH_THRESHOLD = 3;
46
+ /**
47
+ * Reads crash history from the tombstone file and determines if safe mode should be active.
48
+ * Returns active if 3+ crashes occurred within 5 minutes.
49
+ *
50
+ * Safe mode is bypassed when:
51
+ * - devMode is true
52
+ * - A safe-mode override file exists (written by `ouro up --force`)
53
+ */
54
+ function detectSafeMode(tombstonePath, options) {
55
+ const inactive = { active: false, reason: "", enteredAt: "" };
56
+ // Dev mode: never enter safe mode
57
+ if (options?.devMode) {
58
+ return inactive;
59
+ }
60
+ // Check for override file (--force)
61
+ const overridePath = path.join(path.dirname(tombstonePath), exports.SAFE_MODE_OVERRIDE_FILENAME);
62
+ try {
63
+ if (fs.existsSync(overridePath)) {
64
+ return inactive;
65
+ }
66
+ }
67
+ catch {
68
+ // Best-effort check
69
+ }
70
+ // Read tombstone
71
+ let parsed;
72
+ try {
73
+ const raw = fs.readFileSync(tombstonePath, "utf-8");
74
+ parsed = JSON.parse(raw);
75
+ }
76
+ catch {
77
+ return inactive;
78
+ }
79
+ // Extract recentCrashes
80
+ if (!Array.isArray(parsed.recentCrashes)) {
81
+ return inactive;
82
+ }
83
+ const nowMs = options?.now ? options.now() : Date.now();
84
+ const windowStart = nowMs - CRASH_WINDOW_MS;
85
+ // Filter to valid string timestamps within the crash window
86
+ const recentInWindow = parsed.recentCrashes.filter((entry) => {
87
+ if (typeof entry !== "string")
88
+ return false;
89
+ const ts = new Date(entry).getTime();
90
+ if (isNaN(ts))
91
+ return false;
92
+ return ts >= windowStart;
93
+ });
94
+ if (recentInWindow.length < CRASH_THRESHOLD) {
95
+ return inactive;
96
+ }
97
+ const result = {
98
+ active: true,
99
+ reason: `crash loop detected: ${recentInWindow.length} crashes in last 5 minutes`,
100
+ enteredAt: new Date(nowMs).toISOString(),
101
+ };
102
+ (0, runtime_1.emitNervesEvent)({
103
+ level: "error",
104
+ component: "daemon",
105
+ event: "daemon.safe_mode_entered",
106
+ message: result.reason,
107
+ meta: {
108
+ crashCount: recentInWindow.length,
109
+ windowMs: CRASH_WINDOW_MS,
110
+ tombstonePath,
111
+ },
112
+ });
113
+ return result;
114
+ }
115
+ /**
116
+ * Prunes crash entries older than 5 minutes from the tombstone's recentCrashes.
117
+ * Also removes the safe-mode override file if present.
118
+ * Called after successful startup (uptime > stability threshold).
119
+ */
120
+ function pruneOldCrashes(tombstonePath, options) {
121
+ // Remove override file
122
+ const overridePath = path.join(path.dirname(tombstonePath), exports.SAFE_MODE_OVERRIDE_FILENAME);
123
+ try {
124
+ if (fs.existsSync(overridePath)) {
125
+ fs.unlinkSync(overridePath);
126
+ }
127
+ }
128
+ catch {
129
+ // Best-effort
130
+ }
131
+ // Read existing tombstone
132
+ let parsed;
133
+ try {
134
+ const raw = fs.readFileSync(tombstonePath, "utf-8");
135
+ parsed = JSON.parse(raw);
136
+ }
137
+ catch {
138
+ return;
139
+ }
140
+ if (!Array.isArray(parsed.recentCrashes)) {
141
+ return;
142
+ }
143
+ const nowMs = options?.now ? options.now() : Date.now();
144
+ const windowStart = nowMs - CRASH_WINDOW_MS;
145
+ // Keep only entries within the window
146
+ const pruned = parsed.recentCrashes.filter((entry) => {
147
+ if (typeof entry !== "string")
148
+ return false;
149
+ const ts = new Date(entry).getTime();
150
+ if (isNaN(ts))
151
+ return false;
152
+ return ts >= windowStart;
153
+ });
154
+ parsed.recentCrashes = pruned;
155
+ try {
156
+ fs.writeFileSync(tombstonePath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
157
+ }
158
+ catch {
159
+ // Best-effort
160
+ }
161
+ }