@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 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": [
@@ -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,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.766",
2
+ "runtimeVersion": "0.1.0-alpha.767",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-08-30T00:00:00.000Z"
5
5
  }
@@ -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.766</Repository>
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 !== 4
156
- || configEntries.length !== 4
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 four path binds");
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
- && typeof queue.paused === "boolean"
43
- && typeof queue.status === "string" && Buffer.byteLength(queue.status, "utf8") <= 64
44
- && Number.isSafeInteger(queue.queuedJobs) && Number(queue.queuedJobs) >= 0 && Number(queue.queuedJobs) <= 1_000_000
45
- && typeof queue.observedAt === "string" && Number.isFinite(Date.parse(queue.observedAt)) && new Date(queue.observedAt).toISOString() === queue.observedAt
46
- && typeof queue.stateDigest === "string" && /^[a-f0-9]{64}$/u.test(queue.stateDigest);
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.normalize("NFKC").trim().toLocaleLowerCase("en-US").replaceAll("’", "'").replace(/[?!.\s]+$/gu, "");
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: successfulCurrentResult,
64
- validateTerminalAnswer: unsupportedCurrentClaim,
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", sab.readQueue),
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
- function apiKey(iniPath) {
10
- let contents = "";
11
- try {
12
- contents = (0, node_fs_1.readFileSync)(iniPath, "utf8");
13
- }
14
- catch {
15
- throw new Error("SAB queue verification credential is unavailable");
16
- }
17
- const value = contents.match(/^\s*api_key\s*=\s*(\S+)\s*$/mu)?.[1];
18
- if (!value)
19
- throw new Error("SAB queue verification credential is unavailable");
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
- cachedApiKey ??= apiKey(iniPath);
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(cachedApiKey)}`, { signal: AbortSignal.timeout(15_000) });
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 {
@@ -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 sanctuary_runtime_1 = require("./sanctuary-runtime");
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
- const SANCTUARY_SAB_CONFIG_PATH = "/run/sanctuary/sabnzbd.ini";
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)({ iniPath: options.iniPath ?? SANCTUARY_SAB_CONFIG_PATH, fetch: options.fetch, now: options.now });
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 ?? sanctuary_runtime_1.runWithSanctuaryToolReceiptCollection;
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, sanctuary_runtime_1.createSanctuaryToolContext)(options.agentName)) : undefined;
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 requiredToolCalls = options.agentName === "sanctuary" && relationshipAuthorization.profileId === "sanctuary-owner"
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 } : {}),
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.766",
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.766",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.766",
3
+ "version": "0.1.0-alpha.767",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },