@ouro.bot/cli 0.1.0-alpha.697 → 0.1.0-alpha.699

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,22 @@
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.699",
6
+ "changes": [
7
+ "Populate the daemon provider credential cache during selected-provider warm-up so status and context-loss Sentinel report the same provider truth as live provider checks.",
8
+ "Treat first-run scheduled jobs as pending rather than degraded in daemon health, avoiding false ouro up failures for newly native daily habits."
9
+ ]
10
+ },
11
+ {
12
+ "version": "0.1.0-alpha.698",
13
+ "changes": [
14
+ "Open the daemon socket before provider credential warm-up and preload only configured lane providers so slow vault reads cannot make ouro up falsely report a dead daemon.",
15
+ "Require explicitly selected provider credential refreshes to load every requested provider before replacing cached provider state.",
16
+ "Return structured RSVP smoke guidance when the latest snapshot is missing or invalid, with hermetic CLI coverage that cannot send live BlueBubbles messages from tests.",
17
+ "Keep native RSVP update notifications on the legacy message skeleton while replacing only the obsolete script-vs-Slugger footer copy."
18
+ ]
19
+ },
4
20
  {
5
21
  "version": "0.1.0-alpha.697",
6
22
  "changes": [
@@ -119,6 +119,35 @@ if (!ipcState.installed) {
119
119
  message: "starting private-runtime process entrypoint",
120
120
  meta: { entry: "private-runtime", agentName },
121
121
  });
122
+ async function selectedProviderTargets(agentName) {
123
+ try {
124
+ const [{ readAgentConfigForAgent }, { getAgentBundlesRoot }] = await Promise.all([
125
+ Promise.resolve().then(() => __importStar(require("./auth/auth-flow"))),
126
+ Promise.resolve().then(() => __importStar(require("./identity"))),
127
+ ]);
128
+ const { config } = readAgentConfigForAgent(agentName, getAgentBundlesRoot());
129
+ return [...new Set([config.humanFacing.provider, config.agentFacing.provider])];
130
+ }
131
+ catch (error) {
132
+ (0, runtime_1.emitNervesEvent)({
133
+ level: "warn",
134
+ component: "senses",
135
+ event: "senses.provider_refresh_skipped",
136
+ message: "skipping private-runtime provider credential refresh because agent config could not be read",
137
+ meta: {
138
+ entry: "private-runtime",
139
+ agentName,
140
+ error: error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error agent-config failures @preserve */ String(error),
141
+ },
142
+ });
143
+ return [];
144
+ }
145
+ }
146
+ function providerPoolMissingTargets(providerPool, providers) {
147
+ if (!providerPool.ok)
148
+ return true;
149
+ return providers.some((provider) => !providerPool.pool.providers[provider]);
150
+ }
122
151
  // Dynamic import: agent-entry is process-start wiring that starts a sense process.
123
152
  // Using dynamic import avoids a static heart/ -> senses/ dependency.
124
153
  Promise.resolve().then(() => __importStar(require("./runtime-credentials"))).then(async ({ readMachineRuntimeCredentialConfig, readRuntimeCredentialConfig, refreshMachineRuntimeCredentialConfig, refreshRuntimeCredentialConfig, applyRuntimeCredentialBootstrapMessage, waitForRuntimeCredentialBootstrap, }) => {
@@ -135,8 +164,9 @@ Promise.resolve().then(() => __importStar(require("./runtime-credentials"))).the
135
164
  void refreshRuntimeCredentialConfig(agentName, { preserveCachedOnFailure: true }).catch(() => undefined);
136
165
  }
137
166
  const providerPool = readProviderCredentialPool(agentName);
138
- if (!providerPool.ok || Object.keys(providerPool.pool.providers).length === 0) {
139
- await refreshProviderCredentialPool(agentName, { preserveCachedOnFailure: true }).catch(() => undefined);
167
+ const providerTargets = await selectedProviderTargets(agentName);
168
+ if (providerTargets.length > 0 && providerPoolMissingTargets(providerPool, providerTargets)) {
169
+ await refreshProviderCredentialPool(agentName, { preserveCachedOnFailure: true, providers: providerTargets }).catch(() => undefined);
140
170
  }
141
171
  /* v8 ignore next 7 -- process-start best-effort machine credential refresh runs in a child entrypoint and is covered operationally by daemon startup tests @preserve */
142
172
  if (!readMachineRuntimeCredentialConfig(agentName).ok) {
@@ -68,6 +68,7 @@ const context_loss_sentinel_1 = require("../context-loss-sentinel");
68
68
  const provider_credentials_1 = require("../provider-credentials");
69
69
  const runtime_credentials_1 = require("../runtime-credentials");
70
70
  const machine_identity_1 = require("../machine-identity");
71
+ const auth_flow_1 = require("../auth/auth-flow");
71
72
  function parseSocketPath(argv) {
72
73
  const socketIndex = argv.indexOf("--socket");
73
74
  if (socketIndex >= 0) {
@@ -483,16 +484,116 @@ function writeStopCommandHealthState() {
483
484
  // Health writes are best-effort during shutdown.
484
485
  }
485
486
  }
487
+ function providerPreloadTargets(agent) {
488
+ try {
489
+ const { config } = (0, auth_flow_1.readAgentConfigForAgent)(agent, (0, identity_1.getAgentBundlesRoot)());
490
+ return [...new Set([config.humanFacing.provider, config.agentFacing.provider])];
491
+ }
492
+ catch (error) {
493
+ (0, runtime_1.emitNervesEvent)({
494
+ level: "warn",
495
+ component: "daemon",
496
+ event: "daemon.provider_preload_skipped",
497
+ message: "skipping provider credential preload because agent config could not be read",
498
+ meta: {
499
+ agent,
500
+ error: error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error config-read failures @preserve */ String(error),
501
+ },
502
+ });
503
+ return [];
504
+ }
505
+ }
506
+ function providerPreloadMissingTargets(result, providers) {
507
+ return providers.filter((provider) => !result.pool.providers[provider]);
508
+ }
486
509
  async function preloadProviderCredentialPools() {
487
- await Promise.all(managedAgents.map((agent) => (0, provider_credentials_1.refreshProviderCredentialPool)(agent, { preserveCachedOnFailure: true })));
510
+ await Promise.all(managedAgents.map(async (agent) => {
511
+ const providers = providerPreloadTargets(agent);
512
+ if (providers.length === 0)
513
+ return;
514
+ const result = await (0, provider_credentials_1.refreshProviderCredentialPool)(agent, { preserveCachedOnFailure: true, providers });
515
+ if (result.ok) {
516
+ const missingProviders = providerPreloadMissingTargets(result, providers);
517
+ if (missingProviders.length > 0) {
518
+ (0, runtime_1.emitNervesEvent)({
519
+ level: "warn",
520
+ component: "daemon",
521
+ event: "daemon.provider_preload_unavailable",
522
+ message: "provider credential preload returned an incomplete selected provider cache",
523
+ meta: {
524
+ agent,
525
+ reason: "missing",
526
+ error: `missing selected providers: ${missingProviders.join(", ")}`,
527
+ },
528
+ });
529
+ return;
530
+ }
531
+ const records = Object.values(result.pool.providers).filter((record) => !!record);
532
+ (0, provider_credentials_1.cacheProviderCredentialRecords)(agent, records, new Date(result.pool.updatedAt));
533
+ return;
534
+ }
535
+ (0, runtime_1.emitNervesEvent)({
536
+ level: "warn",
537
+ component: "daemon",
538
+ event: "daemon.provider_preload_unavailable",
539
+ message: "provider credential preload could not refresh selected provider cache",
540
+ meta: {
541
+ agent,
542
+ reason: result.reason,
543
+ error: result.error,
544
+ },
545
+ });
546
+ }));
547
+ }
548
+ function startProviderCredentialPoolPreload() {
549
+ return preloadProviderCredentialPools().catch((error) => {
550
+ (0, runtime_1.emitNervesEvent)({
551
+ level: "error",
552
+ component: "daemon",
553
+ event: "daemon.provider_preload_error",
554
+ message: "provider credential preload failed after daemon startup",
555
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error provider preload failures @preserve */ String(error) },
556
+ });
557
+ });
558
+ }
559
+ function scheduleStartupSentinelAfterProviderPreload(agent, preload) {
560
+ void preload.then(async () => {
561
+ const result = await refreshDaemonSentinel(agent, "daemon_startup");
562
+ if (result.status === "critical") {
563
+ (0, runtime_1.emitNervesEvent)({
564
+ level: "error",
565
+ component: "daemon",
566
+ event: "daemon.startup_sentinel_error",
567
+ message: "startup Sentinel refresh reported critical after provider preload",
568
+ meta: {
569
+ agent,
570
+ error: result.message,
571
+ },
572
+ });
573
+ }
574
+ /* v8 ignore start -- refreshDaemonSentinel handles ordinary Sentinel failures; this is a promise-chain guard. @preserve */
575
+ }).catch((error) => {
576
+ (0, runtime_1.emitNervesEvent)({
577
+ level: "error",
578
+ component: "daemon",
579
+ event: "daemon.startup_sentinel_error",
580
+ message: "startup Sentinel refresh failed after provider preload",
581
+ meta: {
582
+ agent,
583
+ error: error instanceof Error ? error.message : String(error),
584
+ },
585
+ });
586
+ });
587
+ /* v8 ignore stop */
488
588
  }
489
589
  /* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
490
- void preloadProviderCredentialPools().then(() => daemon.start()).then(async () => {
590
+ void daemon.start().then(async () => {
591
+ const providerPreload = startProviderCredentialPoolPreload();
491
592
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
492
593
  const ouroPath = (0, os_cron_deps_1.resolveOuroBinaryPath)();
493
594
  const osCronDeps = (0, os_cron_deps_1.createRealOsCronDeps)();
494
595
  for (const agent of managedAgents) {
495
- await refreshDaemonSentinel(agent, "daemon_startup");
596
+ scheduleStartupSentinelAfterProviderPreload(agent, providerPreload);
496
597
  const bundleRoot = path.join(bundlesRoot, `${agent}.ouro`);
497
598
  const habitsDir = path.join(bundleRoot, "habits");
498
599
  const degradedComponent = `habits:${agent}`;
@@ -113,16 +113,10 @@ class HealthMonitor {
113
113
  }
114
114
  const jobs = this.scheduler.listJobs();
115
115
  const neverRan = jobs.filter((job) => !job.lastRun);
116
- if (neverRan.length > 0) {
117
- results.push({
118
- name: "cron-health",
119
- status: "warn",
120
- message: `jobs never run: ${neverRan.map((job) => job.id).join(", ")}`,
121
- });
122
- }
123
- else {
124
- results.push({ name: "cron-health", status: "ok", message: "cron jobs are healthy" });
125
- }
116
+ const firstRunSuffix = neverRan.length > 0
117
+ ? `; pending first run: ${neverRan.map((job) => job.id).join(", ")}`
118
+ : "";
119
+ results.push({ name: "cron-health", status: "ok", message: `cron jobs are healthy${firstRunSuffix}` });
126
120
  const diskPercent = this.diskUsagePercent();
127
121
  if (diskPercent >= 90) {
128
122
  results.push({
@@ -241,6 +241,7 @@ function readProviderCredentialPool(agentName) {
241
241
  }
242
242
  async function refreshProviderCredentialPool(agentName, options = {}) {
243
243
  const providersToRead = selectedProviders(options.providers);
244
+ const requiresAllSelectedProviders = !!options.providers && options.providers.length > 0;
244
245
  try {
245
246
  const store = (0, credential_access_1.getCredentialStore)(agentName);
246
247
  options.onProgress?.(`reading vault items for ${agentName}...`);
@@ -266,6 +267,21 @@ async function refreshProviderCredentialPool(agentName, options = {}) {
266
267
  updatedAt = record.updatedAt;
267
268
  }
268
269
  options.onProgress?.("parsing provider credentials...");
270
+ const missingRequestedProviders = requiresAllSelectedProviders
271
+ ? providersToRead.filter((provider) => !providers[provider])
272
+ : [];
273
+ if (missingRequestedProviders.length > 0) {
274
+ const cached = cachedPools.get(agentName);
275
+ const result = {
276
+ ok: false,
277
+ reason: "missing",
278
+ poolPath: providerCredentialsVaultPath(agentName),
279
+ error: `requested provider credentials missing from ${providerCredentialsVaultPath(agentName)}: ${missingRequestedProviders.join(", ")}`,
280
+ };
281
+ if (options.preserveCachedOnFailure && cached?.ok)
282
+ return cached;
283
+ return options.skipCache ? result : cacheResult(agentName, result);
284
+ }
269
285
  const pool = {
270
286
  schemaVersion: 1,
271
287
  updatedAt,
package/dist/rsvp/cli.js CHANGED
@@ -178,8 +178,21 @@ function readBaselineSnapshot(agentRoot) {
178
178
  ? parseSnapshotFromFile(snapshotFilePath(agentRoot, snapshotId))
179
179
  : null;
180
180
  }
181
- function readLatestSnapshot(agentRoot) {
182
- return parseSnapshotFromFile(latestSnapshotPath(agentRoot));
181
+ function resolveLatestSnapshot(agentRoot) {
182
+ const filePath = latestSnapshotPath(agentRoot);
183
+ if (!fs.existsSync(filePath)) {
184
+ return { ok: false, path: filePath, message: "latest RSVP snapshot missing; run `ouro rsvp refresh --mode shadow --no-send` first" };
185
+ }
186
+ try {
187
+ return { ok: true, snapshot: parseSnapshotFromFile(filePath) };
188
+ }
189
+ catch (error) {
190
+ return {
191
+ ok: false,
192
+ path: filePath,
193
+ message: error instanceof Error ? error.message : /* v8 ignore next -- fs and snapshot parsing failures are Error instances. @preserve */ String(error),
194
+ };
195
+ }
183
196
  }
184
197
  function machineIdForCli() {
185
198
  return (0, machine_identity_1.loadOrCreateMachineIdentity)().machineId;
@@ -609,7 +622,19 @@ async function executeSmoke(command, deps) {
609
622
  result: configResult,
610
623
  });
611
624
  }
