@ouro.bot/cli 0.1.0-alpha.662 → 0.1.0-alpha.664

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.
@@ -318,6 +318,8 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
318
318
  return selectionResult.result;
319
319
  if (selectionResult.disabled)
320
320
  return { ok: true };
321
+ const agentRoot = agentRootFor(agentName, bundlesRoot);
322
+ const shouldRecordReadiness = deps.recordReadiness !== false;
321
323
  deps.onProgress?.(selectedProviderPlan(agentName, selectionResult.bindings));
322
324
  const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
323
325
  const providers = selectedProvidersForBindings(selectionResult.bindings);
@@ -375,32 +377,38 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
375
377
  for (const { group, result } of pingResults) {
376
378
  if (!result.ok) {
377
379
  for (const lane of group.lanes) {
380
+ if (shouldRecordReadiness) {
381
+ (0, provider_readiness_cache_1.recordProviderLaneReadiness)({
382
+ agentRoot,
383
+ agentName,
384
+ lane,
385
+ provider: group.provider,
386
+ model: group.model,
387
+ credentialRevision: group.record.revision,
388
+ status: "failed",
389
+ checkedAt: new Date().toISOString(),
390
+ error: result.message,
391
+ attempts: pingAttemptCount(result),
392
+ });
393
+ }
394
+ }
395
+ firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
396
+ continue;
397
+ }
398
+ for (const lane of group.lanes) {
399
+ if (shouldRecordReadiness) {
378
400
  (0, provider_readiness_cache_1.recordProviderLaneReadiness)({
401
+ agentRoot,
379
402
  agentName,
380
403
  lane,
381
404
  provider: group.provider,
382
405
  model: group.model,
383
406
  credentialRevision: group.record.revision,
384
- status: "failed",
407
+ status: "ready",
385
408
  checkedAt: new Date().toISOString(),
386
- error: result.message,
387
409
  attempts: pingAttemptCount(result),
388
410
  });
389
411
  }
390
- firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
391
- continue;
392
- }
393
- for (const lane of group.lanes) {
394
- (0, provider_readiness_cache_1.recordProviderLaneReadiness)({
395
- agentName,
396
- lane,
397
- provider: group.provider,
398
- model: group.model,
399
- credentialRevision: group.record.revision,
400
- status: "ready",
401
- checkedAt: new Date().toISOString(),
402
- attempts: pingAttemptCount(result),
403
- });
404
412
  }
405
413
  }
406
414
  if (firstFailure)
@@ -76,6 +76,7 @@ const provider_credentials_1 = require("../provider-credentials");
76
76
  const runtime_credentials_1 = require("../runtime-credentials");
77
77
  const provider_binding_resolver_1 = require("../provider-binding-resolver");
78
78
  const provider_visibility_1 = require("../provider-visibility");
79
+ const context_loss_sentinel_1 = require("../context-loss-sentinel");
79
80
  const machine_identity_1 = require("../machine-identity");
80
81
  const provider_models_1 = require("../provider-models");
81
82
  const ouro_version_manager_1 = require("../versioning/ouro-version-manager");
@@ -110,6 +111,7 @@ const stale_bundle_prune_1 = require("./stale-bundle-prune");
110
111
  const up_progress_1 = require("./up-progress");
111
112
  const provider_ping_progress_1 = require("./provider-ping-progress");
112
113
  const provider_ping_1 = require("../provider-ping");
114
+ const provider_readiness_cache_1 = require("../provider-readiness-cache");
113
115
  const agent_discovery_1 = require("./agent-discovery");
114
116
  const boot_sync_probe_1 = require("./boot-sync-probe");
115
117
  const connect_bay_1 = require("./connect-bay");
@@ -368,6 +370,8 @@ function agentResolutionFailureMode(command) {
368
370
  case "attention.history":
369
371
  case "work.card":
370
372
  case "work.gauntlet":
373
+ case "work.sentinel":
374
+ case "work.sentinel.refresh":
371
375
  case "inner.status":
372
376
  case "session.list":
373
377
  return "return-message";
@@ -4628,6 +4632,11 @@ function credentialPingConfig(record) {
4628
4632
  ...record.config,
4629
4633
  };
4630
4634
  }
