@ouro.bot/cli 0.1.0-alpha.20 → 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 (204) hide show
  1. package/README.md +187 -191
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
  4. package/changelog.json +1248 -0
  5. package/dist/heart/active-work.js +832 -0
  6. package/dist/heart/bridges/manager.js +358 -0
  7. package/dist/heart/bridges/state-machine.js +135 -0
  8. package/dist/heart/bridges/store.js +123 -0
  9. package/dist/heart/cares.js +179 -0
  10. package/dist/heart/commitments.js +111 -0
  11. package/dist/heart/config.js +54 -30
  12. package/dist/heart/core.js +620 -124
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-config-check.js +149 -0
  15. package/dist/heart/daemon/agent-discovery.js +81 -0
  16. package/dist/heart/daemon/agent-service.js +360 -0
  17. package/dist/heart/daemon/auth-flow.js +477 -0
  18. package/dist/heart/daemon/cadence.js +70 -0
  19. package/dist/heart/daemon/daemon-cli.js +2443 -217
  20. package/dist/heart/daemon/daemon-entry.js +182 -4
  21. package/dist/heart/daemon/daemon-health.js +136 -0
  22. package/dist/heart/daemon/daemon-runtime-sync.js +231 -0
  23. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  24. package/dist/heart/daemon/daemon.js +460 -18
  25. package/dist/heart/daemon/habit-migration.js +181 -0
  26. package/dist/heart/daemon/habit-parser.js +119 -0
  27. package/dist/heart/daemon/habit-scheduler.js +369 -0
  28. package/dist/heart/daemon/hatch-flow.js +31 -112
  29. package/dist/heart/daemon/hatch-specialist.js +3 -3
  30. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  31. package/dist/heart/daemon/hooks/bundle-meta.js +206 -0
  32. package/dist/heart/daemon/inner-status.js +89 -0
  33. package/dist/heart/daemon/launchd.js +165 -0
  34. package/dist/heart/daemon/log-tailer.js +4 -3
  35. package/dist/heart/daemon/mcp-server.js +591 -0
  36. package/dist/heart/daemon/message-router.js +17 -8
  37. package/dist/heart/daemon/os-cron-deps.js +134 -0
  38. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  39. package/dist/heart/daemon/ouro-path-installer.js +78 -34
  40. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  41. package/dist/heart/daemon/outlook-http.js +439 -0
  42. package/dist/heart/daemon/outlook-read.js +1618 -0
  43. package/dist/heart/daemon/outlook-render.js +1032 -0
  44. package/dist/heart/daemon/outlook-types.js +7 -0
  45. package/dist/heart/daemon/outlook-view.js +194 -0
  46. package/dist/heart/daemon/process-manager.js +93 -0
  47. package/dist/heart/daemon/run-hooks.js +39 -0
  48. package/dist/heart/daemon/runtime-logging.js +58 -15
  49. package/dist/heart/daemon/runtime-metadata.js +101 -0
  50. package/dist/heart/daemon/runtime-mode.js +67 -0
  51. package/dist/heart/daemon/safe-mode.js +161 -0
  52. package/dist/heart/daemon/sense-manager.js +50 -2
  53. package/dist/heart/daemon/session-id-resolver.js +131 -0
  54. package/dist/heart/daemon/skill-management-installer.js +94 -0
  55. package/dist/heart/daemon/socket-client.js +202 -0
  56. package/dist/heart/daemon/specialist-prompt.js +10 -7
  57. package/dist/heart/daemon/specialist-tools.js +49 -3
  58. package/dist/heart/daemon/staged-restart.js +138 -0
  59. package/dist/heart/daemon/task-scheduler.js +3 -25
  60. package/dist/heart/daemon/thoughts.js +507 -0
  61. package/dist/heart/daemon/update-checker.js +111 -0
  62. package/dist/heart/daemon/update-hooks.js +138 -0
  63. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  64. package/dist/heart/delegation.js +62 -0
  65. package/dist/heart/identity.js +147 -55
  66. package/dist/heart/intentions.js +173 -0
  67. package/dist/heart/kicks.js +2 -20
  68. package/dist/heart/migrate-config.js +127 -0
  69. package/dist/heart/model-capabilities.js +48 -0
  70. package/dist/heart/obligations.js +225 -0
  71. package/dist/heart/presence.js +185 -0
  72. package/dist/heart/progress-story.js +42 -0
  73. package/dist/heart/provider-failover.js +88 -0
  74. package/dist/heart/provider-ping.js +178 -0
  75. package/dist/heart/providers/anthropic-token.js +163 -0
  76. package/dist/heart/providers/anthropic.js +187 -41
  77. package/dist/heart/providers/azure.js +116 -10
  78. package/dist/heart/providers/github-copilot.js +157 -0
  79. package/dist/heart/providers/minimax.js +33 -3
  80. package/dist/heart/providers/openai-codex.js +51 -16
  81. package/dist/heart/session-activity.js +172 -0
  82. package/dist/heart/session-recall.js +216 -0
  83. package/dist/heart/start-of-turn-packet.js +232 -0
  84. package/dist/heart/streaming.js +36 -27
  85. package/dist/heart/sync.js +217 -0
  86. package/dist/heart/target-resolution.js +127 -0
  87. package/dist/heart/tempo.js +93 -0
  88. package/dist/heart/temporal-view.js +41 -0
  89. package/dist/heart/tool-activity-callbacks.js +36 -0
  90. package/dist/heart/tool-description.js +135 -0
  91. package/dist/heart/tool-loop.js +200 -0
  92. package/dist/heart/turn-coordinator.js +28 -0
  93. package/dist/mind/associative-recall.js +122 -32
  94. package/dist/mind/bundle-manifest.js +19 -1
  95. package/dist/mind/context.js +125 -14
  96. package/dist/mind/{memory.js → diary.js} +69 -56
  97. package/dist/mind/episodes.js +119 -0
  98. package/dist/mind/file-state.js +179 -0
  99. package/dist/mind/first-impressions.js +16 -2
  100. package/dist/mind/friends/channel.js +56 -0
  101. package/dist/mind/friends/group-context.js +144 -0
  102. package/dist/mind/friends/store-file.js +36 -0
  103. package/dist/mind/friends/trust-explanation.js +74 -0
  104. package/dist/mind/friends/types.js +8 -0
  105. package/dist/mind/journal-index.js +161 -0
  106. package/dist/mind/obligation-steering.js +221 -0
  107. package/dist/mind/obligations.js +138 -0
  108. package/dist/mind/pending.js +76 -9
  109. package/dist/mind/phrases.js +1 -0
  110. package/dist/mind/prompt.js +785 -103
  111. package/dist/mind/scrutiny.js +173 -0
  112. package/dist/mind/token-estimate.js +8 -12
  113. package/dist/nerves/coverage/audit.js +1 -1
  114. package/dist/nerves/coverage/run-artifacts.js +1 -1
  115. package/dist/nerves/event-buffer.js +111 -0
  116. package/dist/nerves/index.js +63 -3
  117. package/dist/nerves/observation.js +20 -0
  118. package/dist/nerves/runtime.js +5 -1
  119. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  120. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  121. package/dist/outlook-ui/index.html +15 -0
  122. package/dist/repertoire/ado-semantic.js +11 -5
  123. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  124. package/dist/repertoire/coding/context-pack.js +330 -0
  125. package/dist/repertoire/coding/feedback.js +197 -30
  126. package/dist/repertoire/coding/manager.js +149 -2
  127. package/dist/repertoire/coding/spawner.js +55 -9
  128. package/dist/repertoire/coding/tools.js +170 -7
  129. package/dist/repertoire/guardrails.js +291 -0
  130. package/dist/repertoire/mcp-client.js +254 -0
  131. package/dist/repertoire/mcp-manager.js +251 -0
  132. package/dist/repertoire/shell-sessions.js +133 -0
  133. package/dist/repertoire/skills.js +14 -23
  134. package/dist/repertoire/tasks/board.js +43 -5
  135. package/dist/repertoire/tasks/fix.js +182 -0
  136. package/dist/repertoire/tasks/index.js +28 -10
  137. package/dist/repertoire/tasks/lifecycle.js +2 -2
  138. package/dist/repertoire/tasks/parser.js +3 -2
  139. package/dist/repertoire/tasks/scanner.js +194 -37
  140. package/dist/repertoire/tasks/transitions.js +11 -28
  141. package/dist/repertoire/tools-base.js +1345 -320
  142. package/dist/repertoire/tools-bluebubbles.js +94 -0
  143. package/dist/repertoire/tools-github.js +1 -6
  144. package/dist/repertoire/tools-teams.js +12 -59
  145. package/dist/repertoire/tools.js +318 -124
  146. package/dist/scripts/claude-code-hook.js +41 -0
  147. package/dist/scripts/claude-code-stop-hook.js +47 -0
  148. package/dist/senses/attention-queue.js +97 -0
  149. package/dist/senses/bluebubbles-client.js +102 -3
  150. package/dist/senses/bluebubbles-entry.js +2 -0
  151. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  152. package/dist/senses/bluebubbles-media.js +118 -23
  153. package/dist/senses/bluebubbles-model.js +12 -4
  154. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  155. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  156. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  157. package/dist/senses/bluebubbles.js +1008 -102
  158. package/dist/senses/cli/adapter.js +274 -0
  159. package/dist/senses/cli/ink-app.js +156 -0
  160. package/dist/senses/cli/inline-diff.js +64 -0
  161. package/dist/senses/cli/message-list.js +51 -0
  162. package/dist/senses/cli/ouro-tui.js +349 -0
  163. package/dist/senses/cli/spinner-imperative.js +135 -0
  164. package/dist/senses/cli/spinner.js +101 -0
  165. package/dist/senses/cli/status-line.js +60 -0
  166. package/dist/senses/cli/streaming-markdown.js +526 -0
  167. package/dist/senses/cli/tool-display.js +83 -0
  168. package/dist/senses/cli/tool-render.js +85 -0
  169. package/dist/senses/cli/tui-store.js +198 -0
  170. package/dist/senses/cli/virtual-list.js +35 -0
  171. package/dist/senses/cli-entry.js +1 -1
  172. package/dist/senses/cli-layout.js +187 -0
  173. package/dist/senses/cli.js +508 -243
  174. package/dist/senses/commands.js +65 -1
  175. package/dist/senses/continuity.js +94 -0
  176. package/dist/senses/habit-turn-message.js +108 -0
  177. package/dist/senses/inner-dialog-worker.js +112 -19
  178. package/dist/senses/inner-dialog.js +592 -84
  179. package/dist/senses/pipeline.js +693 -0
  180. package/dist/senses/shared-turn.js +196 -0
  181. package/dist/senses/surface-tool.js +95 -0
  182. package/dist/senses/teams.js +667 -120
  183. package/dist/senses/trust-gate.js +112 -2
  184. package/package.json +16 -6
  185. package/skills/configure-dev-tools.md +81 -0
  186. package/subagents/README.md +4 -70
  187. package/dist/heart/daemon/subagent-installer.js +0 -134
  188. package/dist/senses/debug-activity.js +0 -107
  189. package/subagents/work-doer.md +0 -235
  190. package/subagents/work-merger.md +0 -618
  191. package/subagents/work-planner.md +0 -382
  192. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  193. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  194. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  195. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  196. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  197. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  198. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  199. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  200. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  201. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  202. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  203. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  204. /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,8 +106,35 @@ 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);
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
+ }
99
138
  const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