612
- const snapshot = readLatestSnapshot(agentRoot);
625
+ const snapshotResult = resolveLatestSnapshot(agentRoot);
626
+ if (!snapshotResult.ok) {
627
+ return {
628
+ ...basePayload(command, false, "RSVP smoke requires latest RSVP snapshot before follow-up can run", {
629
+ allowSend: command.allowSend === true,
630
+ sendAllowed: false,
631
+ requires: "latest RSVP snapshot",
632
+ result: snapshotResult,
633
+ }),
634
+ ok: false,
635
+ };
636
+ }
637
+ const snapshot = snapshotResult.snapshot;
613
638
  const question = command.question ?? "who is pending?";
614
639
  const answer = (0, query_1.queryRsvpSnapshot)(snapshot, { query: question });
615
640
  const wantsLiveSend = command.mode === "live" && command.allowSend === true;
@@ -4,6 +4,7 @@ exports.guestDisplayName = guestDisplayName;
4
4
  exports.computeRsvpDelta = computeRsvpDelta;
5
5
  exports.renderRsvpReport = renderRsvpReport;
6
6
  const runtime_1 = require("../nerves/runtime");
7
+ const RSVP_REPORT_FOOTER = "🤖 Beep boop! Automated RSVP update from Slugger — no need to reply here!";
7
8
  function byId(left, right) {
8
9
  return left.id.localeCompare(right.id);
9
10
  }
@@ -108,7 +109,7 @@ function hasChanges(delta) {
108
109
  function renderRsvpReport(delta) {
109
110
  const lines = ["RSVP Update — Ari & Rachel", ""];
110
111
  if (delta.isFirstRun) {
111
- lines.push("First native RSVP check — current summary:", "");
112
+ lines.push("First check — here's the current summary:", "");
112
113
  }
113
114
  else if (!hasChanges(delta)) {
114
115
  lines.push("No changes since last check.", "");
@@ -144,6 +145,7 @@ function renderRsvpReport(delta) {
144
145
  }
145
146
  }
146
147
  lines.push(summaryLine(delta.summary));
148
+ lines.push("", RSVP_REPORT_FOOTER);
147
149
  const report = lines.join("\n");
148
150
  (0, runtime_1.emitNervesEvent)({
149
151
  component: "rsvp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.697",
3
+ "version": "0.1.0-alpha.699",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",