@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: "
|
|
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:
|
|
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
|
package/dist/nerves/index.js
CHANGED
|
@@ -100,7 +100,7 @@ catch { /* EPIPE: daemon detached, no terminal */ } }, colorize = true) {
|
|
|
100
100
|
_resumeSpinner?.();
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
-
function createStderrSink(write
|
|
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
|
|
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
|