@ouro.bot/cli 0.1.0-alpha.741 → 0.1.0-alpha.743

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 (81) hide show
  1. package/changelog.json +42 -0
  2. package/deploy/unraid/Dockerfile +7 -2
  3. package/deploy/unraid/README.txt +717 -106
  4. package/deploy/unraid/migrate-sanctuary-bundle.mjs +21 -0
  5. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  6. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  7. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  8. package/deploy/unraid/ouro-events/install-usenet-guard.sh +434 -0
  9. package/deploy/unraid/ouro-events/usenet-health.sh +178 -0
  10. package/deploy/unraid/sanctuary-acceptance-contract.json +4 -3
  11. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +5 -1
  12. package/deploy/unraid/sanctuary.ouro/agent.json +2 -2
  13. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +2 -2
  14. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +1 -1
  15. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +3 -10
  16. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -1
  17. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -1
  18. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +4 -0
  19. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +6 -2
  20. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +4 -2
  21. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  22. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +19 -17
  23. package/deploy/unraid/sanctuary.xml +12 -9
  24. package/dist/arc/cares.js +171 -37
  25. package/dist/arc/obligations.js +38 -0
  26. package/dist/heart/awaiting/await-alert.js +4 -2
  27. package/dist/heart/awaiting/await-expiry.js +39 -4
  28. package/dist/heart/awaiting/await-parser.js +8 -0
  29. package/dist/heart/context-loss-gauntlet.js +31 -2
  30. package/dist/heart/context-loss-sentinel.js +2 -0
  31. package/dist/heart/core.js +37 -7
  32. package/dist/heart/cross-chat-delivery.js +1 -0
  33. package/dist/heart/daemon/container-healthcheck.js +3 -1
  34. package/dist/heart/daemon/container-runtime.js +13 -0
  35. package/dist/heart/daemon/container-spec-auditor-main.js +11 -7
  36. package/dist/heart/daemon/container-spec-auditor.js +38 -11
  37. package/dist/heart/daemon/daemon-entry.js +19 -5
  38. package/dist/heart/daemon/daemon.js +154 -22
  39. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +151 -87
  40. package/dist/heart/daemon/sanctuary-acceptance-harness.js +26 -29
  41. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +42 -51
  42. package/dist/heart/daemon/sanctuary-bundle-migration.js +398 -0
  43. package/dist/heart/daemon/sanctuary-scheduler-origin.js +11 -5
  44. package/dist/heart/daemon/supercronic-supervisor.js +16 -1
  45. package/dist/heart/external-events/router.js +1025 -43
  46. package/dist/heart/provider-models.js +10 -3
  47. package/dist/heart/session-events.js +23 -5
  48. package/dist/heart/steward-policy.js +302 -0
  49. package/dist/heart/tool-approval.js +3 -1
  50. package/dist/heart/turn-context.js +1 -0
  51. package/dist/mind/context.js +8 -3
  52. package/dist/mind/pending.js +25 -0
  53. package/dist/mind/prompt.js +49 -1
  54. package/dist/repertoire/guardrails.js +2 -1
  55. package/dist/repertoire/relationship-authorization.js +194 -0
  56. package/dist/repertoire/tools-awaiting.js +229 -26
  57. package/dist/repertoire/tools-base.js +15 -0
  58. package/dist/repertoire/tools-continuity.js +259 -4
  59. package/dist/repertoire/tools-notes.js +29 -3
  60. package/dist/repertoire/tools-session.js +219 -4
  61. package/dist/repertoire/tools-steward-policy.js +88 -0
  62. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  63. package/dist/repertoire/tools-unraid.js +113 -6
  64. package/dist/repertoire/tools.js +104 -9
  65. package/dist/repertoire/unraid-restart.js +86 -4
  66. package/dist/senses/private-runtime-worker.js +36 -3
  67. package/dist/senses/private-runtime.js +310 -63
  68. package/dist/senses/sanctuary-health-acceptance-probe.js +8 -6
  69. package/dist/senses/sanctuary-health-runner.js +51 -96
  70. package/dist/senses/sanctuary-health.js +54 -93
  71. package/dist/senses/sanctuary-runtime.js +36 -2
  72. package/dist/senses/sanctuary-sab.js +80 -0
  73. package/dist/senses/shared-turn.js +64 -9
  74. package/dist/senses/telegram-admission.js +684 -0
  75. package/dist/senses/telegram-approval-runtime.js +46 -6
  76. package/dist/senses/telegram-client.js +236 -72
  77. package/dist/senses/telegram-effect-adapter.js +787 -0
  78. package/dist/senses/telegram-entry.js +4 -1
  79. package/dist/senses/telegram.js +728 -48
  80. package/npm-shrinkwrap.json +6 -6
  81. package/package.json +2 -2
