@ouro.bot/cli 0.1.0-alpha.772 → 0.1.0-alpha.774
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 +15 -0
- package/deploy/unraid/README.txt +34 -73
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +6 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +1 -4
- package/deploy/unraid/sanctuary-acceptance-harness.sh +1 -1
- package/deploy/unraid/sanctuary-unit16-run.sh +5 -6
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/daemon/cli-defaults.js +4 -0
- package/dist/heart/daemon/cli-exec.js +39 -1
- package/dist/heart/daemon/cli-help.js +3 -3
- package/dist/heart/daemon/cli-parse.js +5 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +40 -4
- package/dist/heart/daemon/daemon-entry.js +34 -71
- package/dist/heart/daemon/daemon.js +91 -25
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +52 -21
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +69 -23
- package/dist/heart/daemon/startup-tui.js +1 -0
- package/dist/heart/external-events/router.js +227 -14
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -74,7 +74,6 @@ const runtime_credentials_1 = require("../runtime-credentials");
|
|
|
74
74
|
const machine_identity_1 = require("../machine-identity");
|
|
75
75
|
const container_credential_bootstrap_1 = require("./container-credential-bootstrap");
|
|
76
76
|
const daemon_bootstrap_startup_1 = require("./daemon-bootstrap-startup");
|
|
77
|
-
const auth_flow_1 = require("../auth/auth-flow");
|
|
78
77
|
const sanctuary_health_runner_1 = require("../../senses/sanctuary-health-runner");
|
|
79
78
|
const sanctuary_acceptance_marker_1 = require("./sanctuary-acceptance-marker");
|
|
80
79
|
const sanctuary_scheduler_liveness_1 = require("./sanctuary-scheduler-liveness");
|
|
@@ -588,77 +587,40 @@ function writeStopCommandHealthState() {
|
|
|
588
587
|
// Health writes are best-effort during shutdown.
|
|
589
588
|
}
|
|
590
589
|
}
|
|
591
|
-
function
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
agent,
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if (result.ok) {
|
|
620
|
-
const missingProviders = providerPreloadMissingTargets(result, providers);
|
|
621
|
-
if (missingProviders.length > 0) {
|
|
622
|
-
(0, runtime_1.emitNervesEvent)({
|
|
623
|
-
level: "warn",
|
|
624
|
-
component: "daemon",
|
|
625
|
-
event: "daemon.provider_preload_unavailable",
|
|
626
|
-
message: "provider credential preload returned an incomplete selected provider cache",
|
|
627
|
-
meta: {
|
|
628
|
-
agent,
|
|
629
|
-
reason: "missing",
|
|
630
|
-
error: `missing selected providers: ${missingProviders.join(", ")}`,
|
|
631
|
-
},
|
|
632
|
-
});
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
const records = Object.values(result.pool.providers).filter((record) => !!record);
|
|
636
|
-
(0, provider_credentials_1.cacheProviderCredentialRecords)(agent, records, new Date(result.pool.updatedAt));
|
|
637
|
-
return;
|
|
590
|
+
async function prepareProviderRuntime() {
|
|
591
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
592
|
+
const readiness = await Promise.all(managedAgents.map(async (agent) => {
|
|
593
|
+
try {
|
|
594
|
+
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);
|
|
595
|
+
if (result.ok)
|
|
596
|
+
return null;
|
|
597
|
+
(0, runtime_1.emitNervesEvent)({
|
|
598
|
+
level: "warn",
|
|
599
|
+
component: "daemon",
|
|
600
|
+
event: "daemon.provider_readiness_unavailable",
|
|
601
|
+
message: "fresh provider readiness was unavailable before daemon startup",
|
|
602
|
+
meta: { agent },
|
|
603
|
+
});
|
|
604
|
+
return result.issue ?? {
|
|
605
|
+
summary: `${agent}: provider runtime unavailable`,
|
|
606
|
+
actions: [{ actor: "agent-runnable", command: "ouro doctor" }],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
(0, runtime_1.emitNervesEvent)({
|
|
611
|
+
level: "warn",
|
|
612
|
+
component: "daemon",
|
|
613
|
+
event: "daemon.provider_readiness_unavailable",
|
|
614
|
+
message: "fresh provider readiness check failed before daemon startup",
|
|
615
|
+
meta: { agent },
|
|
616
|
+
});
|
|
617
|
+
throw new Error("provider runtime preparation failed");
|
|
638
618
|
}
|
|
639
|
-
(0, runtime_1.emitNervesEvent)({
|
|
640
|
-
level: "warn",
|
|
641
|
-
component: "daemon",
|
|
642
|
-
event: "daemon.provider_preload_unavailable",
|
|
643
|
-
message: "provider credential preload could not refresh selected provider cache",
|
|
644
|
-
meta: {
|
|
645
|
-
agent,
|
|
646
|
-
reason: result.reason,
|
|
647
|
-
error: result.error,
|
|
648
|
-
},
|
|
649
|
-
});
|
|
650
619
|
}));
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
level: "error",
|
|
656
|
-
component: "daemon",
|
|
657
|
-
event: "daemon.provider_preload_error",
|
|
658
|
-
message: "provider credential preload failed after daemon startup",
|
|
659
|
-
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error provider preload failures @preserve */ String(error) },
|
|
660
|
-
});
|
|
661
|
-
});
|
|
620
|
+
const failures = readiness.filter((entry) => entry !== null);
|
|
621
|
+
if (failures.length > 0) {
|
|
622
|
+
throw (0, daemon_bootstrap_startup_1.createProviderReadinessPreparationFailure)(failures);
|
|
623
|
+
}
|
|
662
624
|
}
|
|
663
625
|
function scheduleStartupSentinelAfterProviderPreload(agent, preload) {
|
|
664
626
|
void preload.then(async () => {
|
|
@@ -693,6 +655,7 @@ function scheduleStartupSentinelAfterProviderPreload(agent, preload) {
|
|
|
693
655
|
/* v8 ignore start -- habit wiring: lambdas delegate to processManager/fs; tested via HabitScheduler unit tests @preserve */
|
|
694
656
|
void (0, daemon_bootstrap_startup_1.startDaemonAfterContainerCredentialBootstrap)({
|
|
695
657
|
loadBootstrap: () => (0, container_credential_bootstrap_1.loadContainerCredentialBootstrap)(managedAgents),
|
|
658
|
+
prepareDaemon: prepareProviderRuntime,
|
|
696
659
|
startDaemon: () => daemon.start(),
|
|
697
660
|
markStartupFailure: () => { _tombstoneWritten = true; },
|
|
698
661
|
exit: (code) => process.exit(code),
|
|
@@ -700,7 +663,7 @@ void (0, daemon_bootstrap_startup_1.startDaemonAfterContainerCredentialBootstrap
|
|
|
700
663
|
if (!started)
|
|
701
664
|
return;
|
|
702
665
|
supercronicSupervisor?.start();
|
|
703
|
-
const providerPreload =
|
|
666
|
+
const providerPreload = Promise.resolve();
|
|
704
667
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
705
668
|
const ouroPath = supercronicSupervisor
|
|
706
669
|
? "/usr/local/bin/node /opt/ouro/dist/heart/daemon/ouro-entry.js"
|
|
@@ -618,6 +618,7 @@ class OuroDaemon {
|
|
|
618
618
|
senseAutostartTimer = null;
|
|
619
619
|
externalEventReconcileTimer = null;
|
|
620
620
|
externalEventReconcileRunning = false;
|
|
621
|
+
constructedAtMs = Date.now();
|
|
621
622
|
mailboxServerFactory;
|
|
622
623
|
privateRuntimePolicyDeps;
|
|
623
624
|
onStopCommandComplete;
|
|
@@ -1503,7 +1504,7 @@ class OuroDaemon {
|
|
|
1503
1504
|
}
|
|
1504
1505
|
return { type: "message", privateTurnDecision: decision, ...(externalEvent ? { externalEvent } : {}) };
|
|
1505
1506
|
}
|
|
1506
|
-
queueExternalEventForPrivateRuntime(record) {
|
|
1507
|
+
queueExternalEventForPrivateRuntime(record, quiet = false) {
|
|
1507
1508
|
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);
|
|
1508
1509
|
const originKey = `${record.source}:${record.eventId}`;
|
|
1509
1510
|
const parsedReceivedAt = Date.parse(record.receivedAt);
|
|
@@ -1523,18 +1524,20 @@ class OuroDaemon {
|
|
|
1523
1524
|
mode: "relay",
|
|
1524
1525
|
packetId: `external-event:${record.agent}:${record.source}:${record.eventId}:generation:${record.generation}:attempt:${record.attemptCount}`,
|
|
1525
1526
|
});
|
|
1526
|
-
(
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1527
|
+
if (!quiet) {
|
|
1528
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1529
|
+
component: "daemon",
|
|
1530
|
+
event: "daemon.external_event_private_runtime_queued",
|
|
1531
|
+
message: "queued external event for private-runtime attention",
|
|
1532
|
+
meta: {
|
|
1533
|
+
agent: record.agent,
|
|
1534
|
+
source: record.source,
|
|
1535
|
+
eventType: record.eventType,
|
|
1536
|
+
eventId: record.eventId,
|
|
1537
|
+
pendingDir,
|
|
1538
|
+
},
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1538
1541
|
}
|
|
1539
1542
|
externalEventRootPath() {
|
|
1540
1543
|
return this.externalEventRoot ?? (0, router_1.getExternalEventRoot)();
|
|
@@ -1575,8 +1578,19 @@ class OuroDaemon {
|
|
|
1575
1578
|
source: primary.source,
|
|
1576
1579
|
eventType: primary.eventType,
|
|
1577
1580
|
eventId: primary.eventId,
|
|
1578
|
-
}, receipt.id, primary.generation, primary.attemptCount), () => {
|
|
1579
|
-
|
|
1581
|
+
}, receipt.id, primary.generation, primary.attemptCount), () => {
|
|
1582
|
+
const batched = claimed.length > 1;
|
|
1583
|
+
for (const record of claimed)
|
|
1584
|
+
this.queueExternalEventForPrivateRuntime(record, batched);
|
|
1585
|
+
if (batched) {
|
|
1586
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1587
|
+
component: "daemon",
|
|
1588
|
+
event: "daemon.external_event_private_runtime_batch_queued",
|
|
1589
|
+
message: "queued an external-event batch for private-runtime attention",
|
|
1590
|
+
meta: { agent: primary.agent, source: primary.source, eventCount: claimed.length },
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
}, lease);
|
|
1580
1594
|
failureClass = wake.denialCode === "managed_runtime_unavailable"
|
|
1581
1595
|
? "managed_runtime_unavailable"
|
|
1582
1596
|
: wake.data?.decision?.denialCode === "provider_lane_unavailable"
|
|
@@ -1616,6 +1630,9 @@ class OuroDaemon {
|
|
|
1616
1630
|
}
|
|
1617
1631
|
const statuses = (0, router_1.listExternalEventStatus)(this.externalEventRootPath());
|
|
1618
1632
|
const dueRecords = [];
|
|
1633
|
+
const providerEvidenceByAgent = new Map();
|
|
1634
|
+
const runtimeEvidenceByAgent = new Map();
|
|
1635
|
+
const recoveredByClass = new Map();
|
|
1619
1636
|
for (const status of statuses) {
|
|
1620
1637
|
if (status.corrupt)
|
|
1621
1638
|
continue;
|
|
@@ -1629,15 +1646,35 @@ class OuroDaemon {
|
|
|
1629
1646
|
let evidence = null;
|
|
1630
1647
|
try {
|
|
1631
1648
|
if (failure.class === "provider_lane_unavailable") {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1649
|
+
if (!providerEvidenceByAgent.has(record.agent)) {
|
|
1650
|
+
providerEvidenceByAgent.set(record.agent, null);
|
|
1651
|
+
const binding = (0, provider_binding_resolver_1.resolveEffectiveProviderBinding)({
|
|
1652
|
+
agentName: record.agent,
|
|
1653
|
+
agentRoot: path.join(this.bundlesRoot, `${record.agent}.ouro`),
|
|
1654
|
+
lane: "inner",
|
|
1655
|
+
});
|
|
1656
|
+
providerEvidenceByAgent.set(record.agent, binding.ok && binding.binding.credential.status === "present"
|
|
1657
|
+
&& binding.binding.readiness.status === "ready" && binding.binding.readiness.checkedAt
|
|
1658
|
+
? { observedAt: binding.binding.readiness.checkedAt }
|
|
1659
|
+
: null);
|
|
1660
|
+
}
|
|
1661
|
+
const current = providerEvidenceByAgent.get(record.agent);
|
|
1662
|
+
const readinessIsCurrentBoot = current && Date.parse(current.observedAt) >= this.constructedAtMs;
|
|
1663
|
+
if (current && ((0, router_1.isExactLegacyProviderRecoveryFailure)(record)
|
|
1664
|
+
? readinessIsCurrentBoot
|
|
1665
|
+
: Date.parse(current.observedAt) > Date.parse(failure.failedAt))) {
|
|
1666
|
+
evidence = { class: failure.class, observedAt: current.observedAt };
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
else if (failure.class === "execution_lease_expired") {
|
|
1670
|
+
if (!runtimeEvidenceByAgent.has(record.agent)) {
|
|
1671
|
+
const snapshot = this.processManager.listAgentSnapshots().find((candidate) => candidate.name === record.agent && candidate.channel === "private-runtime"
|
|
1672
|
+
&& candidate.status === "running" && candidate.pid !== null && candidate.startedAt !== null);
|
|
1673
|
+
runtimeEvidenceByAgent.set(record.agent, snapshot?.startedAt ? { observedAt: snapshot.startedAt } : null);
|
|
1674
|
+
}
|
|
1675
|
+
const current = runtimeEvidenceByAgent.get(record.agent);
|
|
1676
|
+
if (current && Date.parse(current.observedAt) > Date.parse(failure.failedAt)) {
|
|
1677
|
+
evidence = { class: failure.class, observedAt: current.observedAt };
|
|
1641
1678
|
}
|
|
1642
1679
|
}
|
|
1643
1680
|
else if (this.hasManagedPrivateRuntime(record.agent) && Date.parse(now) > Date.parse(failure.failedAt)) {
|
|
@@ -1645,8 +1682,9 @@ class OuroDaemon {
|
|
|
1645
1682
|
}
|
|
1646
1683
|
if (!evidence)
|
|
1647
1684
|
continue;
|
|
1648
|
-
const revival = (0, router_1.reviveExternalEventAfterRecovery)(record.recordPath, { expectedVersion: record.version, expectedGeneration: record.generation, evidence, now: () => now });
|
|
1685
|
+
const revival = (0, router_1.reviveExternalEventAfterRecovery)(record.recordPath, { expectedVersion: record.version, expectedGeneration: record.generation, evidence, now: () => now, quiet: true });
|
|
1649
1686
|
record = revival.record;
|
|
1687
|
+
recoveredByClass.set(failure.class, (recoveredByClass.get(failure.class) ?? 0) + 1);
|
|
1650
1688
|
}
|
|
1651
1689
|
catch (error) {
|
|
1652
1690
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -1668,6 +1706,14 @@ class OuroDaemon {
|
|
|
1668
1706
|
if (due)
|
|
1669
1707
|
dueRecords.push(record);
|
|
1670
1708
|
}
|
|
1709
|
+
if (recoveredByClass.size > 0) {
|
|
1710
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1711
|
+
component: "daemon",
|
|
1712
|
+
event: "daemon.external_event_recovery_batch",
|
|
1713
|
+
message: "requeued external events after cached infrastructure recovery evidence",
|
|
1714
|
+
meta: { recovered: Object.fromEntries(recoveredByClass) },
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1671
1717
|
const batches = new Map();
|
|
1672
1718
|
for (const record of dueRecords) {
|
|
1673
1719
|
const key = `${record.agent}\0${record.source}`;
|
|
@@ -1968,6 +2014,26 @@ class OuroDaemon {
|
|
|
1968
2014
|
data: dispatched,
|
|
1969
2015
|
};
|
|
1970
2016
|
}
|
|
2017
|
+
case "external.event.repair": {
|
|
2018
|
+
if (this.externalEventReconcileRunning)
|
|
2019
|
+
return { ok: false, error: "external event reconciliation is already active; repair made no changes" };
|
|
2020
|
+
this.externalEventReconcileRunning = true;
|
|
2021
|
+
try {
|
|
2022
|
+
try {
|
|
2023
|
+
const result = (0, router_1.repairExternalEventsFromManifest)(command.manifestPath, { root: this.externalEventRootPath() });
|
|
2024
|
+
return { ok: result.failed.length === 0, message: `external event repair applied=${result.applied.length} alreadyApplied=${result.alreadyApplied.length} failed=${result.failed.length} pending=${result.pending.length}`, data: result };
|
|
2025
|
+
}
|
|
2026
|
+
catch (error) {
|
|
2027
|
+
const message = (error instanceof Error ? error.message : /* v8 ignore next -- defensive: repair parser and filesystem dependencies throw Error instances @preserve */ String(error)).slice(0, 1_000);
|
|
2028
|
+
const result = { applied: [], alreadyApplied: [], failed: [{ identity: "<manifest>", error: message }], pending: [] };
|
|
2029
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.external_event_repair", message: "rejected external event repair manifest", meta: { applied: 0, alreadyApplied: 0, failed: 1, pending: 0 } });
|
|
2030
|
+
return { ok: false, error: `external event repair rejected: ${message}`, data: result };
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
finally {
|
|
2034
|
+
this.externalEventReconcileRunning = false;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
1971
2037
|
case "private.decisions": {
|
|
1972
2038
|
const requestedLimit = Number.isInteger(command.limit) && command.limit && command.limit > 0 ? command.limit : 20;
|
|
1973
2039
|
const limit = Math.min(requestedLimit, 1000);
|
|
@@ -255,6 +255,7 @@ function createSanctuaryAcceptanceAdapterDependencies(secretFd = 3, options = {}
|
|
|
255
255
|
interactiveRuntime: executeSanctuaryInteractiveRuntimeOperation,
|
|
256
256
|
hostRequest: options.hostRequest ?? ((payload) => defaultHostRequest(payload, hostBrokerSocket, adapterTimeoutMs)),
|
|
257
257
|
telegramCredentials: () => (0, telegram_1.loadTelegramSenseCredentials)(TARGET_ID),
|
|
258
|
+
createTelegramApi: telegram_client_1.createTelegramBotApi,
|
|
258
259
|
readLiveGrounding: readIndependentSanctuaryGrounding,
|
|
259
260
|
runProductionBoundaryProbe: runSanctuaryProductionBoundaryProbe,
|
|
260
261
|
};
|
|
@@ -1827,12 +1828,6 @@ function exactKeys(value, keys, label) {
|
|
|
1827
1828
|
if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify([...keys].sort()))
|
|
1828
1829
|
throw new Error(`${label} shape is invalid`);
|
|
1829
1830
|
}
|
|
1830
|
-
function positiveDecimal(value, label) {
|
|
1831
|
-
const result = text(value, label);
|
|
1832
|
-
if (!/^[1-9][0-9]*$/u.test(result))
|
|
1833
|
-
throw new Error(`${label} is invalid`);
|
|
1834
|
-
return result;
|
|
1835
|
-
}
|
|
1836
1831
|
function sha256(value) {
|
|
1837
1832
|
return (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
|
|
1838
1833
|
}
|
|
@@ -1845,18 +1840,6 @@ async function runtimeConfig(reader, label, ...args) {
|
|
|
1845
1840
|
throw new Error(`${label} is unavailable`);
|
|
1846
1841
|
return result.config;
|
|
1847
1842
|
}
|
|
1848
|
-
async function storeTelegramBootstrap(payload, deps) {
|
|
1849
|
-
const patch = {
|
|
1850
|
-
telegramBotToken: text(payload.botToken, "Telegram bot credential"),
|
|
1851
|
-
telegramAuthorizedUserId: positiveDecimal(payload.authorizedUserId, "Telegram authorized user"),
|
|
1852
|
-
telegramAuthorizedChatId: positiveDecimal(payload.authorizedChatId, "Telegram authorized chat"),
|
|
1853
|
-
};
|
|
1854
|
-
const stored = await dependency(deps.mergeRuntime, "runtime vault writer")(TARGET_ID, patch);
|
|
1855
|
-
if (!stored.ok || Object.entries(patch).some(([key, value]) => stored.config[key] !== value)) {
|
|
1856
|
-
throw new Error("Telegram bootstrap vault readback failed");
|
|
1857
|
-
}
|
|
1858
|
-
return { stored: true };
|
|
1859
|
-
}
|
|
1860
1843
|
function cursorSnapshot(payload, deps) {
|
|
1861
1844
|
exactKeys(payload, ["allowGenesis", "operation", "schema"], "Telegram cursor snapshot request");
|
|
1862
1845
|
if (payload.schema !== "telegram-cursor-v1" || typeof payload.allowGenesis !== "boolean")
|
|
@@ -2306,6 +2289,54 @@ function materializeConfig(payload, deps) {
|
|
|
2306
2289
|
}
|
|
2307
2290
|
return config;
|
|
2308
2291
|
}
|
|
2292
|
+
async function telegramReadiness(payload, deps) {
|
|
2293
|
+
exactKeys(payload, ["operation"], "Telegram readiness payload");
|
|
2294
|
+
let refreshed;
|
|
2295
|
+
try {
|
|
2296
|
+
refreshed = await dependency(deps.refreshRuntime, "runtime credential refresher")(TARGET_ID);
|
|
2297
|
+
}
|
|
2298
|
+
catch {
|
|
2299
|
+
throw new Error("Telegram runtime credentials are unavailable; actor: human-required; unlock or repair vault runtime/config");
|
|
2300
|
+
}
|
|
2301
|
+
if (!refreshed.ok)
|
|
2302
|
+
throw new Error("Telegram runtime credentials are unavailable; actor: human-required; unlock or repair vault runtime/config");
|
|
2303
|
+
let credentials;
|
|
2304
|
+
try {
|
|
2305
|
+
credentials = dependency(deps.telegramCredentials, "Telegram credentials")();
|
|
2306
|
+
(0, telegram_1.telegramBotIdFromToken)(credentials.botToken);
|
|
2307
|
+
}
|
|
2308
|
+
catch {
|
|
2309
|
+
throw new Error("Telegram runtime credentials are invalid; actor: human-required; repair vault runtime/config");
|
|
2310
|
+
}
|
|
2311
|
+
let api;
|
|
2312
|
+
try {
|
|
2313
|
+
api = dependency(deps.createTelegramApi, "Telegram API factory")({ token: credentials.botToken });
|
|
2314
|
+
}
|
|
2315
|
+
catch {
|
|
2316
|
+
throw new Error("Telegram client initialization failed; actor: agent-runnable; retry Telegram readiness");
|
|
2317
|
+
}
|
|
2318
|
+
let bot;
|
|
2319
|
+
try {
|
|
2320
|
+
bot = await api.request("getMe", {}, AbortSignal.timeout(30_000));
|
|
2321
|
+
}
|
|
2322
|
+
catch {
|
|
2323
|
+
throw new Error("Telegram getMe failed; actor: agent-runnable; retry Telegram readiness");
|
|
2324
|
+
}
|
|
2325
|
+
finally {
|
|
2326
|
+
try {
|
|
2327
|
+
api.stop();
|
|
2328
|
+
}
|
|
2329
|
+
catch {
|
|
2330
|
+
throw new Error("Telegram client cleanup failed; actor: agent-runnable; retry Telegram readiness");
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
if (!bot || typeof bot !== "object" || Array.isArray(bot)
|
|
2334
|
+
|| String(bot.id) !== "8541786263"
|
|
2335
|
+
|| bot.username !== "MendelowCloudButlerBot") {
|
|
2336
|
+
throw new Error("Telegram bot identity mismatch; actor: human-required; repair vault runtime/config");
|
|
2337
|
+
}
|
|
2338
|
+
return { ready: true, identityMatches: true };
|
|
2339
|
+
}
|
|
2309
2340
|
async function executeSanctuaryAcceptanceAdapter(rawPayload, deps = createSanctuaryAcceptanceAdapterDependencies()) {
|
|
2310
2341
|
const payload = object(rawPayload, "acceptance adapter payload");
|
|
2311
2342
|
const operation = text(payload.operation, "operation");
|
|
@@ -2325,9 +2356,6 @@ async function executeSanctuaryAcceptanceAdapter(rawPayload, deps = createSanctu
|
|
|
2325
2356
|
case "revoked-key-auth-rejection":
|
|
2326
2357
|
result = await revokedKeyAuthRejection(payload, deps);
|
|
2327
2358
|
break;
|
|
2328
|
-
case "store_telegram_bootstrap":
|
|
2329
|
-
result = await storeTelegramBootstrap(payload, deps);
|
|
2330
|
-
break;
|
|
2331
2359
|
case "quiesce_telegram_poller":
|
|
2332
2360
|
result = telegramPollerQuiescence(payload, deps);
|
|
2333
2361
|
break;
|
|
@@ -2410,6 +2438,9 @@ async function executeSanctuaryAcceptanceAdapter(rawPayload, deps = createSanctu
|
|
|
2410
2438
|
case "materialize_config":
|
|
2411
2439
|
result = materializeConfig(payload, deps);
|
|
2412
2440
|
break;
|
|
2441
|
+
case "telegram_readiness":
|
|
2442
|
+
result = await telegramReadiness(payload, deps);
|
|
2443
|
+
break;
|
|
2413
2444
|
default: throw new Error("unknown Sanctuary acceptance adapter operation");
|
|
2414
2445
|
}
|
|
2415
2446
|
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_acceptance_adapter_end", message: "Sanctuary acceptance adapter completed", meta: { operation } });
|
|
@@ -45,6 +45,8 @@ const node_fs_1 = require("node:fs");
|
|
|
45
45
|
const node_fs_2 = require("node:fs");
|
|
46
46
|
const path = __importStar(require("node:path"));
|
|
47
47
|
const runtime_1 = require("../../nerves/runtime");
|
|
48
|
+
const telegram_1 = require("../../senses/telegram");
|
|
49
|
+
const runtime_credentials_1 = require("../runtime-credentials");
|
|
48
50
|
const MAX_ADAPTER_OUTPUT = 1_048_576;
|
|
49
51
|
const DEFAULT_ADAPTER_TIMEOUT_MS = 240_000;
|
|
50
52
|
const DEFAULT_TELEGRAM_TIMEOUT_MS = 10_000;
|
|
@@ -59,6 +61,9 @@ function createSanctuaryAcceptanceHarnessDependencies(secretFd = 3, options = {}
|
|
|
59
61
|
const telegramTimeoutMs = options.telegramTimeoutMs ?? DEFAULT_TELEGRAM_TIMEOUT_MS;
|
|
60
62
|
return {
|
|
61
63
|
readSecret: () => (0, node_fs_1.readFileSync)(secretFd, "utf8"),
|
|
64
|
+
refreshRuntime: runtime_credentials_1.refreshRuntimeCredentialConfig,
|
|
65
|
+
mergeRuntime: runtime_credentials_1.mergeRuntimeCredentialConfig,
|
|
66
|
+
telegramCredentials: telegram_1.loadTelegramSenseCredentials,
|
|
62
67
|
runAdapter: async (executable, payload, remainingMs) => {
|
|
63
68
|
requireAbsoluteExecutable(executable);
|
|
64
69
|
const result = (0, node_child_process_1.spawnSync)(executable, [], {
|
|
@@ -92,6 +97,25 @@ function createSanctuaryAcceptanceHarnessDependencies(secretFd = 3, options = {}
|
|
|
92
97
|
telegramTimeoutMs,
|
|
93
98
|
};
|
|
94
99
|
}
|
|
100
|
+
async function canonicalTelegramBootstrapToken(deps) {
|
|
101
|
+
let refreshed;
|
|
102
|
+
try {
|
|
103
|
+
refreshed = await deps.refreshRuntime("sanctuary");
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
throw new Error("Telegram runtime credentials are unavailable; actor: human-required; unlock or repair vault runtime/config");
|
|
107
|
+
}
|
|
108
|
+
if (!refreshed.ok)
|
|
109
|
+
throw new Error("Telegram runtime credentials are unavailable; actor: human-required; unlock or repair vault runtime/config");
|
|
110
|
+
try {
|
|
111
|
+
const token = deps.telegramCredentials("sanctuary").botToken.trim();
|
|
112
|
+
(0, telegram_1.telegramBotIdFromToken)(token);
|
|
113
|
+
return token;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
throw new Error("Telegram runtime credentials are invalid; actor: human-required; repair vault runtime/config");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
95
119
|
function object(value, label) {
|
|
96
120
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
97
121
|
throw new Error(`${label} must be an object`);
|
|
@@ -322,6 +346,10 @@ async function telegramRequest(deps, token, method, body, requestTimeoutMs = dep
|
|
|
322
346
|
throw new Error("Telegram request failed");
|
|
323
347
|
return envelope.result;
|
|
324
348
|
}
|
|
349
|
+
function telegramBootstrapRequestError(method, error) {
|
|
350
|
+
const outcome = error instanceof Error && /timed out/iu.test(error.message) ? "timed out" : "failed";
|
|
351
|
+
return new Error(`Telegram ${method} ${outcome}; actor: agent-runnable; retry Telegram bootstrap`);
|
|
352
|
+
}
|
|
325
353
|
exports.SANCTUARY_UNIT_16_EVIDENCE_LABELS = [
|
|
326
354
|
"unit-12c-1-opaque-identity",
|
|
327
355
|
"unit-14b-3-opaque-identity-live",
|
|
@@ -871,10 +899,8 @@ async function telegramBootstrap(config, deps) {
|
|
|
871
899
|
const noncePath = confinedPath(root, config.noncePath, "noncePath");
|
|
872
900
|
refuseExistingCheckpoint(root, noncePath);
|
|
873
901
|
const pollerAdapter = adapter(config.pollerAdapter, "pollerAdapter");
|
|
874
|
-
|
|
875
|
-
if (pollerAdapter !== PACKAGED_PROVENANCE_ADAPTER || vaultAdapter !== PACKAGED_PROVENANCE_ADAPTER) {
|
|
902
|
+
if (pollerAdapter !== PACKAGED_PROVENANCE_ADAPTER)
|
|
876
903
|
throw new Error("Telegram bootstrap requires the fixed packaged acceptance adapter");
|
|
877
|
-
}
|
|
878
904
|
const deadlineMs = integer(config.deadlineMs, "deadlineMs", 300_000);
|
|
879
905
|
if (deadlineMs > 900_000)
|
|
880
906
|
throw new Error("Telegram bootstrap deadline exceeds 15 minutes");
|
|
@@ -882,11 +908,17 @@ async function telegramBootstrap(config, deps) {
|
|
|
882
908
|
if (pollTimeoutSeconds > 50)
|
|
883
909
|
throw new Error("Telegram poll timeout exceeds 50 seconds");
|
|
884
910
|
const token = deps.readSecret().trim();
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
911
|
+
let getMe;
|
|
912
|
+
try {
|
|
913
|
+
getMe = await telegramRequest(deps, token, "getMe");
|
|
914
|
+
}
|
|
915
|
+
catch (error) {
|
|
916
|
+
throw telegramBootstrapRequestError("getMe", error);
|
|
917
|
+
}
|
|
918
|
+
const bot = object(getMe, "Telegram getMe result");
|
|
919
|
+
if (String(bot.id) !== expectedBotId || bot.username !== expectedUsername) {
|
|
920
|
+
throw new Error("Telegram bot identity mismatch; actor: human-required; repair vault runtime/config");
|
|
921
|
+
}
|
|
890
922
|
const nonce = deps.randomBytes(16).toString("hex");
|
|
891
923
|
const base = {
|
|
892
924
|
schemaVersion: 1,
|
|
@@ -909,11 +941,17 @@ async function telegramBootstrap(config, deps) {
|
|
|
909
941
|
let nextOffset = currentOffset;
|
|
910
942
|
let match;
|
|
911
943
|
while (deps.now() < deadline && !match) {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
944
|
+
let updates;
|
|
945
|
+
try {
|
|
946
|
+
updates = await telegramRequest(deps, token, "getUpdates", {
|
|
947
|
+
offset: nextOffset,
|
|
948
|
+
timeout: pollTimeoutSeconds,
|
|
949
|
+
allowed_updates: ["message"],
|
|
950
|
+
}, (pollTimeoutSeconds + 5) * 1_000);
|
|
951
|
+
}
|
|
952
|
+
catch (error) {
|
|
953
|
+
throw telegramBootstrapRequestError("getUpdates", error);
|
|
954
|
+
}
|
|
917
955
|
if (!Array.isArray(updates))
|
|
918
956
|
throw new Error("Telegram getUpdates result must be an array");
|
|
919
957
|
const parsed = updates.map((entry) => object(entry, "Telegram update"));
|
|
@@ -952,14 +990,20 @@ async function telegramBootstrap(config, deps) {
|
|
|
952
990
|
offsetDigest: digest(nextUpdateId),
|
|
953
991
|
};
|
|
954
992
|
replaceCheckpoint(root, evidencePath, confirmed);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
993
|
+
let stored;
|
|
994
|
+
try {
|
|
995
|
+
stored = await deps.mergeRuntime("sanctuary", {
|
|
996
|
+
telegramAuthorizedUserId: userId,
|
|
997
|
+
telegramAuthorizedChatId: chatId,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
catch {
|
|
1001
|
+
throw new Error("Telegram bootstrap vault update failed; actor: agent-runnable; retry Telegram bootstrap");
|
|
1002
|
+
}
|
|
1003
|
+
if (!stored.ok || stored.config.telegramBotToken !== token
|
|
1004
|
+
|| stored.config.telegramAuthorizedUserId !== userId || stored.config.telegramAuthorizedChatId !== chatId) {
|
|
1005
|
+
throw new Error("Telegram bootstrap vault readback failed; actor: agent-runnable; retry Telegram bootstrap");
|
|
1006
|
+
}
|
|
963
1007
|
replaceCheckpoint(root, evidencePath, { ...confirmed, phase: "vault_committed" });
|
|
964
1008
|
atomicPrivateJson(root, offsetPath, { nextUpdateId });
|
|
965
1009
|
replaceCheckpoint(root, evidencePath, { ...confirmed, phase: "complete", completedAt: deps.now() });
|
|
@@ -1779,9 +1823,11 @@ async function executeSanctuaryAcceptanceHarness(command, rawConfig, deps = crea
|
|
|
1779
1823
|
try {
|
|
1780
1824
|
const config = object(rawConfig, "acceptance config");
|
|
1781
1825
|
switch (command) {
|
|
1782
|
-
case "telegram-bootstrap":
|
|
1783
|
-
await
|
|
1826
|
+
case "telegram-bootstrap": {
|
|
1827
|
+
const token = await canonicalTelegramBootstrapToken(deps);
|
|
1828
|
+
await telegramBootstrap(config, { ...deps, readSecret: () => token });
|
|
1784
1829
|
break;
|
|
1830
|
+
}
|
|
1785
1831
|
case "cursor-snapshot":
|
|
1786
1832
|
await cursorSnapshot(config, deps);
|
|
1787
1833
|
break;
|
|
@@ -15,6 +15,7 @@ exports.assessStability = assessStability;
|
|
|
15
15
|
exports.renderStartupProgress = renderStartupProgress;
|
|
16
16
|
exports.renderWaitingForDaemon = renderWaitingForDaemon;
|
|
17
17
|
exports.pollDaemonStartup = pollDaemonStartup;
|
|
18
|
+
exports.defaultIsProcessAlive = defaultIsProcessAlive;
|
|
18
19
|
const cli_render_1 = require("./cli-render");
|
|
19
20
|
const runtime_1 = require("../../nerves/runtime");
|
|
20
21
|
const terminal_ui_1 = require("./terminal-ui");
|