@ouro.bot/cli 0.1.0-alpha.494 → 0.1.0-alpha.496
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 +12 -7
- package/dist/heart/daemon/doctor.js +129 -0
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
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.496",
|
|
6
|
+
"changes": [
|
|
7
|
+
"New `Lifecycle` category in `ouro doctor` (`src/heart/daemon/doctor.ts:checkLifecycle`). Reads daemon.ndjson from the first available agent bundle and surfaces operator-relevant signal: last activity timestamp + age (warns if older than 5 minutes — daemon may be silent or stopped), daemon restart count in the last hour (warns if >3 — high churn), recent version-install events with installed versions, and any agent_process_error events with reason. Designed to answer the operator's question after the daemon goes silent: 'did it crash? when did it last do anything? did it just upgrade?' This session's daemon went silent at 04:30 UTC with no easy way to diagnose; the new check would have surfaced 'last event 18m ago — daemon may be silent or stopped' immediately. Tail-reads only the last 5000 log lines so doctor stays snappy on chatty daemons. 13 new tests covering recent activity, restart counts, install events, agent_process_error, age formatting, log truncation, and edge cases (malformed JSON, missing meta fields, missing log file, read failure)."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.495",
|
|
12
|
+
"changes": [
|
|
13
|
+
"New regression bundle at `src/__tests__/heart/provider-replay-regressions.test.ts` that captures provider replay-rejection bug shapes in one place — documentation-as-test. Each entry cites the PR that fixed the shape and the runbook entry; future debuggers seeing a 4xx from a provider on what looks like a valid turn can grep this file first to see if the shape was already encountered. Currently bundles the MiniMax-M2.7 inline-`<think>`-plus-tool_calls case (#612), the reused-tool_call_id-misordered-after-pruning case (#613), and a cross-reference stub for the event-id collision class (covered separately in session-events.test.ts). Also documents the contribution pattern: capture the failing shape, write the test BEFORE the fix, land the fix, verify the test passes, cite the PR. Linked from `docs/known-issues-and-recovery.md` so operators triaging a similar bug land on the test bundle by default."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.494",
|
|
6
18
|
"changes": [
|
|
@@ -2616,13 +2628,6 @@
|
|
|
2616
2628
|
"auth verify and auth switch now use pingProvider for real API verification instead of format-only checks. auth switch verifies credentials work before switching."
|
|
2617
2629
|
]
|
|
2618
2630
|
},
|
|
2619
|
-
{
|
|
2620
|
-
"version": "0.1.0-alpha.125",
|
|
2621
|
-
"changes": [
|
|
2622
|
-
"Fix: Default runtime logger is now silent (no stderr sink) so nerves events emitted before logger configuration no longer interleave with the CLI spinner animation.",
|
|
2623
|
-
"Fix: MCP server connect failures now include the command name, args, and a hint to check agent.json mcpServers configuration."
|
|
2624
|
-
]
|
|
2625
|
-
},
|
|
2626
2631
|
{
|
|
2627
2632
|
"version": "0.1.0-alpha.124",
|
|
2628
2633
|
"changes": [
|
|
@@ -14,6 +14,7 @@ exports.checkSenses = checkSenses;
|
|
|
14
14
|
exports.checkHabits = checkHabits;
|
|
15
15
|
exports.checkSecurity = checkSecurity;
|
|
16
16
|
exports.checkDisk = checkDisk;
|
|
17
|
+
exports.checkLifecycle = checkLifecycle;
|
|
17
18
|
exports.runDoctorChecks = runDoctorChecks;
|
|
18
19
|
const runtime_1 = require("../../nerves/runtime");
|
|
19
20
|
const bluebubbles_health_diagnostics_1 = require("./bluebubbles-health-diagnostics");
|
|
@@ -448,9 +449,137 @@ function computeSummary(categories) {
|
|
|
448
449
|
}
|
|
449
450
|
return { passed, warnings, failed };
|
|
450
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Recent daemon lifecycle: surfaces last activity timestamp, recent restarts,
|
|
454
|
+
* version-install events, and process errors from the last hour. Designed
|
|
455
|
+
* to answer the operator's question after the daemon has gone silent: "did
|
|
456
|
+
* it crash? when did it last do anything? did it just upgrade?"
|
|
457
|
+
*
|
|
458
|
+
* Reads daemon.ndjson from the first available agent bundle (one daemon
|
|
459
|
+
* serves all agents, so any agent's bundle has the shared log).
|
|
460
|
+
*/
|
|
461
|
+
function checkLifecycle(deps) {
|
|
462
|
+
const checks = [];
|
|
463
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
464
|
+
const STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
465
|
+
const cutoff = Date.now() - HOUR_MS;
|
|
466
|
+
const agents = discoverAgents(deps);
|
|
467
|
+
let logPath = null;
|
|
468
|
+
for (const agentDir of agents) {
|
|
469
|
+
const candidate = `${deps.bundlesRoot}/${agentDir}/state/daemon/logs/daemon.ndjson`;
|
|
470
|
+
if (deps.existsSync(candidate)) {
|
|
471
|
+
logPath = candidate;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (!logPath) {
|
|
476
|
+
checks.push({ label: "daemon log readable", status: "warn", detail: "no daemon.ndjson found in any agent bundle" });
|
|
477
|
+
return { name: "Lifecycle", checks };
|
|
478
|
+
}
|
|
479
|
+
let lastTs = null;
|
|
480
|
+
let lastEvent = null;
|
|
481
|
+
let startCount = 0;
|
|
482
|
+
let installCount = 0;
|
|
483
|
+
let installVersions = [];
|
|
484
|
+
let processErrors = [];
|
|
485
|
+
let lastEntryAgeMs = Number.POSITIVE_INFINITY;
|
|
486
|
+
try {
|
|
487
|
+
// Read the whole log via deps.readFileSync, then take the tail. For a
|
|
488
|
+
// chatty daemon this can be a few MB; we only inspect the last 5000
|
|
489
|
+
// lines which is enough for the last hour of activity. If the file is
|
|
490
|
+
// small (typical case), reading it all is cheap.
|
|
491
|
+
const raw = deps.readFileSync(logPath);
|
|
492
|
+
const allLines = raw.split("\n").filter((l) => l.trim());
|
|
493
|
+
const usable = allLines.length > 5000 ? allLines.slice(-5000) : allLines;
|
|
494
|
+
for (const line of usable) {
|
|
495
|
+
let parsed;
|
|
496
|
+
try {
|
|
497
|
+
parsed = JSON.parse(line);
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const ts = typeof parsed.ts === "string" ? parsed.ts : null;
|
|
503
|
+
const event = typeof parsed.event === "string" ? parsed.event : null;
|
|
504
|
+
if (!ts || !event)
|
|
505
|
+
continue;
|
|
506
|
+
const tsMs = Date.parse(ts);
|
|
507
|
+
if (Number.isNaN(tsMs))
|
|
508
|
+
continue;
|
|
509
|
+
lastTs = ts;
|
|
510
|
+
lastEvent = event;
|
|
511
|
+
lastEntryAgeMs = Math.min(lastEntryAgeMs, Date.now() - tsMs);
|
|
512
|
+
if (tsMs < cutoff)
|
|
513
|
+
continue;
|
|
514
|
+
if (event === "daemon.daemon_started")
|
|
515
|
+
startCount++;
|
|
516
|
+
if (event === "daemon.cli_version_install_end") {
|
|
517
|
+
installCount++;
|
|
518
|
+
const meta = parsed.meta;
|
|
519
|
+
const ver = typeof meta?.version === "string" ? meta.version : null;
|
|
520
|
+
if (ver)
|
|
521
|
+
installVersions.push(ver);
|
|
522
|
+
}
|
|
523
|
+
if (event === "daemon.agent_process_error") {
|
|
524
|
+
const meta = parsed.meta;
|
|
525
|
+
const reason = typeof meta?.reason === "string" ? meta.reason : "unknown";
|
|
526
|
+
const agent = typeof meta?.agent === "string" ? meta.agent : "unknown";
|
|
527
|
+
processErrors.push(`${agent}: ${reason}`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
checks.push({ label: "daemon log readable", status: "fail", detail: `read failed: ${error instanceof Error ? error.message : /* v8 ignore next -- non-Error throw is unreachable from deps.readFileSync (always Error) @preserve */ String(error)}` });
|
|
533
|
+
return { name: "Lifecycle", checks };
|
|
534
|
+
}
|
|
535
|
+
if (lastTs === null) {
|
|
536
|
+
checks.push({ label: "recent daemon activity", status: "warn", detail: "no parseable events in tail of daemon.ndjson" });
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
const ageSec = Math.round(lastEntryAgeMs / 1000);
|
|
540
|
+
const ageDetail = ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
|
|
541
|
+
if (lastEntryAgeMs > STALE_THRESHOLD_MS) {
|
|
542
|
+
checks.push({
|
|
543
|
+
label: "recent daemon activity",
|
|
544
|
+
status: "warn",
|
|
545
|
+
detail: `last event ${ageDetail} (${lastEvent}) — daemon may be silent or stopped`,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
checks.push({
|
|
550
|
+
label: "recent daemon activity",
|
|
551
|
+
status: "pass",
|
|
552
|
+
detail: `last event ${ageDetail} (${lastEvent})`,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (startCount > 0) {
|
|
557
|
+
checks.push({
|
|
558
|
+
label: "daemon restarts (last hour)",
|
|
559
|
+
status: startCount > 3 ? "warn" : "pass",
|
|
560
|
+
detail: `${startCount} restart${startCount === 1 ? "" : "s"}${startCount > 3 ? " — high churn, investigate" : ""}`,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
if (installCount > 0) {
|
|
564
|
+
checks.push({
|
|
565
|
+
label: "version installs (last hour)",
|
|
566
|
+
status: "pass",
|
|
567
|
+
detail: `installed: ${installVersions.join(", ")}`,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
if (processErrors.length > 0) {
|
|
571
|
+
checks.push({
|
|
572
|
+
label: "agent process errors (last hour)",
|
|
573
|
+
status: "warn",
|
|
574
|
+
detail: `${processErrors.length} error${processErrors.length === 1 ? "" : "s"}: ${processErrors.slice(0, 3).join("; ")}${processErrors.length > 3 ? "..." : ""}`,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
return { name: "Lifecycle", checks };
|
|
578
|
+
}
|
|
451
579
|
const CATEGORY_CHECKERS = [
|
|
452
580
|
{ name: "CLI", fn: checkCliPath },
|
|
453
581
|
{ name: "Daemon", fn: checkDaemon },
|
|
582
|
+
{ name: "Lifecycle", fn: checkLifecycle },
|
|
454
583
|
{ name: "Agents", fn: checkAgents },
|
|
455
584
|
{ name: "Senses", fn: checkSenses },
|
|
456
585
|
{ name: "Habits", fn: checkHabits },
|