@ouro.bot/cli 0.1.0-alpha.810 → 0.1.0-alpha.811
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 +12 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +1 -1
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +8 -7
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/config.js +32 -2
- package/dist/heart/core.js +167 -115
- package/dist/heart/daemon/daemon.js +19 -9
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +88 -50
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +53 -15
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +6 -26
- package/dist/heart/frontend-approval-runtime.js +87 -13
- package/dist/heart/identity.js +17 -5
- package/dist/heart/session-events.js +8 -1
- package/dist/heart/tool-approval.js +64 -31
- package/dist/mind/pending.js +4 -4
- package/dist/repertoire/mcp-manager.js +365 -381
- package/dist/repertoire/mcp-tools.js +66 -25
- package/dist/repertoire/plugin-mcp.js +3 -3
- package/dist/repertoire/shell-sessions.js +8 -7
- package/dist/repertoire/tool-arguments.js +30 -8
- package/dist/repertoire/tools-session.js +30 -5
- package/dist/repertoire/tools-shell.js +30 -14
- package/dist/repertoire/tools-voice.js +6 -6
- package/dist/repertoire/tools.js +231 -205
- package/dist/senses/bluebubbles/index.js +4 -2
- package/dist/senses/cli.js +6 -5
- package/dist/senses/private-runtime.js +9 -3
- package/dist/senses/shared-turn.js +12 -9
- package/dist/senses/teams.js +15 -12
- package/dist/senses/telegram-approval-runtime.js +129 -20
- package/dist/senses/telegram-client.js +19 -2
- package/dist/senses/telegram.js +54 -15
- package/dist/senses/voice/twilio-phone.js +123 -173
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
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.811",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Bind tool discovery and execution to the initiating agent with canonical schemas, per-owner MCP connections and current authorization.",
|
|
8
|
+
"Keep tool schema validation isolated so repeated schema IDs work across turns without borrowing another tool's schema registrations.",
|
|
9
|
+
"Expose Sanctuary's resident native tools only to the configured owner while preserving household and event exclusions and role-specific containment.",
|
|
10
|
+
"Match Sanctuary's Unit-16e containment evidence to the canonical three-mount deployment and reject malformed root-mode observations at both the broker and adapter boundaries.",
|
|
11
|
+
"Preserve approval effect outcomes without replay, confine session reads and isolate foreground and background shell ownership.",
|
|
12
|
+
"Keep CLI and realtime voice tool contexts owner-bound across delayed discovery, transport failure and cancellation.",
|
|
13
|
+
"Allow coverage gates to reuse installed Mailbox UI dependencies through an explicit skip-install option without skipping validation."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.810",
|
|
6
18
|
"changes": [
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"restart-attempt-ledger": { "kind": "fixed-ndjson", "path": "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/restart-attempts.ndjson", "redaction": "scenario-action-target-digests-and-state-only" },
|
|
71
71
|
"telegram-turn-receipts": { "kind": "fixed-ndjson", "path": "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/telegram-turns.ndjson", "redaction": "scenario-update-tool-response-and-delivery-digests-only" },
|
|
72
72
|
"read-only-denial-receipt": { "kind": "persisted-one-shot-live-graphql-denial", "schemaVersion": "sanctuary-read-only-denial-receipt-v1", "path": "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/denial-receipts/<scenario-handle-digest>.json", "attemptPath": "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/denial-attempts/<scenario-handle-digest>.json", "endpoint": "http://127.0.0.1/graphql", "targetSnapshotOperation": "denial_target_snapshot", "resumePolicy": "attempted-or-indeterminate-inspect-before-retry", "redaction": "schema-binding-api-classification-counts-restart-count-and-seven-boundary-digests-only" },
|
|
73
|
-
"containment-audit": { "kind": "effective-container-key-tool-and-audit-boundary", "schemaVersion": "sanctuary-containment-audit-
|
|
73
|
+
"containment-audit": { "kind": "effective-container-key-tool-and-audit-boundary", "schemaVersion": "sanctuary-containment-audit-v2", "path": "/home/ouro/AgentBundles/sanctuary.ouro/tool-profiles.json", "auditPath": "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/telegram-audit-chain.ndjson", "producers": ["container_snapshot", "inventory_keys", "live-bundle-relationship-capability-registry", "canonical-turn-selection", "current-relationship-provider-gates", "telegram-audit-mac-chain"], "redaction": "counts-static-policy-values-and-sha256-digests-only" },
|
|
74
74
|
"container-inspect": { "kind": "fixed-host-snapshot", "path": "/run/ouro-acceptance/container-inspect.json", "redaction": "typed-facts-and-digests-only" },
|
|
75
75
|
"provider-live-check": { "kind": "fixed-runtime-api", "operation": "sanctuary-provider-readiness", "redaction": "exact same-provider lane selection, one credential revision, live ping attempts, and no-fallback booleans only" },
|
|
76
76
|
"live-grounding-read": { "kind": "fixed-runtime-api", "operation": "sanctuary-independent-system-or-storage-read", "redaction": "bounded-system-or-storage-facts-and-digest-only" },
|
|
@@ -492,6 +492,7 @@ async function containerSnapshot(expectedImage) {
|
|
|
492
492
|
|| typeof value.startedAt !== "string" || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[^\s]{1,64}Z$/u.test(value.startedAt)) {
|
|
493
493
|
throw new Error("production container identity is invalid")
|
|
494
494
|
}
|
|
495
|
+
if (typeof value.readOnlyRoot !== "boolean") throw new Error("production container root mode is invalid")
|
|
495
496
|
const ports = object(value.ports ?? {}, "published ports")
|
|
496
497
|
const publishedPortCount = Object.values(ports).reduce((count, bindings) => count + (Array.isArray(bindings) ? bindings.length : 0), 0)
|
|
497
498
|
const mounts = Array.isArray(value.mounts) ? value.mounts.map((raw) => {
|
|
@@ -499,14 +500,14 @@ async function containerSnapshot(expectedImage) {
|
|
|
499
500
|
return { destination: mount.Destination, source: mount.Source, mode: mount.Mode, propagation: mount.Propagation, rw: mount.RW, type: mount.Type }
|
|
500
501
|
}).sort((left, right) => String(left.destination).localeCompare(String(right.destination))) : []
|
|
501
502
|
const expectedMounts = [
|
|
502
|
-
{ destination: "/home/ouro/.ouro-cli", source: PRODUCTION_RUNTIME_SOURCE,
|
|
503
|
-
{ destination: "/home/ouro/AgentBundles/sanctuary.ouro", source: PRODUCTION_BUNDLE_SOURCE,
|
|
504
|
-
{ destination: "/run/ouro-events", source: PRODUCTION_EVENT_SPOOL_SOURCE,
|
|
503
|
+
{ destination: "/home/ouro/.ouro-cli", source: PRODUCTION_RUNTIME_SOURCE, propagation: "rprivate", rw: true, type: "bind" },
|
|
504
|
+
{ destination: "/home/ouro/AgentBundles/sanctuary.ouro", source: PRODUCTION_BUNDLE_SOURCE, propagation: "rprivate", rw: true, type: "bind" },
|
|
505
|
+
{ destination: "/run/ouro-events", source: PRODUCTION_EVENT_SPOOL_SOURCE, propagation: "rprivate", rw: false, type: "bind" },
|
|
505
506
|
]
|
|
506
|
-
const mountsExact = mounts.length === expectedMounts.length && expectedMounts.every((expected) => mounts.some((mount) => mount.destination === expected.destination && mount.source === expected.source && mount.
|
|
507
|
+
const mountsExact = mounts.length === expectedMounts.length && expectedMounts.every((expected) => mounts.some((mount) => mount.destination === expected.destination && mount.source === expected.source && mount.propagation === expected.propagation && mount.rw === expected.rw && mount.type === expected.type))
|
|
507
508
|
const securityExact = value.privileged === false && (value.capAdd === null || (Array.isArray(value.capAdd) && value.capAdd.length === 0))
|
|
508
|
-
&&
|
|
509
|
-
&&
|
|
509
|
+
&& (value.capDrop === null || (Array.isArray(value.capDrop) && value.capDrop.length === 0))
|
|
510
|
+
&& (value.securityOpt === null || (Array.isArray(value.securityOpt) && value.securityOpt.length === 0))
|
|
510
511
|
const running = value.running === true
|
|
511
512
|
if (!running) throw new Error("production container is not running")
|
|
512
513
|
const configuredUser = text(value.user, "container user")
|
|
@@ -534,7 +535,7 @@ async function containerSnapshot(expectedImage) {
|
|
|
534
535
|
user: configuredUser,
|
|
535
536
|
liveProcessUser,
|
|
536
537
|
processBindingDigest,
|
|
537
|
-
readOnlyRoot: value.readOnlyRoot
|
|
538
|
+
readOnlyRoot: value.readOnlyRoot,
|
|
538
539
|
mountCount: mounts.length,
|
|
539
540
|
mountsDigest: createHash("sha256").update(JSON.stringify(mounts)).digest("hex"),
|
|
540
541
|
mountsExact,
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
I am Mendelow Cloud Butler. I care for Ari's home services and help approved household members over Telegram. Ari is my family-trust operator; each other person has their own relationship, permissions, conversation, preferences, and return route.
|
|
4
4
|
|
|
5
|
-
I live in a non-root container on the Unraid host internally named `sanctuary`. People do not need to say Sanctuary or know which backend serves a household outcome.
|
|
5
|
+
I live in a non-root container on the Unraid host internally named `sanctuary`. People do not need to say Sanctuary or know which backend serves a household outcome. I use the typed Unraid GraphQL repertoire for server operations and owner-authorized native tools for resident work inside my existing container boundary.
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 2,
|
|
3
3
|
"profiles": {
|
|
4
4
|
"sanctuary-owner": {
|
|
5
|
-
"version":
|
|
5
|
+
"version": 8,
|
|
6
6
|
"contextScopes": ["household.status", "household.policy", "household.private"],
|
|
7
|
-
"toolNames": ["external_event_disposition", "query_active_work", "save_friend_note", "telegram_contact_manage", "query_cares", "care_manage", "await_condition", "resolve_await", "cancel_await", "list_recent_attachments", "materialize_attachment", "describe_image", "unraid_list_containers", "unraid_get_container_logs", "unraid_get_storage", "sanctuary_get_media_optimization", "sanctuary_search_media_catalog", "unraid_get_disks", "unraid_get_notifications", "unraid_get_system", "sanctuary_get_install_state", "unraid_check_services", "sanctuary_get_download_queue", "sanctuary_resume_download_queue", "unraid_restart_container", "steward_policy_manage", "send_message", "ponder", "rest", "settle", "speak"],
|
|
7
|
+
"toolNames": ["external_event_disposition", "query_active_work", "save_friend_note", "telegram_contact_manage", "query_cares", "care_manage", "await_condition", "resolve_await", "cancel_await", "list_recent_attachments", "materialize_attachment", "describe_image", "unraid_list_containers", "unraid_get_container_logs", "unraid_get_storage", "sanctuary_get_media_optimization", "sanctuary_search_media_catalog", "unraid_get_disks", "unraid_get_notifications", "unraid_get_system", "sanctuary_get_install_state", "unraid_check_services", "sanctuary_get_download_queue", "sanctuary_resume_download_queue", "unraid_restart_container", "steward_policy_manage", "send_message", "ponder", "rest", "settle", "speak", "shell", "shell_status", "shell_tail", "read_file", "write_file", "edit_file", "glob", "grep", "web_search", "search_facts", "consult_diary", "consult_notes", "get_friend_note", "session_summary", "query_session", "set_reasoning_effort", "restart_runtime", "revive_sense"],
|
|
8
8
|
"effectScopes": ["telegram.proactive", "telegram.request_return", "telegram.owner_event"]
|
|
9
9
|
},
|
|
10
10
|
"sanctuary-household": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.811</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
package/dist/heart/config.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.InvalidSessionPathError = void 0;
|
|
36
37
|
exports.loadConfig = loadConfig;
|
|
37
38
|
exports.resetConfigCache = resetConfigCache;
|
|
38
39
|
exports.cacheRuntimeConfigForTests = cacheRuntimeConfigForTests;
|
|
@@ -59,6 +60,7 @@ exports.getLogsDir = getLogsDir;
|
|
|
59
60
|
exports.sanitizeKey = sanitizeKey;
|
|
60
61
|
exports.canonicalizeTelegramSessionKey = canonicalizeTelegramSessionKey;
|
|
61
62
|
exports.slugify = slugify;
|
|
63
|
+
exports.isValidSessionCoordinate = isValidSessionCoordinate;
|
|
62
64
|
exports.resolveSessionPath = resolveSessionPath;
|
|
63
65
|
exports.sessionPath = sessionPath;
|
|
64
66
|
exports.logPath = logPath;
|
|
@@ -373,12 +375,40 @@ function slugify(value) {
|
|
|
373
375
|
.replace(/^-+/, "")
|
|
374
376
|
.replace(/-+$/, "");
|
|
375
377
|
}
|
|
378
|
+
class InvalidSessionPathError extends Error {
|
|
379
|
+
}
|
|
380
|
+
exports.InvalidSessionPathError = InvalidSessionPathError;
|
|
381
|
+
function isValidSessionCoordinate(value) {
|
|
382
|
+
return typeof value === "string" && value.length > 0 && value !== "." && value !== ".." && !/[/\\\0]/.test(value);
|
|
383
|
+
}
|
|
384
|
+
function isInsideSessionRoot(root, target) {
|
|
385
|
+
const relative = path.relative(root, target);
|
|
386
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
387
|
+
}
|
|
376
388
|
function resolveSessionPath(friendId, channel, key, options) {
|
|
377
|
-
const
|
|
389
|
+
const agentRoot = options?.agentRoot ?? (0, identity_1.getAgentRoot)();
|
|
390
|
+
if (options?.confined && (!isValidSessionCoordinate(friendId) || !isValidSessionCoordinate(channel)
|
|
391
|
+
|| typeof key !== "string" || key.includes("\0") || !path.isAbsolute(agentRoot))) {
|
|
392
|
+
throw new InvalidSessionPathError("invalid session coordinates");
|
|
393
|
+
}
|
|
394
|
+
const sessionsRoot = path.join(agentRoot, "state", "sessions");
|
|
395
|
+
const dir = path.join(sessionsRoot, friendId, channel);
|
|
396
|
+
const file = path.join(dir, sanitizeKey(key) + ".json");
|
|
397
|
+
if (options?.confined) {
|
|
398
|
+
if (!isInsideSessionRoot(sessionsRoot, file))
|
|
399
|
+
throw new InvalidSessionPathError("session path escapes its root");
|
|
400
|
+
if (fs.existsSync(file)) {
|
|
401
|
+
const realSessionsRoot = fs.realpathSync(sessionsRoot);
|
|
402
|
+
if (!isInsideSessionRoot(fs.realpathSync(agentRoot), realSessionsRoot)
|
|
403
|
+
|| !isInsideSessionRoot(realSessionsRoot, fs.realpathSync(file))) {
|
|
404
|
+
throw new InvalidSessionPathError("session path escapes the initiating agent");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
378
408
|
if (options?.ensureDir) {
|
|
379
409
|
fs.mkdirSync(dir, { recursive: true });
|
|
380
410
|
}
|
|
381
|
-
return
|
|
411
|
+
return file;
|
|
382
412
|
}
|
|
383
413
|
function sessionPath(friendId, channel, key) {
|
|
384
414
|
return resolveSessionPath(friendId, channel, key, { ensureDir: true });
|