@ouro.bot/cli 0.1.0-alpha.815 → 0.1.0-alpha.817
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/assets/sanctuary-host-launcher.sh +16 -0
- package/changelog.json +18 -0
- package/deploy/unraid/Dockerfile +6 -0
- package/deploy/unraid/README.txt +305 -365
- package/deploy/unraid/docker-man-template-transaction.mjs +192 -7
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +1 -1
- package/deploy/unraid/sanctuary-acceptance-contract.json +7 -7
- package/deploy/unraid/sanctuary-authority-installation.json +36 -0
- package/deploy/unraid/sanctuary-authority-service.sh +30 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +62 -9
- package/deploy/unraid/sanctuary-unit16-run.sh +15 -15
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
- package/deploy/unraid/sanctuary.xml +2 -1
- package/dist/heart/approval-store.js +11 -1
- package/dist/heart/core.js +2 -1
- package/dist/heart/daemon/container-spec-auditor-main.js +9 -8
- package/dist/heart/daemon/container-spec-auditor.js +17 -24
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +95 -91
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +95 -169
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +4 -5
- package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
- package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
- package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
- package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +780 -0
- package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
- package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
- package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
- package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
- package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
- package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
- package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-service.js +615 -0
- package/dist/heart/daemon/sense-manager.js +20 -10
- package/dist/heart/external-events/router.js +31 -15
- package/dist/heart/steward-policy.js +374 -58
- package/dist/heart/tool-approval.js +8 -1
- package/dist/repertoire/relationship-authorization.js +128 -0
- package/dist/repertoire/tools-base.js +9 -13
- package/dist/repertoire/tools-sanctuary-host.js +202 -0
- package/dist/repertoire/tools-steward-policy.js +34 -10
- package/dist/repertoire/tools-unraid.js +79 -32
- package/dist/repertoire/tools.js +37 -25
- package/dist/repertoire/unraid-restart.js +179 -52
- package/dist/senses/private-runtime.js +27 -12
- package/dist/senses/root-host-approval-port.js +345 -0
- package/dist/senses/root-host-approval-runtime.js +393 -0
- package/dist/senses/sanctuary-authority-resident.js +102 -0
- package/dist/senses/sanctuary-health-runner.js +0 -1
- package/dist/senses/sanctuary-media-catalog-contract.js +4 -1
- package/dist/senses/sanctuary-runtime.js +2 -0
- package/dist/senses/telegram-admission.js +7 -0
- package/dist/senses/telegram-approval-runtime.js +130 -12
- package/dist/senses/telegram-attachments.js +5 -1
- package/dist/senses/telegram-authority-transport.js +231 -0
- package/dist/senses/telegram-client.js +31 -8
- package/dist/senses/telegram-entry.js +1 -1
- package/dist/senses/telegram.js +174 -28
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -161,6 +161,9 @@ function runtimeConfigUnavailableDetail(agent, runtimeConfig) {
|
|
|
161
161
|
return `missing vault ${itemName} (${agent})`;
|
|
162
162
|
return `vault ${itemName} unavailable (${compactRuntimeConfigError(agent, runtimeConfig.error)})`;
|
|
163
163
|
}
|
|
164
|
+
function needsMachineRuntimeConfig(agent, sense) {
|
|
165
|
+
return sense === "bluebubbles" || sense === "voice" || sense === "a2a" || (agent === "sanctuary" && sense === "telegram");
|
|
166
|
+
}
|
|
164
167
|
function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent), workbenchHasStaleBundleEntry = false) {
|
|
165
168
|
const base = {
|
|
166
169
|
cli: { configured: true, detail: "local interactive terminal" },
|
|
@@ -183,7 +186,14 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
|
|
|
183
186
|
const integrations = payload.integrations;
|
|
184
187
|
const voice = machinePayload.voice;
|
|
185
188
|
const a2a = machinePayload.a2a;
|
|
186
|
-
if (senses.telegram.enabled) {
|
|
189
|
+
if (senses.telegram.enabled && agent === "sanctuary") {
|
|
190
|
+
const ready = runtimeConfig.ok && machineRuntimeConfig.ok
|
|
191
|
+
&& !Object.hasOwn(payload, "telegramBotToken") && !Object.hasOwn(machinePayload, "telegramBotToken");
|
|
192
|
+
base.telegram = ready
|
|
193
|
+
? { configured: true, detail: "root authority transport; gateway validated at process startup" }
|
|
194
|
+
: { configured: false, detail: "root authority migration requires available tokenless credential inventories" };
|
|
195
|
+
}
|
|
196
|
+
else if (senses.telegram.enabled) {
|
|
187
197
|
const missing = ["telegramBotToken", "telegramAuthorizedUserId", "telegramAuthorizedChatId"]
|
|
188
198
|
.filter((field) => !textField(payload, field));
|
|
189
199
|
base.telegram = missing.length === 0
|
|
@@ -355,7 +365,9 @@ function senseRepairHint(agent, sense) {
|
|
|
355
365
|
return `Agent-runnable: run 'ouro connect a2a --agent ${agent}', then restart with 'ouro up'.`;
|
|
356
366
|
}
|
|
357
367
|
if (sense === "telegram") {
|
|
358
|
-
return
|
|
368
|
+
return agent === "sanctuary"
|
|
369
|
+
? "Agent-runnable: complete or recover the root authority migration; do not restore a resident Telegram token."
|
|
370
|
+
: `Agent-runnable: store Telegram bot/user/chat coordinates with 'ouro connect telegram --agent ${agent}', then restart with 'ouro up'.`;
|
|
359
371
|
}
|
|
360
372
|
/* v8 ignore next -- Workbench is deliberately not daemon-managed, so getSenseInventory never asks the daemon manager for a repair hint @preserve */
|
|
361
373
|
if (sense === "workbench") {
|
|
@@ -655,10 +667,10 @@ class DaemonSenseManager {
|
|
|
655
667
|
this.configRefreshRetryTimers.delete(name);
|
|
656
668
|
this.configRefreshRetryAttempts.delete(name);
|
|
657
669
|
}
|
|
658
|
-
shouldRetryConfigRefresh(sense, runtimeConfig, machineRuntimeConfig) {
|
|
670
|
+
shouldRetryConfigRefresh(agent, sense, runtimeConfig, machineRuntimeConfig) {
|
|
659
671
|
if (!runtimeConfig.ok && runtimeConfig.reason === "unavailable")
|
|
660
672
|
return true;
|
|
661
|
-
if ((
|
|
673
|
+
if (needsMachineRuntimeConfig(agent, sense) &&
|
|
662
674
|
!machineRuntimeConfig.ok &&
|
|
663
675
|
machineRuntimeConfig.reason === "unavailable")
|
|
664
676
|
return true;
|
|
@@ -668,7 +680,7 @@ class DaemonSenseManager {
|
|
|
668
680
|
let retryAfterMs = null;
|
|
669
681
|
try {
|
|
670
682
|
const refreshed = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(parsed.agent, { preserveCachedOnFailure: true });
|
|
671
|
-
const machineRefreshed = parsed.
|
|
683
|
+
const machineRefreshed = needsMachineRuntimeConfig(parsed.agent, parsed.sense)
|
|
672
684
|
? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(parsed.agent, currentMachineId(), { preserveCachedOnFailure: true })
|
|
673
685
|
: (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent);
|
|
674
686
|
const context = this.contexts.get(parsed.agent);
|
|
@@ -677,7 +689,7 @@ class DaemonSenseManager {
|
|
|
677
689
|
return;
|
|
678
690
|
context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, refreshed, machineRefreshed, context.workbenchHasStaleBundleEntry);
|
|
679
691
|
if (!context.facts[parsed.sense].configured) {
|
|
680
|
-
if (this.shouldRetryConfigRefresh(parsed.sense, refreshed, machineRefreshed)) {
|
|
692
|
+
if (this.shouldRetryConfigRefresh(parsed.agent, parsed.sense, refreshed, machineRefreshed)) {
|
|
681
693
|
retryAfterMs = this.nextConfigRetryDelayMs(name);
|
|
682
694
|
}
|
|
683
695
|
else {
|
|
@@ -723,13 +735,11 @@ class DaemonSenseManager {
|
|
|
723
735
|
/* v8 ignore next -- periodic refresh work only exists when a managed background sense is enabled @preserve */
|
|
724
736
|
if (enabledManagedSenses.length === 0)
|
|
725
737
|
return;
|
|
726
|
-
/* v8 ignore start -- periodic freshness refresh uses the same runtime readers covered by startup integration tests @preserve */
|
|
727
738
|
const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
|
|
728
|
-
const needsMachineConfig = enabledManagedSenses.some((sense) =>
|
|
739
|
+
const needsMachineConfig = enabledManagedSenses.some((sense) => needsMachineRuntimeConfig(agent, sense));
|
|
729
740
|
const machineRuntimeConfig = needsMachineConfig
|
|
730
741
|
? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(), { preserveCachedOnFailure: true })
|
|
731
742
|
: (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent);
|
|
732
|
-
/* v8 ignore stop */
|
|
733
743
|
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, runtimeConfig, machineRuntimeConfig, context.workbenchHasStaleBundleEntry);
|
|
734
744
|
});
|
|
735
745
|
await Promise.all(refreshes);
|
|
@@ -835,7 +845,7 @@ class DaemonSenseManager {
|
|
|
835
845
|
return null;
|
|
836
846
|
const managedName = `${parsed.agent}:${parsed.sense}`;
|
|
837
847
|
const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(parsed.agent, { preserveCachedOnFailure: true });
|
|
838
|
-
const needsMachineConfig = parsed.
|
|
848
|
+
const needsMachineConfig = needsMachineRuntimeConfig(parsed.agent, parsed.sense);
|
|
839
849
|
const machineRuntimeConfig = needsMachineConfig
|
|
840
850
|
? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(parsed.agent, currentMachineId(), { preserveCachedOnFailure: true })
|
|
841
851
|
: (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent);
|
|
@@ -290,10 +290,7 @@ function withRecordLock(recordPath, operation) {
|
|
|
290
290
|
}
|
|
291
291
|
};
|
|
292
292
|
acquire();
|
|
293
|
-
|
|
294
|
-
return operation();
|
|
295
|
-
}
|
|
296
|
-
finally {
|
|
293
|
+
const release = () => {
|
|
297
294
|
try {
|
|
298
295
|
/* v8 ignore else -- concurrency fence: a replaced owner must not release its successor's lock @preserve */
|
|
299
296
|
if (fs.readFileSync(ownerPath, "utf8") === owner) {
|
|
@@ -304,6 +301,17 @@ function withRecordLock(recordPath, operation) {
|
|
|
304
301
|
catch {
|
|
305
302
|
// A stale owner must never remove a successor's lock.
|
|
306
303
|
}
|
|
304
|
+
};
|
|
305
|
+
try {
|
|
306
|
+
const result = operation();
|
|
307
|
+
if (result instanceof Promise)
|
|
308
|
+
return result.finally(release);
|
|
309
|
+
release();
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
release();
|
|
314
|
+
throw error;
|
|
307
315
|
}
|
|
308
316
|
}
|
|
309
317
|
function isRecord(value) {
|
|
@@ -1174,18 +1182,26 @@ function claimExternalEvent(recordPath, input) {
|
|
|
1174
1182
|
}, now);
|
|
1175
1183
|
});
|
|
1176
1184
|
}
|
|
1177
|
-
function
|
|
1185
|
+
function renewClaimLocked(recordPath, input, requireCurrent) {
|
|
1186
|
+
const record = readExternalEventRecord(recordPath);
|
|
1187
|
+
if (record.executionState !== "running" || record.claimOwner !== input.owner)
|
|
1188
|
+
throw new Error("External event claim owner mismatch");
|
|
1189
|
+
if (record.generation !== input.expectedGeneration)
|
|
1190
|
+
throw new Error("External event generation mismatch");
|
|
1191
|
+
const leaseMs = input.leaseMs ?? 30_000;
|
|
1192
|
+
if (!Number.isSafeInteger(leaseMs) || leaseMs <= 0)
|
|
1193
|
+
throw new Error("External event claim renewal is invalid");
|
|
1194
|
+
const now = input.now?.() ?? new Date().toISOString();
|
|
1195
|
+
if (requireCurrent && !(Date.parse(record.claimExpiresAt ?? "") > Date.parse(now)))
|
|
1196
|
+
throw new Error("External event claim expired");
|
|
1197
|
+
return commitMutation(recordPath, { ...record, claimExpiresAt: new Date(Date.parse(now) + leaseMs).toISOString() }, now);
|
|
1198
|
+
}
|
|
1199
|
+
function renewExternalEventClaim(recordPath, input, operation) {
|
|
1178
1200
|
return withRecordLock(recordPath, () => {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
throw new Error("External event generation mismatch");
|
|
1184
|
-
const leaseMs = input.leaseMs ?? 30_000;
|
|
1185
|
-
if (!Number.isSafeInteger(leaseMs) || leaseMs <= 0)
|
|
1186
|
-
throw new Error("External event claim renewal is invalid");
|
|
1187
|
-
const now = input.now?.() ?? new Date().toISOString();
|
|
1188
|
-
return commitMutation(recordPath, { ...record, claimExpiresAt: new Date(Date.parse(now) + leaseMs).toISOString() }, now);
|
|
1201
|
+
if (!operation)
|
|
1202
|
+
return renewClaimLocked(recordPath, input, false);
|
|
1203
|
+
const signal = AbortSignal.timeout(25_000);
|
|
1204
|
+
return operation(renewClaimLocked(recordPath, input, true), signal);
|
|
1189
1205
|
});
|
|
1190
1206
|
}
|
|
1191
1207
|
function assertClaim(record, input) {
|