@ouro.bot/cli 0.1.0-alpha.766 → 0.1.0-alpha.767
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 +8 -0
- package/deploy/unraid/README.txt +96 -5
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +0 -2
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -2
- package/dist/heart/daemon/container-spec-auditor.js +3 -5
- package/dist/senses/sanctuary-full-visibility-contract.js +62 -16
- package/dist/senses/sanctuary-runtime.js +16 -2
- package/dist/senses/sanctuary-sab.js +24 -19
- package/dist/senses/shared-turn.js +3 -2
- package/dist/senses/telegram.js +28 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.767",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Make Sanctuary status turns preserve completed live checks and report bounded download-queue unavailability without retries, stale guesses, or empty replies.",
|
|
8
|
+
"Move SAB queue access into the machine vault with guarded one-shot provisioning, live readiness verification, and no persistent host INI mount.",
|
|
9
|
+
"Share the owner-status completion fallback between Telegram's admitted-message replay and normal message paths, and cover its inactive, incomplete, and complete states."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.766",
|
|
6
14
|
"changes": [
|
package/deploy/unraid/README.txt
CHANGED
|
@@ -793,6 +793,8 @@ Effective-spec audit helper:
|
|
|
793
793
|
bootstrap_sanctuary_vault "$IMAGE_ID" \
|
|
794
794
|
/mnt/user/appdata/ouro-butler/runtime/container-credentials.json \
|
|
795
795
|
sanctuary-unraid sanctuary || return $?
|
|
796
|
+
provision_sanctuary_sab_credential "$IMAGE_ID" || return $?
|
|
797
|
+
verify_sanctuary_sab_readiness "$IMAGE_ID" || return $?
|
|
796
798
|
validate_sanctuary_legacy_staging "$PREPARED_LEGACY_CONTAINER_ID" "$PREPARED_LEGACY_IMAGE_ID" || return $?
|
|
797
799
|
LEGACY_STAGING_CONTAINER_ID=$PREPARED_LEGACY_CONTAINER_ID
|
|
798
800
|
LEGACY_STAGING_IMAGE_ID=$PREPARED_LEGACY_IMAGE_ID
|
|
@@ -938,7 +940,6 @@ Effective-spec audit helper:
|
|
|
938
940
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli,dst=/home/ouro/.ouro-cli" \
|
|
939
941
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
|
|
940
942
|
--mount "type=bind,src=/boot/config/custom/ouro-events/spool,dst=/run/ouro-events,readonly" \
|
|
941
|
-
--mount "type=bind,src=/mnt/user/appdata/sabnzbd/sabnzbd.ini,dst=/run/sanctuary/sabnzbd.ini,readonly" \
|
|
942
943
|
"$IMAGE_ID" \
|
|
943
944
|
&& audit_effective ouro-butler-staging "$IMAGE_ID" "$IMAGE_ID" \
|
|
944
945
|
&& docker start ouro-butler-staging \
|
|
@@ -952,7 +953,6 @@ Effective-spec audit helper:
|
|
|
952
953
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli,dst=/home/ouro/.ouro-cli" \
|
|
953
954
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
|
|
954
955
|
--mount "type=bind,src=/boot/config/custom/ouro-events/spool,dst=/run/ouro-events,readonly" \
|
|
955
|
-
--mount "type=bind,src=/mnt/user/appdata/sabnzbd/sabnzbd.ini,dst=/run/sanctuary/sabnzbd.ini,readonly" \
|
|
956
956
|
"$IMAGE_ID" \
|
|
957
957
|
&& audit_effective ouro-butler "$IMAGE_ID" "$IMAGE_ID" \
|
|
958
958
|
&& docker start ouro-butler \
|
|
@@ -1187,6 +1187,75 @@ local unlock: available
|
|
|
1187
1187
|
fi
|
|
1188
1188
|
validate_sanctuary_roots "$BOOTSTRAP_RUNTIME_ROOT" "$BOOTSTRAP_AGENT_ROOT" || return $?
|
|
1189
1189
|
}
|
|
1190
|
+
provision_sanctuary_sab_credential() {
|
|
1191
|
+
SAB_BOOTSTRAP_IMAGE_ID=$1
|
|
1192
|
+
validate_exact_image_id "$SAB_BOOTSTRAP_IMAGE_ID" || return $?
|
|
1193
|
+
SAB_BOOTSTRAP_SOURCE=/mnt/user/appdata/sabnzbd/sabnzbd.ini
|
|
1194
|
+
SAB_BOOTSTRAP_RUNTIME_ROOT=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli
|
|
1195
|
+
SAB_BOOTSTRAP_AGENT_ROOT=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro
|
|
1196
|
+
SAB_BOOTSTRAP_ENVELOPE="$SAB_BOOTSTRAP_RUNTIME_ROOT/container-credentials.json"
|
|
1197
|
+
SAB_BOOTSTRAP_CLAIM="$SAB_BOOTSTRAP_ENVELOPE.consuming"
|
|
1198
|
+
test -f "$SAB_BOOTSTRAP_SOURCE" || return $?
|
|
1199
|
+
test ! -L "$SAB_BOOTSTRAP_SOURCE" || return 1
|
|
1200
|
+
test ! -e "$SAB_BOOTSTRAP_ENVELOPE" || return 1
|
|
1201
|
+
test ! -e "$SAB_BOOTSTRAP_CLAIM" || return 1
|
|
1202
|
+
! docker container inspect ouro-butler-sab-credential-bootstrap >/dev/null 2>&1 || return 1
|
|
1203
|
+
SAB_BOOTSTRAP_TMP=$(mktemp "$SAB_BOOTSTRAP_RUNTIME_ROOT/container-credentials.json.tmp.XXXXXX") || return $?
|
|
1204
|
+
trap 'rm -f -- "$SAB_BOOTSTRAP_TMP"' EXIT || return $?
|
|
1205
|
+
/usr/local/bin/node -e '
|
|
1206
|
+
const fs = require("node:fs");
|
|
1207
|
+
const [source, destination] = process.argv.slice(1);
|
|
1208
|
+
const match = fs.readFileSync(source, "utf8").match(/^\s*api_key\s*=\s*(\S+)\s*$/mu);
|
|
1209
|
+
if (!match || !match[1]) process.exit(1);
|
|
1210
|
+
const credential = { type: "ouro.runtimeCredentialBootstrap", agentName: "sanctuary", machineId: "sanctuary", machineRuntimeConfig: { sabnzbdApiKey: match[1] } };
|
|
1211
|
+
const envelope = { schemaVersion: 1, credentials: [credential] };
|
|
1212
|
+
fs.writeFileSync(destination, `${JSON.stringify(envelope)}\n`, { mode: 0o600, flag: "w" });
|
|
1213
|
+
' "$SAB_BOOTSTRAP_SOURCE" "$SAB_BOOTSTRAP_TMP" || return $?
|
|
1214
|
+
chown 10001:10001 "$SAB_BOOTSTRAP_TMP" || return $?
|
|
1215
|
+
chmod 0600 "$SAB_BOOTSTRAP_TMP" || return $?
|
|
1216
|
+
sync -f "$SAB_BOOTSTRAP_TMP" || return $?
|
|
1217
|
+
mv -f -- "$SAB_BOOTSTRAP_TMP" "$SAB_BOOTSTRAP_ENVELOPE" || return $?
|
|
1218
|
+
sync -f "$SAB_BOOTSTRAP_RUNTIME_ROOT" || return $?
|
|
1219
|
+
trap - EXIT || return $?
|
|
1220
|
+
docker run --rm --pull=never --network host --name ouro-butler-sab-credential-bootstrap --user 10001:10001 \
|
|
1221
|
+
--mount "type=bind,src=$SAB_BOOTSTRAP_RUNTIME_ROOT,dst=/home/ouro/.ouro-cli" \
|
|
1222
|
+
--mount "type=bind,src=$SAB_BOOTSTRAP_AGENT_ROOT,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
|
|
1223
|
+
--entrypoint node "$SAB_BOOTSTRAP_IMAGE_ID" -e '
|
|
1224
|
+
const { loadContainerCredentialBootstrap } = require("/opt/ouro/dist/heart/daemon/container-credential-bootstrap.js");
|
|
1225
|
+
loadContainerCredentialBootstrap(["sanctuary"]).then(
|
|
1226
|
+
() => process.exit(0),
|
|
1227
|
+
() => { process.stderr.write("SAB credential bootstrap failed\n"); process.exit(1); },
|
|
1228
|
+
);
|
|
1229
|
+
' || return $?
|
|
1230
|
+
! docker container inspect ouro-butler-sab-credential-bootstrap >/dev/null 2>&1 || return 1
|
|
1231
|
+
test ! -e "$SAB_BOOTSTRAP_ENVELOPE" || return 1
|
|
1232
|
+
test ! -e "$SAB_BOOTSTRAP_CLAIM" || return 1
|
|
1233
|
+
}
|
|
1234
|
+
verify_sanctuary_sab_readiness() {
|
|
1235
|
+
SAB_READINESS_IMAGE_ID=$1
|
|
1236
|
+
validate_exact_image_id "$SAB_READINESS_IMAGE_ID" || return $?
|
|
1237
|
+
! docker container inspect ouro-butler-sab-readiness >/dev/null 2>&1 || return 1
|
|
1238
|
+
docker run --rm --pull=never --network host --name ouro-butler-sab-readiness --user 10001:10001 \
|
|
1239
|
+
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli,dst=/home/ouro/.ouro-cli" \
|
|
1240
|
+
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
|
|
1241
|
+
--entrypoint node "$SAB_READINESS_IMAGE_ID" -e '
|
|
1242
|
+
(async () => {
|
|
1243
|
+
try {
|
|
1244
|
+
const { refreshMachineRuntimeCredentialConfig } = require("/opt/ouro/dist/heart/runtime-credentials.js");
|
|
1245
|
+
const { createSanctuarySabClient } = require("/opt/ouro/dist/senses/sanctuary-sab.js");
|
|
1246
|
+
const current = await refreshMachineRuntimeCredentialConfig("sanctuary", "sanctuary");
|
|
1247
|
+
if (!current.ok || typeof current.config.sabnzbdApiKey !== "string" || !current.config.sabnzbdApiKey.trim()) throw new Error();
|
|
1248
|
+
const snapshot = await createSanctuarySabClient({ loadApiKey: async () => current.config.sabnzbdApiKey }).readQueue();
|
|
1249
|
+
if (typeof snapshot.paused !== "boolean" || !Number.isSafeInteger(snapshot.queuedJobs) || !snapshot.observedAt) throw new Error();
|
|
1250
|
+
process.stdout.write("Sanctuary SAB queue readiness verified.\n");
|
|
1251
|
+
} catch {
|
|
1252
|
+
process.stderr.write("Sanctuary SAB queue readiness verification failed.\n");
|
|
1253
|
+
process.exitCode = 1;
|
|
1254
|
+
}
|
|
1255
|
+
})();
|
|
1256
|
+
' || return $?
|
|
1257
|
+
! docker container inspect ouro-butler-sab-readiness >/dev/null 2>&1 || return 1
|
|
1258
|
+
}
|
|
1190
1259
|
authenticate_sanctuary_provider() {
|
|
1191
1260
|
AUTH_IMAGE_ID=$1
|
|
1192
1261
|
AUTH_PROVIDER=${2-}
|
|
@@ -1606,6 +1675,13 @@ Update:
|
|
|
1606
1675
|
"$IMAGE_ID" --template /audit/sanctuary.xml --runtime-policy /audit/container-runtime.json --expected-image "$IMAGE_ID"
|
|
1607
1676
|
cleanup_event_asset_stage
|
|
1608
1677
|
trap - EXIT
|
|
1678
|
+
if provision_sanctuary_sab_credential "$IMAGE_ID" \
|
|
1679
|
+
&& verify_sanctuary_sab_readiness "$IMAGE_ID"; then
|
|
1680
|
+
:
|
|
1681
|
+
else
|
|
1682
|
+
SAB_READINESS_STATUS=$?
|
|
1683
|
+
(exit "$SAB_READINESS_STATUS")
|
|
1684
|
+
fi
|
|
1609
1685
|
Guard the atomic autostart disable separately. If it fails, production has not
|
|
1610
1686
|
been touched and the captured status is propagated:
|
|
1611
1687
|
if disable_butler_autostart; then
|
|
@@ -1712,7 +1788,6 @@ ouro-butler-rollback
|
|
|
1712
1788
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli,dst=/home/ouro/.ouro-cli" \
|
|
1713
1789
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
|
|
1714
1790
|
--mount "type=bind,src=/boot/config/custom/ouro-events/spool,dst=/run/ouro-events,readonly" \
|
|
1715
|
-
--mount "type=bind,src=/mnt/user/appdata/sabnzbd/sabnzbd.ini,dst=/run/sanctuary/sabnzbd.ini,readonly" \
|
|
1716
1791
|
"$IMAGE_ID" \
|
|
1717
1792
|
&& audit_effective ouro-butler-staging "$IMAGE_ID" "$AUDIT_RUNNER_IMAGE_ID" \
|
|
1718
1793
|
&& docker start ouro-butler-staging \
|
|
@@ -1761,7 +1836,6 @@ ouro-butler-rollback
|
|
|
1761
1836
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli,dst=/home/ouro/.ouro-cli" \
|
|
1762
1837
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
|
|
1763
1838
|
--mount "type=bind,src=/boot/config/custom/ouro-events/spool,dst=/run/ouro-events,readonly" \
|
|
1764
|
-
--mount "type=bind,src=/mnt/user/appdata/sabnzbd/sabnzbd.ini,dst=/run/sanctuary/sabnzbd.ini,readonly" \
|
|
1765
1839
|
"$IMAGE_ID" \
|
|
1766
1840
|
&& audit_effective ouro-butler "$IMAGE_ID" "$AUDIT_RUNNER_IMAGE_ID" \
|
|
1767
1841
|
&& docker start ouro-butler \
|
|
@@ -2065,7 +2139,6 @@ Restore:
|
|
|
2065
2139
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/runtime/.ouro-cli,dst=/home/ouro/.ouro-cli" \
|
|
2066
2140
|
--mount "type=bind,src=/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro,dst=/home/ouro/AgentBundles/sanctuary.ouro" \
|
|
2067
2141
|
--mount "type=bind,src=/boot/config/custom/ouro-events/spool,dst=/run/ouro-events,readonly" \
|
|
2068
|
-
--mount "type=bind,src=/mnt/user/appdata/sabnzbd/sabnzbd.ini,dst=/run/sanctuary/sabnzbd.ini,readonly" \
|
|
2069
2142
|
"$IMAGE_ID" \
|
|
2070
2143
|
&& audit_effective ouro-butler "$IMAGE_ID" "$AUDIT_RUNNER_IMAGE_ID" \
|
|
2071
2144
|
&& docker start ouro-butler \
|
|
@@ -2131,6 +2204,24 @@ Credential recovery:
|
|
|
2131
2204
|
Never print either envelope's contents or place them in logs or command arguments.
|
|
2132
2205
|
After successful import, both files
|
|
2133
2206
|
are absent and the vault is the only credential source of truth.
|
|
2207
|
+
`provision_sanctuary_sab_credential "$IMAGE_ID"` is the one-shot migration for
|
|
2208
|
+
the existing SAB key. Root reads the fixed host INI, writes only a mode-0600
|
|
2209
|
+
`ouro.runtimeCredentialBootstrap` envelope owned by 10001:10001, and a
|
|
2210
|
+
same-image one-shot container merges `sabnzbdApiKey` into
|
|
2211
|
+
`runtime/machines/sanctuary/config`; the key never appears in argv, the
|
|
2212
|
+
environment, logs, the Unraid template, or the Butler mounts. The bootstrap
|
|
2213
|
+
reconciler is idempotent for the same value and fails closed on a conflict.
|
|
2214
|
+
`verify_sanctuary_sab_readiness "$IMAGE_ID"` then reloads that machine item
|
|
2215
|
+
from the vault and performs a real bounded queue read before any poller
|
|
2216
|
+
cutover. The runtime and agent roots are persistent binds and package-managed
|
|
2217
|
+
updates do not replace the machine vault item, so the credential survives
|
|
2218
|
+
template and image updates.
|
|
2219
|
+
Bootstrap is not the rotation path. After SAB rotates its key, use the
|
|
2220
|
+
existing hidden-prompt merge command
|
|
2221
|
+
`ouro vault config set --agent sanctuary --key sabnzbdApiKey --scope machine`,
|
|
2222
|
+
restart the reviewed Butler container so its machine-config cache refreshes,
|
|
2223
|
+
and rerun `verify_sanctuary_sab_readiness "$IMAGE_ID"`; a conflicting
|
|
2224
|
+
bootstrap is expected to stop rather than overwrite canonical vault state.
|
|
2134
2225
|
Never print or place credential values in logs, templates, command arguments,
|
|
2135
2226
|
backups, or this runbook.
|
|
2136
2227
|
|
|
@@ -18,7 +18,6 @@ const RUNTIME_POLICY_FILE = "/opt/ouro/container-runtime.json"
|
|
|
18
18
|
const PRODUCTION_RUNTIME_SOURCE = "/mnt/user/appdata/ouro-butler/runtime/.ouro-cli"
|
|
19
19
|
const PRODUCTION_BUNDLE_SOURCE = "/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro"
|
|
20
20
|
const PRODUCTION_EVENT_SPOOL_SOURCE = "/boot/config/custom/ouro-events/spool"
|
|
21
|
-
const PRODUCTION_SAB_CONFIG_SOURCE = "/mnt/user/appdata/sabnzbd/sabnzbd.ini"
|
|
22
21
|
const GRAPHQL_ENDPOINT = "http://127.0.0.1/graphql"
|
|
23
22
|
const BOOT_ID = "/proc/sys/kernel/random/boot_id"
|
|
24
23
|
const MDCMD = "/usr/local/sbin/mdcmd"
|
|
@@ -392,7 +391,6 @@ async function containerSnapshot(expectedImage) {
|
|
|
392
391
|
{ destination: "/home/ouro/.ouro-cli", source: PRODUCTION_RUNTIME_SOURCE, mode: "rw", propagation: "rprivate", rw: true, type: "bind" },
|
|
393
392
|
{ destination: "/home/ouro/AgentBundles/sanctuary.ouro", source: PRODUCTION_BUNDLE_SOURCE, mode: "rw", propagation: "rprivate", rw: true, type: "bind" },
|
|
394
393
|
{ destination: "/run/ouro-events", source: PRODUCTION_EVENT_SPOOL_SOURCE, mode: "ro", propagation: "rprivate", rw: false, type: "bind" },
|
|
395
|
-
{ destination: "/run/sanctuary/sabnzbd.ini", source: PRODUCTION_SAB_CONFIG_SOURCE, mode: "ro", propagation: "rprivate", rw: false, type: "bind" },
|
|
396
394
|
]
|
|
397
395
|
const mountsExact = mounts.length === expectedMounts.length && expectedMounts.every((expected) => mounts.some((mount) => mount.destination === expected.destination && mount.source === expected.source && mount.mode === expected.mode && mount.propagation === expected.propagation && mount.rw === expected.rw && mount.type === expected.type))
|
|
398
396
|
const securityExact = value.privileged === false && (value.capAdd === null || (Array.isArray(value.capAdd) && value.capAdd.length === 0))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>Mendelow Cloud Butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.767</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -24,5 +24,4 @@
|
|
|
24
24
|
<Config Name="Runtime state" Target="/home/ouro/.ouro-cli" Default="/mnt/user/appdata/ouro-butler/runtime/.ouro-cli" Mode="rw" Description="Machine-scoped runtime" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/ouro-butler/runtime/.ouro-cli</Config>
|
|
25
25
|
<Config Name="Agent bundle" Target="/home/ouro/AgentBundles/sanctuary.ouro" Default="/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro" Mode="rw" Description="Sanctuary bundle" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro</Config>
|
|
26
26
|
<Config Name="Privileged event spool" Target="/run/ouro-events" Default="/boot/config/custom/ouro-events/spool" Mode="ro" Description="Root-owned detector envelopes, read-only inside the Butler" Type="Path" Display="always" Required="true" Mask="false">/boot/config/custom/ouro-events/spool</Config>
|
|
27
|
-
<Config Name="SAB queue verification" Target="/run/sanctuary/sabnzbd.ini" Default="/mnt/user/appdata/sabnzbd/sabnzbd.ini" Mode="ro" Description="Existing SAB configuration used only for an independent read after a verified protective pause" Type="Path" Display="advanced" Required="true" Mask="false">/mnt/user/appdata/sabnzbd/sabnzbd.ini</Config>
|
|
28
27
|
</Container>
|
|
@@ -7,13 +7,11 @@ const EXPECTED_BINDS = [
|
|
|
7
7
|
"/mnt/user/appdata/ouro-butler/runtime/.ouro-cli:/home/ouro/.ouro-cli:rw",
|
|
8
8
|
"/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro:/home/ouro/AgentBundles/sanctuary.ouro:rw",
|
|
9
9
|
"/boot/config/custom/ouro-events/spool:/run/ouro-events:ro",
|
|
10
|
-
"/mnt/user/appdata/sabnzbd/sabnzbd.ini:/run/sanctuary/sabnzbd.ini:ro",
|
|
11
10
|
];
|
|
12
11
|
const EXPECTED_MOUNTS = [
|
|
13
12
|
["/mnt/user/appdata/ouro-butler/runtime/.ouro-cli", "/home/ouro/.ouro-cli", true, "rprivate"],
|
|
14
13
|
["/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro", "/home/ouro/AgentBundles/sanctuary.ouro", true, "rprivate"],
|
|
15
14
|
["/boot/config/custom/ouro-events/spool", "/run/ouro-events", false, "rprivate"],
|
|
16
|
-
["/mnt/user/appdata/sabnzbd/sabnzbd.ini", "/run/sanctuary/sabnzbd.ini", false, "rprivate"],
|
|
17
15
|
];
|
|
18
16
|
const LEGACY_ALPHA742_IMAGE = "sha256:681449ad47a2621705cd339b481e6339236b31dc65e195b1cf5025d0f2191d7d";
|
|
19
17
|
const LEGACY_ALPHA742_MOUNTS = EXPECTED_MOUNTS.slice(0, 2);
|
|
@@ -152,10 +150,10 @@ function auditSanctuaryStagedFiles(input) {
|
|
|
152
150
|
const mode = match[1].match(/\bMode="([^"]+)"/u)?.[1];
|
|
153
151
|
return type === "Path" && target && mode ? `${match[2]}:${target}:${mode}` : "invalid";
|
|
154
152
|
});
|
|
155
|
-
if (configOpenCount !==
|
|
156
|
-
|| configEntries.length !==
|
|
153
|
+
if (configOpenCount !== EXPECTED_BINDS.length
|
|
154
|
+
|| configEntries.length !== EXPECTED_BINDS.length
|
|
157
155
|
|| JSON.stringify([...pathConfigs].sort()) !== JSON.stringify([...EXPECTED_BINDS].sort())) {
|
|
158
|
-
violations.push("template Config entries must equal the canonical
|
|
156
|
+
violations.push("template Config entries must equal the canonical path binds");
|
|
159
157
|
}
|
|
160
158
|
const postArgsOpenCount = [...input.templateXml.matchAll(/<PostArgs\b/gu)].length;
|
|
161
159
|
const postArgs = [...input.templateXml.matchAll(/<PostArgs(?:(?:\s*\/>)|>([^<]*)<\/PostArgs>)/gu)];
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanctuaryFullVisibilityEmptyResponse = sanctuaryFullVisibilityEmptyResponse;
|
|
3
4
|
exports.sanctuaryFullVisibilityRequiredToolCalls = sanctuaryFullVisibilityRequiredToolCalls;
|
|
4
5
|
const runtime_1 = require("../nerves/runtime");
|
|
5
6
|
const REQUIRED_TOOL_NAMES = ["query_active_work", "query_cares", "unraid_get_system", "unraid_list_containers", "unraid_get_storage", "sanctuary_get_download_queue"];
|
|
6
7
|
const WHOLE_STATUS_REQUESTS = new Set(["what are you working on", "what's going on with sanctuary"]);
|
|
7
|
-
function unsupportedCurrentClaim(answer) {
|
|
8
|
+
function unsupportedCurrentClaim(answer, queueUnavailable) {
|
|
9
|
+
if (queueUnavailable && answer.trim().length === 0)
|
|
10
|
+
return "Give Ari the current results that did complete and say plainly that the download queue is currently unavailable; do not return an empty answer.";
|
|
11
|
+
if (queueUnavailable) {
|
|
12
|
+
const unsupportedQueueClaim = answer.split(/[,;!?\n]|(?<!\d)\.(?!\d)/u).some((clause) => {
|
|
13
|
+
if (!/\b(?:download(?:s| queue)?|queue|SABnzbd)\b/iu.test(clause))
|
|
14
|
+
return false;
|
|
15
|
+
const explicitlyUnverified = /\b(?:(?:cannot|can't|could not|couldn't|unable to) (?:currently )?(?:be )?(?:verif(?:y|ied)|read|check(?:ed)?|confirm(?:ed)?)|(?:do not|don't) know(?: whether)?|unknown|unavailable|unverified|stale|historical|previous|prior|failed)\b|\bneeds? (?:a )?(?:fresh |authoritative )*(?:check|read|verification)\b/iu.test(clause);
|
|
16
|
+
return !explicitlyUnverified;
|
|
17
|
+
});
|
|
18
|
+
if (unsupportedQueueClaim)
|
|
19
|
+
return "The current download-queue read is unavailable. Preserve the other current results, but do not claim a queue state; say plainly that downloads could not be verified.";
|
|
20
|
+
}
|
|
8
21
|
const unsupported = answer.replace(/docker\.img/giu, "docker image").split(/[,;!?\n]|(?<!\d)\.(?!\d)/u).some((sentence) => {
|
|
9
22
|
if (!/docker image(?: disk)?/iu.test(sentence))
|
|
10
23
|
return false;
|
|
@@ -24,43 +37,76 @@ function unsupportedCurrentClaim(answer) {
|
|
|
24
37
|
});
|
|
25
38
|
return unsupportedProviderClaim ? "The current queue read does not prove provider credit or authentication status. Do not report Astraweb, block-credit, or authentication failure as current; say that the provider needs a fresh authoritative check." : undefined;
|
|
26
39
|
}
|
|
40
|
+
function exactQueueUnavailableResult(value) {
|
|
41
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
42
|
+
return false;
|
|
43
|
+
const result = value;
|
|
44
|
+
if (JSON.stringify(Object.keys(result).sort()) !== JSON.stringify(["error", "observedAt", "ok"]))
|
|
45
|
+
return false;
|
|
46
|
+
if (result.ok !== false || typeof result.observedAt !== "string" || !Number.isFinite(Date.parse(result.observedAt)) || new Date(result.observedAt).toISOString() !== result.observedAt)
|
|
47
|
+
return false;
|
|
48
|
+
const error = result.error;
|
|
49
|
+
return !!error && typeof error === "object" && !Array.isArray(error)
|
|
50
|
+
&& JSON.stringify(Object.keys(error).sort()) === JSON.stringify(["code"])
|
|
51
|
+
&& ["credential_unavailable", "request_unavailable", "malformed_response"].includes(String(error.code));
|
|
52
|
+
}
|
|
27
53
|
function successfulCurrentResult(name, result) {
|
|
28
54
|
if (Buffer.byteLength(result, "utf8") > 1_000_000)
|
|
29
|
-
return false;
|
|
55
|
+
return { valid: false, queueUnavailable: false };
|
|
30
56
|
if (name === "query_active_work")
|
|
31
|
-
return result.trimStart().startsWith("this is my current top-level live world-state.");
|
|
57
|
+
return { valid: result.trimStart().startsWith("this is my current top-level live world-state."), queueUnavailable: false };
|
|
32
58
|
try {
|
|
33
59
|
const parsed = JSON.parse(result);
|
|
34
60
|
if (name === "query_cares")
|
|
35
|
-
return Array.isArray(parsed);
|
|
61
|
+
return { valid: Array.isArray(parsed), queueUnavailable: false };
|
|
36
62
|
if (name === "sanctuary_get_download_queue") {
|
|
63
|
+
if (exactQueueUnavailableResult(parsed))
|
|
64
|
+
return { valid: true, queueUnavailable: true };
|
|
37
65
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
38
|
-
return false;
|
|
66
|
+
return { valid: false, queueUnavailable: false };
|
|
39
67
|
const queue = parsed;
|
|
40
68
|
const exactKeys = ["observedAt", "paused", "queuedJobs", "stateDigest", "status"];
|
|
41
|
-
return JSON.stringify(Object.keys(queue).sort()) === JSON.stringify(exactKeys)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
69
|
+
return { valid: JSON.stringify(Object.keys(queue).sort()) === JSON.stringify(exactKeys)
|
|
70
|
+
&& typeof queue.paused === "boolean"
|
|
71
|
+
&& typeof queue.status === "string" && Buffer.byteLength(queue.status, "utf8") <= 64
|
|
72
|
+
&& Number.isSafeInteger(queue.queuedJobs) && Number(queue.queuedJobs) >= 0 && Number(queue.queuedJobs) <= 1_000_000
|
|
73
|
+
&& typeof queue.observedAt === "string" && Number.isFinite(Date.parse(queue.observedAt)) && new Date(queue.observedAt).toISOString() === queue.observedAt
|
|
74
|
+
&& typeof queue.stateDigest === "string" && /^[a-f0-9]{64}$/u.test(queue.stateDigest), queueUnavailable: false };
|
|
47
75
|
}
|
|
48
|
-
return !!parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed.ok === true;
|
|
76
|
+
return { valid: !!parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed.ok === true, queueUnavailable: false };
|
|
49
77
|
}
|
|
50
78
|
catch {
|
|
51
|
-
return false;
|
|
79
|
+
return { valid: false, queueUnavailable: false };
|
|
52
80
|
}
|
|
53
81
|
}
|
|
82
|
+
function normalizedRequest(request) {
|
|
83
|
+
return request.normalize("NFKC").trim().toLocaleLowerCase("en-US").replaceAll("’", "'").replace(/[?!.\s]+$/gu, "");
|
|
84
|
+
}
|
|
85
|
+
function sanctuaryFullVisibilityEmptyResponse(request) {
|
|
86
|
+
return WHOLE_STATUS_REQUESTS.has(normalizedRequest(request))
|
|
87
|
+
? "I couldn't finish a trustworthy Sanctuary status check because a current check was unavailable. I won't guess or reuse old alerts; please try again shortly."
|
|
88
|
+
: undefined;
|
|
89
|
+
}
|
|
54
90
|
function sanctuaryFullVisibilityRequiredToolCalls(request, advertisedToolNames) {
|
|
55
|
-
const normalized = request
|
|
91
|
+
const normalized = normalizedRequest(request);
|
|
56
92
|
if (!WHOLE_STATUS_REQUESTS.has(normalized) || !REQUIRED_TOOL_NAMES.every((name) => advertisedToolNames.includes(name)))
|
|
57
93
|
return undefined;
|
|
58
94
|
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.sanctuary_full_visibility_reads_required", message: "required current Sanctuary visibility reads", meta: { toolCount: REQUIRED_TOOL_NAMES.length } });
|
|
95
|
+
let queueUnavailable = false;
|
|
96
|
+
const completed = new Set();
|
|
59
97
|
return {
|
|
60
98
|
names: REQUIRED_TOOL_NAMES,
|
|
61
99
|
retryMessage: "Before answering, read current active work, cares, system health, service state, storage, and the download queue. Current tool facts outrank care history; a stale care is a recheck item, not a present-tense fact. Then give Ari one compact household summary; do not ask him to choose a status slice.",
|
|
62
100
|
requireSuccessfulResults: true,
|
|
63
|
-
validateRequiredToolResult:
|
|
64
|
-
|
|
101
|
+
validateRequiredToolResult: (name, result) => {
|
|
102
|
+
const validation = successfulCurrentResult(name, result);
|
|
103
|
+
if (name === "sanctuary_get_download_queue" && validation.valid)
|
|
104
|
+
queueUnavailable = validation.queueUnavailable;
|
|
105
|
+
if (validation.valid)
|
|
106
|
+
completed.add(name);
|
|
107
|
+
return validation.valid;
|
|
108
|
+
},
|
|
109
|
+
validateTerminalAnswer: (answer) => unsupportedCurrentClaim(answer, queueUnavailable),
|
|
110
|
+
emptyResponseFallback: () => REQUIRED_TOOL_NAMES.every((name) => completed.has(name)) ? sanctuaryFullVisibilityEmptyResponse(request) : undefined,
|
|
65
111
|
};
|
|
66
112
|
}
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.runWithSanctuaryToolReceiptCollection = runWithSanctuaryToolReceiptCollection;
|
|
37
|
+
exports.loadSanctuarySabApiKey = loadSanctuarySabApiKey;
|
|
37
38
|
exports.createSanctuaryToolContext = createSanctuaryToolContext;
|
|
38
39
|
const fs = __importStar(require("node:fs"));
|
|
39
40
|
const path = __importStar(require("node:path"));
|
|
@@ -92,6 +93,9 @@ function required(config, field) {
|
|
|
92
93
|
throw new Error(`Sanctuary ${field} is missing`);
|
|
93
94
|
return value.trim();
|
|
94
95
|
}
|
|
96
|
+
async function loadSanctuarySabApiKey(agentName) {
|
|
97
|
+
return required(machineConfig(agentName), "sabnzbdApiKey");
|
|
98
|
+
}
|
|
95
99
|
function requiredObject(config, field) {
|
|
96
100
|
const value = config[field];
|
|
97
101
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -192,7 +196,7 @@ function createSanctuaryToolContext(agentName) {
|
|
|
192
196
|
const endpoint = required(initial, "unraidGraphqlUrl");
|
|
193
197
|
const readClient = new unraid_client_1.UnraidClient({ endpoint, apiKey: required(initial, "unraidReadApiKey") });
|
|
194
198
|
const reads = (0, tools_unraid_1.createUnraidReadTools)(readClient);
|
|
195
|
-
const sab = (0, sanctuary_sab_1.createSanctuarySabClient)();
|
|
199
|
+
const sab = (0, sanctuary_sab_1.createSanctuarySabClient)({ loadApiKey: () => loadSanctuarySabApiKey(agentName) });
|
|
196
200
|
const mediaOptimization = (0, sanctuary_media_optimization_1.createSanctuaryMediaOptimizationClient)(optionalJellyfin(initial));
|
|
197
201
|
const acceptanceRead = (toolName, read) => async (...args) => {
|
|
198
202
|
try {
|
|
@@ -238,7 +242,17 @@ function createSanctuaryToolContext(agentName) {
|
|
|
238
242
|
const services = await Promise.all(sanctuary_health_1.SANCTUARY_PUBLIC_ENDPOINTS.map(async (url) => ({ name: new URL(url).hostname.split(".")[0], ...await (0, sanctuary_health_1.probeSanctuaryEndpoint)(url) })));
|
|
239
243
|
return { ok: true, data: { observedAt: new Date().toISOString(), services, degraded: services.some((service) => !service.ok) } };
|
|
240
244
|
}),
|
|
241
|
-
getDownloadQueue: acceptanceRead("sanctuary_get_download_queue",
|
|
245
|
+
getDownloadQueue: acceptanceRead("sanctuary_get_download_queue", async () => {
|
|
246
|
+
try {
|
|
247
|
+
return await sab.readQueue();
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
const code = (0, sanctuary_sab_1.sanctuarySabReadUnavailableCode)(error);
|
|
251
|
+
if (code)
|
|
252
|
+
return { ok: false, error: { code }, observedAt: new Date().toISOString() };
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}),
|
|
242
256
|
getMediaOptimization: acceptanceRead("sanctuary_get_media_optimization", mediaOptimization.read),
|
|
243
257
|
resumeDownloadQueue: async () => {
|
|
244
258
|
try {
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SANCTUARY_SAB_CREDENTIAL_UNAVAILABLE = void 0;
|
|
4
|
+
exports.sanctuarySabReadUnavailableCode = sanctuarySabReadUnavailableCode;
|
|
3
5
|
exports.createSanctuarySabClient = createSanctuarySabClient;
|
|
4
6
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
const node_fs_1 = require("node:fs");
|
|
6
7
|
const runtime_1 = require("../nerves/runtime");
|
|
7
|
-
const DEFAULT_INI_PATH = "/run/sanctuary/sabnzbd.ini";
|
|
8
8
|
const BASE_URL = "http://127.0.0.1:8090/api";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return value;
|
|
9
|
+
exports.SANCTUARY_SAB_CREDENTIAL_UNAVAILABLE = "SAB queue verification credential is unavailable";
|
|
10
|
+
function sanctuarySabReadUnavailableCode(error) {
|
|
11
|
+
if (!(error instanceof Error))
|
|
12
|
+
return undefined;
|
|
13
|
+
if (error.message === exports.SANCTUARY_SAB_CREDENTIAL_UNAVAILABLE)
|
|
14
|
+
return "credential_unavailable";
|
|
15
|
+
if (error.message === "SAB queue request failed")
|
|
16
|
+
return "request_unavailable";
|
|
17
|
+
if (error.message === "SAB queue response is malformed")
|
|
18
|
+
return "malformed_response";
|
|
19
|
+
return undefined;
|
|
21
20
|
}
|
|
22
21
|
function queuedJobs(value) {
|
|
23
22
|
const parsed = typeof value === "string" && /^\d{1,7}$/u.test(value) ? Number(value) : value;
|
|
@@ -25,15 +24,21 @@ function queuedJobs(value) {
|
|
|
25
24
|
throw new Error("SAB queue response is malformed");
|
|
26
25
|
return Number(parsed);
|
|
27
26
|
}
|
|
28
|
-
function createSanctuarySabClient(options
|
|
29
|
-
const iniPath = options.iniPath ?? DEFAULT_INI_PATH;
|
|
27
|
+
function createSanctuarySabClient(options) {
|
|
30
28
|
const fetchImpl = options.fetch ?? fetch;
|
|
31
|
-
let cachedApiKey = null;
|
|
32
29
|
const request = async (mode) => {
|
|
33
|
-
|
|
30
|
+
let apiKey;
|
|
31
|
+
try {
|
|
32
|
+
apiKey = (await options.loadApiKey()).trim();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new Error(exports.SANCTUARY_SAB_CREDENTIAL_UNAVAILABLE);
|
|
36
|
+
}
|
|
37
|
+
if (!apiKey)
|
|
38
|
+
throw new Error(exports.SANCTUARY_SAB_CREDENTIAL_UNAVAILABLE);
|
|
34
39
|
let response;
|
|
35
40
|
try {
|
|
36
|
-
response = await fetchImpl(`${BASE_URL}?mode=${mode}${mode === "queue" ? "&output=json" : ""}&apikey=${encodeURIComponent(
|
|
41
|
+
response = await fetchImpl(`${BASE_URL}?mode=${mode}${mode === "queue" ? "&output=json" : ""}&apikey=${encodeURIComponent(apiKey)}`, { signal: AbortSignal.timeout(15_000) });
|
|
37
42
|
}
|
|
38
43
|
catch {
|
|
39
44
|
throw new Error("SAB queue request failed");
|
|
@@ -430,14 +430,15 @@ async function runSenseTurn(options) {
|
|
|
430
430
|
if (persistPromise)
|
|
431
431
|
await persistPromise;
|
|
432
432
|
const postTurnSession = (0, context_1.loadSession)(sessPath);
|
|
433
|
+
const emptyFallback = options.emptyResponseFallback?.();
|
|
433
434
|
if (postTurnSession?.messages) {
|
|
434
435
|
const recovered = extractOutwardSenseDeliveryText(postTurnSession.messages);
|
|
435
|
-
finalResponse = recovered ?? "(agent responded but response was empty)";
|
|
436
|
+
finalResponse = recovered ?? emptyFallback ?? "(agent responded but response was empty)";
|
|
436
437
|
if (recovered)
|
|
437
438
|
responseCausalSessionEventId = newOutwardCoordinates(persistedEvents, existingEventIds).at(-1)?.eventId;
|
|
438
439
|
}
|
|
439
440
|
else {
|
|
440
|
-
finalResponse = "(agent responded but response was empty)";
|
|
441
|
+
finalResponse = emptyFallback ?? "(agent responded but response was empty)";
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
444
|
else {
|
package/dist/senses/telegram.js
CHANGED
|
@@ -63,11 +63,12 @@ const runtime_credentials_1 = require("../heart/runtime-credentials");
|
|
|
63
63
|
const runtime_1 = require("../nerves/runtime");
|
|
64
64
|
const nerves_1 = require("../nerves");
|
|
65
65
|
const sanctuary_interactive_control_1 = require("./sanctuary-interactive-control");
|
|
66
|
+
const sanctuary_runtime_1 = require("./sanctuary-runtime");
|
|
66
67
|
const sanctuary_sab_1 = require("./sanctuary-sab");
|
|
67
68
|
const sanctuary_download_credit_presentation_1 = require("./sanctuary-download-credit-presentation");
|
|
68
69
|
const shared_turn_1 = require("./shared-turn");
|
|
69
70
|
const telegram_client_1 = require("./telegram-client");
|
|
70
|
-
const
|
|
71
|
+
const sanctuary_runtime_2 = require("./sanctuary-runtime");
|
|
71
72
|
const sanctuary_full_visibility_contract_1 = require("./sanctuary-full-visibility-contract");
|
|
72
73
|
const sanctuary_storage_optimization_contract_1 = require("./sanctuary-storage-optimization-contract");
|
|
73
74
|
const sanctuary_grounding_1 = require("./sanctuary-grounding");
|
|
@@ -87,11 +88,14 @@ const pending_1 = require("../mind/pending");
|
|
|
87
88
|
const relationship_authorization_1 = require("../repertoire/relationship-authorization");
|
|
88
89
|
const telegram_admission_1 = require("./telegram-admission");
|
|
89
90
|
const telegram_effect_adapter_1 = require("./telegram-effect-adapter");
|
|
90
|
-
|
|
91
|
+
function createFullVisibilityProgress() {
|
|
92
|
+
const progress = {};
|
|
93
|
+
return { progress, emptyResponseFallback: () => progress.fallback?.() };
|
|
94
|
+
}
|
|
91
95
|
function createSabQueueProtectiveStateVerifier(options = {}) {
|
|
92
96
|
let client = null;
|
|
93
97
|
return async (action) => {
|
|
94
|
-
client ??= (0, sanctuary_sab_1.createSanctuarySabClient)({
|
|
98
|
+
client ??= (0, sanctuary_sab_1.createSanctuarySabClient)({ loadApiKey: options.loadApiKey ?? (() => (0, sanctuary_runtime_1.loadSanctuarySabApiKey)("sanctuary")), fetch: options.fetch, now: options.now });
|
|
95
99
|
const snapshot = await client.readQueue();
|
|
96
100
|
const paused = snapshot.paused;
|
|
97
101
|
const digest = (0, node_crypto_1.createHash)("sha256").update(`sabnzbd.queue.paused=${String(paused)}`).digest("hex");
|
|
@@ -685,7 +689,7 @@ function createTelegramSenseApp(options) {
|
|
|
685
689
|
? new telegram_admission_1.FileTelegramAdmissionStore(path.join(agentRoot, "state", "senses", "telegram", "admissions"))
|
|
686
690
|
: undefined;
|
|
687
691
|
const runTurn = options.runTurn ?? options._runTurn ?? shared_turn_1.runSenseTurn;
|
|
688
|
-
const collectToolReceipts = options._runWithToolReceiptCollection ??
|
|
692
|
+
const collectToolReceipts = options._runWithToolReceiptCollection ?? sanctuary_runtime_2.runWithSanctuaryToolReceiptCollection;
|
|
689
693
|
const useSanctuaryRuntime = options.agentName === "sanctuary" && !options.runTurn;
|
|
690
694
|
const readScenarioHandleDigest = () => (options.acceptanceMarker
|
|
691
695
|
? options.acceptanceMarker()
|
|
@@ -878,7 +882,7 @@ function createTelegramSenseApp(options) {
|
|
|
878
882
|
let approvalTransport;
|
|
879
883
|
let interactiveControl;
|
|
880
884
|
try {
|
|
881
|
-
toolContext = useSanctuaryRuntime ? (options._toolContext ?? (0,
|
|
885
|
+
toolContext = useSanctuaryRuntime ? (options._toolContext ?? (0, sanctuary_runtime_2.createSanctuaryToolContext)(options.agentName)) : undefined;
|
|
882
886
|
if (toolContext && options.telegramContactManager)
|
|
883
887
|
toolContext.telegramContactManager = options.telegramContactManager;
|
|
884
888
|
approvalRuntime = options.approvalRuntime ?? (useSanctuaryRuntime ? (options._createApprovalRuntime ?? telegram_approval_runtime_1.createTelegramApprovalRuntime)({
|
|
@@ -1076,10 +1080,21 @@ function createTelegramSenseApp(options) {
|
|
|
1076
1080
|
};
|
|
1077
1081
|
return async ({ runAgentOptions }) => {
|
|
1078
1082
|
const relationshipAuthorization = await resolveLiveRelationshipAuthorization();
|
|
1079
|
-
const
|
|
1083
|
+
const storageOptimization = options.agentName === "sanctuary" && relationshipAuthorization.profileId === "sanctuary-owner"
|
|
1080
1084
|
? (0, sanctuary_storage_optimization_contract_1.sanctuaryStorageOptimizationRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames)
|
|
1081
|
-
?? (0, sanctuary_full_visibility_contract_1.sanctuaryFullVisibilityRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames)
|
|
1082
1085
|
: undefined;
|
|
1086
|
+
const fullVisibility = options.agentName === "sanctuary" && relationshipAuthorization.profileId === "sanctuary-owner" && !storageOptimization
|
|
1087
|
+
? (0, sanctuary_full_visibility_contract_1.sanctuaryFullVisibilityRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames)
|
|
1088
|
+
: undefined;
|
|
1089
|
+
if (input.fullVisibilityProgress && fullVisibility)
|
|
1090
|
+
input.fullVisibilityProgress.fallback = fullVisibility.emptyResponseFallback;
|
|
1091
|
+
const requiredToolCalls = storageOptimization ?? (fullVisibility ? {
|
|
1092
|
+
names: fullVisibility.names,
|
|
1093
|
+
retryMessage: fullVisibility.retryMessage,
|
|
1094
|
+
requireSuccessfulResults: fullVisibility.requireSuccessfulResults,
|
|
1095
|
+
validateRequiredToolResult: fullVisibility.validateRequiredToolResult,
|
|
1096
|
+
validateTerminalAnswer: fullVisibility.validateTerminalAnswer,
|
|
1097
|
+
} : undefined);
|
|
1083
1098
|
return {
|
|
1084
1099
|
...runAgentOptions,
|
|
1085
1100
|
...(requiredToolCalls ? { requiredToolCalls } : {}),
|
|
@@ -1118,6 +1133,7 @@ function createTelegramSenseApp(options) {
|
|
|
1118
1133
|
},
|
|
1119
1134
|
}
|
|
1120
1135
|
: undefined;
|
|
1136
|
+
const fullVisibility = createFullVisibilityProgress();
|
|
1121
1137
|
const result = await runTurn({
|
|
1122
1138
|
agentName: options.agentName,
|
|
1123
1139
|
channel: "telegram",
|
|
@@ -1130,7 +1146,8 @@ function createTelegramSenseApp(options) {
|
|
|
1130
1146
|
...(orientationFrame ? { orientationFrame } : {}),
|
|
1131
1147
|
toolContext: { ...(toolContext ?? {}), attachmentIds: input.attachmentIds ?? [] },
|
|
1132
1148
|
prepareRunAgentOptions: prepareRelationshipRunAgentOptions({ friendId: input.friendId, requestId: input.requestId,
|
|
1133
|
-
sessionEventId: input.eventId, userId: input.userId, chatId: input.chatId, sessionKey: input.sessionKey, userMessage: input.text }),
|
|
1149
|
+
sessionEventId: input.eventId, userId: input.userId, chatId: input.chatId, sessionKey: input.sessionKey, userMessage: input.text, fullVisibilityProgress: fullVisibility.progress }),
|
|
1150
|
+
emptyResponseFallback: fullVisibility.emptyResponseFallback,
|
|
1134
1151
|
deliverySink: { onDelivery: (delivery) => deliver(delivery.text, delivery.kind === "settle") },
|
|
1135
1152
|
});
|
|
1136
1153
|
if (effects.length === 0 && result.response.trim())
|
|
@@ -1309,6 +1326,7 @@ function createTelegramSenseApp(options) {
|
|
|
1309
1326
|
inbound: { text: message.text, reference: inboundReference, attachmentIds: hydrated.attachmentIds, relations: ingressRelations },
|
|
1310
1327
|
})
|
|
1311
1328
|
: null;
|
|
1329
|
+
const fullVisibility = createFullVisibilityProgress();
|
|
1312
1330
|
const collected = await collectToolReceipts(() => runTurn({
|
|
1313
1331
|
agentName: options.agentName,
|
|
1314
1332
|
channel: "telegram",
|
|
@@ -1324,6 +1342,7 @@ function createTelegramSenseApp(options) {
|
|
|
1324
1342
|
ingressRelations,
|
|
1325
1343
|
...(ingressReceipt ? { precommittedIngress: ingressReceipt } : {}),
|
|
1326
1344
|
turnMetricsObserver,
|
|
1345
|
+
emptyResponseFallback: fullVisibility.emptyResponseFallback,
|
|
1327
1346
|
deliverySink: {
|
|
1328
1347
|
onDelivery: async (delivery) => {
|
|
1329
1348
|
if (groundingIntentTool) {
|
|
@@ -1347,6 +1366,7 @@ function createTelegramSenseApp(options) {
|
|
|
1347
1366
|
chatId: message.chatId,
|
|
1348
1367
|
sessionKey: currentSessionKey,
|
|
1349
1368
|
userMessage: message.text,
|
|
1369
|
+
fullVisibilityProgress: fullVisibility.progress,
|
|
1350
1370
|
}),
|
|
1351
1371
|
} : {}),
|
|
1352
1372
|
...(approvalRuntime ? { approvalCoordinatorFactory: approvalRuntime.coordinator } : {}),
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.767",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.767",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|