@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.200

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 (215) hide show
  1. package/README.md +187 -191
  2. package/SerpentGuide.ouro/agent.json +82 -0
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +6 -3
  4. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +2 -2
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +1248 -0
  7. package/dist/heart/active-work.js +832 -0
  8. package/dist/heart/bridges/manager.js +358 -0
  9. package/dist/heart/bridges/state-machine.js +135 -0
  10. package/dist/heart/bridges/store.js +123 -0
  11. package/dist/heart/cares.js +179 -0
  12. package/dist/heart/commitments.js +111 -0
  13. package/dist/heart/config.js +114 -28
  14. package/dist/heart/core.js +689 -120
  15. package/dist/heart/cross-chat-delivery.js +146 -0
  16. package/dist/heart/daemon/agent-config-check.js +149 -0
  17. package/dist/heart/daemon/agent-discovery.js +81 -0
  18. package/dist/heart/daemon/agent-service.js +360 -0
  19. package/dist/heart/daemon/auth-flow.js +477 -0
  20. package/dist/heart/daemon/cadence.js +70 -0
  21. package/dist/heart/daemon/daemon-cli.js +2897 -193
  22. package/dist/heart/daemon/daemon-entry.js +194 -8
  23. package/dist/heart/daemon/daemon-health.js +136 -0
  24. package/dist/heart/daemon/daemon-runtime-sync.js +231 -0
  25. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  26. package/dist/heart/daemon/daemon.js +488 -13
  27. package/dist/heart/daemon/habit-migration.js +181 -0
  28. package/dist/heart/daemon/habit-parser.js +119 -0
  29. package/dist/heart/daemon/habit-scheduler.js +369 -0
  30. package/dist/heart/daemon/hatch-animation.js +35 -0
  31. package/dist/heart/daemon/hatch-flow.js +32 -122
  32. package/dist/heart/daemon/hatch-specialist.js +7 -2
  33. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  34. package/dist/heart/daemon/hooks/bundle-meta.js +206 -0
  35. package/dist/heart/daemon/inner-status.js +89 -0
  36. package/dist/heart/daemon/launchd.js +165 -0
  37. package/dist/heart/daemon/log-tailer.js +4 -3
  38. package/dist/heart/daemon/mcp-server.js +591 -0
  39. package/dist/heart/daemon/message-router.js +17 -8
  40. package/dist/heart/daemon/os-cron-deps.js +134 -0
  41. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  42. package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
  43. package/dist/heart/daemon/ouro-path-installer.js +221 -0
  44. package/dist/heart/daemon/ouro-uti.js +11 -2
  45. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  46. package/dist/heart/daemon/outlook-http.js +439 -0
  47. package/dist/heart/daemon/outlook-read.js +1618 -0
  48. package/dist/heart/daemon/outlook-render.js +1032 -0
  49. package/dist/heart/daemon/outlook-types.js +7 -0
  50. package/dist/heart/daemon/outlook-view.js +194 -0
  51. package/dist/heart/daemon/process-manager.js +94 -1
  52. package/dist/heart/daemon/run-hooks.js +39 -0
  53. package/dist/heart/daemon/runtime-logging.js +61 -14
  54. package/dist/heart/daemon/runtime-metadata.js +219 -0
  55. package/dist/heart/daemon/runtime-mode.js +67 -0
  56. package/dist/heart/daemon/safe-mode.js +161 -0
  57. package/dist/heart/daemon/sense-manager.js +314 -0
  58. package/dist/heart/daemon/session-id-resolver.js +131 -0
  59. package/dist/heart/daemon/skill-management-installer.js +94 -0
  60. package/dist/heart/daemon/socket-client.js +202 -0
  61. package/dist/heart/daemon/specialist-orchestrator.js +129 -0
  62. package/dist/heart/daemon/specialist-prompt.js +101 -0
  63. package/dist/heart/daemon/specialist-tools.js +283 -0
  64. package/dist/heart/daemon/staged-restart.js +138 -0
  65. package/dist/heart/daemon/task-scheduler.js +3 -25
  66. package/dist/heart/daemon/thoughts.js +507 -0
  67. package/dist/heart/daemon/update-checker.js +111 -0
  68. package/dist/heart/daemon/update-hooks.js +138 -0
  69. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  70. package/dist/heart/delegation.js +62 -0
  71. package/dist/heart/identity.js +220 -52
  72. package/dist/heart/intentions.js +173 -0
  73. package/dist/heart/kicks.js +2 -20
  74. package/dist/heart/migrate-config.js +127 -0
  75. package/dist/heart/model-capabilities.js +48 -0
  76. package/dist/heart/obligations.js +225 -0
  77. package/dist/heart/presence.js +185 -0
  78. package/dist/heart/progress-story.js +42 -0
  79. package/dist/heart/provider-failover.js +88 -0
  80. package/dist/heart/provider-ping.js +178 -0
  81. package/dist/heart/providers/anthropic-token.js +163 -0
  82. package/dist/heart/providers/anthropic.js +199 -36
  83. package/dist/heart/providers/azure.js +116 -10
  84. package/dist/heart/providers/github-copilot.js +157 -0
  85. package/dist/heart/providers/minimax.js +33 -3
  86. package/dist/heart/providers/openai-codex.js +51 -16
  87. package/dist/heart/sense-truth.js +61 -0
  88. package/dist/heart/session-activity.js +172 -0
  89. package/dist/heart/session-recall.js +216 -0
  90. package/dist/heart/start-of-turn-packet.js +232 -0
  91. package/dist/heart/streaming.js +120 -33
  92. package/dist/heart/sync.js +217 -0
  93. package/dist/heart/target-resolution.js +127 -0
  94. package/dist/heart/tempo.js +93 -0
  95. package/dist/heart/temporal-view.js +41 -0
  96. package/dist/heart/tool-activity-callbacks.js +36 -0
  97. package/dist/heart/tool-description.js +135 -0
  98. package/dist/heart/tool-loop.js +200 -0
  99. package/dist/heart/turn-coordinator.js +28 -0
  100. package/dist/mind/associative-recall.js +122 -32
  101. package/dist/mind/bundle-manifest.js +77 -1
  102. package/dist/mind/context.js +125 -14
  103. package/dist/mind/{memory.js → diary.js} +69 -56
  104. package/dist/mind/episodes.js +119 -0
  105. package/dist/mind/file-state.js +179 -0
  106. package/dist/mind/first-impressions.js +16 -2
  107. package/dist/mind/friends/channel.js +64 -0
  108. package/dist/mind/friends/group-context.js +144 -0
  109. package/dist/mind/friends/store-file.js +36 -0
  110. package/dist/mind/friends/trust-explanation.js +74 -0
  111. package/dist/mind/friends/types.js +9 -1
  112. package/dist/mind/journal-index.js +161 -0
  113. package/dist/mind/obligation-steering.js +221 -0
  114. package/dist/mind/obligations.js +138 -0
  115. package/dist/mind/pending.js +76 -9
  116. package/dist/mind/phrases.js +1 -0
  117. package/dist/mind/prompt.js +861 -105
  118. package/dist/mind/scrutiny.js +173 -0
  119. package/dist/mind/token-estimate.js +8 -12
  120. package/dist/nerves/cli-logging.js +15 -2
  121. package/dist/nerves/coverage/audit.js +1 -1
  122. package/dist/nerves/coverage/run-artifacts.js +1 -1
  123. package/dist/nerves/event-buffer.js +111 -0
  124. package/dist/nerves/index.js +63 -3
  125. package/dist/nerves/observation.js +20 -0
  126. package/dist/nerves/runtime.js +5 -1
  127. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  128. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  129. package/dist/outlook-ui/index.html +15 -0
  130. package/dist/repertoire/ado-client.js +4 -2
  131. package/dist/repertoire/ado-semantic.js +11 -5
  132. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  133. package/dist/repertoire/coding/context-pack.js +330 -0
  134. package/dist/repertoire/coding/feedback.js +301 -0
  135. package/dist/repertoire/coding/index.js +4 -1
  136. package/dist/repertoire/coding/manager.js +210 -4
  137. package/dist/repertoire/coding/spawner.js +58 -12
  138. package/dist/repertoire/coding/tools.js +209 -7
  139. package/dist/repertoire/data/ado-endpoints.json +188 -0
  140. package/dist/repertoire/guardrails.js +291 -0
  141. package/dist/repertoire/mcp-client.js +254 -0
  142. package/dist/repertoire/mcp-manager.js +251 -0
  143. package/dist/repertoire/shell-sessions.js +133 -0
  144. package/dist/repertoire/skills.js +14 -23
  145. package/dist/repertoire/tasks/board.js +43 -5
  146. package/dist/repertoire/tasks/fix.js +182 -0
  147. package/dist/repertoire/tasks/index.js +28 -10
  148. package/dist/repertoire/tasks/lifecycle.js +2 -2
  149. package/dist/repertoire/tasks/parser.js +3 -2
  150. package/dist/repertoire/tasks/scanner.js +194 -37
  151. package/dist/repertoire/tasks/transitions.js +11 -28
  152. package/dist/repertoire/tools-base.js +1360 -271
  153. package/dist/repertoire/tools-bluebubbles.js +94 -0
  154. package/dist/repertoire/tools-github.js +1 -6
  155. package/dist/repertoire/tools-teams.js +65 -59
  156. package/dist/repertoire/tools.js +318 -91
  157. package/dist/scripts/claude-code-hook.js +41 -0
  158. package/dist/scripts/claude-code-stop-hook.js +47 -0
  159. package/dist/senses/attention-queue.js +97 -0
  160. package/dist/senses/bluebubbles-client.js +532 -0
  161. package/dist/senses/bluebubbles-entry.js +13 -0
  162. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  163. package/dist/senses/bluebubbles-media.js +339 -0
  164. package/dist/senses/bluebubbles-model.js +261 -0
  165. package/dist/senses/bluebubbles-mutation-log.js +116 -0
  166. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  167. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  168. package/dist/senses/bluebubbles.js +1327 -0
  169. package/dist/senses/cli/adapter.js +274 -0
  170. package/dist/senses/cli/ink-app.js +156 -0
  171. package/dist/senses/cli/inline-diff.js +64 -0
  172. package/dist/senses/cli/message-list.js +51 -0
  173. package/dist/senses/cli/ouro-tui.js +349 -0
  174. package/dist/senses/cli/spinner-imperative.js +135 -0
  175. package/dist/senses/cli/spinner.js +101 -0
  176. package/dist/senses/cli/status-line.js +60 -0
  177. package/dist/senses/cli/streaming-markdown.js +526 -0
  178. package/dist/senses/cli/tool-display.js +83 -0
  179. package/dist/senses/cli/tool-render.js +85 -0
  180. package/dist/senses/cli/tui-store.js +198 -0
  181. package/dist/senses/cli/virtual-list.js +35 -0
  182. package/dist/senses/cli-entry.js +1 -1
  183. package/dist/senses/cli-layout.js +187 -0
  184. package/dist/senses/cli.js +687 -262
  185. package/dist/senses/commands.js +65 -1
  186. package/dist/senses/continuity.js +94 -0
  187. package/dist/senses/habit-turn-message.js +108 -0
  188. package/dist/senses/inner-dialog-worker.js +112 -19
  189. package/dist/senses/inner-dialog.js +592 -84
  190. package/dist/senses/pipeline.js +693 -0
  191. package/dist/senses/shared-turn.js +196 -0
  192. package/dist/senses/surface-tool.js +95 -0
  193. package/dist/senses/teams.js +817 -151
  194. package/dist/senses/trust-gate.js +112 -2
  195. package/package.json +24 -7
  196. package/skills/configure-dev-tools.md +81 -0
  197. package/subagents/README.md +4 -70
  198. package/AdoptionSpecialist.ouro/agent.json +0 -20
  199. package/dist/heart/daemon/subagent-installer.js +0 -125
  200. package/dist/inner-worker-entry.js +0 -4
  201. package/subagents/work-doer.md +0 -233
  202. package/subagents/work-merger.md +0 -624
  203. package/subagents/work-planner.md +0 -373
  204. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  205. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  206. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  207. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  208. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  209. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  210. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  211. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  212. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  213. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  214. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  215. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OUTLOOK_DEFAULT_PORT = exports.OUTLOOK_DEFAULT_INNER_VISIBILITY = exports.OUTLOOK_RELEASE_INTERACTION_MODEL = exports.OUTLOOK_PRODUCT_NAME = void 0;