100
139
  const child = this.spawnFn("node", args, {
101
140
  cwd: runCwd,
@@ -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,52 +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 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);
79
122
  const sinks = config.sinks.map((sinkName) => {
80
123
  if (sinkName === "terminal") {
81
124
  return (0, nerves_1.createTerminalSink)();
82
125
  }
83
- const ndjsonPath = path.join(homeDir, ".agentstate", "daemon", "logs", `${processName}.ndjson`);
126
+ const ndjsonPath = path.join(logsDir, `${processName}.ndjson`);
84
127
  return (0, nerves_1.createNdjsonFileSink)(ndjsonPath);
85
128
  });
86
129
  const logger = (0, nerves_1.createLogger)({
@@ -34,7 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getRuntimeMetadata = getRuntimeMetadata;
37
+ const crypto_1 = require("crypto");
37
38
  const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
38
40
  const path = __importStar(require("path"));
39
41
  const childProcess = __importStar(require("child_process"));
40
42
  const identity_1 = require("../identity");
@@ -85,10 +87,98 @@ function readLastUpdated(repoRoot, packageJsonPath, statSyncImpl, execFileSyncIm
85
87
  return { value: UNKNOWN_METADATA, source: "unknown" };
86
88
  }
87
89
  }
90
+ function readHomeDir() {
91
+ const homedirImpl = optionalFunction(os, "homedir");
92
+ if (!homedirImpl) {
93
+ return null;
94
+ }
95
+ try {
96
+ return homedirImpl.call(os);
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
102
+ function listConfigTargets(bundlesRoot, secretsRoot, daemonLoggingPath, readdirSyncImpl) {
103
+ if (!readdirSyncImpl)
104
+ return [];
105
+ const targets = new Set();
106
+ if (daemonLoggingPath) {
107
+ targets.add(daemonLoggingPath);
108
+ }
109
+ try {
110
+ const bundleEntries = readdirSyncImpl(bundlesRoot, { withFileTypes: true });
111
+ for (const entry of bundleEntries) {
112
+ if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
113
+ continue;
114
+ targets.add(path.join(bundlesRoot, entry.name, "agent.json"));
115
+ }
116
+ }
117
+ catch {
118
+ // ignore unreadable bundle roots
119
+ }
120
+ if (secretsRoot) {
121
+ try {
122
+ const secretEntries = readdirSyncImpl(secretsRoot, { withFileTypes: true });
123
+ for (const entry of secretEntries) {
124
+ if (!entry.isDirectory())
125
+ continue;
126
+ targets.add(path.join(secretsRoot, entry.name, "secrets.json"));
127
+ }
128
+ }
129
+ catch {
130
+ // ignore unreadable secrets roots
131
+ }
132
+ }
133
+ return [...targets].sort();
134
+ }
135
+ function readConfigFingerprint(targets, readFileSyncImpl, existsSyncImpl) {
136
+ if (!readFileSyncImpl || !existsSyncImpl) {
137
+ return {
138
+ value: UNKNOWN_METADATA,
139
+ source: "unknown",
140
+ trackedFiles: targets.length,
141
+ presentFiles: 0,
142
+ };
143
+ }
144
+ const hash = (0, crypto_1.createHash)("sha256");
145
+ let presentFiles = 0;
146
+ for (const target of targets) {
147
+ hash.update(target);
148
+ hash.update("\0");
149
+ if (!existsSyncImpl(target)) {
150
+ hash.update("missing");
151
+ hash.update("\0");
152
+ continue;
153
+ }
154
+ presentFiles += 1;
155
+ hash.update("present");
156
+ hash.update("\0");
157
+ try {
158
+ hash.update(readFileSyncImpl(target, "utf-8"));
159
+ }
160
+ catch {
161
+ hash.update("unreadable");
162
+ }
163
+ hash.update("\0");
164
+ }
165
+ return {
166
+ value: hash.digest("hex"),
167
+ source: "content-hash",
168
+ trackedFiles: targets.length,
169
+ presentFiles,
170
+ };
171
+ }
88
172
  function getRuntimeMetadata(deps = {}) {
89
173
  const repoRoot = deps.repoRoot ?? (0, identity_1.getRepoRoot)();
174
+ const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
175
+ const homeDir = readHomeDir();
176
+ const secretsRoot = deps.secretsRoot ?? (homeDir ? path.join(homeDir, ".agentsecrets") : null);
177
+ const daemonLoggingPath = deps.daemonLoggingPath ?? (0, identity_1.getAgentDaemonLoggingConfigPath)();
90
178
  const readFileSyncImpl = deps.readFileSync ?? optionalFunction(fs, "readFileSync")?.bind(fs) ?? null;
91
179
  const statSyncImpl = deps.statSync ?? optionalFunction(fs, "statSync")?.bind(fs) ?? null;
180
+ const readdirSyncImpl = deps.readdirSync ?? optionalFunction(fs, "readdirSync")?.bind(fs) ?? null;
181
+ const existsSyncImpl = deps.existsSync ?? optionalFunction(fs, "existsSync")?.bind(fs) ?? null;
92
182
  const execFileSyncImpl = deps.execFileSync
93
183
  ?? optionalFunction(childProcess, "execFileSync")?.bind(childProcess)
94
184
  ?? null;
@@ -101,6 +191,8 @@ function getRuntimeMetadata(deps = {}) {
101
191
  throw new Error("git unavailable");
102
192
  }))
103
193
  : { value: UNKNOWN_METADATA, source: "unknown" };
194
+ const configTargets = listConfigTargets(bundlesRoot, secretsRoot, daemonLoggingPath, readdirSyncImpl);
195
+ const configFingerprint = readConfigFingerprint(configTargets, readFileSyncImpl, existsSyncImpl);
104
196
  (0, runtime_1.emitNervesEvent)({
105
197
  component: "daemon",
106
198
  event: "daemon.runtime_metadata_read",
@@ -109,10 +201,19 @@ function getRuntimeMetadata(deps = {}) {
109
201
  version,
110
202
  lastUpdated: lastUpdated.value,
111
203
  lastUpdatedSource: lastUpdated.source,
204
+ repoRoot,
205
+ configFingerprint: configFingerprint.value === UNKNOWN_METADATA
206
+ ? UNKNOWN_METADATA
207
+ : configFingerprint.value.slice(0, 12),
208
+ configFingerprintSource: configFingerprint.source,
209
+ configTrackedFiles: configFingerprint.trackedFiles,
210
+ configPresentFiles: configFingerprint.presentFiles,
112
211
  },
113
212
  });
114
213
  return {
115
214
  version,
116
215
  lastUpdated: lastUpdated.value,
216
+ repoRoot,
217
+ configFingerprint: configFingerprint.value,
117
218
  };
118
219
  }