@ouro.bot/cli 0.1.0-alpha.692 → 0.1.0-alpha.693

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,13 @@
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.693",
6
+ "changes": [
7
+ "Load vault-backed provider credentials before manual context-loss Sentinel refreshes so a fresh CLI process cannot report false not-loaded provider warnings.",
8
+ "Have doctor lifecycle checks read the machine-local daemon log alongside bundle-local logs so stale historical bundle logs cannot mask live daemon activity."
9
+ ]
10
+ },
4
11
  {
5
12
  "version": "0.1.0-alpha.692",
6
13
  "changes": [
@@ -7647,6 +7647,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7647
7647
  throw new Error("work sentinel requires --agent <name>");
7648
7648
  const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
7649
7649
  const agentRoot = deps.agentBundleRoot ?? path.join(bundlesRoot, `${command.agent}.ouro`);
7650
+ if (command.kind === "work.sentinel.refresh") {
7651
+ await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent, { preserveCachedOnFailure: true });
7652
+ }
7650
7653
  const sentinel = command.kind === "work.sentinel.refresh"
7651
7654
  ? await refreshContextLossSentinel(command.agent, agentRoot, { trigger: "manual_cli", homeDir: deps.homeDir })
7652
7655
  : readContextLossSentinelView(agentRoot, { limit: 20 });
@@ -7885,6 +7888,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7885
7888
  fetchImpl: deps.fetchImpl ?? fetch,
7886
7889
  socketPath: deps.socketPath,
7887
7890
  bundlesRoot: deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(),
7891
+ daemonLogsDir: path.join((0, ouro_version_manager_1.getOuroCliHome)(deps.homeDir), "daemon", "logs"),
7888
7892
  homedir: os.homedir(),
7889
7893
  envPath: process.env.PATH ?? "",
7890
7894
  platform: process.platform,
@@ -783,83 +783,106 @@ function computeSummary(categories) {
783
783
  * to answer the operator's question after the daemon has gone silent: "did
784
784
  * it crash? when did it last do anything? did it just upgrade?"
785
785
  *
786
- * Reads daemon.ndjson from the first available agent bundle (one daemon
787
- * serves all agents, so any agent's bundle has the shared log).
786
+ * Reads the machine-local daemon.ndjson when available, plus any
787
+ * bundle-local copies left by older runtimes. The newest parseable event
788
+ * is the liveness signal; stale bundle logs cannot mask a live daemon.
788
789
  */
789
790
  function checkLifecycle(deps) {
790
791
  const checks = [];
791
792
  const HOUR_MS = 60 * 60 * 1000;
792
793
  const STALE_THRESHOLD_MS = 5 * 60 * 1000;
793
- const cutoff = Date.now() - HOUR_MS;
794
+ const now = Date.now();
795
+ const cutoff = now - HOUR_MS;
796
+ const logPaths = [];
797
+ const seenLogPaths = new Set();
798
+ const addLogPath = (candidate) => {
799
+ if (seenLogPaths.has(candidate) || !deps.existsSync(candidate))
800
+ return;
801
+ seenLogPaths.add(candidate);
802
+ logPaths.push(candidate);
803
+ };
804
+ if (deps.daemonLogsDir) {
805
+ addLogPath(`${deps.daemonLogsDir}/daemon.ndjson`);
806
+ }
794
807
  const agents = discoverAgents(deps);
795
- let logPath = null;
796
808
  for (const agentDir of agents) {
797
- const candidate = `${deps.bundlesRoot}/${agentDir}/state/daemon/logs/daemon.ndjson`;
798
- if (deps.existsSync(candidate)) {
799
- logPath = candidate;
800
- break;
801
- }
809
+ addLogPath(`${deps.bundlesRoot}/${agentDir}/state/daemon/logs/daemon.ndjson`);
802
810
  }
803
- if (!logPath) {
804
- checks.push({ label: "daemon log readable", status: "warn", detail: "no daemon.ndjson found in any agent bundle" });
811
+ if (logPaths.length === 0) {
812
+ checks.push({ label: "daemon log readable", status: "warn", detail: "no daemon.ndjson found in daemon or agent bundle logs" });
805
813
  return { name: "Lifecycle", checks };
806
814
  }
807
815
  let lastTs = null;
808
816
  let lastEvent = null;
817
+ let lastEventTimeMs = Number.NEGATIVE_INFINITY;
809
818
  let startCount = 0;
810
819
  let installCount = 0;
811
820
  let installVersions = [];
812
821
  let processErrors = [];
813
822
  let lastEntryAgeMs = Number.POSITIVE_INFINITY;
814
- try {
815
- // Read the whole log via deps.readFileSync, then take the tail. For a
816
- // chatty daemon this can be a few MB; we only inspect the last 5000
817
- // lines which is enough for the last hour of activity. If the file is
818
- // small (typical case), reading it all is cheap.
819
- const raw = deps.readFileSync(logPath);
820
- const allLines = raw.split("\n").filter((l) => l.trim());
821
- const usable = allLines.length > 5000 ? allLines.slice(-5000) : allLines;
822
- for (const line of usable) {
823
- let parsed;
824
- try {
825
- parsed = JSON.parse(line);
826
- }
827
- catch {
828
- continue;
829
- }
830
- const ts = typeof parsed.ts === "string" ? parsed.ts : null;
831
- const event = typeof parsed.event === "string" ? parsed.event : null;
832
- if (!ts || !event)
833
- continue;
834
- const tsMs = Date.parse(ts);
835
- if (Number.isNaN(tsMs))
836
- continue;
837
- lastTs = ts;
838
- lastEvent = event;
839
- lastEntryAgeMs = Math.min(lastEntryAgeMs, Date.now() - tsMs);
840
- if (tsMs < cutoff)
841
- continue;
842
- if (event === "daemon.daemon_started")
843
- startCount++;
844
- if (event === "daemon.cli_version_install_end") {
845
- installCount++;
846
- const meta = parsed.meta;
847
- const ver = typeof meta?.version === "string" ? meta.version : null;
848
- if (ver)
849
- installVersions.push(ver);
850
- }
851
- if (event === "daemon.agent_process_error") {
852
- const meta = parsed.meta;
853
- const reason = typeof meta?.reason === "string" ? meta.reason : "unknown";
854
- const agent = typeof meta?.agent === "string" ? meta.agent : "unknown";
855
- processErrors.push(`${agent}: ${reason}`);
823
+ let readCount = 0;
824
+ const readErrors = [];
825
+ for (const logPath of logPaths) {
826
+ try {
827
+ // Read the whole log via deps.readFileSync, then take the tail. For a
828
+ // chatty daemon this can be a few MB; we only inspect the last 5000
829
+ // lines which is enough for the last hour of activity. If the file is
830
+ // small (typical case), reading it all is cheap.
831
+ const raw = deps.readFileSync(logPath);
832
+ readCount++;
833
+ const allLines = raw.split("\n").filter((l) => l.trim());
834
+ const usable = allLines.length > 5000 ? allLines.slice(-5000) : allLines;
835
+ for (const line of usable) {
836
+ let parsed;
837
+ try {
838
+ parsed = JSON.parse(line);
839
+ }
840
+ catch {
841
+ continue;
842
+ }
843
+ const ts = typeof parsed.ts === "string" ? parsed.ts : null;
844
+ const event = typeof parsed.event === "string" ? parsed.event : null;
845
+ if (!ts || !event)
846
+ continue;
847
+ const tsMs = Date.parse(ts);
848
+ if (Number.isNaN(tsMs))
849
+ continue;
850
+ if (tsMs > lastEventTimeMs) {
851
+ lastEventTimeMs = tsMs;
852
+ lastTs = ts;
853
+ lastEvent = event;
854
+ lastEntryAgeMs = Math.max(0, now - tsMs);
855
+ }
856
+ if (tsMs < cutoff)
857
+ continue;
858
+ if (event === "daemon.daemon_started")
859
+ startCount++;
860
+ if (event === "daemon.cli_version_install_end") {
861
+ installCount++;
862
+ const meta = parsed.meta;
863
+ const ver = typeof meta?.version === "string" ? meta.version : null;
864
+ if (ver)
865
+ installVersions.push(ver);
866
+ }
867
+ if (event === "daemon.agent_process_error") {
868
+ const meta = parsed.meta;
869
+ const reason = typeof meta?.reason === "string" ? meta.reason : "unknown";
870
+ const agent = typeof meta?.agent === "string" ? meta.agent : "unknown";
871
+ processErrors.push(`${agent}: ${reason}`);
872
+ }
856
873
  }
857
874
  }
875
+ catch (error) {
876
+ readErrors.push(`${logPath}: ${error instanceof Error ? error.message : /* v8 ignore next -- non-Error throw is unreachable from deps.readFileSync (always Error) @preserve */ String(error)}`);
877
+ }
858
878
  }
859
- catch (error) {
860
- 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)}` });
879
+ if (readCount === 0) {
880
+ checks.push({ label: "daemon log readable", status: "fail", detail: `read failed: ${readErrors.join("; ")}` });
861
881
  return { name: "Lifecycle", checks };
862
882
  }
883
+ if (readErrors.length > 0) {
884
+ checks.push({ label: "daemon log readable", status: "warn", detail: `some daemon logs were unreadable: ${readErrors.join("; ")}` });
885
+ }
863
886
  if (lastTs === null) {
864
887
  checks.push({ label: "recent daemon activity", status: "warn", detail: "no parseable events in tail of daemon.ndjson" });
865
888
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.692",
3
+ "version": "0.1.0-alpha.693",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",