@@ -615,10 +615,14 @@ class OuroDaemon {
615
615
  mailboxServer = null;
616
616
  socketIdentity = null;
617
617
  senseAutostartTimer = null;
618
+ externalEventReconcileTimer = null;
619
+ externalEventReconcileRunning = false;
618
620
  mailboxServerFactory;
619
621
  privateRuntimePolicyDeps;
620
622
  onStopCommandComplete;
621
623
  externalEventRoot;
624
+ privilegedEventSpoolRoot;
625
+ privilegedEventScanner;
622
626
  rsvpHabitRunner;
623
627
  nativeHabitRunner;
624
628
  nativeHabitMatch;
@@ -640,6 +644,8 @@ class OuroDaemon {
640
644
  this.privateRuntimePolicyDeps = options.privateRuntimePolicyDeps ?? {};
641
645
  this.onStopCommandComplete = options.onStopCommandComplete ?? null;
642
646
  this.externalEventRoot = options.externalEventRoot ?? null;
647
+ this.privilegedEventSpoolRoot = options.privilegedEventSpoolRoot ?? "/run/ouro-events";
648
+ this.privilegedEventScanner = options.privilegedEventScanner ?? router_1.scanPrivilegedEventSpool;
643
649
  this.rsvpHabitRunner = options.rsvpHabitRunner;
644
650
  this.nativeHabitRunner = options.nativeHabitRunner;
645
651
  this.nativeHabitMatch = options.nativeHabitMatch;
@@ -694,6 +700,7 @@ class OuroDaemon {
694
700
  agentName,
695
701
  agentRoot: path.join(this.bundlesRoot, `${agentName}.ouro`),
696
702
  })));
703
+ const externalEvents = (0, router_1.listExternalEventStatus)(this.externalEventRoot ?? (0, router_1.getExternalEventRoot)());
697
704
  const mailboxUrl = this.mailboxServer?.origin ?? "http://127.0.0.1:0";
