@ouro.bot/cli 0.1.0-alpha.15 → 0.1.0-alpha.150

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 (150) hide show
  1. package/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
  2. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  3. package/README.md +151 -205
  4. package/changelog.json +985 -0
  5. package/dist/heart/active-work.js +633 -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/commitments.js +105 -0
  10. package/dist/heart/config.js +66 -21
  11. package/dist/heart/core.js +645 -112
  12. package/dist/heart/cross-chat-delivery.js +146 -0
  13. package/dist/heart/daemon/agent-discovery.js +81 -0
  14. package/dist/heart/daemon/agent-service.js +360 -0
  15. package/dist/heart/daemon/auth-flow.js +457 -0
  16. package/dist/heart/daemon/cadence.js +70 -0
  17. package/dist/heart/daemon/daemon-cli.js +2301 -205
  18. package/dist/heart/daemon/daemon-entry.js +131 -4
  19. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  20. package/dist/heart/daemon/daemon-tombstone.js +106 -0
  21. package/dist/heart/daemon/daemon.js +372 -3
  22. package/dist/heart/daemon/habit-migration.js +181 -0
  23. package/dist/heart/daemon/habit-parser.js +119 -0
  24. package/dist/heart/daemon/habit-scheduler.js +252 -0
  25. package/dist/heart/daemon/hatch-animation.js +10 -3
  26. package/dist/heart/daemon/hatch-flow.js +28 -111
  27. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  28. package/dist/heart/daemon/inner-status.js +89 -0
  29. package/dist/heart/daemon/launchd.js +159 -0
  30. package/dist/heart/daemon/log-tailer.js +4 -3
  31. package/dist/heart/daemon/mcp-server.js +591 -0
  32. package/dist/heart/daemon/message-router.js +17 -8
  33. package/dist/heart/daemon/os-cron-deps.js +134 -0
  34. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  35. package/dist/heart/daemon/ouro-path-installer.js +78 -34
  36. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  37. package/dist/heart/daemon/process-manager.js +75 -0
  38. package/dist/heart/daemon/run-hooks.js +37 -0
  39. package/dist/heart/daemon/runtime-logging.js +58 -15
  40. package/dist/heart/daemon/runtime-metadata.js +219 -0
  41. package/dist/heart/daemon/runtime-mode.js +67 -0
  42. package/dist/heart/daemon/sense-manager.js +50 -2
  43. package/dist/heart/daemon/session-id-resolver.js +131 -0
  44. package/dist/heart/daemon/skill-management-installer.js +94 -0
  45. package/dist/heart/daemon/socket-client.js +202 -0
  46. package/dist/heart/daemon/specialist-orchestrator.js +2 -2
  47. package/dist/heart/daemon/specialist-prompt.js +10 -7
  48. package/dist/heart/daemon/specialist-tools.js +53 -4
  49. package/dist/heart/daemon/staged-restart.js +114 -0
  50. package/dist/heart/daemon/task-scheduler.js +3 -25
  51. package/dist/heart/daemon/thoughts.js +507 -0
  52. package/dist/heart/daemon/update-checker.js +111 -0
  53. package/dist/heart/daemon/update-hooks.js +138 -0
  54. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  55. package/dist/heart/delegation.js +62 -0
  56. package/dist/heart/identity.js +64 -21
  57. package/dist/heart/kicks.js +2 -20
  58. package/dist/heart/model-capabilities.js +48 -0
  59. package/dist/heart/obligations.js +197 -0
  60. package/dist/heart/progress-story.js +42 -0
  61. package/dist/heart/provider-failover.js +88 -0
  62. package/dist/heart/provider-ping.js +159 -0
  63. package/dist/heart/providers/anthropic-token.js +163 -0
  64. package/dist/heart/providers/anthropic.js +195 -34
  65. package/dist/heart/providers/azure.js +115 -9
  66. package/dist/heart/providers/github-copilot.js +157 -0
  67. package/dist/heart/providers/minimax.js +33 -3
  68. package/dist/heart/providers/openai-codex.js +49 -14
  69. package/dist/heart/session-activity.js +173 -0
  70. package/dist/heart/session-recall.js +216 -0
  71. package/dist/heart/streaming.js +117 -33
  72. package/dist/heart/target-resolution.js +127 -0
  73. package/dist/heart/tool-loop.js +200 -0
  74. package/dist/heart/turn-coordinator.js +28 -0
  75. package/dist/mind/associative-recall.js +122 -32
  76. package/dist/mind/bundle-manifest.js +14 -0
  77. package/dist/mind/context.js +125 -14
  78. package/dist/mind/{memory.js → diary.js} +70 -56
  79. package/dist/mind/first-impressions.js +16 -2
  80. package/dist/mind/friends/channel.js +44 -0
  81. package/dist/mind/friends/group-context.js +144 -0
  82. package/dist/mind/friends/store-file.js +19 -0
  83. package/dist/mind/friends/trust-explanation.js +74 -0
  84. package/dist/mind/friends/types.js +8 -0
  85. package/dist/mind/journal-index.js +161 -0
  86. package/dist/mind/obligation-steering.js +221 -0
  87. package/dist/mind/obligations.js +134 -0
  88. package/dist/mind/pending.js +76 -9
  89. package/dist/mind/phrases.js +1 -0
  90. package/dist/mind/prompt.js +607 -88
  91. package/dist/mind/token-estimate.js +8 -12
  92. package/dist/nerves/cli-logging.js +15 -2
  93. package/dist/nerves/coverage/run-artifacts.js +1 -1
  94. package/dist/nerves/index.js +59 -2
  95. package/dist/nerves/runtime.js +5 -1
  96. package/dist/repertoire/ado-client.js +4 -2
  97. package/dist/repertoire/ado-semantic.js +6 -0
  98. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  99. package/dist/repertoire/coding/context-pack.js +254 -0
  100. package/dist/repertoire/coding/feedback.js +301 -0
  101. package/dist/repertoire/coding/index.js +4 -1
  102. package/dist/repertoire/coding/manager.js +210 -4
  103. package/dist/repertoire/coding/spawner.js +58 -12
  104. package/dist/repertoire/coding/tools.js +176 -4
  105. package/dist/repertoire/data/ado-endpoints.json +188 -0
  106. package/dist/repertoire/guardrails.js +290 -0
  107. package/dist/repertoire/mcp-client.js +254 -0
  108. package/dist/repertoire/mcp-manager.js +198 -0
  109. package/dist/repertoire/skills.js +14 -23
  110. package/dist/repertoire/tasks/board.js +12 -0
  111. package/dist/repertoire/tasks/index.js +23 -9
  112. package/dist/repertoire/tasks/parser.js +0 -2
  113. package/dist/repertoire/tasks/transitions.js +1 -20
  114. package/dist/repertoire/tools-base.js +990 -273
  115. package/dist/repertoire/tools-bluebubbles.js +94 -0
  116. package/dist/repertoire/tools-github.js +1 -6
  117. package/dist/repertoire/tools-teams.js +65 -59
  118. package/dist/repertoire/tools.js +281 -91
  119. package/dist/scripts/claude-code-hook.js +41 -0
  120. package/dist/scripts/claude-code-stop-hook.js +47 -0
  121. package/dist/senses/attention-queue.js +97 -0
  122. package/dist/senses/bluebubbles-client.js +258 -5
  123. package/dist/senses/bluebubbles-entry.js +2 -0
  124. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  125. package/dist/senses/bluebubbles-media.js +339 -0
  126. package/dist/senses/bluebubbles-model.js +12 -4
  127. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  128. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  129. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  130. package/dist/senses/bluebubbles.js +981 -45
  131. package/dist/senses/cli-entry.js +1 -1
  132. package/dist/senses/cli-layout.js +187 -0
  133. package/dist/senses/cli.js +374 -131
  134. package/dist/senses/contextual-heartbeat.js +106 -0
  135. package/dist/senses/continuity.js +94 -0
  136. package/dist/senses/debug-activity.js +154 -0
  137. package/dist/senses/inner-dialog-worker.js +112 -19
  138. package/dist/senses/inner-dialog.js +621 -84
  139. package/dist/senses/pipeline.js +463 -0
  140. package/dist/senses/shared-turn.js +196 -0
  141. package/dist/senses/surface-tool.js +95 -0
  142. package/dist/senses/teams.js +796 -141
  143. package/dist/senses/trust-gate.js +112 -2
  144. package/package.json +12 -4
  145. package/skills/configure-dev-tools.md +81 -0
  146. package/subagents/README.md +4 -70
  147. package/dist/heart/daemon/subagent-installer.js +0 -134
  148. package/subagents/work-doer.md +0 -233
  149. package/subagents/work-merger.md +0 -624
  150. package/subagents/work-planner.md +0 -373
@@ -51,15 +51,21 @@ class DaemonProcessManager {
51
51
  now;
52
52
  setTimeoutFn;
53
53
  clearTimeoutFn;
54
+ cooldownRecoveryMs;
55
+ maxCooldownRetries;
56
+ existsSyncFn;
54
57
  constructor(options) {
55
58
  this.maxRestartsPerHour = options.maxRestartsPerHour ?? 10;
56
59
  this.stabilityThresholdMs = options.stabilityThresholdMs ?? 60_000;
57
60
  this.initialBackoffMs = options.initialBackoffMs ?? 1_000;
58
61
  this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
62
+ this.cooldownRecoveryMs = options.cooldownRecoveryMs ?? 5 * 60 * 1_000;
63
+ this.maxCooldownRetries = options.maxCooldownRetries ?? 3;
59
64
  this.spawnFn = options.spawn ?? ((command, args, spawnOptions) => (0, child_process_1.spawn)(command, args, spawnOptions));
60
65
  this.now = options.now ?? (() => Date.now());
61
66
  this.setTimeoutFn = options.setTimeoutFn ?? ((cb, delay) => setTimeout(cb, delay));
62
67
  this.clearTimeoutFn = options.clearTimeoutFn ?? ((timer) => clearTimeout(timer));
68
+ this.existsSyncFn = options.existsSync ?? null;
63
69
  for (const agent of options.agents) {
64
70
  this.agents.set(agent.name, {
65
71
  config: agent,
@@ -67,6 +73,8 @@ class DaemonProcessManager {
67
73
  restartTimer: null,
68
74
  crashTimestamps: [],
69
75
  stopRequested: false,
76
+ cooldownTimer: null,
77
+ cooldownRetryCount: 0,
70
78
  snapshot: {
71
79
  name: agent.name,
72
80
  channel: agent.channel,
@@ -76,6 +84,8 @@ class DaemonProcessManager {
76
84
  startedAt: null,
77
85
  lastCrashAt: null,
78
86
  backoffMs: this.initialBackoffMs,
87
+ lastExitCode: null,
88
+ lastSignal: null,
79
89
  },
80
90
  });
81
91
  }
@@ -96,6 +106,17 @@ class DaemonProcessManager {
96
106
  state.snapshot.status = "starting";
97
107
  const runCwd = (0, identity_1.getRepoRoot)();
98
108
  const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
109
+ if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
110
+ state.snapshot.status = "crashed";
111
+ (0, runtime_1.emitNervesEvent)({
112
+ level: "error",
113
+ component: "daemon",
114
+ event: "daemon.agent_entry_missing",
115
+ message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
116
+ meta: { agent, entryScript },
117
+ });
118
+ return;
119
+ }
99
120
  const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
100
121
  const child = this.spawnFn("node", args, {
101
122
  cwd: runCwd,
@@ -112,6 +133,17 @@ class DaemonProcessManager {
112
133
  message: "daemon started managed agent process",
113
134
  meta: { agent, pid: child.pid ?? null, cwd: runCwd },
114
135
  });
136
+ /* v8 ignore start — child process error handler; requires real spawn to trigger */
137
+ child.on("error", (err) => {
138
+ (0, runtime_1.emitNervesEvent)({
139
+ level: "warn",
140
+ component: "daemon",
141
+ event: "daemon.agent_process_error",
142
+ message: "managed agent process emitted error",
143
+ meta: { agent, error: err.message },
144
+ });
145
+ });
146
+ /* v8 ignore stop */
115
147
  child.once("exit", (code, signal) => {
116
148
  this.onExit(state, code, signal);
117
149
  });
@@ -119,6 +151,7 @@ class DaemonProcessManager {
119
151
  async stopAgent(agent) {
120
152
  const state = this.requireAgent(agent);
121
153
  this.clearRestartTimer(state);
154
+ this.clearCooldownTimer(state);
122
155
  state.stopRequested = true;
123
156
  if (!state.process) {
124
157
  state.snapshot.status = "stopped";
@@ -179,6 +212,8 @@ class DaemonProcessManager {
179
212
  return;
180
213
  state.process = null;
181
214
  state.snapshot.pid = null;
215
+ state.snapshot.lastExitCode = code;
216
+ state.snapshot.lastSignal = signal;
182
217
  const crashed = !state.stopRequested && code !== 0;
183
218
  const now = this.now();
184
219
  const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
@@ -209,6 +244,7 @@ class DaemonProcessManager {
209
244
  message: "managed agent exceeded restart limit and is marked crashed",
210
245
  meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
211
246
  });
247
+ this.scheduleCooldownRecovery(state);
212
248
  return;
213
249
  }
214
250
  state.snapshot.status = "starting";
@@ -226,6 +262,45 @@ class DaemonProcessManager {
226
262
  this.clearTimeoutFn(state.restartTimer);
227
263
  state.restartTimer = null;
228
264
  }
265
+ scheduleCooldownRecovery(state) {
266
+ if (state.cooldownRetryCount >= this.maxCooldownRetries) {
267
+ (0, runtime_1.emitNervesEvent)({
268
+ level: "error",
269
+ component: "daemon",
270
+ event: "daemon.agent_permanent_failure",
271
+ message: "managed agent exhausted all cooldown retries — permanently stopped",
272
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
273
+ });
274
+ return;
275
+ }
276
+ this.clearCooldownTimer(state);
277
+ state.cooldownTimer = this.setTimeoutFn(() => {
278
+ state.cooldownRetryCount += 1;
279
+ state.crashTimestamps = [];
280
+ state.snapshot.backoffMs = this.initialBackoffMs;
281
+ state.snapshot.status = "starting";
282
+ state.snapshot.restartCount += 1;
283
+ (0, runtime_1.emitNervesEvent)({
284
+ component: "daemon",
285
+ event: "daemon.agent_cooldown_recovery",
286
+ message: "attempting cooldown recovery for managed agent",
287
+ meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
288
+ });
289
+ void this.startAgent(state.config.name);
290
+ }, this.cooldownRecoveryMs);
291
+ (0, runtime_1.emitNervesEvent)({
292
+ component: "daemon",
293
+ event: "daemon.agent_cooldown_scheduled",
294
+ message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
295
+ meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
296
+ });
297
+ }
298
+ clearCooldownTimer(state) {
299
+ if (state.cooldownTimer === null)
300
+ return;
301
+ this.clearTimeoutFn(state.cooldownTimer);
302
+ state.cooldownTimer = null;
303
+ }
229
304
  requireAgent(agent) {
230
305
  const state = this.agents.get(agent);
231
306
  if (!state) {
@@ -0,0 +1,37 @@
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
+ async function runHooks(deps) {
7
+ (0, runtime_1.emitNervesEvent)({
8
+ component: "daemon",
9
+ event: "daemon.run_hooks_start",
10
+ message: "running update hooks",
11
+ meta: { bundlesRoot: deps.bundlesRoot },
12
+ });
13
+ try {
14
+ deps.registerUpdateHook(bundle_meta_1.bundleMetaHook);
15
+ const currentVersion = deps.getPackageVersion();
16
+ await deps.applyPendingUpdates(deps.bundlesRoot, currentVersion);
17
+ (0, runtime_1.emitNervesEvent)({
18
+ component: "daemon",
19
+ event: "daemon.run_hooks_success",
20
+ message: "update hooks completed successfully",
21
+ meta: { bundlesRoot: deps.bundlesRoot },
22
+ });
23
+ return 0;
24
+ }
25
+ catch (err) {
26
+ (0, runtime_1.emitNervesEvent)({
27
+ component: "daemon",
28
+ event: "daemon.run_hooks_error",
29
+ message: "update hooks failed",
30
+ meta: {
31
+ bundlesRoot: deps.bundlesRoot,
32
+ error: err instanceof Error ? err.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(err),
33
+ },
34
+ });
35
+ return 1;
36
+ }
37
+ }
@@ -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)({
@@ -0,0 +1,219 @@
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 os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const childProcess = __importStar(require("child_process"));
42
+ const identity_1 = require("../identity");
43
+ const runtime_1 = require("../../nerves/runtime");
44
+ const UNKNOWN_METADATA = "unknown";
45
+ function optionalFunction(target, key) {
46
+ try {
47
+ const candidate = target[key];
48
+ return typeof candidate === "function" ? candidate : null;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ function readVersion(packageJsonPath, readFileSyncImpl) {
55
+ try {
56
+ const parsed = JSON.parse(readFileSyncImpl(packageJsonPath, "utf-8"));
57
+ return typeof parsed.version === "string" && parsed.version.trim().length > 0
58
+ ? parsed.version.trim()
59
+ : UNKNOWN_METADATA;
60
+ }
61
+ catch {
62
+ return UNKNOWN_METADATA;
63
+ }
64
+ }
65
+ function readLastUpdated(repoRoot, packageJsonPath, statSyncImpl, execFileSyncImpl) {
66
+ try {
67
+ const raw = execFileSyncImpl("git", ["log", "-1", "--format=%cI"], {
68
+ cwd: repoRoot,
69
+ encoding: "utf-8",
70
+ stdio: ["ignore", "pipe", "ignore"],
71
+ }).trim();
72
+ if (raw.length > 0) {
73
+ return { value: raw, source: "git" };
74
+ }
75
+ }
76
+ catch {
77
+ // fall through to mtime fallback
78
+ }
79
+ try {
80
+ const stats = statSyncImpl(packageJsonPath);
81
+ return {
82
+ value: stats.mtime.toISOString(),
83
+ source: "package-json-mtime",
84
+ };
85
+ }
86
+ catch {
87
+ return { value: UNKNOWN_METADATA, source: "unknown" };
88
+ }
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
+ }
172
+ function getRuntimeMetadata(deps = {}) {
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)();
178
+ const readFileSyncImpl = deps.readFileSync ?? optionalFunction(fs, "readFileSync")?.bind(fs) ?? null;
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;
182
+ const execFileSyncImpl = deps.execFileSync
183
+ ?? optionalFunction(childProcess, "execFileSync")?.bind(childProcess)
184
+ ?? null;
185
+ const packageJsonPath = path.join(repoRoot, "package.json");
186
+ const version = readFileSyncImpl
187
+ ? readVersion(packageJsonPath, readFileSyncImpl)
188
+ : UNKNOWN_METADATA;
189
+ const lastUpdated = statSyncImpl
190
+ ? readLastUpdated(repoRoot, packageJsonPath, statSyncImpl, execFileSyncImpl ?? (() => {
191
+ throw new Error("git unavailable");
192
+ }))
193
+ : { value: UNKNOWN_METADATA, source: "unknown" };
194
+ const configTargets = listConfigTargets(bundlesRoot, secretsRoot, daemonLoggingPath, readdirSyncImpl);
195
+ const configFingerprint = readConfigFingerprint(configTargets, readFileSyncImpl, existsSyncImpl);
196
+ (0, runtime_1.emitNervesEvent)({
197
+ component: "daemon",
198
+ event: "daemon.runtime_metadata_read",
199
+ message: "read runtime metadata",
200
+ meta: {
201
+ version,
202
+ lastUpdated: lastUpdated.value,
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,
211
+ },
212
+ });
213
+ return {
214
+ version,
215
+ lastUpdated: lastUpdated.value,
216
+ repoRoot,
217
+ configFingerprint: configFingerprint.value,
218
+ };
219
+ }
@@ -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
+ }
@@ -38,12 +38,14 @@ const fs = __importStar(require("fs"));
38
38
  const os = __importStar(require("os"));
39
39
  const path = __importStar(require("path"));
40
40
  const runtime_1 = require("../../nerves/runtime");
41
+ const bluebubbles_runtime_state_1 = require("../../senses/bluebubbles-runtime-state");
41
42
  const identity_1 = require("../identity");
42
43
  const sense_truth_1 = require("../sense-truth");
43
44
  const process_manager_1 = require("./process-manager");
44
45
  const DEFAULT_TEAMS_PORT = 3978;
45
46
  const DEFAULT_BLUEBUBBLES_PORT = 18790;
46
47
  const DEFAULT_BLUEBUBBLES_WEBHOOK_PATH = "/bluebubbles-webhook";
48
+ const BLUEBUBBLES_RUNTIME_FRESHNESS_WINDOW_MS = 90_000;
47
49
  function defaultSenses() {
48
50
  return {
49
51
  cli: { ...identity_1.DEFAULT_AGENT_SENSES.cli },
@@ -175,12 +177,45 @@ function runtimeInfoFor(status) {
175
177
  return { runtime: "running" };
176
178
  return { runtime: "error" };
177
179
  }
180
+ function blueBubblesRuntimeStateIsFresh(lastCheckedAt, now = Date.now()) {
181
+ if (!lastCheckedAt) {
182
+ return false;
183
+ }
184
+ const checkedAt = Date.parse(lastCheckedAt);
185
+ if (!Number.isFinite(checkedAt)) {
186
+ return false;
187
+ }
188
+ return checkedAt >= now - BLUEBUBBLES_RUNTIME_FRESHNESS_WINDOW_MS;
189
+ }
190
+ function readBlueBubblesRuntimeFacts(agent, bundlesRoot, snapshot) {
191
+ const agentRoot = path.join(bundlesRoot, `${agent}.ouro`);
192
+ const runtimePath = path.join(agentRoot, "state", "senses", "bluebubbles", "runtime.json");
193
+ if (!fs.existsSync(runtimePath)) {
194
+ return { runtime: snapshot?.runtime };
195
+ }
196
+ const state = (0, bluebubbles_runtime_state_1.readBlueBubblesRuntimeState)(agent, agentRoot);
197
+ if (!blueBubblesRuntimeStateIsFresh(state.lastCheckedAt)) {
198
+ return { runtime: snapshot?.runtime };
199
+ }
200
+ if (state.upstreamStatus === "error") {
201
+ return {
202
+ runtime: "error",
203
+ detail: state.detail,
204
+ };
205
+ }
206
+ if (state.upstreamStatus === "ok") {
207
+ return { runtime: "running" };
208
+ }
209
+ return { runtime: snapshot?.runtime };
210
+ }
178
211
  class DaemonSenseManager {
179
212
  processManager;
180
213
  contexts;
214
+ bundlesRoot;
181
215
  constructor(options) {
182
216
  const bundlesRoot = options.bundlesRoot ?? path.join(os.homedir(), "AgentBundles");
183
217
  const secretsRoot = options.secretsRoot ?? path.join(os.homedir(), ".agentsecrets");
218
+ this.bundlesRoot = bundlesRoot;
184
219
  this.contexts = new Map(options.agents.map((agent) => {
185
220
  const senses = readAgentSenses(path.join(bundlesRoot, `${agent}.ouro`, "agent.json"));
186
221
  const facts = senseFactsFromSecrets(agent, senses, path.join(secretsRoot, agent, "secrets.json"));
@@ -216,6 +251,13 @@ class DaemonSenseManager {
216
251
  async stopAll() {
217
252
  await this.processManager.stopAll();
218
253
  }
254
+ /* v8 ignore start -- pid collection for orphan cleanup pidfile @preserve */
255
+ listManagedPids() {
256
+ return this.processManager.listAgentSnapshots()
257
+ .map((s) => s.pid)
258
+ .filter((pid) => pid !== null && pid !== undefined);
259
+ }
260
+ /* v8 ignore stop */
219
261
  listSenseRows() {
220
262
  const runtime = new Map();
221
263
  for (const snapshot of this.processManager.listAgentSnapshots()) {
@@ -227,6 +269,7 @@ class DaemonSenseManager {
227
269
  runtime.set(parsed.agent, current);
228
270
  }
229
271
  const rows = [...this.contexts.entries()].flatMap(([agent, context]) => {
272
+ const blueBubblesRuntimeFacts = readBlueBubblesRuntimeFacts(agent, this.bundlesRoot, runtime.get(agent)?.bluebubbles);
230
273
  const runtimeInfo = {
231
274
  cli: { configured: true },
232
275
  teams: {
@@ -235,7 +278,7 @@ class DaemonSenseManager {
235
278
  },
236
279
  bluebubbles: {
237
280
  configured: context.facts.bluebubbles.configured,
238
- ...(runtime.get(agent)?.bluebubbles ?? {}),
281
+ ...blueBubblesRuntimeFacts,
239
282
  },
240
283
  };
241
284
  const inventory = (0, sense_truth_1.getSenseInventory)({ senses: context.senses }, runtimeInfo);
@@ -245,7 +288,12 @@ class DaemonSenseManager {
245
288
  label: entry.label,
246
289
  enabled: entry.enabled,
247
290
  status: entry.status,
248
- detail: entry.enabled ? context.facts[entry.sense].detail : "not enabled in agent.json",
291
+ detail: entry.enabled
292
+ ? entry.sense === "bluebubbles"
293
+ ? blueBubblesRuntimeFacts.detail
294
+ ?? context.facts[entry.sense].detail
295
+ : context.facts[entry.sense].detail
296
+ : "not enabled in agent.json",
249
297
  }));
250
298
  });
251
299
  (0, runtime_1.emitNervesEvent)({