4
+ exports.OUTLOOK_PRODUCT_NAME = "Ouro Outlook";
5
+ exports.OUTLOOK_RELEASE_INTERACTION_MODEL = "read-only";
6
+ exports.OUTLOOK_DEFAULT_INNER_VISIBILITY = "summary";
7
+ exports.OUTLOOK_DEFAULT_PORT = 6876;
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildOutlookMachineView = buildOutlookMachineView;
4
+ exports.buildOutlookAgentView = buildOutlookAgentView;
5
+ const runtime_1 = require("../../nerves/runtime");
6
+ const outlook_types_1 = require("./outlook-types");
7
+ function deriveAttention(agent) {
8
+ if (agent.degraded.status === "degraded") {
9
+ return { level: "degraded", label: "Degraded" };
10
+ }
11
+ if (agent.freshness.status === "stale") {
12
+ return { level: "stale", label: "Stale" };
13
+ }
14
+ if (agent.tasks.blockedCount > 0 || agent.coding.blockedCount > 0) {
15
+ return { level: "blocked", label: "Blocked" };
16
+ }
17
+ if (agent.tasks.liveCount > 0 || agent.coding.activeCount > 0 || agent.obligations.openCount > 0) {
18
+ return { level: "active", label: "Active" };
19
+ }
20
+ return { level: "idle", label: "Idle" };
21
+ }
22
+ function deriveAgentAttention(agent) {
23
+ return deriveAttention({
24
+ agentName: agent.agentName,
25
+ enabled: agent.enabled,
26
+ freshness: agent.freshness,
27
+ degraded: agent.degraded,
28
+ tasks: {
29
+ liveCount: agent.tasks.liveCount,
30
+ blockedCount: agent.tasks.blockedCount,
31
+ },
32
+ obligations: {
33
+ openCount: agent.obligations.openCount,
34
+ },
35
+ coding: {
36
+ activeCount: agent.coding.activeCount,
37
+ blockedCount: agent.coding.blockedCount,
38
+ },
39
+ });
40
+ }
41
+ function buildTotals(machine) {
42
+ return machine.agents.reduce((totals, agent) => ({
43
+ agents: totals.agents + 1,
44
+ enabledAgents: totals.enabledAgents + (agent.enabled ? 1 : 0),
45
+ degradedAgents: totals.degradedAgents + (agent.degraded.status === "degraded" ? 1 : 0),
46
+ staleAgents: totals.staleAgents + (agent.freshness.status === "stale" ? 1 : 0),
47
+ liveTasks: totals.liveTasks + agent.tasks.liveCount,
48
+ blockedTasks: totals.blockedTasks + agent.tasks.blockedCount,
49
+ openObligations: totals.openObligations + agent.obligations.openCount,
50
+ activeCodingAgents: totals.activeCodingAgents + (agent.coding.activeCount > 0 ? 1 : 0),
51
+ blockedCodingAgents: totals.blockedCodingAgents + (agent.coding.blockedCount > 0 ? 1 : 0),
52
+ }), {
53
+ agents: 0,
54
+ enabledAgents: 0,
55
+ degradedAgents: 0,
56
+ staleAgents: 0,
57
+ liveTasks: 0,
58
+ blockedTasks: 0,
59
+ openObligations: 0,
60
+ activeCodingAgents: 0,
61
+ blockedCodingAgents: 0,
62
+ });
63
+ }
64
+ function deriveMood(machine, daemon) {
65
+ if (machine.degraded.status === "degraded" || daemon.health === "warn") {
66
+ return "strained";
67
+ }
68
+ if (machine.freshness.status === "stale") {
69
+ return "watchful";
70
+ }
71
+ return "calm";
72
+ }
73
+ function buildOutlookMachineView(input) {
74
+ const totals = buildTotals(input.machine);
75
+ const agents = input.machine.agents
76
+ .map((agent) => ({
77
+ ...agent,
78
+ attention: deriveAttention(agent),
79
+ }))
80
+ .sort((left, right) => {
81
+ const leftAt = left.freshness.latestActivityAt ?? "";
82
+ const rightAt = right.freshness.latestActivityAt ?? "";
83
+ return rightAt.localeCompare(leftAt);
84
+ });
85
+ return {
86
+ overview: {
87
+ productName: outlook_types_1.OUTLOOK_PRODUCT_NAME,
88
+ observedAt: input.machine.observedAt,
89
+ primaryEntryPoint: input.daemon.outlookUrl,
90
+ daemon: input.daemon,
91
+ runtime: input.machine.runtime,
92
+ freshness: input.machine.freshness,
93
+ degraded: input.machine.degraded,
94
+ totals,
95
+ mood: deriveMood(input.machine, input.daemon),
96
+ entrypoints: [
97
+ { kind: "web", label: "Open Outlook", target: input.daemon.outlookUrl },
98
+ { kind: "cli", label: "CLI JSON", target: "ouro outlook --json" },
99
+ ],
100
+ },
101
+ agents,
102
+ };
103
+ }
104
+ function normalizeViewer(viewer) {
105
+ return {
106
+ kind: viewer?.kind ?? "human",
107
+ agentName: viewer?.agentName,
108
+ innerDetail: viewer?.innerDetail ?? "summary",
109
+ };
110
+ }
111
+ function buildInnerView(inner, viewer) {
112
+ if (viewer.innerDetail === "deep") {
113
+ return {
114
+ mode: "deep",
115
+ status: inner.status,
116
+ summary: inner.surfacedSummary,
117
+ hasPending: inner.hasPending,
118
+ origin: inner.origin,
119
+ obligationStatus: inner.obligationStatus,
120
+ };
121
+ }
122
+ return {
123
+ mode: "summary",
124
+ status: inner.status,
125
+ summary: inner.surfacedSummary,
126
+ hasPending: inner.hasPending,
127
+ };
128
+ }
129
+ function buildRecentActivity(agent) {
130
+ const items = [
131
+ ...agent.coding.items.map((item) => ({
132
+ kind: "coding",
133
+ at: item.lastActivityAt,
134
+ label: item.checkpoint ?? `${item.runner} ${item.status}`,
135
+ detail: item.workdir,
136
+ })),
137
+ ...agent.sessions.items.map((item) => ({
138
+ kind: "session",
139
+ at: item.lastActivityAt,
140
+ label: `${item.friendName} via ${item.channel}`,
141
+ detail: item.key,
142
+ })),
143
+ ...agent.obligations.items.map((item) => ({
144
+ kind: "obligation",
145
+ at: item.updatedAt,
146
+ label: item.content,
147
+ detail: item.nextAction ?? item.status,
148
+ })),
149
+ ];
150
+ if (agent.inner.latestActivityAt) {
151
+ items.push({
152
+ kind: "inner",
153
+ at: agent.inner.latestActivityAt,
154
+ label: agent.inner.surfacedSummary ?? agent.inner.status,
155
+ detail: agent.inner.hasPending ? "pending inner work" : agent.inner.obligationStatus ?? "no linked obligation",
156
+ });
157
+ }
158
+ return items
159
+ .filter((item) => Number.isFinite(Date.parse(item.at)))
160
+ .sort((left, right) => right.at.localeCompare(left.at))
161
+ .slice(0, 4);
162
+ }
163
+ function buildOutlookAgentView(input) {
164
+ /* v8 ignore next */
165
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.outlook_view_agent", message: `building outlook view for ${input.agent.agentName}`, meta: { agent: input.agent.agentName } });
166
+ const viewer = normalizeViewer(input.viewer);
167
+ return {
168
+ productName: outlook_types_1.OUTLOOK_PRODUCT_NAME,
169
+ interactionModel: outlook_types_1.OUTLOOK_RELEASE_INTERACTION_MODEL,
170
+ viewer,
171
+ agent: {
172
+ agentName: input.agent.agentName,
173
+ agentRoot: input.agent.agentRoot,
174
+ enabled: input.agent.enabled,
175
+ provider: input.agent.provider,
176
+ senses: input.agent.senses,
177
+ freshness: input.agent.freshness,
178
+ degraded: input.agent.degraded,
179
+ attention: deriveAgentAttention(input.agent),
180
+ },
181
+ work: {
182
+ tasks: input.agent.tasks,
183
+ obligations: input.agent.obligations,
184
+ sessions: input.agent.sessions,
185
+ coding: input.agent.coding,
186
+ bridges: input.agent.tasks.activeBridges,
187
+ },
188
+ inner: buildInnerView(input.agent.inner, viewer),
189
+ activity: {
190
+ freshness: input.agent.freshness,
191
+ recent: buildRecentActivity(input.agent),
192
+ },
193
+ };
194
+ }
@@ -51,15 +51,23 @@ class DaemonProcessManager {
51
51
  now;
52
52
  setTimeoutFn;
53
53
  clearTimeoutFn;
54
+ cooldownRecoveryMs;
55
+ maxCooldownRetries;
56
+ existsSyncFn;
57
+ configCheckFn;
54
58
  constructor(options) {
55
59
  this.maxRestartsPerHour = options.maxRestartsPerHour ?? 10;
56
60
  this.stabilityThresholdMs = options.stabilityThresholdMs ?? 60_000;
57
61
  this.initialBackoffMs = options.initialBackoffMs ?? 1_000;
58
62
  this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
63
+ this.cooldownRecoveryMs = options.cooldownRecoveryMs ?? 5 * 60 * 1_000;
64
+ this.maxCooldownRetries = options.maxCooldownRetries ?? 3;
59
65
  this.spawnFn = options.spawn ?? ((command, args, spawnOptions) => (0, child_process_1.spawn)(command, args, spawnOptions));
60
66
  this.now = options.now ?? (() => Date.now());
61
67
  this.setTimeoutFn = options.setTimeoutFn ?? ((cb, delay) => setTimeout(cb, delay));
62
68
  this.clearTimeoutFn = options.clearTimeoutFn ?? ((timer) => clearTimeout(timer));
69
+ this.existsSyncFn = options.existsSync ?? null;
70
+ this.configCheckFn = options.configCheck ?? null;
63
71
  for (const agent of options.agents) {
64
72
  this.agents.set(agent.name, {
65
73
  config: agent,
@@ -67,6 +75,8 @@ class DaemonProcessManager {
67
75
  restartTimer: null,
68
76
  crashTimestamps: [],
69
77
  stopRequested: false,
78
+ cooldownTimer: null,
79
+ cooldownRetryCount: 0,
70
80
  snapshot: {
71
81
  name: agent.name,
72
82
  channel: agent.channel,
@@ -76,6 +86,8 @@ class DaemonProcessManager {
76
86
  startedAt: null,
77
87
  lastCrashAt: null,
78
88
  backoffMs: this.initialBackoffMs,
89
+ lastExitCode: null,
90
+ lastSignal: null,
79
91
  },
80
92
  });
81
93
  }
@@ -94,9 +106,36 @@ class DaemonProcessManager {
94
106
  this.clearRestartTimer(state);
95
107
  state.stopRequested = false;
96
108
  state.snapshot.status = "starting";
109
+ if (this.configCheckFn) {
110
+ const result = this.configCheckFn(agent);
111
+ if (!result.ok) {
112
+ state.snapshot.status = "crashed";
113
+ (0, runtime_1.emitNervesEvent)({
114
+ level: "error",
115
+ component: "daemon",
116
+ event: "daemon.agent_config_invalid",
117
+ message: result.error ?? "agent config validation failed",
118
+ meta: { agent, fix: result.fix ?? null },
119
+ });
120
+ process.stderr.write(`[daemon] ${agent}: ${result.error}\n` +
121
+ (result.fix ? ` Fix: ${result.fix}\n` : ""));
122
+ return;
123
+ }
124
+ }
97
125
  const runCwd = (0, identity_1.getRepoRoot)();
98
126
  const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
99
- const args = [entryScript, "--agent", agent, ...(state.config.args ?? [])];
127
+ if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
128
+ state.snapshot.status = "crashed";
129
+ (0, runtime_1.emitNervesEvent)({
130
+ level: "error",
131
+ component: "daemon",
132
+ event: "daemon.agent_entry_missing",
133
+ message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
134
+ meta: { agent, entryScript },
135
+ });
136
+ return;
137
+ }
138
+ const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
100
139
  const child = this.spawnFn("node", args, {
101
140
  cwd: runCwd,
102
141
  env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
@@ -112,6 +151,17 @@ class DaemonProcessManager {
112
151
  message: "daemon started managed agent process",
113
152
  meta: { agent, pid: child.pid ?? null, cwd: runCwd },
114
153
  });
154
+ /* v8 ignore start — child process error handler; requires real spawn to trigger */
155
+ child.on("error", (err) => {
156
+ (0, runtime_1.emitNervesEvent)({
157
+ level: "warn",
158
+ component: "daemon",
159
+ event: "daemon.agent_process_error",
160
+ message: "managed agent process emitted error",
161
+ meta: { agent, error: err.message },
162
+ });
163
+ });
164
+ /* v8 ignore stop */
115
165
  child.once("exit", (code, signal) => {
116
166
  this.onExit(state, code, signal);
117
167
  });
@@ -119,6 +169,7 @@ class DaemonProcessManager {
119
169
  async stopAgent(agent) {
120
170
  const state = this.requireAgent(agent);
121
171
  this.clearRestartTimer(state);
172
+ this.clearCooldownTimer(state);
122
173
  state.stopRequested = true;
123
174
  if (!state.process) {
124
175
  state.snapshot.status = "stopped";
@@ -179,6 +230,8 @@ class DaemonProcessManager {
179
230
  return;
180
231
  state.process = null;
181
232
  state.snapshot.pid = null;
233
+ state.snapshot.lastExitCode = code;
234
+ state.snapshot.lastSignal = signal;
182
235
  const crashed = !state.stopRequested && code !== 0;
183
236
  const now = this.now();
184
237
  const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
@@ -209,6 +262,7 @@ class DaemonProcessManager {
209
262
  message: "managed agent exceeded restart limit and is marked crashed",
210
263
  meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
211
264
  });
265
+ this.scheduleCooldownRecovery(state);
212
266
  return;
213
267
  }
214
268
  state.snapshot.status = "starting";
@@ -226,6 +280,45 @@ class DaemonProcessManager {
226
280
  this.clearTimeoutFn(state.restartTimer);
227
281
  state.restartTimer = null;
228
282
  }
283
+ scheduleCooldownRecovery(state) {
284
+ if (state.cooldownRetryCount >= this.maxCooldownRetries) {
285
+ (0, runtime_1.emitNervesEvent)({
286
+ level: "error",
287
+ component: "daemon",
288
+ event: "daemon.agent_permanent_failure",
289
+ message: "managed agent exhausted all cooldown retries — permanently stopped",
290
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
291
+ });
292
+ return;
293
+ }
294
+ this.clearCooldownTimer(state);
295
+ state.cooldownTimer = this.setTimeoutFn(() => {
296
+ state.cooldownRetryCount += 1;
297
+ state.crashTimestamps = [];
298
+ state.snapshot.backoffMs = this.initialBackoffMs;
299
+ state.snapshot.status = "starting";
300
+ state.snapshot.restartCount += 1;
301
+ (0, runtime_1.emitNervesEvent)({
302
+ component: "daemon",
303
+ event: "daemon.agent_cooldown_recovery",
304
+ message: "attempting cooldown recovery for managed agent",
305
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
306
+ });
307
+ void this.startAgent(state.config.name);
308
+ }, this.cooldownRecoveryMs);
309
+ (0, runtime_1.emitNervesEvent)({
310
+ component: "daemon",
311
+ event: "daemon.agent_cooldown_scheduled",
312
+ message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
313
+ meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
314
+ });
315
+ }
316
+ clearCooldownTimer(state) {
317
+ if (state.cooldownTimer === null)
318
+ return;
319
+ this.clearTimeoutFn(state.cooldownTimer);
320
+ state.cooldownTimer = null;
321
+ }
229
322
  requireAgent(agent) {
230
323
  const state = this.agents.get(agent);
231
324
  if (!state) {
@@ -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,48 +35,95 @@ 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 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 };
60
+ }
46
61
  function isLogLevel(value) {
47
62
  return value === "debug" || value === "info" || value === "warn" || value === "error";
48
63
  }
49
- function resolveRuntimeLoggingConfig(configPath) {
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
+ }
77
+ function resolveRuntimeLoggingConfig(configPath, processName) {
78
+ const processDefault = defaultLoggingForProcess(processName);
50
79
  let parsed = null;
51
80
  try {
52
81
  const raw = fs.readFileSync(configPath, "utf-8");
53
82
  parsed = JSON.parse(raw);
54
83
  }
55
84
  catch {
56
- return { ...DEFAULT_RUNTIME_LOGGING };
85
+ return { ...processDefault };
57
86
  }
58
87
  if (!parsed || typeof parsed !== "object") {
59
- return { ...DEFAULT_RUNTIME_LOGGING };
88
+ return { ...processDefault };
60
89
  }
61
90
  const candidate = parsed;
62
- const level = isLogLevel(candidate.level) ? candidate.level : DEFAULT_RUNTIME_LOGGING.level;
63
- const sinks = Array.isArray(candidate.sinks)
64
- ? candidate.sinks.filter((entry) => entry === "terminal" || entry === "ndjson")
65
- : 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
+ }
66
97
  return {
67
98
  level,
68
- sinks: sinks.length > 0 ? [...new Set(sinks)] : [...DEFAULT_RUNTIME_LOGGING.sinks],
99
+ sinks,
69
100
  };
70
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
+ }
71
112
  function configureDaemonRuntimeLogger(processName, options = {}) {
72
- const homeDir = options.homeDir ?? os.homedir();
73
- const configPath = options.configPath ?? path.join(homeDir, ".agentstate", "daemon", "logging.json");
74
- const config = resolveRuntimeLoggingConfig(configPath);
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));
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);
75
122
  const sinks = config.sinks.map((sinkName) => {
76
123
  if (sinkName === "terminal") {
77
124
  return (0, nerves_1.createTerminalSink)();
78
125
  }
79
- const ndjsonPath = path.join(homeDir, ".agentstate", "daemon", "logs", `${processName}.ndjson`);
126
+ const ndjsonPath = path.join(logsDir, `${processName}.ndjson`);
80
127
  return (0, nerves_1.createNdjsonFileSink)(ndjsonPath);
81
128
  });
82
129
  const logger = (0, nerves_1.createLogger)({