698
705
  return {
699
706
  overview: {
@@ -714,6 +721,7 @@ class OuroDaemon {
714
721
  sync,
715
722
  agents,
716
723
  ...(providers.length > 0 ? { providers } : {}),
724
+ ...(externalEvents.length > 0 ? { externalEvents } : {}),
717
725
  };
718
726
  }
719
727
  overlaySchedulerHealth(healthChecks) {
@@ -801,6 +809,11 @@ class OuroDaemon {
801
809
  // (daemon manages multiple agents; agent identity must be set before loading MCP config)
802
810
  await this.orphanStartupDrain(this.socketPath);
803
811
  await this.openCommandSocket();
812
+ await this.reconcileExternalEvents();
813
+ this.externalEventReconcileTimer = setInterval(() => {
814
+ void this.reconcileExternalEvents();
815
+ }, 1_000);
816
+ this.externalEventReconcileTimer.unref?.();
804
817
  this.triggerAutoStartAgents();
805
818
  this.triggerAutoStartSensesWhenAgentsSettled();
806
819
  // Write all managed PIDs to disk so the next daemon can clean up
@@ -1105,6 +1118,10 @@ class OuroDaemon {
1105
1118
  clearTimeout(this.senseAutostartTimer);
1106
1119
  this.senseAutostartTimer = null;
1107
1120
  }
1121
+ if (this.externalEventReconcileTimer) {
1122
+ clearInterval(this.externalEventReconcileTimer);
1123
+ this.externalEventReconcileTimer = null;
1124
+ }
1108
1125
  const workerStopTasks = [Promise.resolve().then(() => this.processManager.stopAll())];
1109
1126
  if (this.senseManager) {
1110
1127
  workerStopTasks.push(Promise.resolve().then(() => this.senseManager.stopAll()));
@@ -1279,14 +1296,14 @@ class OuroDaemon {
1279
1296
  ],
1280
1297
  };
1281
1298
  }
1282
- buildExternalEventPrivateWakeCommand(command, receiptId) {
1299
+ buildExternalEventPrivateWakeCommand(command, receiptId, generation, attemptCount) {
1283
1300
  return {
1284
1301
  kind: "private.wake",
1285
1302
  agent: command.agent,
1286
1303
  reason: `external event ${command.source}/${command.eventType}`,
1287
1304
  triggerSource: "external-event",
1288
1305
  budgetClass: "interactive",
1289
- idempotencyKey: `external-event:${command.agent}:${command.source}:${command.eventId}`,
1306
+ idempotencyKey: `external-event:${command.agent}:${command.source}:${command.eventId}:generation:${generation}:attempt:${attemptCount}`,
1290
1307
  originRefs: [
1291
1308
  { kind: "external-event", id: command.eventId, source: command.source, eventType: command.eventType },
1292
1309
  { kind: "queue-receipt", id: receiptId },
@@ -1456,7 +1473,7 @@ class OuroDaemon {
1456
1473
  data: result,
1457
1474
  };
1458
1475
  }
1459
- buildPrivateRuntimeWorkerWakeMessage(command, decision) {
1476
+ buildPrivateRuntimeWorkerWakeMessage(command, decision, externalEvent) {
1460
1477
  const awaitName = (0, await_private_wake_1.awaitNameFromPrivateWakeCommand)(command);
1461
1478
  if (awaitName) {
1462
1479
  return {
@@ -1483,13 +1500,13 @@ class OuroDaemon {
1483
1500
  privateTurnDecision: decision,
1484
1501
  };
1485
1502
  }
1486
- return { type: "message", privateTurnDecision: decision };
1503
+ return { type: "message", privateTurnDecision: decision, ...(externalEvent ? { externalEvent } : {}) };
1487
1504
  }
1488
1505
  queueExternalEventForPrivateRuntime(record) {
1489
1506
  const pendingDir = path.join(this.bundlesRoot, `${record.agent}.ouro`, "state", "pending", pending_1.PRIVATE_RUNTIME_PENDING.friendId, pending_1.PRIVATE_RUNTIME_PENDING.channel, pending_1.PRIVATE_RUNTIME_PENDING.key);
1490
1507
  const originKey = `${record.source}:${record.eventId}`;
1491
1508
  const parsedReceivedAt = Date.parse(record.receivedAt);
1492
- (0, pending_1.queuePendingMessage)(pendingDir, {
1509
+ (0, pending_1.queuePendingMessageOnce)(pendingDir, {
1493
1510
  from: "ouro-external-event",
1494
1511
  friendId: "ouro-external-event",
1495
1512
  channel: "external-event",
@@ -1503,6 +1520,7 @@ class OuroDaemon {
1503
1520
  },
1504
1521
  obligationStatus: "pending",
1505
1522
  mode: "relay",
1523
+ packetId: `external-event:${record.agent}:${record.source}:${record.eventId}:generation:${record.generation}:attempt:${record.attemptCount}`,
1506
1524
  });
1507
1525
  (0, runtime_1.emitNervesEvent)({
1508
1526
  component: "daemon",
@@ -1517,7 +1535,124 @@ class OuroDaemon {
1517
1535
  },
1518
1536
  });
1519
1537
  }
1520
- async handlePrivateRuntimeWake(command, beforeDispatch) {
1538
+ externalEventRootPath() {
1539
+ return this.externalEventRoot ?? (0, router_1.getExternalEventRoot)();
1540
+ }
1541
+ externalEventLease(record) {
1542
+ /* v8 ignore next -- dispatcher calls this only with the record returned by claimExternalEvent @preserve */
1543
+ if (!record.claimOwner)
1544
+ throw new Error("External event dispatch requires a claimed receipt");
1545
+ return {
1546
+ schemaVersion: 1,
1547
+ recordPath: record.recordPath,
1548
+ agent: record.agent,
1549
+ source: record.source,
1550
+ eventId: record.eventId,
1551
+ generation: record.generation,
1552
+ observationRevision: record.observationRevision,
1553
+ claimOwner: record.claimOwner,
1554
+ };
1555
+ }
1556
+ async dispatchExternalEvents(records) {
1557
+ const claimed = [];
1558
+ try {
1559
+ for (const record of records) {
1560
+ const owner = `external-event:${record.agent}:${record.source}:${record.eventId}:generation:${record.generation}:attempt:${record.attemptCount + 1}`;
1561
+ claimed.push((0, router_1.claimExternalEvent)(record.recordPath, { owner, expectedVersion: record.version, expectedGeneration: record.generation }));
1562
+ }
1563
+ const primary = claimed[0];
1564
+ const [primaryLease, ...relatedEvents] = claimed.map((record) => this.externalEventLease(record));
1565
+ const lease = { ...primaryLease, ...(relatedEvents.length > 0 ? { relatedEvents } : {}) };
1566
+ const receipt = {
1567
+ id: `external-event:${primary.agent}:${primary.source}:${primary.eventId}:generation:${primary.generation}:attempt:${primary.attemptCount}`,
1568
+ queuedAt: primary.updatedAt,
1569
+ };
1570
+ const wake = await this.handlePrivateRuntimeWake(this.buildExternalEventPrivateWakeCommand({
1571
+ kind: "external.event.submit",
1572
+ agent: primary.agent,
1573
+ source: primary.source,
1574
+ eventType: primary.eventType,
1575
+ eventId: primary.eventId,
1576
+ }, receipt.id, primary.generation, primary.attemptCount), () => { for (const record of claimed)
1577
+ this.queueExternalEventForPrivateRuntime(record); }, lease);
1578
+ const denied = !wake.ok || wake.data?.decision?.executable === false;
1579
+ if (denied)
1580
+ throw new Error(wake.error ?? wake.message ?? /* v8 ignore next -- daemon wake responses always carry an error or message @preserve */ "external-event private turn was denied");
1581
+ return { events: claimed, event: primary, receipt, wake };
1582
+ }
1583
+ catch (error) {
1584
+ for (const record of claimed) {
1585
+ const latest = (0, router_1.readExternalEventRecord)(record.recordPath);
1586
+ if (latest.executionState === "running" && latest.claimOwner === record.claimOwner) {
1587
+ (0, router_1.failExternalEventAttempt)(latest.recordPath, {
1588
+ owner: record.claimOwner, expectedVersion: latest.version, expectedGeneration: latest.generation,
1589
+ error: error instanceof Error ? error.message : String(error),
1590
+ });
1591
+ }
1592
+ }
1593
+ throw error;
1594
+ }
1595
+ }
1596
+ dispatchExternalEvent(record) {
1597
+ return this.dispatchExternalEvents([record]);
1598
+ }
1599
+ async reconcileExternalEvents() {
1600
+ /* v8 ignore next -- re-entrancy guard is exercised only by overlapping real timer callbacks @preserve */
1601
+ if (this.externalEventReconcileRunning)
1602
+ return;
1603
+ this.externalEventReconcileRunning = true;
1604
+ try {
1605
+ const now = new Date().toISOString();
1606
+ if (fs.existsSync(this.privilegedEventSpoolRoot)) {
1607
+ this.privilegedEventScanner({ spoolRoot: this.privilegedEventSpoolRoot, eventRoot: this.externalEventRootPath() });
1608
+ }
1609
+ const statuses = (0, router_1.listExternalEventStatus)(this.externalEventRootPath());
1610
+ const dueRecords = [];
1611
+ for (const status of statuses) {
1612
+ if (status.corrupt)
1613
+ continue;
1614
+ let record = (0, router_1.readExternalEventRecord)(status.recordPath);
1615
+ if (record.dispatchEnabled === false)
1616
+ continue;
1617
+ if (record.executionState === "running" && record.claimExpiresAt && Date.parse(record.claimExpiresAt) <= Date.parse(now)) {
1618
+ record = (0, router_1.reconcileExternalEvent)(record.recordPath);
1619
+ }
1620
+ const due = record.executionState === "received"
1621
+ || record.executionState === "queued"
1622
+ || (record.executionState === "retry_wait" && record.nextAttemptAt !== null && Date.parse(record.nextAttemptAt) <= Date.parse(now));
1623
+ if (due)
1624
+ dueRecords.push(record);
1625
+ }
1626
+ const batches = new Map();
1627
+ for (const record of dueRecords) {
1628
+ const key = `${record.agent}\0${record.source}`;
1629
+ const batch = batches.get(key) ?? [];
1630
+ batch.push(record);
1631
+ batches.set(key, batch);
1632
+ }
1633
+ for (const records of batches.values()) {
1634
+ for (let offset = 0; offset < records.length; offset += 32) {
1635
+ const batch = records.slice(offset, offset + 32);
1636
+ try {
1637
+ await this.dispatchExternalEvents(batch);
1638
+ }
1639
+ catch (error) {
1640
+ (0, runtime_1.emitNervesEvent)({
1641
+ level: "warn",
1642
+ component: "daemon",
1643
+ event: "daemon.external_event_dispatch_error",
1644
+ message: "external event dispatch failed",
1645
+ meta: { recordPath: batch[0].recordPath, batchSize: batch.length, error: error instanceof Error ? error.message : /* v8 ignore next -- dispatch failures are Error objects @preserve */ String(error) },
1646
+ });
1647
+ }
1648
+ }
1649
+ }
1650
+ }
1651
+ finally {
1652
+ this.externalEventReconcileRunning = false;
1653
+ }
1654
+ }
1655
+ async handlePrivateRuntimeWake(command, beforeDispatch, externalEvent) {
1521
1656
  if (!this.hasManagedPrivateRuntime(command.agent)) {
1522
1657
  return {
1523
1658
  ok: false,
@@ -1537,7 +1672,7 @@ class OuroDaemon {
1537
1672
  }
1538
1673
  await beforeDispatch?.(decision);
1539
1674
  await this.processManager.startAgent(command.agent);
1540
- this.processManager.sendToAgent?.(command.agent, this.buildPrivateRuntimeWorkerWakeMessage(command, decision));
1675
+ this.processManager.sendToAgent?.(command.agent, this.buildPrivateRuntimeWorkerWakeMessage(command, decision, externalEvent));
1541
1676
  return {
1542
1677
  ok: true,
1543
1678
  message: `woke private runtime for ${command.agent}`,
@@ -1766,27 +1901,24 @@ class OuroDaemon {
1766
1901
  evidence: command.evidence,
1767
1902
  payloadPath: command.payloadPath,
1768
1903
  priority: command.priority,
1904
+ observationRevision: command.observationRevision,
1905
+ transition: command.transition,
1769
1906
  }, {
1770
1907
  ...(this.externalEventRoot ? { root: this.externalEventRoot } : {}),
1908
+ dispatchEnabled: command.wake !== false,
1771
1909
  });
1772
- const receipt = await this.router.send({
1773
- from: "ouro-external-event",
1774
- to: command.agent,
1775
- content: (0, router_1.buildExternalEventMessage)(record),
1776
- priority: record.priority,
1777
- sessionId: command.sessionId,
1778
- taskRef: command.taskRef ?? `${record.source}:${record.eventId}`,
1779
- });
1780
- let wake = null;
1781
- if (command.wake !== false) {
1782
- wake = await this.handlePrivateRuntimeWake(this.buildExternalEventPrivateWakeCommand(command, receipt.id), () => this.queueExternalEventForPrivateRuntime(record));
1910
+ if (!record.shouldWake || command.wake === false) {
1911
+ return {
1912
+ ok: true,
1913
+ message: `updated external event ${record.source}/${record.eventId} without wake`,
1914
+ data: { event: record, receipt: null, wake: null },
1915
+ };
1783
1916
  }
1917
+ const dispatched = await this.dispatchExternalEvent(record);
1784
1918
  return {
1785
1919
  ok: true,
1786
- message: command.wake === false
1787
- ? `queued external event ${record.source}/${record.eventId} as ${receipt.id}`
1788
- : `queued external event ${record.source}/${record.eventId} as ${receipt.id}; ${wake?.message ?? "wake skipped"}`,
1789
- data: { event: record, receipt, wake },
1920
+ message: `queued external event ${record.source}/${record.eventId} as ${dispatched.receipt.id}; ${dispatched.wake.message ?? "wake skipped"}`,
1921
+ data: dispatched,
1790
1922
  };
1791
1923
  }
1792
1924
  case "private.decisions": {