@ouro.bot/cli 0.1.0-alpha.934 → 0.1.0-alpha.936

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.
Files changed (40) hide show
  1. package/changelog.json +18 -0
  2. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
  3. package/deploy/unraid/sanctuary.xml +1 -1
  4. package/dist/heart/daemon/cli-defaults.js +5 -0
  5. package/dist/heart/daemon/cli-exec.js +9 -0
  6. package/dist/heart/daemon/cli-help.js +2 -2
  7. package/dist/heart/daemon/cli-parse.js +2 -1
  8. package/dist/heart/daemon/daemon.js +14 -0
  9. package/dist/heart/daemon/ouro-entry.js +4 -1
  10. package/dist/heart/daemon/runtime-logging.js +1 -1
  11. package/dist/heart/daemon/sense-manager.js +53 -0
  12. package/dist/heart/mailbox/mailbox-http-hooks.js +1 -0
  13. package/dist/heart/mailbox/mailbox-http-routes.js +8 -0
  14. package/dist/heart/mailbox/mailbox-read.js +3 -1
  15. package/dist/heart/mailbox/mailbox-supervision.js +131 -0
  16. package/dist/heart/shepherd/custody-types.js +2 -0
  17. package/dist/heart/shepherd/mandates.js +352 -0
  18. package/dist/heart/shepherd/operation-commands.js +19 -0
  19. package/dist/heart/shepherd/operations.js +135 -0
  20. package/dist/heart/shepherd/supervision.js +114 -0
  21. package/dist/heart/shepherd/types.js +2 -0
  22. package/dist/mailbox-ui/assets/index-DsiKaPyH.css +1 -0
  23. package/dist/mailbox-ui/assets/index-xU5krmwW.js +1 -0
  24. package/dist/mailbox-ui/index.html +2 -2
  25. package/dist/nerves/coverage/file-completeness.js +4 -0
  26. package/dist/repertoire/tools-shell.js +9 -4
  27. package/dist/senses/shepherd/checkin-store.js +209 -41
  28. package/dist/senses/shepherd/cmux.js +69 -23
  29. package/dist/senses/shepherd/guidance.js +5 -2
  30. package/dist/senses/shepherd/herdr.js +84 -2
  31. package/dist/senses/shepherd/observation-publisher.js +320 -0
  32. package/dist/senses/shepherd/operations-defaults.js +86 -0
  33. package/dist/senses/shepherd/operations.js +159 -0
  34. package/dist/senses/shepherd/recovery.js +543 -0
  35. package/dist/senses/shepherd/sense.js +8 -0
  36. package/dist/senses/teams.js +13 -1
  37. package/npm-shrinkwrap.json +2 -2
  38. package/package.json +1 -1
  39. package/dist/mailbox-ui/assets/index-Cyv4mw3Y.js +0 -1
  40. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +0 -1
