@ouro.bot/cli 0.1.0-alpha.213 → 0.1.0-alpha.215

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.
package/changelog.json CHANGED
@@ -1,6 +1,19 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.215",
6
+ "changes": [
7
+ "fix: surface tool proactive delivery no longer gated by 24-hour session threshold — findFreshestFriendSession was called with activeOnly:true, filtering out sessions older than 24h even when the agent explicitly wants to send a proactive message. Both the bridge path and direct path now find any session regardless of age. Proactive BB delivery and trust checks still apply."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.214",
12
+ "changes": [
13
+ "fix: daemon death diagnostics — createStderrSink() bypassed EPIPE-safe default in createTerminalSink(), causing uncaught EPIPE crashes when daemon runs detached. Removed redundant unsafe default so the existing try-catch fires.",
14
+ "fix: daemon tombstone now covers all exit paths — unhandledRejection writes tombstone with full error+stack (was just a warn log, but Node 15+ terminates on these). Added process.on('exit') catch-all for any unanticipated exit. SIGINT/SIGTERM marked graceful to avoid false positives. _lastKnownCause threads real error through to exit handler."
15
+ ]
16
+ },
4
17
  {
5
18
  "version": "0.1.0-alpha.213",
6
19
  "changes": [
@@ -181,6 +181,7 @@ void daemon.start().then(() => {
181
181
  /* v8 ignore start -- startup failure + signal handlers: call process.exit, untestable in vitest @preserve */
182
182
  }).catch(async (err) => {
183
183
  const error = err instanceof Error ? err : new Error(String(err));
184
+ _tombstoneWritten = true;
184
185
  (0, daemon_tombstone_1.writeDaemonTombstone)("startupFailure", error);
185
186
  (0, runtime_1.emitNervesEvent)({
186
187
  level: "error",
@@ -194,6 +195,7 @@ void daemon.start().then(() => {
194
195
  process.exit(1);
195
196
  });
196
197
  process.on("SIGINT", () => {
198
+ _gracefulShutdown = true;
197
199
  for (const s of habitSchedulers) {
198
200
  s.stopWatching();
199
201
  s.stop();
@@ -202,6 +204,7 @@ process.on("SIGINT", () => {
202
204
  void daemon.stop().then(() => process.exit(0));
203
205
  });
204
206
  process.on("SIGTERM", () => {
207
+ _gracefulShutdown = true;
205
208
  for (const s of habitSchedulers) {
206
209
  s.stopWatching();
207
210
  s.stop();
@@ -217,6 +220,9 @@ process.stderr?.on?.("error", () => { });
217
220
  /* v8 ignore stop */
218
221
  /* v8 ignore start -- global exception handlers: genuinely untestable in vitest; exercised by real daemon crashes @preserve */
219
222
  let _uncaughtCount = 0;
223
+ let _tombstoneWritten = false;
224
+ let _gracefulShutdown = false;
225
+ let _lastKnownCause = null;
220
226
  const CIRCUIT_BREAKER_WINDOW_MS = 60_000;
221
227
  const CIRCUIT_BREAKER_MAX = 10;
222
228
  process.on("uncaughtException", (error) => {
@@ -224,7 +230,9 @@ process.on("uncaughtException", (error) => {
224
230
  if (error.code === "EPIPE")
225
231
  return;
226
232
  _uncaughtCount++;
233
+ _lastKnownCause = error;
227
234
  setTimeout(() => { _uncaughtCount--; }, CIRCUIT_BREAKER_WINDOW_MS).unref();
235
+ _tombstoneWritten = true;
228
236
  (0, daemon_tombstone_1.writeDaemonTombstone)("uncaughtException", error);
229
237
  (0, runtime_1.emitNervesEvent)({
230
238
  level: "error",
@@ -248,13 +256,25 @@ process.on("uncaughtException", (error) => {
248
256
  }
249
257
  });
250
258
  process.on("unhandledRejection", (reason) => {
259
+ const error = reason instanceof Error ? reason : new Error(String(reason));
260
+ _lastKnownCause = error;
261
+ _tombstoneWritten = true;
262
+ (0, daemon_tombstone_1.writeDaemonTombstone)("unhandledRejection", error);
251
263
  (0, runtime_1.emitNervesEvent)({
252
- level: "warn",
264
+ level: "error",
253
265
  component: "daemon",
254
266
  event: "daemon.unhandled_rejection",
255
267
  message: "unhandled promise rejection in daemon process",
256
- meta: { reason: reason instanceof Error ? reason.message : String(reason) },
268
+ meta: { reason: error.message, stack: error.stack ?? null },
257
269
  });
258
270
  });
271
+ // Catch-all: write tombstone on any exit where we didn't already record the cause.
272
+ // process.on('exit') is synchronous-only — writeDaemonTombstone uses writeFileSync, so it works.
273
+ process.on("exit", (code) => {
274
+ if (_tombstoneWritten || _gracefulShutdown)
275
+ return;
276
+ const reason = code === 0 ? "unexpectedCleanExit" : "unexpectedExit";
277
+ const error = _lastKnownCause ?? new Error(`daemon exited with code ${code} (no specific cause captured)`);
278
+ (0, daemon_tombstone_1.writeDaemonTombstone)(reason, error);
279
+ });
259
280
  /* v8 ignore stop */
260
- // daemon stdio fix
@@ -100,7 +100,7 @@ catch { /* EPIPE: daemon detached, no terminal */ } }, colorize = true) {
100
100
  _resumeSpinner?.();
101
101
  };
102
102
  }
103
- function createStderrSink(write = (chunk) => process.stderr.write(chunk)) {
103
+ function createStderrSink(write) {
104
104
  return createTerminalSink(write);
105
105
  }
106
106
  const DEFAULT_MAX_LOG_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
@@ -88,7 +88,7 @@ exports.surfaceToolDefinition = {
88
88
  const { createBridgeManager } = await Promise.resolve().then(() => __importStar(require("../heart/bridges/manager")));
89
89
  const bridge = createBridgeManager().getBridge(queueItem.bridgeId);
90
90
  if (bridge && bridge.lifecycle !== "completed" && bridge.lifecycle !== "cancelled") {
91
- const allSessions = (0, session_activity_1.listSessionActivity)({ sessionsDir, friendsDir, agentName });
91
+ const allSessions = (0, session_activity_1.listSessionActivity)({ sessionsDir, friendsDir, agentName, activeThresholdMs: Number.MAX_SAFE_INTEGER });
92
92
  const bridgeTarget = allSessions.find((activity) => activity.friendId === friendId
93
93
  && activity.channel !== "inner"
94
94
  && bridge.attachedSessions.some((s) => s.friendId === activity.friendId && s.channel === activity.channel && s.key === activity.key));
@@ -124,13 +124,12 @@ exports.surfaceToolDefinition = {
124
124
  }
125
125
  }
126
126
  }
127
- // Priority 2: Freshest active friend session
127
+ // Priority 2: Freshest friend session (no active threshold — proactive outreach shouldn't be gated by session age)
128
128
  const freshest = (0, session_activity_1.findFreshestFriendSession)({
129
129
  sessionsDir,
130
130
  friendsDir,
131
131
  agentName,
132
132
  friendId,
133
- activeOnly: true,
134
133
  });
135
134
  if (freshest && freshest.channel !== "inner") {
136
135
  // Attempt proactive BB delivery
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.213",
3
+ "version": "0.1.0-alpha.215",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",