4635
+ function pingAttemptCount(result) {
4636
+ if (Array.isArray(result.attempts))
4637
+ return result.attempts.length;
4638
+ return undefined;
4639
+ }
4631
4640
  async function readProviderCredentialRecord(agent, provider, _deps, options = {}) {
4632
4641
  const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agent, { ...options, providers: [provider] });
4633
4642
  if (poolResult.ok) {
@@ -4764,7 +4773,8 @@ async function executeProviderCheck(command, deps) {
4764
4773
  return message;
4765
4774
  };
4766
4775
  try {
4767
- const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
4776
+ const { config, configPath } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
4777
+ const agentRoot = path.dirname(configPath);
4768
4778
  const binding = providerConfigBinding(config, command.lane);
4769
4779
  progress.startPhase(`reading ${binding.provider} credentials`);
4770
4780
  const credential = await readProviderCredentialRecord(command.agent, binding.provider, deps, {
@@ -4788,6 +4798,19 @@ async function executeProviderCheck(command, deps) {
4788
4798
  });
4789
4799
  const status = pingResult.ok ? "ready" : `failed (${pingResult.message})`;
4790
4800
  progress.completePhase(`checking ${binding.provider} / ${binding.model}`, status);
4801
+ const attempts = pingAttemptCount(pingResult);
4802
+ (0, provider_readiness_cache_1.recordProviderLaneReadiness)({
4803
+ agentRoot,
4804
+ agentName: command.agent,
4805
+ lane: command.lane,
4806
+ provider: binding.provider,
4807
+ model: binding.model,
4808
+ credentialRevision: credential.record.revision,
4809
+ status: pingResult.ok ? "ready" : "failed",
4810
+ checkedAt: new Date().toISOString(),
4811
+ ...(pingResult.ok ? {} : { error: pingResult.message }),
4812
+ ...(attempts === undefined ? {} : { attempts }),
4813
+ });
4791
4814
  const message = `${command.agent} ${command.lane} ${binding.provider} / ${binding.model}: ${status}`;
4792
4815
  (0, runtime_1.emitNervesEvent)({
4793
4816
  component: "daemon",
@@ -5573,6 +5596,40 @@ function resolveClonePath(options, checkExists, deps) {
5573
5596
  return cloneTarget;
5574
5597
  }
5575
5598
  /* v8 ignore stop */
5599
+ const HOOK_SENTINEL_LOCK_TIMEOUT_MS = 500;
5600
+ function hookSentinelGitStatus() {
5601
+ return {
5602
+ ok: false,
5603
+ error: "skipped during session-start hook to keep lifecycle hook bounded; run `ouro work sentinel refresh --agent <agent>` for full bundle git status",
5604
+ };
5605
+ }
5606
+ async function refreshHookSentinel(command, deps) {
5607
+ if (command.event !== "session-start")
5608
+ return;
5609
+ const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
5610
+ const bundleRoot = path.join(bundlesRoot, `${command.agent}.ouro`);
5611
+ try {
5612
+ await (0, context_loss_sentinel_1.refreshContextLossSentinel)(command.agent, bundleRoot, {
5613
+ trigger: "session_start",
5614
+ lockTimeoutMs: HOOK_SENTINEL_LOCK_TIMEOUT_MS,
5615
+ gitStatus: hookSentinelGitStatus,
5616
+ });
5617
+ }
5618
+ catch (error) {
5619
+ (0, runtime_1.emitNervesEvent)({
5620
+ level: "warn",
5621
+ component: "daemon",
5622
+ event: "daemon.hook_sentinel_refresh_error",
5623
+ message: "claude code hook Sentinel refresh failed",
5624
+ meta: {
5625
+ agent: command.agent,
5626
+ eventType: command.event,
5627
+ lockTimeoutMs: HOOK_SENTINEL_LOCK_TIMEOUT_MS,
5628
+ error: error instanceof Error ? error.message : String(error),
5629
+ },
5630
+ });
5631
+ }
5632
+ }
5576
5633
  // ── Main CLI execution ──
5577
5634
  async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDeps)()) {
5578
5635
  if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
@@ -5622,6 +5679,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
5622
5679
  throw new Error(resolvedCommand.message);
5623
5680
  }
5624
5681
  let command = resolvedCommand.command;
5682
+ if (command.kind === "hook") {
5683
+ await refreshHookSentinel(command, deps);
5684
+ }
5625
5685
  if (args.length === 0) {
5626
5686
  const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : (0, cli_defaults_1.defaultListDiscoveredAgents)());
5627
5687
  /* v8 ignore start -- the interactive home shell is exercised extensively in daemon-cli tests; V8 miscounts this orchestrator because it chains through recursive command handoffs and early chat health exits @preserve */
@@ -7343,6 +7403,22 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7343
7403
  deps.writeStdout(message);
7344
7404
  return message;
7345
7405
  }
7406
+ // ── context-loss Sentinel (local, no daemon socket needed) ──
7407
+ if (command.kind === "work.sentinel" || command.kind === "work.sentinel.refresh") {
7408
+ const { formatContextLossSentinelJson, formatContextLossSentinelText, readContextLossSentinelView, refreshContextLossSentinel, } = await Promise.resolve().then(() => __importStar(require("../context-loss-sentinel")));
7409
+ if (!command.agent)
7410
+ throw new Error("work sentinel requires --agent <name>");
7411
+ const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
7412
+ const agentRoot = deps.agentBundleRoot ?? path.join(bundlesRoot, `${command.agent}.ouro`);
7413
+ const sentinel = command.kind === "work.sentinel.refresh"
7414
+ ? await refreshContextLossSentinel(command.agent, agentRoot, { trigger: "manual_cli", homeDir: deps.homeDir })
7415
+ : readContextLossSentinelView(agentRoot, { limit: 20 });
7416
+ const message = command.format === "json"
7417
+ ? formatContextLossSentinelJson(sentinel)
7418
+ : formatContextLossSentinelText(sentinel);
7419
+ deps.writeStdout(message);
7420
+ return message;
7421
+ }
7346
7422
  // ── inner dialog status (local, no daemon socket needed) ──
7347
7423
  /* v8 ignore start -- inner status handler: requires real agent state on disk @preserve */
7348
7424
  if (command.kind === "inner.status") {
@@ -153,10 +153,10 @@ exports.COMMAND_REGISTRY = {
153
153
  },
154
154
  work: {
155
155
  category: "Tasks",
156
- description: "Show durable Arc work state or run the context-loss gauntlet.",
157
- usage: "ouro work card|gauntlet [--agent <name>] [--format text|json|--json]",
158
- example: "ouro work gauntlet --agent slugger --format json",
159
- subcommands: ["card", "gauntlet"],
156
+ description: "Show durable Arc work state, recovery Sentinel state, or run the context-loss gauntlet.",
157
+ usage: "ouro work card|gauntlet|sentinel [refresh] [--agent <name>] [--format text|json|--json]",
158
+ example: "ouro work sentinel --agent slugger --format json",
159
+ subcommands: ["card", "gauntlet", "sentinel"],
160
160
  },
161
161
  "work card": {
162
162
  category: "Tasks",
@@ -172,6 +172,13 @@ exports.COMMAND_REGISTRY = {
172
172
  example: "ouro work gauntlet --agent slugger --format json",
173
173
  hidden: true,
174
174
  },
175
+ "work sentinel": {
176
+ category: "Tasks",
177
+ description: "Show read-only Arc Sentinel recovery state, or explicitly refresh it.",
178
+ usage: "ouro work sentinel [refresh] [--agent <name>] [--format text|json|--json]",
179
+ example: "ouro work sentinel refresh --agent slugger --format json",
180
+ hidden: true,
181
+ },
175
182
  "migrate-to-desk": {
176
183
  category: "Tasks",
177
184
  description: "Migrate a legacy `tasks/` tree into the new `desk/` shape (copy semantics — source untouched).",
@@ -114,7 +114,7 @@ function usage() {
114
114
  " ouro friend create --name <name> [--trust <level>] [--agent <name>]",
115
115
  " ouro friend update <id> --trust <level> [--agent <name>]",
116
116
  " ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
117
- " ouro work card|gauntlet [--agent <name>] [--format text|json|--json]",
117
+ " ouro work card|gauntlet|sentinel [refresh] [--agent <name>] [--format text|json|--json]",
118
118
  " ouro inner [--agent <name>]",
119
119
  " ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
120
120
  " ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
@@ -985,11 +985,13 @@ function parseProviderCheckCommand(args) {
985
985
  }
986
986
  function parseProviderCommand(args) {
987
987
  const sub = args[0];
988
+ if (sub === "check")
989
+ return parseProviderCheckCommand(args.slice(1));
988
990
  const { agent, rest } = extractAgentFlag(args.slice(1));
989
991
  if (sub === "refresh" && rest.length === 0) {
990
992
  return { kind: "provider.refresh", ...(agent ? { agent } : {}) };
991
993
  }
992
- throw new Error("Usage: ouro provider refresh [--agent <name>]");
994
+ throw new Error("Usage: ouro provider refresh [--agent <name>] OR ouro provider check [--agent <name>] --lane outward|inner");
993
995
  }
994
996
  function parseSessionCommand(args) {
995
997
  const { agent, rest: cleaned } = extractAgentFlag(args);
@@ -1011,28 +1013,42 @@ function parseAttentionCommand(args) {
1011
1013
  }
1012
1014
  return { kind: "attention.list", ...(agent ? { agent } : {}) };
1013
1015
  }
1014
- function parseWorkCommand(args) {
1015
- const { agent, rest: cleaned } = extractAgentFlag(args);
1016
- const sub = cleaned[0];
1017
- if (sub !== "card" && sub !== "gauntlet") {
1018
- throw new Error("Usage: ouro work card|gauntlet [--agent <name>] [--format text|json|--json]");
1019
- }
1016
+ function parseWorkFormat(args, usageText) {
1020
1017
  let format = "text";
1021
- for (let i = 1; i < cleaned.length; i += 1) {
1022
- if (cleaned[i] === "--json") {
1018
+ for (let i = 0; i < args.length; i += 1) {
1019
+ if (args[i] === "--json") {
1023
1020
  format = "json";
1024
1021
  continue;
1025
1022
  }
1026
- if (cleaned[i] === "--format" && cleaned[i + 1]) {
1027
- const value = cleaned[++i];
1023
+ if (args[i] === "--format" && args[i + 1]) {
1024
+ const value = args[++i];
1028
1025
  if (value !== "text" && value !== "json") {
1029
1026
  throw new Error("--format must be text or json");
1030
1027
  }
1031
1028
  format = value;
1032
1029
  continue;
1033
1030
  }
1034
- throw new Error(`Usage: ouro work ${sub} [--agent <name>] [--format text|json|--json]`);
1031
+ throw new Error(usageText);
1032
+ }
1033
+ return format;
1034
+ }
1035
+ function parseWorkCommand(args) {
1036
+ const { agent, rest: cleaned } = extractAgentFlag(args);
1037
+ const sub = cleaned[0];
1038
+ if (sub === "sentinel") {
1039
+ const refresh = cleaned[1] === "refresh";
1040
+ const usageText = "Usage: ouro work sentinel [refresh] [--agent <name>] [--format text|json|--json]";
1041
+ const format = parseWorkFormat(cleaned.slice(refresh ? 2 : 1), usageText);
1042
+ return {
1043
+ kind: refresh ? "work.sentinel.refresh" : "work.sentinel",
1044
+ ...(agent ? { agent } : {}),
1045
+ ...(format !== "text" ? { format } : {}),
1046
+ };
1047
+ }
1048
+ if (sub !== "card" && sub !== "gauntlet") {
1049
+ throw new Error("Usage: ouro work card|gauntlet|sentinel [refresh] [--agent <name>] [--format text|json|--json]");
1035
1050
  }
1051
+ const format = parseWorkFormat(cleaned.slice(1), `Usage: ouro work ${sub} [--agent <name>] [--format text|json|--json]`);
1036
1052
  return { kind: sub === "card" ? "work.card" : "work.gauntlet", ...(agent ? { agent } : {}), ...(format !== "text" ? { format } : {}) };
1037
1053
  }
1038
1054
  function parseThoughtsCommand(args) {
@@ -62,6 +62,7 @@ const pulse_1 = require("./pulse");
62
62
  const socket_client_1 = require("./socket-client");
63
63
  const bundle_manifest_1 = require("../../mind/bundle-manifest");
64
64
  const mcp_canary_1 = require("./mcp-canary");
65
+ const context_loss_sentinel_1 = require("../context-loss-sentinel");
65
66
  function parseSocketPath(argv) {
66
67
  const socketIndex = argv.indexOf("--socket");
67
68
  if (socketIndex >= 0) {
@@ -92,6 +93,31 @@ if (mode === "dev") {
92
93
  });
93
94
  }
94
95
  const managedAgents = (0, agent_discovery_1.listEnabledBundleAgents)();
96
+ function sentinelHealthStatus(receipt) {
97
+ if (receipt.verdict === "ready")
98
+ return "ok";
99
+ if (receipt.verdict === "watch")
100
+ return "warn";
101
+ return "critical";
102
+ }
103
+ async function refreshDaemonSentinel(agent, trigger, daemonHealthResults = []) {
104
+ const bundleRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${agent}.ouro`);
105
+ try {
106
+ const receipt = await (0, context_loss_sentinel_1.refreshContextLossSentinel)(agent, bundleRoot, { trigger, daemonHealthResults });
107
+ return {
108
+ name: `context-loss-sentinel:${agent}`,
109
+ status: sentinelHealthStatus(receipt),
110
+ message: `Sentinel ${receipt.verdict}: ${receipt.summary}`,
111
+ };
112
+ }
113
+ catch (error) {
114
+ return {
115
+ name: `context-loss-sentinel:${agent}`,
116
+ status: "critical",
117
+ message: `Sentinel refresh failed: ${error instanceof Error ? error.message : String(error)}`,
118
+ };
119
+ }
120
+ }
95
121
  const processManager = new process_manager_1.DaemonProcessManager({
96
122
  agents: managedAgents.map((agent) => ({
97
123
  name: agent,
@@ -133,6 +159,7 @@ const senseManager = new sense_manager_1.DaemonSenseManager({
133
159
  const healthMonitor = new health_monitor_1.HealthMonitor({
134
160
  processManager,
135
161
  scheduler,
162
+ sentinelChecker: (resultsSoFar) => Promise.all(managedAgents.map((agent) => refreshDaemonSentinel(agent, "daemon_health", resultsSoFar))),
136
163
  senseProbeProvider: () => [
137
164
  ...senseManager.listHealthProbes(),
138
165
  ...managedAgents.map((agent) => (0, mcp_canary_1.createMcpStatusCanaryProbe)({
@@ -349,11 +376,12 @@ function writeStopCommandHealthState() {
349
376
  }
350
377
  }
351
378
  /* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
352
- void daemon.start().then(() => {
379
+ void daemon.start().then(async () => {
353
380
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
354
381
  const ouroPath = (0, os_cron_deps_1.resolveOuroBinaryPath)();
355
382
  const osCronDeps = (0, os_cron_deps_1.createRealOsCronDeps)();
356
383
  for (const agent of managedAgents) {
384
+ await refreshDaemonSentinel(agent, "daemon_startup");
357
385
  const bundleRoot = path.join(bundlesRoot, `${agent}.ouro`);
358
386
  const habitsDir = path.join(bundleRoot, "habits");
359
387
  const degradedComponent = `habits:${agent}`;
@@ -7,6 +7,7 @@ class HealthMonitor {
7
7
  scheduler;
8
8
  alertSink;
9
9
  diskUsagePercent;
10
+ sentinelChecker;
10
11
  onCriticalAgent;
11
12
  onCriticalSense;
12
13
  senseProbes;
@@ -18,6 +19,7 @@ class HealthMonitor {
18
19
  this.scheduler = options.scheduler;
19
20
  this.alertSink = options.alertSink ?? (() => undefined);
20
21
  this.diskUsagePercent = options.diskUsagePercent ?? (() => 0);
22
+ this.sentinelChecker = options.sentinelChecker ?? (() => []);
21
23
  this.onCriticalAgent = options.onCriticalAgent ?? (() => undefined);
22
24
  this.onCriticalSense = options.onCriticalSense ?? (() => undefined);
23
25
  this.senseProbes = options.senseProbes ?? [];
@@ -178,6 +180,7 @@ class HealthMonitor {
178
180
  });
179
181
  }
180
182
  }
183
+ results.push(...await this.sentinelChecker(results.map((result) => ({ ...result }))));
181
184
  this.lastResults = results.map((result) => ({ ...result }));
182
185
  for (const result of results) {
183
186
  (0, runtime_1.emitNervesEvent)({
@@ -125,6 +125,8 @@ function writeRecordScaffold(bundleRoot) {
125
125
  fs.writeFileSync(recordPaths.entitiesPath, "{}\n", "utf-8");
126
126
  fs.mkdirSync(path.join(bundleRoot, "arc", "flight-recorder", "events"), { recursive: true });
127
127
  fs.mkdirSync(path.join(bundleRoot, "arc", "flight-recorder", "habit-receipts"), { recursive: true });
128
+ fs.mkdirSync(path.join(bundleRoot, "arc", "flight-recorder", "context-loss-sentinel", "history"), { recursive: true });
129
+ fs.mkdirSync(path.join(bundleRoot, "arc", "flight-recorder", "context-loss-sentinel", "receipts"), { recursive: true });
128
130
  fs.mkdirSync(path.join(bundleRoot, "arc", "claims"), { recursive: true });
129
131
  }
130
132
  function writeHatchlingAgentConfig(bundleRoot, input) {
@@ -55,6 +55,7 @@ function createMailboxHttpReadHooks(options) {
55
55
  readAgentChanges: options.readAgentChanges ?? ((agentName) => (0, mailbox_read_1.readChangesView)(agentRoot(agentName))),
56
56
  readAgentSelfFix: options.readAgentSelfFix ?? ((agentName) => (0, mailbox_read_1.readSelfFixView)(agentRoot(agentName))),
57
57
  readAgentContextLossGauntlet: options.readAgentContextLossGauntlet ?? ((agentName) => (0, mailbox_read_1.readContextLossGauntletView)(agentRoot(agentName), agentName)),
58
+ readAgentSentinel: options.readAgentSentinel ?? ((agentName) => (0, mailbox_read_1.readSentinelView)(agentRoot(agentName))),
58
59
  readAgentNoteDecisions: options.readAgentNoteDecisions ?? ((agentName) => (0, mailbox_read_1.readNoteDecisionView)(agentRoot(agentName))),
59
60
  readAgentHabits: options.readAgentHabits ?? ((agentName) => (0, mailbox_read_1.readHabitView)(agentRoot(agentName))),
60
61
  readAgentMail: options.readAgentMail ?? ((agentName) => (0, mailbox_read_1.readMailView)(agentName)),
@@ -176,6 +176,10 @@ async function handleAgentRoute(request, response, context) {
176
176
  (0, mailbox_http_response_1.writeJson)(response, 200, options.hooks.readAgentContextLossGauntlet(agent));
177
177
  return;
178
178
  }
179
+ if (surface === "sentinel") {
180
+ (0, mailbox_http_response_1.writeJson)(response, 200, options.hooks.readAgentSentinel(agent));
181
+ return;
182
+ }
179
183
  if (surface === "note-decisions") {
180
184
  (0, mailbox_http_response_1.writeJson)(response, 200, options.hooks.readAgentNoteDecisions(agent));
181
185
  return;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readSelfFixView = exports.readMailboxContinuity = exports.readOrientationView = exports.readObligationDetailView = exports.readNoteDecisionView = exports.readContextLossGauntletView = exports.readChangesView = exports.readNeedsMeView = exports.readNotesView = exports.readLogView = exports.readHabitView = exports.readFriendView = exports.readDeskPrefs = exports.readDaemonHealthDeep = exports.readCodingDeep = exports.readBridgeInventory = exports.readAttentionView = exports.readMailView = exports.readMailMessageView = exports.readSessionTranscript = exports.readSessionInventory = exports.readMailboxMachineState = exports.readMailboxAgentState = exports.readObligationSummary = void 0;
3
+ exports.readSelfFixView = exports.readMailboxContinuity = exports.readOrientationView = exports.readObligationDetailView = exports.readNoteDecisionView = exports.readSentinelView = exports.readContextLossGauntletView = exports.readChangesView = exports.readNeedsMeView = exports.readNotesView = exports.readLogView = exports.readHabitView = exports.readFriendView = exports.readDeskPrefs = exports.readDaemonHealthDeep = exports.readCodingDeep = exports.readBridgeInventory = exports.readAttentionView = exports.readMailView = exports.readMailMessageView = exports.readSessionTranscript = exports.readSessionInventory = exports.readMailboxMachineState = exports.readMailboxAgentState = exports.readObligationSummary = void 0;
4
4
  var agent_machine_1 = require("./readers/agent-machine");
5
5
  Object.defineProperty(exports, "readObligationSummary", { enumerable: true, get: function () { return agent_machine_1.readObligationSummary; } });
6
6
  Object.defineProperty(exports, "readMailboxAgentState", { enumerable: true, get: function () { return agent_machine_1.readMailboxAgentState; } });
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "readNeedsMeView", { enumerable: true, get: funct
25
25
  var continuity_readers_1 = require("./readers/continuity-readers");
26
26
  Object.defineProperty(exports, "readChangesView", { enumerable: true, get: function () { return continuity_readers_1.readChangesView; } });
27
27
  Object.defineProperty(exports, "readContextLossGauntletView", { enumerable: true, get: function () { return continuity_readers_1.readContextLossGauntletView; } });
28
+ Object.defineProperty(exports, "readSentinelView", { enumerable: true, get: function () { return continuity_readers_1.readSentinelView; } });
28
29
  Object.defineProperty(exports, "readNoteDecisionView", { enumerable: true, get: function () { return continuity_readers_1.readNoteDecisionView; } });
29
30
  Object.defineProperty(exports, "readObligationDetailView", { enumerable: true, get: function () { return continuity_readers_1.readObligationDetailView; } });
30
31
  Object.defineProperty(exports, "readOrientationView", { enumerable: true, get: function () { return continuity_readers_1.readOrientationView; } });
@@ -39,6 +39,7 @@ exports.readObligationDetailView = readObligationDetailView;
39
39
  exports.readChangesView = readChangesView;
40
40
  exports.readSelfFixView = readSelfFixView;
41
41
  exports.readContextLossGauntletView = readContextLossGauntletView;
42
+ exports.readSentinelView = readSentinelView;
42
43
  exports.readNoteDecisionView = readNoteDecisionView;
43
44
  const fs = __importStar(require("fs"));
44
45
  const path = __importStar(require("path"));
@@ -50,6 +51,7 @@ const presence_1 = require("../../../arc/presence");
50
51
  const active_work_1 = require("../../active-work");
51
52
  const session_activity_1 = require("../../session-activity");
52
53
  const context_loss_gauntlet_1 = require("../../context-loss-gauntlet");
54
+ const context_loss_sentinel_1 = require("../../context-loss-sentinel");
53
55
  const agent_machine_1 = require("./agent-machine");
54
56
  function sortOpenObligations(obligations) {
55
57
  const statusPriority = {
@@ -295,6 +297,9 @@ function readSelfFixView(_agentRoot) {
295
297
  function readContextLossGauntletView(agentRoot, agentName) {
296
298
  return (0, context_loss_gauntlet_1.runContextLossGauntlet)(agentName, agentRoot);
297
299
  }
300
+ function readSentinelView(agentRoot) {
301
+ return (0, context_loss_sentinel_1.readContextLossSentinelView)(agentRoot, { limit: 20 });
302
+ }
298
303
  function readNoteDecisionView(agentRoot, limit = 50) {
299
304
  const logPath = path.join(agentRoot, "state", "mailbox", "note-decisions.jsonl");
300
305
  const legacyLogPath = path.join(agentRoot, "state", "outlook", "note-decisions.jsonl");
@@ -153,7 +153,7 @@ function resolveCredential(poolResult, provider, agentName) {
153
153
  warnings: [missingCredentialWarning(provider)],
154
154
  };
155
155
  }
156
- function resolveReadiness(agentName, lane, provider, model, credential) {
156
+ function resolveReadiness(agentName, agentRoot, lane, provider, model, credential) {
157
157
  if (credential.status === "missing") {
158
158
  return { status: "unknown", reason: "credential-missing" };
159
159
  }
@@ -162,6 +162,7 @@ function resolveReadiness(agentName, lane, provider, model, credential) {
162
162
  }
163
163
  if (credential.status === "present") {
164
164
  const cached = (0, provider_readiness_cache_1.readProviderLaneReadiness)({
165
+ agentRoot,
165
166
  agentName,
166
167
  lane,
167
168
  provider,
@@ -239,7 +240,7 @@ function resolveEffectiveProviderBinding(input) {
239
240
  }
240
241
  const poolResult = (0, provider_credentials_1.readProviderCredentialPool)(input.agentName);
241
242
  const credentialResult = resolveCredential(poolResult, agentConfigResult.provider, input.agentName);
242
- const readiness = resolveReadiness(input.agentName, laneResolution.lane, agentConfigResult.provider, agentConfigResult.model, credentialResult.credential);
243
+ const readiness = resolveReadiness(input.agentName, input.agentRoot, laneResolution.lane, agentConfigResult.provider, agentConfigResult.model, credentialResult.credential);
243
244
  const warnings = [
244
245
  ...laneResolution.warnings,
245
246
  ...credentialResult.warnings,
@@ -1,15 +1,91 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.recordProviderLaneReadiness = recordProviderLaneReadiness;
4
37
  exports.readProviderLaneReadiness = readProviderLaneReadiness;
5
38
  exports.clearProviderReadinessCache = clearProviderReadinessCache;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
6
41
  const runtime_1 = require("../nerves/runtime");
7
42
  const readinessByLane = new Map();
8
43
  function cacheKey(agentName, lane) {
9
44
  return `${agentName}\0${lane}`;
10
45
  }
11
- function recordProviderLaneReadiness(entry) {
46
+ function readinessStorePath(agentRoot) {
47
+ return path.join(agentRoot, "state", "providers", "readiness.json");
48
+ }
49
+ function defaultReadinessStore() {
50
+ return { schemaVersion: 1, updatedAt: new Date(0).toISOString(), lanes: {} };
51
+ }
52
+ function readReadinessStore(agentRoot) {
53
+ try {
54
+ const parsed = JSON.parse(fs.readFileSync(readinessStorePath(agentRoot), "utf-8"));
55
+ if (parsed.schemaVersion === 1 && parsed.lanes && typeof parsed.lanes === "object")
56
+ return parsed;
57
+ }
58
+ catch {
59
+ return defaultReadinessStore();
60
+ }
61
+ return defaultReadinessStore();
62
+ }
63
+ function writeReadinessStore(agentRoot, entry) {
64
+ const storePath = readinessStorePath(agentRoot);
65
+ const store = readReadinessStore(agentRoot);
66
+ const updated = {
67
+ schemaVersion: 1,
68
+ updatedAt: entry.checkedAt,
69
+ lanes: { ...store.lanes, [entry.lane]: entry },
70
+ };
71
+ fs.mkdirSync(path.dirname(storePath), { recursive: true });
72
+ fs.writeFileSync(storePath, `${JSON.stringify(updated, null, 2)}\n`, "utf-8");
73
+ }
74
+ function matchesLookup(entry, input) {
75
+ return entry.agentName === input.agentName
76
+ && entry.lane === input.lane
77
+ && entry.provider === input.provider
78
+ && entry.model === input.model
79
+ && entry.credentialRevision === input.credentialRevision;
80
+ }
81
+ function isNewerReadiness(candidate, existing) {
82
+ return Date.parse(candidate.checkedAt) > Date.parse(existing.checkedAt);
83
+ }
84
+ function recordProviderLaneReadiness(input) {
85
+ const { agentRoot, ...entry } = input;
12
86
  readinessByLane.set(cacheKey(entry.agentName, entry.lane), { ...entry });
87
+ if (agentRoot)
88
+ writeReadinessStore(agentRoot, entry);
13
89
  (0, runtime_1.emitNervesEvent)({
14
90
  component: "config/identity",
15
91
  event: "config.provider_readiness_recorded",
@@ -25,15 +101,18 @@ function recordProviderLaneReadiness(entry) {
25
101
  }
26
102
  function readProviderLaneReadiness(input) {
27
103
  const entry = readinessByLane.get(cacheKey(input.agentName, input.lane));
28
- if (!entry)
29
- return null;
30
- if (entry.provider !== input.provider)
31
- return null;
32
- if (entry.model !== input.model)
33
- return null;
34
- if (entry.credentialRevision !== input.credentialRevision)
104
+ if (!input.agentRoot)
105
+ return entry && matchesLookup(entry, input) ? { ...entry } : null;
106
+ const durable = readReadinessStore(input.agentRoot).lanes[input.lane];
107
+ const memoryMatch = entry && matchesLookup(entry, input) ? entry : null;
108
+ const durableMatch = durable && matchesLookup(durable, input) ? durable : null;
109
+ const selected = memoryMatch && durableMatch
110
+ ? (isNewerReadiness(durableMatch, memoryMatch) ? durableMatch : memoryMatch)
111
+ : (durableMatch ?? memoryMatch);
112
+ if (!selected)
35
113
  return null;
36
- return { ...entry };
114
+ readinessByLane.set(cacheKey(selected.agentName, selected.lane), { ...selected });
115
+ return { ...selected };
37
116
  }
38
117
  function clearProviderReadinessCache() {
39
118
  readinessByLane.clear();