package/changelog.json CHANGED
@@ -1,6 +1,24 @@
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.936",
6
+ "changes": [
7
+ "Add live Ouro Mailbox Supervision with aged terminal observations, explicit source failures, historical judgments and held-input visibility.",
8
+ "Add existing-owner local commands for mandate revisions, session pauses, exact custody inspection, evidence-derived retirement and recoverable offline archives.",
9
+ "Preserve original uncertain outcomes and replay barriers through reconciliation, migration, archive interruption and retry.",
10
+ "Trace unavailable supervision observations and verified maintenance quiescence without leaking source contents, and preserve CLI startup telemetry when command parsing fails.",
11
+ "Keep Herdr observation watchers stopped during callback-driven shutdown and prevent obsolete inventory refreshes from creating duplicate polling timers.",
12
+ "Explicitly label retained legacy automation and receipt-less sends as unqualified; stored mandates do not yet govern every trigger, and Shepherd v1 remains unqualified."
13
+ ]
14
+ },
15
+ {
16
+ "version": "0.1.0-alpha.935",
17
+ "changes": [
18
+ "Keep Teams requests running when advisory typing updates are rejected, with correlated warnings and unchanged single-attempt reply custody.",
19
+ "Run foreground shell commands without blocking receiver I/O, preserving timeouts, closed stdin and command error output."
20
+ ]
21
+ },
4
22
  {
5
23
  "version": "0.1.0-alpha.934",
6
24
  "changes": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.934",
2
+ "runtimeVersion": "0.1.0-alpha.936",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>ouro-butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.934</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.936</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -733,6 +733,11 @@ async function defaultRunSerpentGuide() {
733
733
  function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SOCKET_PATH) {
734
734
  return {
735
735
  socketPath,
736
+ runShepherdOperation: async (command) => {
737
+ const { runShepherdOperation } = await Promise.resolve().then(() => __importStar(require("../../senses/shepherd/operations")));
738
+ const { createDefaultShepherdOperationDeps } = await Promise.resolve().then(() => __importStar(require("../../senses/shepherd/operations-defaults")));
739
+ return runShepherdOperation(command, createDefaultShepherdOperationDeps(async (agent) => (0, socket_client_1.sendDaemonCommand)(socketPath, { kind: "daemon.shepherd_quiesce", agent }, { timeoutMs: socket_client_1.DEFAULT_DAEMON_COMMAND_TIMEOUT_MS })));
740
+ },
736
741
  sendCommand: (commandSocketPath, command) => (0, socket_client_1.sendDaemonCommand)(commandSocketPath, command, {
737
742
  timeoutMs: command.kind === "daemon.status"
738
743
  ? socket_client_1.DEFAULT_DAEMON_STATUS_TIMEOUT_MS
@@ -7897,6 +7897,15 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7897
7897
  await runMcpServeCliLifecycle(server, mcpInput);
7898
7898
  return "";
7899
7899
  }
7900
+ if (command.kind === "shepherd.status" || command.kind === "shepherd.inspect" || command.kind === "shepherd.pause"
7901
+ || command.kind === "shepherd.resume" || command.kind === "shepherd.mandate.grant" || command.kind === "shepherd.mandate.revoke"
7902
+ || command.kind === "shepherd.reconcile" || command.kind === "shepherd.maintenance") {
7903
+ if (!deps.runShepherdOperation)
7904
+ throw new Error("Shepherd local administration service is unavailable");
7905
+ const text = JSON.stringify(await deps.runShepherdOperation(command), null, 2);
7906
+ deps.writeStdout(text);
7907
+ return text;
7908
+ }
7900
7909
  if (command.kind === "shepherd.chat") {
7901
7910
  const daemonResult = deps.ensureDaemonForFrontend
7902
7911
  ? await deps.ensureDaemonForFrontend(command.agent)
@@ -120,10 +120,10 @@ exports.COMMAND_REGISTRY = {
120
120
  },
121
121
  shepherd: {
122
122
  category: "Agents",
123
- description: "Open an independent Ouro composer for one coding terminal. --web opens the compact graphical view in cmux; the managed cmux Shepherd sense also installs a native Talk to Ouro button. Without --web, /cancel cancels only Ouro's turn and /quit exits. Not an input hook in the coding agent.",
123
+ description: "Open an independent Ouro composer or administer Shepherd as the existing local owner. Administration is same-UID authority, not proof of human presence. status/inspect are read-only; mutations require --expected-revision and --request-id. pause/resume require --host and --session; resume never releases held custody. mandate grant requires --project, --task, --expires-at; revoke requires --id. reconcile --id --resolution retired derives host evidence; delivered is unsupported until native receipt lookup is qualified. maintenance --destination requires fresh disabled-and-stopped daemon proof and retired terminals. --web opens the compact graphical view in cmux. Not an input hook in the coding agent.",
124
124
  usage: "ouro shepherd chat --agent <name> [--session <UUID>] [--web]",
125
125
  example: "ouro shepherd chat --agent ouroboros",
126
- subcommands: ["chat"],
126
+ subcommands: ["chat", "status", "inspect", "pause", "resume", "mandate", "reconcile", "maintenance"],
127
127
  },
128
128
  config: {
129
129
  category: "Agents",
@@ -53,6 +53,7 @@ const path = __importStar(require("path"));
53
53
  const friends_1 = require("@ouro.bot/friends");
54
54
  const cli_help_1 = require("./cli-help");
55
55
  const surface_1 = require("../../senses/shepherd/surface");
56
+ const operations_1 = require("../shepherd/operations");
56
57
  const local_audio_control_1 = require("../../senses/voice/local-audio-control");
57
58
  const cli_desk_1 = require("./cli-desk");
58
59
  const flight_recorder_1 = require("../../arc/flight-recorder");
@@ -3124,7 +3125,7 @@ function parseOuroCommand(args) {
3124
3125
  if (head === "shepherd") {
3125
3126
  const usage = "Usage: ouro shepherd chat --agent <name> [--session <UUID>] [--web]";
3126
3127
  if (args[1] !== "chat")
3127
- throw new Error(usage);
3128
+ return (0, operations_1.parseShepherdOperation)(args.slice(1));
3128
3129
  const values = new Map();
3129
3130
  for (let i = 2; i < args.length; i += 1) {
3130
3131
  const flag = args[i];
@@ -1931,6 +1931,20 @@ class OuroDaemon {
1931
1931
  data,
1932
1932
  };
1933
1933
  }
1934
+ case "daemon.shepherd_stopped": {
1935
+ if (typeof command.agent !== "string" || !this.senseManager?.shepherdStoppedProof) {
1936
+ return { ok: false, error: "Shepherd lifecycle proof is unavailable" };
1937
+ }
1938
+ const data = this.senseManager.shepherdStoppedProof(command.agent);
1939
+ return { ok: true, data };
1940
+ }
1941
+ case "daemon.shepherd_quiesce": {
1942
+ if (typeof command.agent !== "string" || !this.senseManager?.quiesceShepherd) {
1943
+ return { ok: false, error: "Shepherd lifecycle control is unavailable" };
1944
+ }
1945
+ const data = await this.senseManager.quiesceShepherd(command.agent);
1946
+ return { ok: true, data };
1947
+ }
1934
1948
  case "daemon.health": {
1935
1949
  const checks = await this.healthMonitor.runChecks();
1936
1950
  const summary = checks.map((check) => `${check.name}:${check.status}:${check.message}`).join("\n");
@@ -5,7 +5,10 @@ const runtime_1 = require("../../nerves/runtime");
5
5
  const cli_arg_redaction_1 = require("./cli-arg-redaction");
6
6
  const daemon_cli_1 = require("./daemon-cli");
7
7
  const runtime_logging_1 = require("./runtime-logging");
8
- (0, runtime_logging_1.configureDaemonRuntimeLogger)("ouro");
8
+ if (process.argv[2] === "shepherd" && process.argv[3] !== "chat")
9
+ (0, runtime_logging_1.configureDaemonRuntimeLogger)("ouro", { machine: true });
10
+ else
11
+ (0, runtime_logging_1.configureDaemonRuntimeLogger)("ouro");
9
12
  (0, runtime_1.emitNervesEvent)({
10
13
  component: "daemon",
11
14
  event: "daemon.cli_entry_start",
@@ -125,7 +125,7 @@ function resolveLoggingPathTarget(explicit) {
125
125
  }
126
126
  }
127
127
  function configureDaemonRuntimeLogger(processName, options = {}) {
128
- const pathTarget = resolveLoggingPathTarget(options.agentName);
128
+ const pathTarget = options.machine ? { kind: "machine" } : resolveLoggingPathTarget(options.agentName);
129
129
  const configPath = options.configPath
130
130
  ?? (pathTarget.kind === "machine"
131
131
  ? machineDaemonLoggingConfigPath(options.homeDir)
@@ -593,6 +593,8 @@ class DaemonSenseManager {
593
593
  configRefreshRetryTimers = new Map();
594
594
  configRefreshRetryAttempts = new Map();
595
595
  bundlesRoot;
596
+ quiescedShepherdOwners = new Set();
597
+ initiallyUnmanagedShepherdOwners;
596
598
  constructor(options) {
597
599
  const bundlesRoot = options.bundlesRoot ?? path.join(os.homedir(), "AgentBundles");
598
600
  this.bundlesRoot = bundlesRoot;
@@ -601,6 +603,7 @@ class DaemonSenseManager {
601
603
  const facts = senseFactsFromRuntimeConfig(agent, senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent), workbenchHasStaleBundleEntry);
602
604
  return [agent, { senses, workbenchHasStaleBundleEntry, facts }];
603
605
  }));
606
+ this.initiallyUnmanagedShepherdOwners = new Set([...this.contexts.entries()].filter(([, context]) => !context.senses.shepherd.enabled).map(([agent]) => agent));
604
607
  const managedSenseAgents = [...this.contexts.entries()].flatMap(([agent, context]) => {
605
608
  return ["teams", "bluebubbles", "mail", "voice", "a2a", "telegram", "shepherd"]
606
609
  .filter((sense) => context.senses[sense].enabled)
@@ -620,6 +623,8 @@ class DaemonSenseManager {
620
623
  const parsed = parseSenseSnapshotName(name);
621
624
  if (!parsed)
622
625
  return { ok: true };
626
+ if (parsed.sense === "shepherd" && this.quiescedShepherdOwners.has(parsed.agent))
627
+ return { ok: false, skip: true, error: "Shepherd is quiesced for maintenance until daemon restart" };
623
628
  const context = this.contexts.get(parsed.agent);
624
629
  if (!context)
625
630
  return { ok: true };
@@ -711,6 +716,8 @@ class DaemonSenseManager {
711
716
  /* v8 ignore next -- defensive: config refreshes are only scheduled for known agent contexts @preserve */
712
717
  if (!context)
713
718
  return;
719
+ if (parsed.sense === "shepherd" && this.quiescedShepherdOwners.has(parsed.agent))
720
+ return;
714
721
  context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, refreshed, machineRefreshed, context.workbenchHasStaleBundleEntry);
715
722
  if (!context.facts[parsed.sense].configured) {
716
723
  if (this.shouldRetryConfigRefresh(parsed.agent, parsed.sense, refreshed, machineRefreshed)) {
@@ -723,6 +730,8 @@ class DaemonSenseManager {
723
730
  }
724
731
  this.clearConfigRetryState(name);
725
732
  setTimeout(() => {
733
+ if (parsed.sense === "shepherd" && this.quiescedShepherdOwners.has(parsed.agent))
734
+ return;
726
735
  void this.processManager.startAgent?.(name).catch((error) => {
727
736
  (0, runtime_1.emitNervesEvent)({
728
737
  level: "error",
@@ -814,6 +823,47 @@ class DaemonSenseManager {
814
823
  .filter((pid) => pid !== null && pid !== undefined);
815
824
  }
816
825
  /* v8 ignore stop */
826
+ assertShepherdDisabled(agent) {
827
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(agent) || !this.contexts.has(agent))
828
+ throw new Error("unknown Shepherd owner");
829
+ const config = JSON.parse(fs.readFileSync(path.join(this.bundlesRoot, `${agent}.ouro`, "agent.json"), "utf8"));
830
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
831
+ if (!object(config) || !object(config.senses) || !object(config.senses.shepherd) || config.senses.shepherd.enabled !== false) {
832
+ throw new Error("maintenance requires explicitly disabled Shepherd in fresh agent.json");
833
+ }
834
+ }
835
+ async quiesceShepherd(agent) {
836
+ this.assertShepherdDisabled(agent);
837
+ this.quiescedShepherdOwners.add(agent);
838
+ this.contexts.get(agent).senses.shepherd.enabled = false;
839
+ const managedName = `${agent}:shepherd`;
840
+ const snapshot = this.processManager.listAgentSnapshots().find(row => row.name === managedName);
841
+ if (snapshot && (snapshot.status !== "stopped" || snapshot.pid !== null)) {
842
+ if (!this.processManager.stopAgent)
843
+ throw new Error("managed Shepherd stop is unavailable");
844
+ const retry = this.configRefreshRetryTimers.get(managedName);
845
+ if (retry)
846
+ clearTimeout(retry);
847
+ this.configRefreshRetryTimers.delete(managedName);
848
+ this.configRefreshRetryAttempts.delete(managedName);
849
+ await this.processManager.stopAgent(managedName);
850
+ }
851
+ return this.shepherdStoppedProof(agent);
852
+ }
853
+ shepherdStoppedProof(agent) {
854
+ this.assertShepherdDisabled(agent);
855
+ const snapshots = this.processManager.listAgentSnapshots().filter(row => row.name === `${agent}:shepherd`);
856
+ // Disabled-at-startup senses are never registered with this process manager.
857
+ if (snapshots.length === 0 && this.initiallyUnmanagedShepherdOwners.has(agent)) {
858
+ return { kind: "daemon_lifecycle", checkedAt: new Date().toISOString(), enabled: false, processState: "stopped" };
859
+ }
860
+ if (snapshots.length !== 1)
861
+ throw new Error("actual managed Shepherd process snapshot is unavailable");
862
+ const snapshot = snapshots[0];
863
+ if (snapshot.status !== "stopped" || snapshot.pid !== null)
864
+ throw new Error("maintenance requires stopped Shepherd with no active PID");
865
+ return { kind: "daemon_lifecycle", checkedAt: new Date().toISOString(), enabled: false, processState: "stopped" };
866
+ }
817
867
  listHealthProbes() {
818
868
  const probes = [];
819
869
  for (const [agent, context] of this.contexts.entries()) {
@@ -854,6 +904,9 @@ class DaemonSenseManager {
854
904
  return probes;
855
905
  }
856
906
  async restartSense(managedName) {
907
+ const parsed = parseSenseSnapshotName(managedName);
908
+ if (parsed?.sense === "shepherd" && this.quiescedShepherdOwners.has(parsed.agent))
909
+ throw new Error("Shepherd is quiesced for maintenance until daemon restart");
857
910
  if (this.processManager.restartAgent) {
858
911
  await this.processManager.restartAgent(managedName);
859
912
  return;
@@ -66,6 +66,7 @@ function createMailboxHttpReadHooks(options) {
66
66
  const agentRoot = (agentName) => resolveAgentRoot(bundlesRoot, agentName);
67
67
  return {
68
68
  agentRoot,
69
+ readAgentSupervision: options.readAgentSupervision ?? ((agentName) => (0, mailbox_read_1.readMailboxSupervision)(agentName, { agentRoot: agentRoot(agentName) })),
69
70
  readAgentSessions: options.readAgentSessions ?? ((agentName) => (0, mailbox_read_1.readSessionInventory)(agentName, readOptions)),
70
71
  readAgentTranscript: options.readAgentTranscript ?? ((agentName, friendId, channel, key) => (0, mailbox_read_1.readSessionTranscript)(agentName, friendId, channel, key, readOptions)),
71
72
  readAgentCoding: options.readAgentCoding ?? ((agentName) => (0, mailbox_read_1.readCodingDeep)(agentRoot(agentName))),
@@ -173,6 +173,14 @@ function createMailboxHttpRequestHandler(options) {
173
173
  }
174
174
  async function handleAgentRoute(request, response, context) {
175
175
  const { agent, surface, options } = context;
176
+ if (surface === "supervision") {
177
+ if (request.method !== "GET") {
178
+ (0, mailbox_http_response_1.writeJson)(response, 405, { ok: false, error: "Supervision is read-only; use the local owner CLI for administration" });
179
+ return;
180
+ }
181
+ (0, mailbox_http_response_1.writeJson)(response, 200, options.hooks.readAgentSupervision(agent));
182
+ return;
183
+ }
176
184
  if (!surface) {
177
185
  const view = options.readAgentView?.(agent);
178
186
  if (view) {
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readSelfFixView = exports.readMailboxContinuity = exports.readOrientationView = exports.readObligationDetailView = exports.readNoteDecisionView = exports.readSentinelView = exports.readContextLossGauntletView = exports.readChangesView = exports.readNeedsMeView = exports.readNotesView = exports.readLogView = exports.readContextPacketView = exports.readContextPacketListView = exports.readHabitView = exports.readHabitRunView = exports.readHabitRunReceiptView = exports.readHabitSessionSummaryView = exports.readHabitSessionSummaryListView = 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.readContextPacketView = exports.readContextPacketListView = exports.readHabitView = exports.readHabitRunView = exports.readHabitRunReceiptView = exports.readHabitSessionSummaryView = exports.readHabitSessionSummaryListView = 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 = exports.readMailboxSupervision = void 0;
4
+ var mailbox_supervision_1 = require("./mailbox-supervision");
5
+ Object.defineProperty(exports, "readMailboxSupervision", { enumerable: true, get: function () { return mailbox_supervision_1.readMailboxSupervision; } });
4
6
  var agent_machine_1 = require("./readers/agent-machine");
5
7
  Object.defineProperty(exports, "readObligationSummary", { enumerable: true, get: function () { return agent_machine_1.readObligationSummary; } });
6
8
  Object.defineProperty(exports, "readMailboxAgentState", { enumerable: true, get: function () { return agent_machine_1.readMailboxAgentState; } });
@@ -0,0 +1,131 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readMailboxSupervision = readMailboxSupervision;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const runtime_1 = require("../../nerves/runtime");
40
+ const supervision_1 = require("../shepherd/supervision");
41
+ function owned(metadata) {
42
+ if (!process.getuid || metadata.uid !== process.getuid())
43
+ throw new Error("Observation requires current-user ownership");
44
+ }
45
+ function directory(candidate, privateMode) {
46
+ const metadata = fs.lstatSync(candidate, { throwIfNoEntry: false });
47
+ if (!metadata)
48
+ return false;
49
+ owned(metadata);
50
+ if (!metadata.isDirectory() || (privateMode ? (metadata.mode & 0o7777) !== 0o700 : (metadata.mode & 0o022) !== 0)) {
51
+ throw new Error("Observation requires unlinked private directories and non-writable ancestors");
52
+ }
53
+ return true;
54
+ }
55
+ function regular(metadata) {
56
+ owned(metadata);
57
+ if (!metadata.isFile() || (metadata.mode & 0o7777) !== 0o600 || metadata.nlink !== 1
58
+ || metadata.size > supervision_1.SHEPHERD_OBSERVATION_MAX_BYTES)
59
+ throw new Error("Observation requires a bounded private regular file");
60
+ }
61
+ function unavailable(agentName, nowMs, code) {
62
+ (0, runtime_1.emitNervesEvent)({
63
+ component: "heart", level: "warn", event: "heart.shepherd_observation_unavailable",
64
+ message: "Supervision observation is unavailable; no current terminal counts can be established", meta: { code },
65
+ });
66
+ const view = (0, supervision_1.buildShepherdSupervisionView)({
67
+ schemaVersion: 1, agentName, observedAt: new Date(nowMs).toISOString(),
68
+ sourceHealth: { host: "missing", guidance: "missing", mandates: "missing", returns: "missing", custody: "missing", notices: "missing" },
69
+ sessions: { totalCount: null, truncated: true, items: [] },
70
+ timeline: { totalCount: null, truncated: true, items: [] },
71
+ custody: { revision: null, bytes: null, capacity: "unknown", claims: { totalCount: null, truncated: true, items: [] } },
72
+ sourceErrors: [{ source: "replica", code, detail: code === "observation_missing"
73
+ ? "No Shepherd observation is available. Check that the Shepherd sense is running."
74
+ : "The Shepherd observation could not be read safely or has an invalid schema." }],
75
+ }, nowMs);
76
+ view.freshness = { status: "unknown", observedAt: null, ageMs: null };
77
+ return view;
78
+ }
79
+ /** A bounded read-only replica, never an authority file or a live terminal access path. */
80
+ function readMailboxSupervision(agentName, options) {
81
+ const nowMs = options.nowMs ?? Date.now();
82
+ try {
83
+ let current = path.resolve(options.agentRoot);
84
+ if (!directory(current, false))
85
+ return unavailable(agentName, nowMs, "observation_missing");
86
+ for (const name of ["state", "senses", "shepherd"]) {
87
+ current = path.join(current, name);
88
+ if (!directory(current, name === "shepherd"))
89
+ return unavailable(agentName, nowMs, "observation_missing");
90
+ }
91
+ const file = path.join(current, supervision_1.SHEPHERD_OBSERVATION_FILE);
92
+ const before = fs.lstatSync(file, { throwIfNoEntry: false });
93
+ if (!before)
94
+ return unavailable(agentName, nowMs, "observation_missing");
95
+ regular(before);
96
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
97
+ let raw;
98
+ try {
99
+ const opened = fs.fstatSync(fd);
100
+ regular(opened);
101
+ if (opened.ino !== before.ino || opened.dev !== before.dev)
102
+ throw new Error("Observation changed during read");
103
+ const buffer = Buffer.alloc(supervision_1.SHEPHERD_OBSERVATION_MAX_BYTES + 1);
104
+ let size = 0;
105
+ while (size < buffer.length) {
106
+ const received = fs.readSync(fd, buffer, size, buffer.length - size, null);
107
+ if (!received)
108
+ break;
109
+ size += received;
110
+ }
111
+ if (size > supervision_1.SHEPHERD_OBSERVATION_MAX_BYTES)
112
+ throw new Error("Observation exceeds read limit");
113
+ const after = fs.fstatSync(fd);
114
+ if (after.size !== opened.size || after.mtimeMs !== opened.mtimeMs || after.ctimeMs !== opened.ctimeMs) {
115
+ throw new Error("Observation changed during read");
116
+ }
117
+ raw = buffer.toString("utf8", 0, size);
118
+ }
119
+ finally {
120
+ fs.closeSync(fd);
121
+ }
122
+ const snapshot = JSON.parse(raw);
123
+ if (!(0, supervision_1.isShepherdSupervisionSnapshot)(snapshot) || snapshot.agentName !== agentName)
124
+ throw new Error("Invalid observation identity or schema");
125
+ return (0, supervision_1.buildShepherdSupervisionView)(snapshot, nowMs);
126
+ }
127
+ catch {
128
+ // Do not echo untrusted file contents, paths or operating-system errors into the browser.
129
+ return unavailable(agentName, nowMs, "observation_invalid");
130
+ }
131
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });