@ouro.bot/cli 0.1.0-alpha.733 → 0.1.0-alpha.735

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 (89) hide show
  1. package/changelog.json +34 -0
  2. package/deploy/unraid/Dockerfile +27 -0
  3. package/deploy/unraid/README.txt +1605 -0
  4. package/deploy/unraid/audit-container-spec.sh +4 -0
  5. package/deploy/unraid/container-runtime.json +1 -0
  6. package/deploy/unraid/sanctuary-acceptance-adapter.sh +37 -0
  7. package/deploy/unraid/sanctuary-acceptance-contract.json +200 -0
  8. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  9. package/deploy/unraid/sanctuary-deployment-target.mjs +705 -0
  10. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  11. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +1777 -0
  12. package/deploy/unraid/sanctuary-unit16-run.sh +502 -0
  13. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  14. package/deploy/unraid/sanctuary.ouro/agent.json +24 -0
  15. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  16. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  17. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  18. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +18 -0
  19. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -0
  20. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -0
  21. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +3 -0
  22. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +5 -0
  23. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +7 -0
  24. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +21 -0
  25. package/deploy/unraid/sanctuary.xml +25 -0
  26. package/dist/heart/approval-files.js +113 -0
  27. package/dist/heart/approval-store.js +168 -9
  28. package/dist/heart/auth/auth-flow.js +17 -1
  29. package/dist/heart/autonomy-budget.js +44 -1
  30. package/dist/heart/awaiting/await-scheduler.js +6 -2
  31. package/dist/heart/config.js +8 -0
  32. package/dist/heart/core.js +117 -132
  33. package/dist/heart/daemon/cli-exec.js +73 -1
  34. package/dist/heart/daemon/cli-help.js +5 -0
  35. package/dist/heart/daemon/cli-parse.js +6 -4
  36. package/dist/heart/daemon/container-credential-bootstrap.js +208 -0
  37. package/dist/heart/daemon/container-healthcheck.js +94 -0
  38. package/dist/heart/daemon/container-runtime.js +84 -0
  39. package/dist/heart/daemon/container-spec-auditor-main.js +156 -0
  40. package/dist/heart/daemon/container-spec-auditor.js +156 -0
  41. package/dist/heart/daemon/daemon-bootstrap-startup.js +41 -0
  42. package/dist/heart/daemon/daemon-entry.js +116 -28
  43. package/dist/heart/daemon/daemon-health.js +10 -0
  44. package/dist/heart/daemon/daemon.js +92 -5
  45. package/dist/heart/daemon/runtime-logging.js +6 -0
  46. package/dist/heart/daemon/runtime-mode.js +13 -2
  47. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2365 -0
  48. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1624 -0
  49. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  50. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +985 -0
  51. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  52. package/dist/heart/daemon/sanctuary-scheduler-origin.js +126 -0
  53. package/dist/heart/daemon/sense-manager.js +25 -6
  54. package/dist/heart/daemon/supercronic-supervisor.js +267 -0
  55. package/dist/heart/habits/habit-scheduler.js +7 -3
  56. package/dist/heart/identity.js +13 -3
  57. package/dist/heart/provider-credentials.js +9 -1
  58. package/dist/heart/provider-models.js +8 -0
  59. package/dist/heart/provider-ping.js +5 -2
  60. package/dist/heart/providers/openai-compatible.js +177 -0
  61. package/dist/heart/runtime-credentials.js +217 -8
  62. package/dist/heart/sense-truth.js +2 -0
  63. package/dist/heart/tool-approval.js +1 -0
  64. package/dist/heart/turn-context.js +8 -0
  65. package/dist/mind/prompt.js +9 -0
  66. package/dist/nerves/coverage/source-scanner.js +4 -4
  67. package/dist/nerves/index.js +75 -9
  68. package/dist/nerves/runtime.js +12 -0
  69. package/dist/repertoire/tool-arguments.js +39 -0
  70. package/dist/repertoire/tools-unraid.js +353 -0
  71. package/dist/repertoire/tools.js +19 -1
  72. package/dist/repertoire/unraid-client.js +155 -0
  73. package/dist/repertoire/unraid-restart.js +130 -0
  74. package/dist/senses/private-runtime-worker.js +3 -2
  75. package/dist/senses/sanctuary-grounding.js +118 -0
  76. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  77. package/dist/senses/sanctuary-health-acceptance-probe.js +772 -0
  78. package/dist/senses/sanctuary-health-runner.js +146 -0
  79. package/dist/senses/sanctuary-health.js +447 -0
  80. package/dist/senses/sanctuary-interactive-control.js +306 -0
  81. package/dist/senses/sanctuary-runtime.js +206 -0
  82. package/dist/senses/shared-turn.js +11 -3
  83. package/dist/senses/telegram-approval-runtime.js +526 -0
  84. package/dist/senses/telegram-audit-ledger.js +220 -0
  85. package/dist/senses/telegram-client.js +1471 -0
  86. package/dist/senses/telegram-entry.js +80 -0
  87. package/dist/senses/telegram.js +1232 -0
  88. package/npm-shrinkwrap.json +6558 -0
  89. package/package.json +4 -2
