@ouro.bot/cli 0.1.0-alpha.733 → 0.1.0-alpha.734
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.json +25 -0
- package/deploy/unraid/Dockerfile +27 -0
- package/deploy/unraid/README.txt +1368 -0
- package/deploy/unraid/audit-container-spec.sh +4 -0
- package/deploy/unraid/container-runtime.json +1 -0
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +37 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +200 -0
- package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
- package/deploy/unraid/sanctuary-deployment-target.mjs +705 -0
- package/deploy/unraid/sanctuary-safe-rename.py +24 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +1777 -0
- package/deploy/unraid/sanctuary-unit16-run.sh +502 -0
- package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
- package/deploy/unraid/sanctuary.ouro/agent.json +24 -0
- package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +18 -0
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +21 -0
- package/deploy/unraid/sanctuary.xml +25 -0
- package/dist/heart/approval-files.js +113 -0
- package/dist/heart/approval-store.js +168 -9
- package/dist/heart/auth/auth-flow.js +17 -1
- package/dist/heart/autonomy-budget.js +44 -1
- package/dist/heart/awaiting/await-scheduler.js +6 -2
- package/dist/heart/config.js +8 -0
- package/dist/heart/core.js +117 -132
- package/dist/heart/daemon/cli-exec.js +73 -1
- package/dist/heart/daemon/cli-help.js +5 -0
- package/dist/heart/daemon/cli-parse.js +6 -4
- package/dist/heart/daemon/container-credential-bootstrap.js +185 -0
- package/dist/heart/daemon/container-healthcheck.js +94 -0
- package/dist/heart/daemon/container-runtime.js +84 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +156 -0
- package/dist/heart/daemon/container-spec-auditor.js +156 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +41 -0
- package/dist/heart/daemon/daemon-entry.js +116 -28
- package/dist/heart/daemon/daemon-health.js +10 -0
- package/dist/heart/daemon/daemon.js +92 -5
- package/dist/heart/daemon/runtime-logging.js +6 -0
- package/dist/heart/daemon/runtime-mode.js +13 -2
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2365 -0
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +1624 -0
- package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +985 -0
- package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +126 -0
- package/dist/heart/daemon/sense-manager.js +25 -6
- package/dist/heart/daemon/supercronic-supervisor.js +267 -0
- package/dist/heart/habits/habit-scheduler.js +7 -3
- package/dist/heart/identity.js +13 -3
- package/dist/heart/provider-credentials.js +9 -1
- package/dist/heart/provider-models.js +8 -0
- package/dist/heart/provider-ping.js +5 -2
- package/dist/heart/providers/openai-compatible.js +177 -0
- package/dist/heart/runtime-credentials.js +217 -8
- package/dist/heart/sense-truth.js +2 -0
- package/dist/heart/tool-approval.js +1 -0
- package/dist/heart/turn-context.js +8 -0
- package/dist/mind/prompt.js +9 -0
- package/dist/nerves/coverage/source-scanner.js +4 -4
- package/dist/nerves/index.js +75 -9
- package/dist/nerves/runtime.js +12 -0
- package/dist/repertoire/tool-arguments.js +39 -0
- package/dist/repertoire/tools-unraid.js +353 -0
- package/dist/repertoire/tools.js +19 -1
- package/dist/repertoire/unraid-client.js +155 -0
- package/dist/repertoire/unraid-restart.js +130 -0
- package/dist/senses/private-runtime-worker.js +3 -2
- package/dist/senses/sanctuary-grounding.js +118 -0
- package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
- package/dist/senses/sanctuary-health-acceptance-probe.js +772 -0
- package/dist/senses/sanctuary-health-runner.js +146 -0
- package/dist/senses/sanctuary-health.js +447 -0
- package/dist/senses/sanctuary-interactive-control.js +306 -0
- package/dist/senses/sanctuary-runtime.js +206 -0
- package/dist/senses/shared-turn.js +11 -3
- package/dist/senses/telegram-approval-runtime.js +526 -0
- package/dist/senses/telegram-audit-ledger.js +220 -0
- package/dist/senses/telegram-client.js +1471 -0
- package/dist/senses/telegram-entry.js +80 -0
- package/dist/senses/telegram.js +1232 -0
- package/npm-shrinkwrap.json +6558 -0
- 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
|
|
385
|
-
const
|
|
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
|
},
|