@@ -0,0 +1,227 @@
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.readSanctuaryHealthCursor = readSanctuaryHealthCursor;
37
+ exports.durableExclusiveJson = durableExclusiveJson;
38
+ exports.consumeSanctuarySchedulerFire = consumeSanctuarySchedulerFire;
39
+ exports.publishSanctuarySchedulerReceipt = publishSanctuarySchedulerReceipt;
40
+ exports.sanctuarySchedulerLivenessReceiptMac = sanctuarySchedulerLivenessReceiptMac;
41
+ exports.verifySanctuarySchedulerLivenessReceiptMac = verifySanctuarySchedulerLivenessReceiptMac;
42
+ exports.recordSanctuarySchedulerLivenessReceipt = recordSanctuarySchedulerLivenessReceipt;
43
+ const node_crypto_1 = require("node:crypto");
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const runtime_1 = require("../../nerves/runtime");
47
+ const SHA256 = /^[0-9a-f]{64}$/u;
48
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
49
+ const CRONTAB_PATH = "/home/ouro/.ouro-cli/scheduler/sanctuary.crontab";
50
+ const COMMAND = "/usr/local/bin/node /opt/ouro/dist/heart/daemon/ouro-entry.js poke sanctuary --habit sanctuary-health --trigger cron";
51
+ function readSanctuaryHealthCursor(agentRoot) {
52
+ const state = JSON.parse(fs.readFileSync(path.join(agentRoot, "state", "health", "sanctuary-health.json"), "utf8"));
53
+ if (!Array.isArray(state.sweepReceipts) || !Array.isArray(state.deliveredReceipts))
54
+ throw new Error("Sanctuary health cursor state is invalid");
55
+ return { sweepCount: state.sweepReceipts.length, deliveryCount: state.deliveredReceipts.length };
56
+ }
57
+ function fsyncDirectory(directory, deps) {
58
+ const descriptor = deps.openSync(directory, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
59
+ try {
60
+ deps.fsyncSync(descriptor);
61
+ }
62
+ finally {
63
+ deps.closeSync(descriptor);
64
+ }
65
+ }
66
+ function ensureDurableDirectory(directory, deps) {
67
+ try {
68
+ deps.mkdirSync(directory, { mode: 0o700 });
69
+ }
70
+ catch (error) {
71
+ const code = error.code;
72
+ if (code === "EEXIST") {
73
+ fsyncDirectory(directory, deps);
74
+ fsyncDirectory(path.dirname(directory), deps);
75
+ return;
76
+ }
77
+ if (code !== "ENOENT")
78
+ throw error;
79
+ const parent = path.dirname(directory);
80
+ if (parent === directory)
81
+ throw error;
82
+ ensureDurableDirectory(parent, deps);
83
+ try {
84
+ deps.mkdirSync(directory, { mode: 0o700 });
85
+ }
86
+ catch (retryError) {
87
+ if (retryError.code !== "EEXIST")
88
+ throw retryError;
89
+ }
90
+ }
91
+ fsyncDirectory(directory, deps);
92
+ fsyncDirectory(path.dirname(directory), deps);
93
+ }
94
+ function durableExclusiveJson(filePath, value, deps = fs) {
95
+ const directory = path.dirname(filePath);
96
+ ensureDurableDirectory(directory, deps);
97
+ const temporary = `${filePath}.tmp-${process.pid}-${(0, node_crypto_1.randomUUID)()}`;
98
+ let handle = null;
99
+ try {
100
+ handle = deps.openSync(temporary, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW, 0o600);
101
+ deps.writeFileSync(handle, `${JSON.stringify(value)}\n`, "utf8");
102
+ deps.fsyncSync(handle);
103
+ deps.closeSync(handle);
104
+ handle = null;
105
+ deps.linkSync(temporary, filePath);
106
+ fsyncDirectory(directory, deps);
107
+ }
108
+ finally {
109
+ if (handle !== null)
110
+ deps.closeSync(handle);
111
+ try {
112
+ deps.unlinkSync(temporary);
113
+ }
114
+ catch (error) {
115
+ if (error.code !== "ENOENT")
116
+ throw error;
117
+ }
118
+ finally {
119
+ fsyncDirectory(directory, deps);
120
+ }
121
+ }
122
+ }
123
+ function consumeSanctuarySchedulerFire(agentRoot, origin) {
124
+ if (!UUID.test(origin.schedulerRunId) || origin.occurrenceId !== `cron:${origin.slot}`)
125
+ throw new Error("Sanctuary scheduler fire claim is invalid");
126
+ const claimId = (0, node_crypto_1.createHash)("sha256").update(`sanctuary\0sanctuary-health\0${origin.slot}\0${origin.occurrenceId}`).digest("hex");
127
+ const claimPath = path.join(agentRoot, "state", "scheduler", "sanctuary-fire-claims", `${claimId}.json`);
128
+ try {
129
+ durableExclusiveJson(claimPath, { schemaVersion: "sanctuary-scheduler-fire-claim-v1", ...origin });
130
+ }
131
+ catch (error) {
132
+ if (error.code === "EEXIST") {
133
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "daemon", event: "daemon.sanctuary_scheduler_fire_rejected", message: "Rejected replayed Sanctuary scheduler fire", meta: { occurrenceId: origin.occurrenceId, schedulerRunId: origin.schedulerRunId } });
134
+ throw new Error("Sanctuary scheduler fire replay rejected");
135
+ }
136
+ throw error;
137
+ }
138
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_scheduler_fire_consumed", message: "Consumed Sanctuary scheduler fire before execution", meta: { occurrenceId: origin.occurrenceId, schedulerRunId: origin.schedulerRunId } });
139
+ }
140
+ function publishSanctuarySchedulerReceipt(filePath, value, deps = fs) {
141
+ try {
142
+ durableExclusiveJson(filePath, value, deps);
143
+ }
144
+ catch (error) {
145
+ if (error.code === "EEXIST")
146
+ throw new Error("Sanctuary scheduler liveness receipt already exists");
147
+ throw error;
148
+ }
149
+ }
150
+ function unsignedReceipt(value) {
151
+ return Object.fromEntries(Object.entries(value).filter(([key]) => key !== "receiptMac"));
152
+ }
153
+ function sanctuarySchedulerLivenessReceiptMac(identityKey, value) {
154
+ return (0, node_crypto_1.createHmac)("sha256", identityKey).update(`sanctuary-scheduler-liveness-receipt-v2\0${JSON.stringify(unsignedReceipt(value))}`).digest("hex");
155
+ }
156
+ function verifySanctuarySchedulerLivenessReceiptMac(identityKey, value) {
157
+ const observed = value.receiptMac;
158
+ if (typeof observed !== "string" || !SHA256.test(observed))
159
+ return false;
160
+ return (0, node_crypto_1.timingSafeEqual)(Buffer.from(observed, "hex"), Buffer.from(sanctuarySchedulerLivenessReceiptMac(identityKey, value), "hex"));
161
+ }
162
+ function validateSupervisor(snapshot) {
163
+ const manifest = snapshot.manifest;
164
+ if (snapshot.schemaVersion !== "supercronic-supervisor-snapshot-v1" || snapshot.daemonPid !== process.pid
165
+ || snapshot.childCount !== 1 || snapshot.healthy !== true || !Number.isSafeInteger(snapshot.childPid) || snapshot.childPid <= 1
166
+ || snapshot.binaryPath !== "/usr/local/bin/supercronic" || snapshot.crontabPath !== CRONTAB_PATH
167
+ || JSON.stringify(snapshot.args) !== JSON.stringify(["-split-logs", "-inotify", CRONTAB_PATH])
168
+ || snapshot.namespace !== "habit:sanctuary" || manifest.length !== 1
169
+ || manifest[0]?.id !== "sanctuary:sanctuary-health" || manifest[0].agent !== "sanctuary"
170
+ || manifest[0].taskId !== "sanctuary-health" || manifest[0].schedule !== "*/15 * * * *"
171
+ || manifest[0].taskPath !== "/home/ouro/AgentBundles/sanctuary.ouro/habits/sanctuary-health.md"
172
+ || manifest[0].command !== COMMAND || !snapshot.renderedCrontab.includes(`# ouro:habit:sanctuary:sanctuary:sanctuary-health\n*/15 * * * * ${COMMAND}\n`)) {
173
+ throw new Error("Sanctuary scheduler supervisor attestation is invalid");
174
+ }
175
+ }
176
+ function recordSanctuarySchedulerLivenessReceipt(input) {
177
+ if (input.trigger !== "cron")
178
+ throw new Error("Sanctuary scheduler liveness requires cron provenance");
179
+ if (input.scenario.label !== "unit-16f-cron-fingerprint" || !SHA256.test(input.scenario.scenarioHandleDigest))
180
+ throw new Error("Sanctuary scheduler liveness scenario is invalid");
181
+ if (input.occurrenceId.length === 0 || !UUID.test(input.runnerId))
182
+ throw new Error("Sanctuary scheduler liveness runner provenance is invalid");
183
+ if (!/^[A-Za-z0-9_-]{43}$/u.test(input.identityKey) || input.schedulerOrigin.parentPid !== input.supervisor.childPid
184
+ || input.schedulerOrigin.scenarioHandleDigest !== input.scenario.scenarioHandleDigest || input.occurrenceId !== input.schedulerOrigin.occurrenceId
185
+ || input.occurrenceId !== `cron:${input.schedulerOrigin.slot}`
186
+ || !UUID.test(input.schedulerOrigin.schedulerRunId)
187
+ || input.schedulerOrigin.slot.length === 0 || !SHA256.test(input.schedulerOrigin.proofMac))
188
+ throw new Error("Sanctuary scheduler liveness authenticated origin is invalid");
189
+ if (input.providerInvocationCount !== 0 || input.privateTurnCount !== 0)
190
+ throw new Error("Sanctuary scheduler liveness performed paid work");
191
+ validateSupervisor(input.supervisor);
192
+ const statePath = path.join(input.agentRoot, "state", "health", "sanctuary-health.json");
193
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
194
+ if (!Array.isArray(state.sweepReceipts) || !Array.isArray(state.deliveredReceipts))
195
+ throw new Error("Sanctuary scheduler liveness state is invalid");
196
+ const after = { sweepCount: state.sweepReceipts.length, deliveryCount: state.deliveredReceipts.length };
197
+ const matching = state.sweepReceipts.slice(input.before.sweepCount).filter((row) => row.scenarioHandleDigest === input.scenario.scenarioHandleDigest);
198
+ const sweep = matching[0];
199
+ if (after.sweepCount - input.before.sweepCount !== 1 || matching.length !== 1 || !sweep
200
+ || after.deliveryCount - input.before.deliveryCount !== 0 || sweep.opened !== 0 || sweep.recovered !== 0 || sweep.digestDue !== false || sweep.deliveryId !== undefined) {
201
+ throw new Error("Sanctuary scheduler liveness requires exactly one unchanged sweep");
202
+ }
203
+ const unsigned = {
204
+ schemaVersion: "sanctuary-scheduler-liveness-receipt-v1",
205
+ label: "unit-16f-cron-fingerprint",
206
+ scenarioHandleDigest: input.scenario.scenarioHandleDigest,
207
+ trigger: "cron",
208
+ occurrenceId: input.occurrenceId,
209
+ runnerId: input.runnerId,
210
+ recordedAt: (input.now ?? (() => new Date()))().toISOString(),
211
+ before: input.before,
212
+ after,
213
+ sweepDelta: 1,
214
+ deliveryDelta: 0,
215
+ providerInvocationCount: 0,
216
+ privateTurnCount: 0,
217
+ sweep: { recordDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(sweep)).digest("hex"), opened: 0, recovered: 0, digestDue: false, deliveryId: null },
218
+ supervisor: input.supervisor,
219
+ schedulerOrigin: input.schedulerOrigin,
220
+ nonReplay: true,
221
+ };
222
+ const receipt = { ...unsigned, receiptMac: sanctuarySchedulerLivenessReceiptMac(input.identityKey, unsigned) };
223
+ const receiptPath = path.join(input.agentRoot, "state", "acceptance", "scheduler-liveness-receipts", `${input.scenario.scenarioHandleDigest}.json`);
224
+ publishSanctuarySchedulerReceipt(receiptPath, receipt);
225
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_scheduler_liveness", message: "Scheduler-origin Sanctuary health sweep attested", meta: { scenarioHandleDigest: input.scenario.scenarioHandleDigest, occurrenceId: input.occurrenceId, runnerId: input.runnerId } });
226
+ return receipt;
227
+ }
@@ -0,0 +1,126 @@
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.defaultSanctuarySchedulerOriginDeps = defaultSanctuarySchedulerOriginDeps;
37
+ exports.createSanctuarySchedulerFireCommand = createSanctuarySchedulerFireCommand;
38
+ exports.verifySanctuarySchedulerFireCommand = verifySanctuarySchedulerFireCommand;
39
+ const node_crypto_1 = require("node:crypto");
40
+ const fs = __importStar(require("node:fs"));
41
+ const identity_1 = require("../identity");
42
+ const runtime_1 = require("../../nerves/runtime");
43
+ const telegram_1 = require("../../senses/telegram");
44
+ const sanctuary_acceptance_marker_1 = require("./sanctuary-acceptance-marker");
45
+ const CRONTAB_PATH = "/home/ouro/.ouro-cli/scheduler/sanctuary.crontab";
46
+ const SUPERCRONIC = "/usr/local/bin/supercronic";
47
+ const SUPERCRONIC_CMDLINE = `${SUPERCRONIC}\0-split-logs\0-inotify\0${CRONTAB_PATH}\0`;
48
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
49
+ const SHA256 = /^[0-9a-f]{64}$/u;
50
+ const SLOT_MS = 15 * 60 * 1_000;
51
+ function processIdentity(stat) {
52
+ const close = stat.lastIndexOf(")");
53
+ if (close < 1)
54
+ throw new Error("scheduler process identity is invalid");
55
+ const fields = stat.slice(close + 2).trim().split(/\s+/u);
56
+ const parentPid = Number(fields[1]);
57
+ const startTime = fields[19];
58
+ if (!Number.isSafeInteger(parentPid) || parentPid < 0 || !startTime || !/^[0-9]+$/u.test(startTime))
59
+ throw new Error("scheduler process identity is invalid");
60
+ return { parentPid, startTime };
61
+ }
62
+ function slotFor(now) {
63
+ return new Date(Math.floor(now.getTime() / SLOT_MS) * SLOT_MS).toISOString();
64
+ }
65
+ function unsigned(command) {
66
+ return Object.fromEntries(Object.entries(command).filter(([key]) => key !== "proofMac"));
67
+ }
68
+ function mac(key, command) {
69
+ return (0, node_crypto_1.createHmac)("sha256", key).update(JSON.stringify(unsigned(command))).digest("hex");
70
+ }
71
+ function safeEqual(left, right) {
72
+ if (!SHA256.test(left) || !SHA256.test(right))
73
+ return false;
74
+ return (0, node_crypto_1.timingSafeEqual)(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
75
+ }
76
+ function defaultSanctuarySchedulerOriginDeps(agentRoot = (0, identity_1.getAgentRoot)("sanctuary"), markerReader = () => (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)("sanctuary")) {
77
+ return {
78
+ platform: process.platform, pid: process.pid, ppid: process.ppid, now: () => new Date(),
79
+ readFile: (target) => fs.readFileSync(target, "utf8"), readLink: (target) => fs.readlinkSync(target),
80
+ identityKey: () => (0, telegram_1.readOrCreateTelegramIdentityKey)(agentRoot), marker: markerReader, randomId: () => (0, node_crypto_1.randomUUID)(),
81
+ };
82
+ }
83
+ function createSanctuarySchedulerFireCommand(command, deps = defaultSanctuarySchedulerOriginDeps()) {
84
+ if (command.agent !== "sanctuary" || command.habitName !== "sanctuary-health" || command.trigger !== "cron" || deps.platform !== "linux")
85
+ return null;
86
+ try {
87
+ if (deps.readLink(`/proc/${deps.ppid}/exe`) !== SUPERCRONIC || deps.readFile(`/proc/${deps.ppid}/cmdline`) !== SUPERCRONIC_CMDLINE)
88
+ return null;
89
+ const invocation = processIdentity(deps.readFile(`/proc/${deps.pid}/stat`));
90
+ const parent = processIdentity(deps.readFile(`/proc/${deps.ppid}/stat`));
91
+ const marker = deps.marker();
92
+ if (invocation.parentPid !== deps.ppid)
93
+ return null;
94
+ const slot = slotFor(deps.now());
95
+ const value = {
96
+ kind: "habit.scheduler-fire", agent: "sanctuary", habitName: "sanctuary-health", trigger: "cron",
97
+ slot, occurrenceId: `cron:${slot}`, schedulerRunId: deps.randomId(), invocationPid: deps.pid, parentPid: deps.ppid,
98
+ parentStartTime: parent.startTime, invocationStartTime: invocation.startTime,
99
+ scenarioHandleDigest: marker?.label === "unit-16f-cron-fingerprint" ? marker.scenarioHandleDigest : null,
100
+ };
101
+ return { ...value, proofMac: mac(deps.identityKey(), value) };
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }
107
+ function verifySanctuarySchedulerFireCommand(command, deps) {
108
+ if (!command || command.kind !== "habit.scheduler-fire" || command.agent !== "sanctuary" || command.habitName !== "sanctuary-health" || command.trigger !== "cron"
109
+ || !UUID.test(command.schedulerRunId) || (command.scenarioHandleDigest !== null && !SHA256.test(command.scenarioHandleDigest)) || command.scenarioHandleDigest !== deps.scenarioHandleDigest
110
+ || command.slot !== slotFor(deps.now()) || command.occurrenceId !== `cron:${command.slot}`)
111
+ throw new Error("scheduler fire slot or binding is invalid");
112
+ if (!safeEqual(command.proofMac, mac(deps.identityKey, command)))
113
+ throw new Error("scheduler fire authentication failed");
114
+ const invocation = processIdentity(deps.readFile(`/proc/${command.invocationPid}/stat`));
115
+ const parent = processIdentity(deps.readFile(`/proc/${command.parentPid}/stat`));
116
+ if (command.parentPid !== deps.childPid || invocation.parentPid !== command.parentPid || invocation.startTime !== command.invocationStartTime
117
+ || parent.startTime !== command.parentStartTime || deps.readLink(`/proc/${command.parentPid}/exe`) !== SUPERCRONIC
118
+ || deps.readFile(`/proc/${command.parentPid}/cmdline`) !== SUPERCRONIC_CMDLINE)
119
+ throw new Error("scheduler fire ancestry is invalid");
120
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_scheduler_origin_authenticated", message: "Sanctuary scheduler origin authenticated", meta: { schedulerRunId: command.schedulerRunId, slot: command.slot } });
121
+ return {
122
+ slot: command.slot, schedulerRunId: command.schedulerRunId, invocationPid: command.invocationPid, parentPid: command.parentPid,
123
+ parentStartTime: command.parentStartTime, invocationStartTime: command.invocationStartTime, proofMac: command.proofMac,
124
+ scenarioHandleDigest: command.scenarioHandleDigest, occurrenceId: command.occurrenceId,
125
+ };
126
+ }
@@ -60,6 +60,7 @@ function defaultSenses() {
60
60
  mail: { ...identity_1.DEFAULT_AGENT_SENSES.mail },
61
61
  voice: { ...identity_1.DEFAULT_AGENT_SENSES.voice },
62
62
  a2a: { ...identity_1.DEFAULT_AGENT_SENSES.a2a },
63
+ telegram: { ...identity_1.DEFAULT_AGENT_SENSES.telegram },
63
64
  workbench: { ...identity_1.DEFAULT_AGENT_SENSES.workbench },
64
65
  };
65
66
  }
@@ -100,7 +101,7 @@ function readAgentSenses(agentJsonPath) {
100
101
  && !Array.isArray(rawMcpServers)
101
102
  && Object.prototype.hasOwnProperty.call(rawMcpServers, "ouro_workbench");
102
103
  const workbenchHasStaleBundleEntry = workbenchSensePresent || workbenchMcpPresent;
103
- for (const sense of ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "workbench"]) {
104
+ for (const sense of ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "telegram", "workbench"]) {
104
105
  const rawSense = rawSenses[sense];
105
106
  if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
106
107
  continue;
@@ -168,6 +169,7 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
168
169
  mail: { configured: false, detail: "not enabled in agent.json" },
169
170
  voice: { configured: false, detail: "not enabled in agent.json" },
170
171
  a2a: { configured: false, detail: "not enabled in agent.json" },
172
+ telegram: { configured: false, detail: "not enabled in agent.json" },
171
173
  workbench: { configured: false, detail: "not enabled in agent.json" },
172
174
  };
173
175
  const payload = runtimeConfig.ok ? runtimeConfig.config : {};
@@ -181,6 +183,13 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
181
183
  const integrations = payload.integrations;
182
184
  const voice = machinePayload.voice;
183
185
  const a2a = machinePayload.a2a;
186
+ if (senses.telegram.enabled) {
187
+ const missing = ["telegramBotToken", "telegramAuthorizedUserId", "telegramAuthorizedChatId"]
188
+ .filter((field) => !textField(payload, field));
189
+ base.telegram = missing.length === 0
190
+ ? { configured: true, detail: "private long poll" }
191
+ : { configured: false, detail: `missing runtime/config: ${missing.join(", ")}` };
192
+ }
184
193
  if (senses.teams.enabled) {
185
194
  const missing = [];
186
195
  if (!textField(teams, "clientId"))
@@ -345,6 +354,9 @@ function senseRepairHint(agent, sense) {
345
354
  if (sense === "a2a") {
346
355
  return `Agent-runnable: run 'ouro connect a2a --agent ${agent}', then restart with 'ouro up'.`;
347
356
  }
357
+ if (sense === "telegram") {
358
+ return `Agent-runnable: store Telegram bot/user/chat coordinates with 'ouro connect telegram --agent ${agent}', then restart with 'ouro up'.`;
359
+ }
348
360
  /* v8 ignore next -- Workbench is deliberately not daemon-managed, so getSenseInventory never asks the daemon manager for a repair hint @preserve */
349
361
  if (sense === "workbench") {
350
362
  return `Agent-runnable: install Ouro Workbench.app, then launch the boss through Workbench or run 'ouro mcp-serve --agent ${agent} --workbench-mcp'; 'ouro connect workbench --agent ${agent}' only cleans stale bundle entries.`;
@@ -359,7 +371,7 @@ function parseSenseSnapshotName(name) {
359
371
  if (parts.length !== 2)
360
372
  return null;
361
373
  const [agent, sense] = parts;
362
- if (sense !== "teams" && sense !== "bluebubbles" && sense !== "mail" && sense !== "voice" && sense !== "a2a")
374
+ if (sense !== "teams" && sense !== "bluebubbles" && sense !== "mail" && sense !== "voice" && sense !== "a2a" && sense !== "telegram")
363
375
  return null;
364
376
  return { agent, sense };
365
377
  }
@@ -377,12 +389,15 @@ function managedSenseEntry(sense) {
377
389
  return "senses/voice-entry.js";
378
390
  if (sense === "a2a")
379
391
  return "senses/a2a-entry.js";
392
+ if (sense === "telegram")
393
+ return "senses/telegram-entry.js";
380
394
  return "senses/mail-entry.js";
381
395
  }
382
396
  function runtimeCredentialBootstrapFor(agent, sense) {
383
397
  const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent);
384
- const machineId = sense === "bluebubbles" || sense === "voice" || sense === "a2a" ? currentMachineId() : undefined;
385
- const machine = sense === "bluebubbles" || sense === "voice" || sense === "a2a" ? (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent) : null;
398
+ const usesMachineConfig = sense === "bluebubbles" || sense === "voice" || sense === "a2a" || sense === "telegram";
399
+ const machineId = usesMachineConfig ? currentMachineId() : undefined;
400
+ const machine = usesMachineConfig ? (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent) : null;
386
401
  const providerPool = (0, provider_credentials_1.readProviderCredentialPool)(agent);
387
402
  const providerCredentialRecords = providerPool.ok
388
403
  ? Object.values(providerPool.pool.providers).filter((record) => !!record)
@@ -551,7 +566,7 @@ class DaemonSenseManager {
551
566
  return [agent, { senses, workbenchHasStaleBundleEntry, facts }];
552
567
  }));
553
568
  const managedSenseAgents = [...this.contexts.entries()].flatMap(([agent, context]) => {
554
- return ["teams", "bluebubbles", "mail", "voice", "a2a"]
569
+ return ["teams", "bluebubbles", "mail", "voice", "a2a", "telegram"]
555
570
  .filter((sense) => context.senses[sense].enabled)
556
571
  .map((sense) => ({
557
572
  name: `${agent}:${sense}`,
@@ -703,7 +718,7 @@ class DaemonSenseManager {
703
718
  }
704
719
  async refreshEnabledSenseConfigs() {
705
720
  const refreshes = [...this.contexts.entries()].map(async ([agent, context]) => {
706
- const enabledManagedSenses = ["teams", "bluebubbles", "mail", "voice", "a2a"]
721
+ const enabledManagedSenses = ["teams", "bluebubbles", "mail", "voice", "a2a", "telegram"]
707
722
  .filter((sense) => context.senses[sense].enabled);
708
723
  /* v8 ignore next -- periodic refresh work only exists when a managed background sense is enabled @preserve */
709
724
  if (enabledManagedSenses.length === 0)
@@ -872,6 +887,10 @@ class DaemonSenseManager {
872
887
  configured: context.facts.a2a.configured,
873
888
  ...(runtime.get(agent)?.a2a ?? {}),
874
889
  },
890
+ telegram: {
891
+ configured: context.facts.telegram.configured,
892
+ ...(runtime.get(agent)?.telegram ?? {}),
893
+ },
875
894
  workbench: {
876
895
  configured: context.facts.workbench.configured,
877
896
  },