@ouro.bot/cli 0.1.0-alpha.99 → 0.1.0-alpha.990
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/LICENSE +21 -0
- package/README.md +171 -23
- package/RepairGuide.ouro/agent.json +5 -0
- package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
- package/RepairGuide.ouro/psyche/SOUL.md +55 -0
- package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
- package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
- package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
- package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/assets/bluebubbles-host +264 -0
- package/assets/sanctuary-host-launcher.sh +16 -0
- package/changelog.json +6251 -16
- package/deploy/unraid/Dockerfile +58 -0
- package/deploy/unraid/INSTALL.md +64 -0
- package/deploy/unraid/README.txt +2997 -0
- package/deploy/unraid/audit-container-spec.sh +4 -0
- package/deploy/unraid/container-runtime.json +1 -0
- package/deploy/unraid/credit-gateway.md +95 -0
- package/deploy/unraid/docker-man-template-transaction.mjs +727 -0
- package/deploy/unraid/docker-man-template-xml.cjs +105 -0
- package/deploy/unraid/install.sh +274 -0
- package/deploy/unraid/migrate-sanctuary-bundle.mjs +63 -0
- package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
- package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
- package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
- package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
- package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
- package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
- package/deploy/unraid/sanctuary-authority-installation.json +36 -0
- package/deploy/unraid/sanctuary-authority-service.sh +30 -0
- package/deploy/unraid/sanctuary-butler-upgrade.mjs +1499 -0
- package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
- package/deploy/unraid/sanctuary-replay-gate.mjs +2114 -0
- package/deploy/unraid/sanctuary-safe-rename.py +24 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2088 -0
- package/deploy/unraid/sanctuary-unit16-run.sh +570 -0
- package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
- package/deploy/unraid/sanctuary.ouro/agent.json +66 -0
- package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
- package/deploy/unraid/sanctuary.ouro/awaiting/house-care-sweep.md +17 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
- package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +2255 -0
- package/deploy/unraid/sanctuary.ouro/product/PRODUCT.md +13 -0
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +7 -0
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +7 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +15 -0
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +3 -0
- package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +232 -0
- package/deploy/unraid/sanctuary.xml +29 -0
- package/dist/a2a/card.js +57 -0
- package/dist/a2a/client.js +290 -0
- package/dist/a2a/config.js +50 -0
- package/dist/a2a/delegated-command-grants.js +93 -0
- package/dist/a2a/delegated-command.js +197 -0
- package/dist/a2a/delegation-stores.js +56 -0
- package/dist/a2a/did-resolution.js +205 -0
- package/dist/a2a/escalation-grants.js +84 -0
- package/dist/a2a/escalation-wiring.js +60 -0
- package/dist/a2a/identity.js +258 -0
- package/dist/a2a/inbound-share.js +131 -0
- package/dist/a2a/mission-result-wire.js +265 -0
- package/dist/a2a/onboarding.js +133 -0
- package/dist/a2a/operator-trust.js +399 -0
- package/dist/a2a/outbox-client.js +59 -0
- package/dist/a2a/outbox-store.js +188 -0
- package/dist/a2a/outbox-wire.js +106 -0
- package/dist/a2a/pin-store.js +167 -0
- package/dist/a2a/refusals.js +53 -0
- package/dist/a2a/relay-client.js +106 -0
- package/dist/a2a/relay-config-vault.js +64 -0
- package/dist/a2a/relay-contacts.js +120 -0
- package/dist/a2a/relay-inbox.js +360 -0
- package/dist/a2a/relay-retirements.js +89 -0
- package/dist/a2a/replay-grant-holders.js +33 -0
- package/dist/a2a/replay-harness.js +188 -0
- package/dist/a2a/resolution-proof.js +53 -0
- package/dist/a2a/sealed-chat.js +114 -0
- package/dist/a2a/seen-ledger.js +214 -0
- package/dist/a2a/server.js +808 -0
- package/dist/a2a/task-store.js +69 -0
- package/dist/a2a/transport.js +100 -0
- package/dist/a2a/trust-commands.js +203 -0
- package/dist/a2a/trusted-files.js +185 -0
- package/dist/a2a/types.js +3 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +360 -0
- package/dist/arc/episodes.js +118 -0
- package/dist/arc/evolution.js +490 -0
- package/dist/arc/flight-recorder.js +1034 -0
- package/dist/arc/intentions.js +134 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +386 -0
- package/dist/arc/packets.js +336 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +57 -0
- package/dist/commerce/store.js +755 -0
- package/dist/commerce/types.js +3 -0
- package/dist/gateway/keys.js +117 -0
- package/dist/gateway/server.js +429 -0
- package/dist/heart/active-work.js +798 -40
- package/dist/heart/agent-entry.js +252 -3
- package/dist/heart/approval-files.js +113 -0
- package/dist/heart/approval-store.js +960 -0
- package/dist/heart/attachments/image-normalize.js +201 -0
- package/dist/heart/attachments/materialize.js +97 -0
- package/dist/heart/attachments/originals.js +88 -0
- package/dist/heart/attachments/render.js +29 -0
- package/dist/heart/attachments/sources/bluebubbles.js +156 -0
- package/dist/heart/attachments/sources/cli-local-file.js +78 -0
- package/dist/heart/attachments/sources/index.js +18 -0
- package/dist/heart/attachments/sources/telegram.js +71 -0
- package/dist/heart/attachments/store.js +132 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +511 -0
- package/dist/heart/autonomy-budget.js +478 -0
- package/dist/heart/awaiting/a2a-await-delivery.js +109 -0
- package/dist/heart/awaiting/await-alert.js +152 -0
- package/dist/heart/awaiting/await-expiry.js +143 -0
- package/dist/heart/awaiting/await-loader.js +91 -0
- package/dist/heart/awaiting/await-parser.js +152 -0
- package/dist/heart/awaiting/await-runtime-state.js +130 -0
- package/dist/heart/awaiting/await-scheduler.js +391 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bluebubbles-host-account.js +78 -0
- package/dist/heart/bridges/manager.js +137 -17
- package/dist/heart/bridges/store.js +14 -2
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +44 -18
- package/dist/heart/config-registry.js +331 -0
- package/dist/heart/config.js +178 -131
- package/dist/heart/context-loss-gauntlet.js +387 -0
- package/dist/heart/context-loss-sentinel.js +1204 -0
- package/dist/heart/core.js +2378 -586
- package/dist/heart/cross-chat-delivery.js +9 -22
- package/dist/heart/daemon/agent-config-check.js +474 -0
- package/dist/heart/daemon/agent-discovery.js +283 -3
- package/dist/heart/daemon/agent-home.js +319 -0
- package/dist/heart/daemon/agent-service.js +542 -0
- package/dist/heart/daemon/agentic-repair.js +547 -0
- package/dist/heart/daemon/await-private-wake.js +59 -0
- package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
- package/dist/heart/daemon/bluebubbles-host.js +273 -0
- package/dist/heart/daemon/boot-sync-probe.js +197 -0
- package/dist/heart/daemon/cadence.js +180 -0
- package/dist/heart/daemon/cli-arg-redaction.js +26 -0
- package/dist/heart/daemon/cli-defaults.js +881 -0
- package/dist/heart/daemon/cli-desk.js +331 -0
- package/dist/heart/daemon/cli-exec.js +9280 -0
- package/dist/heart/daemon/cli-help.js +754 -0
- package/dist/heart/daemon/cli-parse.js +3329 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +819 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/connect-bay.js +323 -0
- package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
- package/dist/heart/daemon/container-healthcheck.js +112 -0
- package/dist/heart/daemon/container-runtime.js +97 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +171 -0
- package/dist/heart/daemon/container-spec-auditor.js +241 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
- package/dist/heart/daemon/daemon-cli.js +29 -2373
- package/dist/heart/daemon/daemon-entry.js +953 -11
- package/dist/heart/daemon/daemon-health.js +186 -0
- package/dist/heart/daemon/daemon-rollup.js +57 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +2110 -104
- package/dist/heart/daemon/desk-admin.js +420 -0
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +1766 -0
- package/dist/heart/daemon/freshness.js +345 -0
- package/dist/heart/daemon/habit-private-wake.js +61 -0
- package/dist/heart/daemon/health-monitor.js +154 -11
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
- package/dist/heart/daemon/http-health-probe.js +80 -0
- package/dist/heart/daemon/human-command-screens.js +234 -0
- package/dist/heart/daemon/human-readiness.js +114 -0
- package/dist/heart/daemon/inner-status.js +78 -0
- package/dist/heart/daemon/interactive-repair.js +394 -0
- package/dist/heart/daemon/launchd.js +37 -8
- package/dist/heart/daemon/log-tailer.js +79 -10
- package/dist/heart/daemon/logs-prune.js +132 -0
- package/dist/heart/daemon/mcp-canary.js +524 -0
- package/dist/heart/daemon/message-router.js +65 -5
- package/dist/heart/daemon/migrate-to-desk.js +848 -0
- package/dist/heart/daemon/os-cron-deps.js +135 -0
- package/dist/heart/daemon/os-cron.js +22 -14
- package/dist/heart/daemon/ouro-bot-entry.js +6 -3
- package/dist/heart/daemon/ouro-entry.js +9 -3
- package/dist/heart/daemon/plugin-cli.js +432 -0
- package/dist/heart/daemon/process-manager.js +760 -61
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/provider-ping-progress.js +83 -0
- package/dist/heart/daemon/prunable-bundle.js +144 -0
- package/dist/heart/daemon/pulse.js +511 -0
- package/dist/heart/daemon/readiness-repair.js +365 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +47 -14
- package/dist/heart/daemon/runtime-metadata.js +2 -30
- package/dist/heart/daemon/runtime-mode.js +13 -2
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2662 -0
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +1832 -0
- package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +956 -0
- package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
- package/dist/heart/daemon/sanctuary-authority-epoch.js +291 -0
- package/dist/heart/daemon/sanctuary-authority-installation.js +161 -0
- package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +1332 -0
- package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
- package/dist/heart/daemon/sanctuary-bundle-migration.js +878 -0
- package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
- package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +493 -0
- package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
- package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
- package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
- package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
- package/dist/heart/daemon/sanctuary-package-management.js +103 -0
- package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +475 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-service.js +676 -0
- package/dist/heart/daemon/sense-manager.js +786 -48
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +1 -1
- package/dist/heart/daemon/socket-client.js +180 -12
- package/dist/heart/daemon/stale-bundle-prune.js +113 -0
- package/dist/heart/daemon/startup-tui.js +339 -0
- package/dist/heart/daemon/supercronic-supervisor.js +282 -0
- package/dist/heart/daemon/task-scheduler.js +117 -39
- package/dist/heart/daemon/terminal-ui.js +503 -0
- package/dist/heart/daemon/thoughts.js +138 -54
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +1 -4
- package/dist/heart/external-events/router.js +1531 -0
- package/dist/heart/failure-reports.js +336 -0
- package/dist/heart/file-lock.js +109 -0
- package/dist/heart/frontend-approval-runtime.js +506 -0
- package/dist/heart/frontend-journal.js +215 -0
- package/dist/heart/frontend-session-service.js +271 -0
- package/dist/heart/frontend-socket-client.js +173 -0
- package/dist/heart/frontend-socket.js +434 -0
- package/dist/heart/frontend-turn-errors.js +20 -0
- package/dist/heart/habits/habit-cancel.js +810 -0
- package/dist/heart/habits/habit-lifecycle.js +1327 -0
- package/dist/heart/habits/habit-migration.js +194 -0
- package/dist/heart/habits/habit-parser.js +278 -0
- package/dist/heart/habits/habit-runtime-state.js +93 -0
- package/dist/heart/habits/habit-scheduler.js +653 -0
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +636 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
- package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
- package/dist/heart/identity.js +239 -59
- package/dist/heart/kept-notes.js +289 -0
- package/dist/heart/kicks.js +1 -1
- package/dist/heart/machine-identity.js +175 -0
- package/dist/heart/mail-import-discovery.js +356 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +99 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +406 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +272 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +41 -0
- package/dist/heart/mailbox/mailbox-supervision.js +131 -0
- package/dist/heart/mailbox/mailbox-types.js +27 -0
- package/dist/heart/mailbox/mailbox-view.js +197 -0
- package/dist/heart/mailbox/readers/agent-machine.js +428 -0
- package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
- package/dist/heart/mailbox/readers/mail.js +375 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
- package/dist/heart/mailbox/readers/sessions.js +231 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +760 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +83 -16
- package/dist/heart/orientation-frame.js +376 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/primitives.js +1 -1
- package/dist/heart/private-runtime/decision-renderer.js +158 -0
- package/dist/heart/private-runtime/index.js +18 -0
- package/dist/heart/private-runtime/ledger.js +221 -0
- package/dist/heart/private-runtime/policy.js +260 -0
- package/dist/heart/private-runtime/types.js +2 -0
- package/dist/heart/progress-story.js +1 -1
- package/dist/heart/provider-attempt.js +137 -0
- package/dist/heart/provider-binding-resolver.js +273 -0
- package/dist/heart/provider-credentials.js +449 -0
- package/dist/heart/provider-failover.js +311 -0
- package/dist/heart/provider-models.js +96 -0
- package/dist/heart/provider-ping.js +266 -0
- package/dist/heart/provider-readiness-cache.js +119 -0
- package/dist/heart/provider-visibility.js +188 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +248 -67
- package/dist/heart/providers/azure.js +23 -11
- package/dist/heart/providers/error-classification.js +129 -0
- package/dist/heart/providers/github-copilot.js +38 -39
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +26 -8
- package/dist/heart/providers/openai-codex-token.js +349 -0
- package/dist/heart/providers/openai-codex.js +55 -40
- package/dist/heart/providers/openai-compatible.js +177 -0
- package/dist/heart/reach-channel.js +85 -0
- package/dist/heart/reply-style.js +52 -0
- package/dist/heart/run-ledger.js +247 -0
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +608 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +24 -4
- package/dist/heart/session-activity.js +94 -24
- package/dist/heart/session-events.js +1521 -0
- package/dist/heart/session-playback-cli-main.js +5 -0
- package/dist/heart/session-playback-cli.js +36 -0
- package/dist/heart/session-playback.js +231 -0
- package/dist/heart/session-redaction-repair-cli-main.js +31 -0
- package/dist/heart/session-redaction-repair.js +552 -0
- package/dist/heart/session-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +183 -0
- package/dist/heart/session-transcript.js +133 -0
- package/dist/heart/shelf-claims.js +78 -0
- package/dist/heart/shepherd/custody-types.js +2 -0
- package/dist/heart/shepherd/mandates.js +352 -0
- package/dist/heart/shepherd/operation-commands.js +19 -0
- package/dist/heart/shepherd/operations.js +135 -0
- package/dist/heart/shepherd/supervision.js +114 -0
- package/dist/heart/shepherd/types.js +2 -0
- package/dist/heart/shepherd-config.js +123 -0
- package/dist/heart/source-grounding.js +313 -0
- package/dist/heart/start-of-turn-packet.js +372 -0
- package/dist/heart/steward-policy.js +656 -0
- package/dist/heart/streaming.js +631 -207
- package/dist/heart/structured-output.js +196 -0
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync-health.js +152 -0
- package/dist/heart/sync.js +726 -0
- package/dist/heart/target-resolution.js +11 -7
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/timeouts.js +101 -0
- package/dist/heart/tool-activity-callbacks.js +59 -0
- package/dist/heart/tool-approval.js +382 -0
- package/dist/heart/tool-description.js +155 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +206 -0
- package/dist/heart/tool-output-budget.js +41 -0
- package/dist/heart/turn-context.js +496 -0
- package/dist/heart/turn-execution-lease.js +30 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +3 -2
- package/dist/heart/versioning/ouro-path-installer.js +432 -0
- package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
- package/dist/heart/versioning/ouro-version-manager.js +432 -0
- package/dist/heart/versioning/release-sources.js +173 -0
- package/dist/heart/versioning/self-update-defaults.js +294 -0
- package/dist/heart/versioning/self-update.js +196 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
- package/dist/heart/versioning/update-hooks.js +154 -0
- package/dist/heart/versioning/update-state.js +126 -0
- package/dist/heart/versioning/updater-launchd.js +150 -0
- package/dist/heart/versioning/version-intent.js +114 -0
- package/dist/heart/work-card.js +380 -0
- package/dist/mailbox-ui/assets/index-Dbska1tZ.js +1 -0
- package/dist/mailbox-ui/assets/index-DsiKaPyH.css +1 -0
- package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
- package/dist/mailbox-ui/index.html +16 -0
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +889 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/cache-sync-cli.js +58 -0
- package/dist/mailroom/core.js +803 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +568 -0
- package/dist/mailroom/hosted-cache-sync.js +384 -0
- package/dist/mailroom/mbox-import.js +393 -0
- package/dist/mailroom/migration.js +164 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +292 -0
- package/dist/mailroom/search-cache.js +513 -0
- package/dist/mailroom/search-relevance.js +319 -0
- package/dist/mailroom/smtp-ingress.js +176 -0
- package/dist/mailroom/source-state.js +176 -0
- package/dist/mailroom/thread.js +109 -0
- package/dist/mailroom/travel-extract.js +89 -0
- package/dist/mind/bundle-manifest.js +25 -2
- package/dist/mind/context.js +328 -139
- package/dist/mind/desk-section.js +362 -0
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +68 -77
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/friend-link-codes.js +209 -0
- package/dist/mind/friend-merge.js +336 -0
- package/dist/mind/house-names.js +43 -0
- package/dist/mind/{associative-recall.js → note-search.js} +47 -58
- package/dist/mind/obligation-steering.js +199 -3
- package/dist/mind/outbound-record.js +168 -0
- package/dist/mind/pending.js +36 -10
- package/dist/mind/prompt-budget.js +479 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1152 -206
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/record-paths.js +312 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/session-transaction.js +622 -0
- package/dist/nerves/cli-logging.js +7 -1
- package/dist/nerves/coverage/audit-rules.js +15 -6
- package/dist/nerves/coverage/audit.js +28 -2
- package/dist/nerves/coverage/cli.js +1 -1
- package/dist/nerves/coverage/contract.js +5 -5
- package/dist/nerves/coverage/file-completeness.js +143 -5
- package/dist/nerves/coverage/run-artifacts.js +55 -35
- package/dist/nerves/coverage/source-scanner.js +4 -4
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +293 -9
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/redact.js +79 -0
- package/dist/nerves/review/cli-main.js +5 -0
- package/dist/nerves/review/cli.js +156 -0
- package/dist/nerves/review/core.js +159 -0
- package/dist/nerves/runtime.js +17 -1
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +16 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +1199 -0
- package/dist/repertoire/bundle-templates.js +71 -0
- package/dist/repertoire/bw-installer.js +231 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +331 -0
- package/dist/repertoire/coding/feedback.js +146 -34
- package/dist/repertoire/coding/index.js +15 -3
- package/dist/repertoire/coding/manager.js +159 -10
- package/dist/repertoire/coding/spawner.js +40 -11
- package/dist/repertoire/coding/tools.js +182 -18
- package/dist/repertoire/coding/workbench-client.js +272 -0
- package/dist/repertoire/coding/workbench-manager.js +346 -0
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +183 -0
- package/dist/repertoire/desk/classifier.js +362 -0
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +1196 -20
- package/dist/repertoire/house-sweep.js +471 -0
- package/dist/repertoire/mcp-client.js +58 -5
- package/dist/repertoire/mcp-manager.js +427 -139
- package/dist/repertoire/mcp-tools.js +132 -0
- package/dist/repertoire/mcp-write-guard.js +46 -0
- package/dist/repertoire/plugin-mcp.js +175 -0
- package/dist/repertoire/plugins.js +253 -0
- package/dist/repertoire/relationship-authorization.js +322 -0
- package/dist/repertoire/sanctuary-container-policy.js +50 -0
- package/dist/repertoire/shell-sessions.js +134 -0
- package/dist/repertoire/skills.js +48 -4
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tool-arguments.js +182 -0
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-a2a.js +742 -0
- package/dist/repertoire/tools-attachments.js +322 -0
- package/dist/repertoire/tools-awaiting.js +897 -0
- package/dist/repertoire/tools-base.js +83 -1168
- package/dist/repertoire/tools-bluebubbles.js +2 -0
- package/dist/repertoire/tools-bridge.js +144 -0
- package/dist/repertoire/tools-bundle.js +995 -0
- package/dist/repertoire/tools-commerce.js +253 -0
- package/dist/repertoire/tools-config.js +186 -0
- package/dist/repertoire/tools-continuity.js +588 -0
- package/dist/repertoire/tools-credential.js +383 -0
- package/dist/repertoire/tools-escalation.js +84 -0
- package/dist/repertoire/tools-evolution.js +527 -0
- package/dist/repertoire/tools-files.js +362 -0
- package/dist/repertoire/tools-flight.js +290 -0
- package/dist/repertoire/tools-flow.js +119 -0
- package/dist/repertoire/tools-friend-link.js +57 -0
- package/dist/repertoire/tools-github.js +3 -8
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools-house-care.js +174 -0
- package/dist/repertoire/tools-mail.js +1828 -0
- package/dist/repertoire/tools-notes.js +501 -0
- package/dist/repertoire/tools-obligations.js +182 -0
- package/dist/repertoire/tools-orientation.js +31 -0
- package/dist/repertoire/tools-record.js +482 -0
- package/dist/repertoire/tools-rsvp.js +139 -0
- package/dist/repertoire/tools-runtime.js +238 -0
- package/dist/repertoire/tools-sanctuary-host.js +202 -0
- package/dist/repertoire/tools-session.js +1141 -0
- package/dist/repertoire/tools-shell.js +154 -0
- package/dist/repertoire/tools-shepherd-guidance.js +171 -0
- package/dist/repertoire/tools-shepherd.js +325 -0
- package/dist/repertoire/tools-steward-policy.js +147 -0
- package/dist/repertoire/tools-stripe.js +224 -0
- package/dist/repertoire/tools-surface.js +369 -0
- package/dist/repertoire/tools-teams.js +50 -39
- package/dist/repertoire/tools-telegram-contacts.js +37 -0
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +982 -0
- package/dist/repertoire/tools-unraid.js +556 -0
- package/dist/repertoire/tools-user-profile.js +146 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools-voice-local.js +198 -0
- package/dist/repertoire/tools-voice.js +145 -0
- package/dist/repertoire/tools.js +519 -156
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/unraid-client.js +155 -0
- package/dist/repertoire/unraid-restart.js +339 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +686 -0
- package/dist/rsvp/aisleplanner-client.js +354 -0
- package/dist/rsvp/cli.js +936 -0
- package/dist/rsvp/config.js +327 -0
- package/dist/rsvp/cutover.js +453 -0
- package/dist/rsvp/diagnostics.js +232 -0
- package/dist/rsvp/diff-renderer.js +176 -0
- package/dist/rsvp/habit-policy.js +110 -0
- package/dist/rsvp/habit-stage.js +123 -0
- package/dist/rsvp/incident-bundle.js +97 -0
- package/dist/rsvp/migration.js +204 -0
- package/dist/rsvp/native-habit-runner.js +630 -0
- package/dist/rsvp/outbound-state.js +628 -0
- package/dist/rsvp/query.js +118 -0
- package/dist/rsvp/replay.js +233 -0
- package/dist/rsvp/snapshot.js +232 -0
- package/dist/rsvp/spend-ledger.js +175 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/a2a-entry.js +140 -0
- package/dist/senses/acp-server.js +386 -0
- package/dist/senses/attention-queue.js +186 -0
- package/dist/senses/await-turn-message.js +65 -0
- package/dist/senses/bluebubbles/active-turns.js +216 -0
- package/dist/senses/bluebubbles/attachment-cache.js +53 -0
- package/dist/senses/bluebubbles/attachment-download.js +137 -0
- package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +393 -43
- package/dist/senses/bluebubbles/context-packet.js +207 -0
- package/dist/senses/bluebubbles/context-smoke.js +144 -0
- package/dist/senses/bluebubbles/entry.js +90 -0
- package/dist/senses/bluebubbles/host-account-verifier.js +88 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
- package/dist/senses/bluebubbles/index.js +4054 -0
- package/dist/senses/bluebubbles/latest-turn.js +311 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
- package/dist/senses/bluebubbles/outbound-state.js +335 -0
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/reaction-policy.js +47 -0
- package/dist/senses/bluebubbles/replay.js +284 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
- package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- package/dist/senses/bluebubbles/webhook-registration.js +257 -0
- package/dist/senses/bluebubbles-meta-guard.js +39 -0
- package/dist/senses/cli/bracketed-paste.js +82 -0
- package/dist/senses/cli/image-paste.js +287 -0
- package/dist/senses/cli/image-ref-navigation.js +75 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/input-keys.js +174 -0
- package/dist/senses/cli/kill-ring.js +86 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +607 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +85 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +241 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli.js +570 -309
- package/dist/senses/commands.js +66 -3
- package/dist/senses/context-packet-ledger.js +371 -0
- package/dist/senses/context-packets.js +154 -0
- package/dist/senses/habit-lifecycle-guard.js +24 -0
- package/dist/senses/habit-turn-message.js +160 -0
- package/dist/senses/ingress-evidence.js +2 -0
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +524 -0
- package/dist/senses/pipeline.js +1158 -204
- package/dist/senses/private-runtime-worker.js +857 -0
- package/dist/senses/private-runtime.js +1627 -0
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/root-host-approval-port.js +345 -0
- package/dist/senses/root-host-approval-runtime.js +393 -0
- package/dist/senses/sanctuary-authority-resident.js +102 -0
- package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
- package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
- package/dist/senses/sanctuary-grounding.js +118 -0
- package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
- package/dist/senses/sanctuary-health-acceptance-probe.js +786 -0
- package/dist/senses/sanctuary-health-runner.js +106 -0
- package/dist/senses/sanctuary-health.js +442 -0
- package/dist/senses/sanctuary-house.js +97 -0
- package/dist/senses/sanctuary-install-state-contract.js +169 -0
- package/dist/senses/sanctuary-interactive-control.js +410 -0
- package/dist/senses/sanctuary-media-catalog-contract.js +268 -0
- package/dist/senses/sanctuary-media-optimization.js +517 -0
- package/dist/senses/sanctuary-runtime.js +390 -0
- package/dist/senses/sanctuary-sab.js +85 -0
- package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
- package/dist/senses/shared-turn.js +832 -0
- package/dist/senses/shepherd/agent-presentation.js +88 -0
- package/dist/senses/shepherd/attention-page.js +166 -0
- package/dist/senses/shepherd/attention-sidebar.js +76 -0
- package/dist/senses/shepherd/attention.js +295 -0
- package/dist/senses/shepherd/checkin-config.js +76 -0
- package/dist/senses/shepherd/checkin-store.js +386 -0
- package/dist/senses/shepherd/client.js +196 -0
- package/dist/senses/shepherd/cmux.js +483 -0
- package/dist/senses/shepherd/composer.js +336 -0
- package/dist/senses/shepherd/copilot-app.js +648 -0
- package/dist/senses/shepherd/feedback-reply.js +108 -0
- package/dist/senses/shepherd/feedback-source.js +20 -0
- package/dist/senses/shepherd/feedback-store.js +331 -0
- package/dist/senses/shepherd/feedback-submit.js +182 -0
- package/dist/senses/shepherd/feedback-worker.js +208 -0
- package/dist/senses/shepherd/guidance.js +273 -0
- package/dist/senses/shepherd/herdr.js +427 -0
- package/dist/senses/shepherd/host.js +17 -0
- package/dist/senses/shepherd/human-decision-guard.js +29 -0
- package/dist/senses/shepherd/judge-evaluation-command.js +183 -0
- package/dist/senses/shepherd/judge-evaluation-entry.js +13 -0
- package/dist/senses/shepherd/judge-evaluation.js +155 -0
- package/dist/senses/shepherd/judge.js +210 -0
- package/dist/senses/shepherd/notice-outbox.js +262 -0
- package/dist/senses/shepherd/observation-publisher.js +320 -0
- package/dist/senses/shepherd/operations-defaults.js +86 -0
- package/dist/senses/shepherd/operations.js +159 -0
- package/dist/senses/shepherd/owner-notice.js +489 -0
- package/dist/senses/shepherd/prompt-state.js +60 -0
- package/dist/senses/shepherd/recovery.js +543 -0
- package/dist/senses/shepherd/redact.js +66 -0
- package/dist/senses/shepherd/returns.js +79 -0
- package/dist/senses/shepherd/sense.js +849 -0
- package/dist/senses/shepherd/shepherd-judge-corpus-v1.json +170 -0
- package/dist/senses/shepherd/surface.js +123 -0
- package/dist/senses/shepherd/target-binding.js +13 -0
- package/dist/senses/shepherd/terminal-process.js +147 -0
- package/dist/senses/shepherd-entry.js +98 -0
- package/dist/senses/surface-tool.js +109 -0
- package/dist/senses/teams-entry.js +68 -8
- package/dist/senses/teams-image.js +119 -0
- package/dist/senses/teams.js +998 -337
- package/dist/senses/telegram-admission.js +718 -0
- package/dist/senses/telegram-approval-runtime.js +793 -0
- package/dist/senses/telegram-attachments.js +186 -0
- package/dist/senses/telegram-audit-ledger.js +220 -0
- package/dist/senses/telegram-authority-transport.js +231 -0
- package/dist/senses/telegram-client.js +1755 -0
- package/dist/senses/telegram-effect-adapter.js +904 -0
- package/dist/senses/telegram-entry.js +83 -0
- package/dist/senses/telegram-handoff.js +154 -0
- package/dist/senses/telegram.js +2474 -0
- package/dist/senses/trust-gate.js +104 -9
- package/dist/senses/voice/audio-playback.js +237 -0
- package/dist/senses/voice/audio-routing.js +119 -0
- package/dist/senses/voice/call-auth.js +250 -0
- package/dist/senses/voice/elevenlabs.js +202 -0
- package/dist/senses/voice/floor-control.js +431 -0
- package/dist/senses/voice/floor-controller.js +115 -0
- package/dist/senses/voice/golden-path.js +116 -0
- package/dist/senses/voice/index.js +29 -0
- package/dist/senses/voice/local-audio-control.js +344 -0
- package/dist/senses/voice/local-audio-devices.js +516 -0
- package/dist/senses/voice/local-audio-disclosure.js +174 -0
- package/dist/senses/voice/local-audio-identity.js +103 -0
- package/dist/senses/voice/local-audio-join.js +325 -0
- package/dist/senses/voice/local-audio-mute.js +199 -0
- package/dist/senses/voice/local-audio-notify.js +124 -0
- package/dist/senses/voice/local-audio-prompts.js +116 -0
- package/dist/senses/voice/local-audio-transport.js +427 -0
- package/dist/senses/voice/local-audio.js +281 -0
- package/dist/senses/voice/local-media-stream.js +145 -0
- package/dist/senses/voice/meeting.js +113 -0
- package/dist/senses/voice/outbound.js +190 -0
- package/dist/senses/voice/phone.js +33 -0
- package/dist/senses/voice/playback.js +139 -0
- package/dist/senses/voice/process-guards.js +51 -0
- package/dist/senses/voice/realtime-eval.js +496 -0
- package/dist/senses/voice/realtime-tool-schema.js +195 -0
- package/dist/senses/voice/realtime-trace.js +531 -0
- package/dist/senses/voice/transcript.js +70 -0
- package/dist/senses/voice/turn.js +191 -0
- package/dist/senses/voice/twilio-phone-runtime.js +836 -0
- package/dist/senses/voice/twilio-phone.js +5454 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +161 -0
- package/dist/senses/voice-entry.js +83 -0
- package/dist/senses/voice-realtime-eval-command.js +99 -0
- package/dist/senses/voice-realtime-eval-entry.js +21 -0
- package/dist/senses/voice-twilio-entry.js +87 -0
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +265 -0
- package/dist/util/frontmatter.js +69 -0
- package/npm-shrinkwrap.json +8342 -0
- package/package.json +76 -13
- package/skills/agent-commerce.md +113 -0
- package/skills/browser-navigation.md +117 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
- package/skills/configure-dev-tools.md +99 -0
- package/skills/travel-planning.md +138 -0
- package/dist/heart/daemon/auth-flow.js +0 -430
- package/dist/heart/daemon/ouro-path-installer.js +0 -260
- package/dist/heart/daemon/ouro-version-manager.js +0 -171
- package/dist/heart/daemon/update-hooks.js +0 -138
- package/dist/heart/obligations.js +0 -191
- package/dist/heart/safe-workspace.js +0 -381
- package/dist/heart/session-recall.js +0 -116
- package/dist/mind/friends/channel.js +0 -84
- package/dist/mind/friends/group-context.js +0 -144
- package/dist/mind/friends/resolver.js +0 -84
- package/dist/mind/friends/store-file.js +0 -190
- package/dist/mind/friends/store.js +0 -4
- package/dist/mind/friends/tokens.js +0 -26
- package/dist/mind/friends/trust-explanation.js +0 -74
- package/dist/mind/friends/types.js +0 -29
- package/dist/repertoire/tasks/board.js +0 -134
- package/dist/repertoire/tasks/index.js +0 -224
- package/dist/repertoire/tasks/lifecycle.js +0 -80
- package/dist/repertoire/tasks/middleware.js +0 -65
- package/dist/repertoire/tasks/parser.js +0 -173
- package/dist/repertoire/tasks/scanner.js +0 -132
- package/dist/repertoire/tasks/transitions.js +0 -144
- package/dist/senses/bluebubbles-entry.js +0 -13
- package/dist/senses/bluebubbles.js +0 -1181
- package/dist/senses/debug-activity.js +0 -154
- package/dist/senses/inner-dialog-worker.js +0 -90
- package/dist/senses/inner-dialog.js +0 -525
- package/subagents/README.md +0 -7
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
- /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
- /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
- /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
|
@@ -33,12 +33,21 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.OURO_CLI_TRUST_MANIFEST = void 0;
|
|
36
|
+
exports.OURO_CLI_TRUST_MANIFEST = exports.PSYCHE_REFUSAL = void 0;
|
|
37
|
+
exports.canonicalTarget = canonicalTarget;
|
|
38
|
+
exports.insideAgentFolder = insideAgentFolder;
|
|
39
|
+
exports.psycheFileClass = psycheFileClass;
|
|
40
|
+
exports.psycheWriteAllowed = psycheWriteAllowed;
|
|
41
|
+
exports.psycheEditNote = psycheEditNote;
|
|
37
42
|
exports.guardInvocation = guardInvocation;
|
|
38
43
|
const fs = __importStar(require("node:fs"));
|
|
39
44
|
const os = __importStar(require("node:os"));
|
|
40
|
-
const
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
46
|
+
const friends_1 = require("@ouro.bot/friends");
|
|
47
|
+
const shell_quote_1 = require("shell-quote");
|
|
41
48
|
const runtime_1 = require("../nerves/runtime");
|
|
49
|
+
const store_1 = require("../commerce/store");
|
|
50
|
+
const identity_1 = require("../heart/identity");
|
|
42
51
|
const deny = (reason) => ({ allowed: false, reason });
|
|
43
52
|
const allow = { allowed: true };
|
|
44
53
|
// --- reason templates ---
|
|
@@ -55,17 +64,165 @@ const REASONS = {
|
|
|
55
64
|
// --- read-only tools that never need guardrails ---
|
|
56
65
|
const READ_ONLY_TOOLS = new Set(["read_file", "glob", "grep"]);
|
|
57
66
|
// --- protected path detection ---
|
|
58
|
-
const PROTECTED_PATH_SEGMENTS = [
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
const PROTECTED_PATH_SEGMENTS = [
|
|
68
|
+
".git/",
|
|
69
|
+
".ouro-cli/vault-unlock/",
|
|
70
|
+
".ouro-cli/vault-unlock-dpapi/",
|
|
71
|
+
"state/policy/",
|
|
72
|
+
// The pre-trust-directory home of the escalation grants; the file is no longer read, and a model must not recreate it.
|
|
73
|
+
"state/a2a/escalation-grants.json",
|
|
74
|
+
// Where to queue mail for offline peers; a model that rewrote a contact could send a peer's mail to its own relay.
|
|
75
|
+
"state/a2a/relay-contacts/",
|
|
76
|
+
// Relay retirements awaiting their vault write; a forged marker would skip draining an old relay inbox.
|
|
77
|
+
"state/a2a/relay-retired/",
|
|
78
|
+
"state/outbox/",
|
|
79
|
+
"state/reports/",
|
|
80
|
+
];
|
|
81
|
+
const PROTECTED_FILENAMES = ["agent.json", "tool-profiles.json"];
|
|
82
|
+
/** How many symlinks a dangling-link walk follows before refusing (the Linux kernel's own limit is 40). */
|
|
83
|
+
const MAX_SYMLINK_HOPS = 40;
|
|
84
|
+
/** A lookup that failed only because part of the path does not exist yet; any other failure leaves the target unknown. */
|
|
85
|
+
function rethrowUnlessMissing(error) {
|
|
86
|
+
const code = error.code;
|
|
87
|
+
if (code !== "ENOENT" && code !== "ENOTDIR")
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The real location of `lexical`: `realpathSync.native` of the deepest part that exists, the final file included, with the
|
|
92
|
+
* missing remainder appended. A dangling symlink is followed, from its real parent, to where a write through it would land.
|
|
93
|
+
* Throws when the filesystem cannot say where the path lands (a denied lookup, a symlink loop, too many links), so the
|
|
94
|
+
* guard can refuse rather than fall back to the lexical path.
|
|
95
|
+
*/
|
|
96
|
+
function canonicalize(lexical) {
|
|
97
|
+
let current = lexical;
|
|
98
|
+
const missing = [];
|
|
99
|
+
let hops = 0;
|
|
100
|
+
for (;;) {
|
|
101
|
+
try {
|
|
102
|
+
return path.join(fs.realpathSync.native(current), ...missing);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
rethrowUnlessMissing(error);
|
|
106
|
+
}
|
|
107
|
+
let stat;
|
|
108
|
+
try {
|
|
109
|
+
stat = fs.lstatSync(current);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
rethrowUnlessMissing(error);
|
|
113
|
+
}
|
|
114
|
+
if (stat?.isSymbolicLink()) {
|
|
115
|
+
if (hops >= MAX_SYMLINK_HOPS)
|
|
116
|
+
throw new Error(`too many symbolic links resolving ${lexical}`);
|
|
117
|
+
hops += 1;
|
|
118
|
+
current = path.resolve(fs.realpathSync.native(path.dirname(current)), fs.readlinkSync(current));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const parent = path.dirname(current);
|
|
122
|
+
if (parent === current)
|
|
123
|
+
return path.join(current, ...missing);
|
|
124
|
+
missing.unshift(path.basename(current));
|
|
125
|
+
current = parent;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Every place a file tool could write `p`: resolved against the agent root and against the repo root (which is what
|
|
130
|
+
* `write_file` and `edit_file` use), each in its lexical and canonical form. An absolute `p` is resolved only as itself.
|
|
131
|
+
*/
|
|
132
|
+
function canonicalTarget(p, agentRoot) {
|
|
133
|
+
const lexicals = path.isAbsolute(p) ? [p] : [path.resolve(agentRoot, p), path.resolve((0, identity_1.getRepoRoot)(), p)];
|
|
134
|
+
return lexicals.flatMap((lexical) => [lexical, canonicalize(lexical)]);
|
|
135
|
+
}
|
|
136
|
+
/** True when any form of `p` is the agent's `folder` (in either its lexical or its canonical form) or under it. */
|
|
137
|
+
function insideAgentFolder(p, agentRoot, folder) {
|
|
138
|
+
const lexicalFolder = path.resolve(agentRoot, folder);
|
|
139
|
+
const folders = [lexicalFolder, canonicalize(lexicalFolder)];
|
|
140
|
+
return canonicalTarget(p, agentRoot).some((target) => folders.some((dir) => target === dir || target.startsWith(`${dir}${path.sep}`)));
|
|
141
|
+
}
|
|
142
|
+
// --- the psyche belongs to the agent ---
|
|
143
|
+
// Every agent writes its own TACIT.md: what it learns about how its house works. The rest of its psyche (who it is) changes only
|
|
144
|
+
// when a family human asks it directly, one to one, on a channel that proves who they are.
|
|
145
|
+
exports.PSYCHE_REFUSAL = "that file is part of me; I change it when my owner asks me in our own chat.";
|
|
146
|
+
const PSYCHE_IDENTITY_CHANNELS = new Set(["telegram", "bluebubbles", "teams"]);
|
|
147
|
+
/** `tacit` for the agent's psyche/TACIT.md, `identity` for anything else in psyche/ (the folder and new files included), `null` outside it. */
|
|
148
|
+
function psycheFileClass(filePath, agentRoot) {
|
|
149
|
+
if (!filePath || !agentRoot || !insideAgentFolder(filePath, agentRoot, "psyche"))
|
|
150
|
+
return null;
|
|
151
|
+
const lexicalFolder = path.resolve(agentRoot, "psyche");
|
|
152
|
+
const folders = [lexicalFolder, canonicalize(lexicalFolder)];
|
|
153
|
+
const tacits = folders.map((folder) => path.join(folder, "TACIT.md"));
|
|
154
|
+
// Every form of the path that lands in psyche/ must be TACIT: a link that sends one form of it to SOUL makes it identity.
|
|
155
|
+
const inPsyche = canonicalTarget(filePath, agentRoot).filter((target) => folders.some((dir) => target === dir || target.startsWith(`${dir}${path.sep}`)));
|
|
156
|
+
return inPsyche.every((target) => tacits.includes(target)) ? "tacit" : "identity";
|
|
157
|
+
}
|
|
158
|
+
/** The lstat of `p`, or null when it does not exist; throws on any other lookup failure. */
|
|
159
|
+
function lstatIfPresent(p) {
|
|
160
|
+
try {
|
|
161
|
+
return fs.lstatSync(p);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error.code === "ENOENT")
|
|
165
|
+
return null;
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Whether the person in this conversation may have the agent write this psyche file. */
|
|
170
|
+
function psycheWriteAllowed(fileClass, _filePath, context) {
|
|
171
|
+
const agentRoot = context.agentRoot;
|
|
172
|
+
/* v8 ignore next -- a file is only classed as psyche when there is an agent root @preserve */
|
|
173
|
+
if (!agentRoot)
|
|
174
|
+
return false;
|
|
175
|
+
try {
|
|
176
|
+
if (lstatIfPresent(path.join(agentRoot, "psyche"))?.isSymbolicLink())
|
|
177
|
+
return false;
|
|
178
|
+
if (fileClass === "tacit") {
|
|
179
|
+
// A symlink or a hard link would carry the write to some other file.
|
|
180
|
+
const tacit = lstatIfPresent(path.join(agentRoot, "psyche", "TACIT.md"));
|
|
181
|
+
if (tacit && (tacit.isSymbolicLink() || tacit.nlink > 1))
|
|
182
|
+
return false;
|
|
183
|
+
if (context.friendId === undefined)
|
|
184
|
+
return true;
|
|
185
|
+
return context.trustLevel === undefined || context.trustLevel === "friend" || context.trustLevel === "family";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return context.friendKind === "human"
|
|
192
|
+
&& context.trustLevel === "family"
|
|
193
|
+
&& context.channel !== undefined && PSYCHE_IDENTITY_CHANNELS.has(context.channel)
|
|
194
|
+
&& context.isGroupChat !== true
|
|
195
|
+
// Teams does not yet tell the turn whether it is a group chat, so there only an explicit one-to-one counts.
|
|
196
|
+
&& (context.channel !== "teams" || context.isGroupChat === false);
|
|
197
|
+
}
|
|
198
|
+
/** The line a successful write to an identity file carries back to the agent. */
|
|
199
|
+
function psycheEditNote(file) {
|
|
200
|
+
return `psyche edit: I changed psyche/${file}. I tell my owner in one line which file changed and why.`;
|
|
201
|
+
}
|
|
202
|
+
/** The agent's psyche folder, in the lexical form the shell check uses; the file tools classify with `psycheFileClass`. */
|
|
203
|
+
function isPsychePath(filePath, agentRoot) {
|
|
204
|
+
const psyche = path.resolve(agentRoot, "psyche");
|
|
205
|
+
const target = path.resolve(agentRoot, filePath);
|
|
206
|
+
return target === psyche || target.startsWith(`${psyche}${path.sep}`);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The friends store (`friends/` under the agent root) is written by the harness's own code, which the model's file tools and
|
|
210
|
+
* shell redirects must not reach: a model talked into editing a record could rewrite trust, admission or a legacy grant.
|
|
211
|
+
* Scoped to this agent's root, so a project directory that happens to be named `friends` stays editable. Defence in depth
|
|
212
|
+
* only: a shell can still write anywhere its uid can, which is why delegated-command authority lives outside the bundle.
|
|
213
|
+
*/
|
|
214
|
+
function isFriendsStorePath(filePath, agentRoot) {
|
|
215
|
+
const store = path.resolve(agentRoot, "friends");
|
|
216
|
+
const target = path.resolve(agentRoot, filePath);
|
|
217
|
+
return target === store || target.startsWith(`${store}${path.sep}`);
|
|
61
218
|
}
|
|
62
219
|
function isProtectedPath(filePath) {
|
|
63
220
|
for (const segment of PROTECTED_PATH_SEGMENTS) {
|
|
64
221
|
if (filePath.includes(`/${segment}`) || filePath.startsWith(segment))
|
|
65
222
|
return true;
|
|
66
223
|
}
|
|
67
|
-
for (const
|
|
68
|
-
if (
|
|
224
|
+
for (const name of PROTECTED_FILENAMES) {
|
|
225
|
+
if (path.basename(filePath) === name)
|
|
69
226
|
return true;
|
|
70
227
|
}
|
|
71
228
|
return false;
|
|
@@ -99,6 +256,196 @@ function shellWritesToProtectedPath(command) {
|
|
|
99
256
|
return true;
|
|
100
257
|
return false;
|
|
101
258
|
}
|
|
259
|
+
// --- the psyche folder in shell commands ---
|
|
260
|
+
// The agent changes its own psyche through write_file and edit_file, where the trust gate below decides who may ask for it.
|
|
261
|
+
// The shell never writes there, for anyone: a shell command that mentions the psyche folder at all runs only if every part of
|
|
262
|
+
// it is a read-only command, because enumerating the ways to write a file (>, >>, tee, cp, mv, sed -i, node -e, python, cd then
|
|
263
|
+
// redirect) is a list the model can always step around.
|
|
264
|
+
const PSYCHE_READ_ONLY_COMMANDS = new Set(["cat", "ls", "head", "tail", "grep", "wc", "sha256sum", "stat", "cd", "pwd"]);
|
|
265
|
+
const SEGMENT_OPERATORS = new Set(["&&", "||", ";", "|"]);
|
|
266
|
+
// --- globs and expansions that could land inside a protected folder ---
|
|
267
|
+
// A word such as /bundle/fr*nds/x.json, /bundle/friend?/x.json or ${F}ends/x.json names no protected folder literally, but the
|
|
268
|
+
// shell could resolve it into one. A word like that counts as mentioning the folder when its directory part can match the folder's
|
|
269
|
+
// path, so the read-only rule below applies to the command.
|
|
270
|
+
const EXPANSION = "\u0001";
|
|
271
|
+
const STABLE_VARIABLES = ["HOME", "TMPDIR", "USER"];
|
|
272
|
+
function neutraliseExpansions(command) {
|
|
273
|
+
return command.replace(/\$\([^)]*\)|`[^`]*`|\$\{([A-Za-z_]\w*)\}|\$([A-Za-z_]\w*)|\$\{[^}]*\}/g, (_match, braced, plain) => {
|
|
274
|
+
const name = braced ?? plain;
|
|
275
|
+
if (name !== undefined && STABLE_VARIABLES.includes(name) && !new RegExp(`\\b${name}=`).test(command) && process.env[name])
|
|
276
|
+
return process.env[name];
|
|
277
|
+
return EXPANSION;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function wordPattern(word) {
|
|
281
|
+
const generalised = word.replace(/\{[^}]*\}/g, EXPANSION).replace(/\[[^\]]*\]/g, "?");
|
|
282
|
+
let source = "";
|
|
283
|
+
for (const char of generalised)
|
|
284
|
+
source += char === EXPANSION ? ".*" : char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[.+^$()|\\\]{}[]/g, "\\$&");
|
|
285
|
+
return new RegExp(`^${source}$`);
|
|
286
|
+
}
|
|
287
|
+
function globOrExpansionCouldReach(command, folder, agentRoot) {
|
|
288
|
+
const tokens = neutraliseExpansions(command).split(/[\s<>|;&()'"]+/).filter((token) => /[*?[{\u0001]/.test(token));
|
|
289
|
+
return tokens.some((token) => {
|
|
290
|
+
const word = token.startsWith("~") ? expandHome(token) : token;
|
|
291
|
+
const absolute = word.startsWith("/") || word.startsWith(EXPANSION) ? word : `${agentRoot}/${word}`;
|
|
292
|
+
const dir = absolute.slice(0, Math.max(absolute.lastIndexOf("/"), 0));
|
|
293
|
+
return wordPattern(absolute).test(folder) || wordPattern(dir).test(folder);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function commandMentionsPsyche(command, words, agentRoot) {
|
|
297
|
+
const psyche = path.resolve(agentRoot, "psyche");
|
|
298
|
+
if (globOrExpansionCouldReach(command, psyche, agentRoot))
|
|
299
|
+
return true;
|
|
300
|
+
if (command.includes(psyche) || words.some((word) => word.includes(psyche)))
|
|
301
|
+
return true;
|
|
302
|
+
if (words.some((word) => word === "psyche" || isPsychePath(word, agentRoot)))
|
|
303
|
+
return true;
|
|
304
|
+
// A relative "psyche/..." inside code or a quoted argument, when this agent has such a folder.
|
|
305
|
+
if (/(?:^|[\s'"=(])psyche\//.test(command) && fs.existsSync(psyche))
|
|
306
|
+
return true;
|
|
307
|
+
// ~/..., $HOME/... and paths relative to a cd or -C target that land in this agent's psyche/.
|
|
308
|
+
return commandMentionsAgentFolder(command, words, agentRoot, "psyche");
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* The shared rule: when a shell command mentions a protected folder at all, it runs only if every part of it is a read-only
|
|
312
|
+
* command. The mention test is the only thing that differs between folders.
|
|
313
|
+
*/
|
|
314
|
+
function protectedFolderShellWriteRefused(command, mentions) {
|
|
315
|
+
const entries = (0, shell_quote_1.parse)(command);
|
|
316
|
+
const words = entries.filter((entry) => typeof entry === "string");
|
|
317
|
+
if (!mentions(words))
|
|
318
|
+
return false;
|
|
319
|
+
// Substitution, heredocs and any $VAR or ${VAR} expansion: the shell could build a protected path or a command word we cannot see.
|
|
320
|
+
if (/\$[({A-Za-z_]|`|<\(|<</.test(command))
|
|
321
|
+
return true;
|
|
322
|
+
let expectCommandWord = true;
|
|
323
|
+
for (const entry of entries) {
|
|
324
|
+
if (typeof entry === "string") {
|
|
325
|
+
if (expectCommandWord && !/^\w+=/.test(entry)) {
|
|
326
|
+
if (!PSYCHE_READ_ONLY_COMMANDS.has(entry))
|
|
327
|
+
return true;
|
|
328
|
+
expectCommandWord = false;
|
|
329
|
+
}
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const op = "op" in entry ? entry.op : "comment";
|
|
333
|
+
if (SEGMENT_OPERATORS.has(op))
|
|
334
|
+
expectCommandWord = true;
|
|
335
|
+
else if (op !== "glob")
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
function psycheShellWriteRefused(command, agentRoot) {
|
|
341
|
+
if (!agentRoot)
|
|
342
|
+
return false;
|
|
343
|
+
try {
|
|
344
|
+
return protectedFolderShellWriteRefused(command, (words) => commandMentionsPsyche(command, words, agentRoot));
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// The filesystem could not say where a path that names psyche/ lands: refuse rather than guess.
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// --- the friends store in shell commands ---
|
|
352
|
+
// Same stance as the psyche folder: a command that names this agent's friends store runs only if it is read-only, because a list of
|
|
353
|
+
// the ways to write a file (>, >>, tee, cp, mv, sed -i, node -e, python, cd then redirect, ~ paths) is one the model can step around.
|
|
354
|
+
function expandHome(word) {
|
|
355
|
+
return word === "~" || word.startsWith("~/") ? path.join(os.homedir(), word.slice(1)) : word;
|
|
356
|
+
}
|
|
357
|
+
function commandMentionsFriendsStore(command, words, agentRoot) {
|
|
358
|
+
if (globOrExpansionCouldReach(command, path.resolve(agentRoot, "friends"), agentRoot))
|
|
359
|
+
return true;
|
|
360
|
+
// Only path-shaped words resolve against the store: an ordinary word such as "friends" in prose is not a path.
|
|
361
|
+
if (words.some((word) => word.includes("/") && isFriendsStorePath(expandHome(word), agentRoot)))
|
|
362
|
+
return true;
|
|
363
|
+
// A bare `friends` names the store only as the target of a cd; elsewhere it is an ordinary word ("my friends are kind").
|
|
364
|
+
if (words.some((word, index) => word.startsWith("friends/") || (word === "friends" && words[index - 1] === "cd")))
|
|
365
|
+
return true;
|
|
366
|
+
// A variable or substitution in front of the path ($BUNDLE/friends/x.json): the folder name is still in the command.
|
|
367
|
+
if (/\$[({A-Za-z_]|`/.test(command) && /(?:^|[\/\s'"=])friends(?:$|[\/\s'"])/.test(command))
|
|
368
|
+
return true;
|
|
369
|
+
// An absolute or relative path inside code or a quoted argument, such as node -e "...'/bundle/friends/x.json'...".
|
|
370
|
+
const store = path.resolve(agentRoot, "friends");
|
|
371
|
+
if (command.includes(store) || /(?:^|[\s'"=(])friends\//.test(command))
|
|
372
|
+
return true;
|
|
373
|
+
// Paths relative to a cd or -C target that land in this agent's friends/.
|
|
374
|
+
return commandMentionsAgentFolder(command, words, agentRoot, "friends");
|
|
375
|
+
}
|
|
376
|
+
function friendsShellWriteRefused(command, agentRoot) {
|
|
377
|
+
if (!agentRoot)
|
|
378
|
+
return false;
|
|
379
|
+
try {
|
|
380
|
+
return protectedFolderShellWriteRefused(command, (words) => commandMentionsFriendsStore(command, words, agentRoot));
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// The filesystem could not say where a path that names friends/ lands: refuse rather than guess.
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// --- where a path in a shell command lands ---
|
|
388
|
+
// A command can name one of this agent's folders by an absolute path, by ~/ or $HOME/, or relative to wherever it cd's (or
|
|
389
|
+
// git -C's) first. Each word whose path has a part exactly `<name>` is resolved against every directory the command could be
|
|
390
|
+
// working in, lexically and canonically, and counts when it lands in this agent's <name>/.
|
|
391
|
+
const UNFOLLOWABLE_WORKING_DIRECTORY = /(?:^|[\s;&|(`])(?:pushd|popd)(?:\s|$)|(?:^|[\s;&|(`])cd\s+(?:--|["']?\$)|\s-C\s*["']?\$|(?:^|[\s;&|(`/])(?:sh|bash|zsh|dash|ksh)\s+(?:-\w+\s+)*-\w*c\b/;
|
|
392
|
+
/** Words of a shell command that name a path whose part is exactly `name`, wherever they sit (code and quotes included). */
|
|
393
|
+
function namedFolderWords(command, name) {
|
|
394
|
+
const part = new RegExp(`(?:^|/)${name}(?:/|$)`);
|
|
395
|
+
return command.split(/[\s'"=();|&<>`]+/).filter((token) => part.test(token));
|
|
396
|
+
}
|
|
397
|
+
function commandMentionsAgentFolder(command, words, agentRoot, name) {
|
|
398
|
+
const lexicalFolder = path.resolve(agentRoot, name);
|
|
399
|
+
if (globOrExpansionCouldReach(command, lexicalFolder, agentRoot))
|
|
400
|
+
return true;
|
|
401
|
+
const candidates = namedFolderWords(command, name);
|
|
402
|
+
if (candidates.length === 0)
|
|
403
|
+
return false;
|
|
404
|
+
// A variable in front of the path ($HOME/.../<name>/x): where it lands is the shell's to say, so it counts.
|
|
405
|
+
if (candidates.some((candidate) => candidate.includes("$")))
|
|
406
|
+
return true;
|
|
407
|
+
// A working directory this guard cannot follow (pushd, cd --, a cd or -C to a variable, or a nested shell's -c string):
|
|
408
|
+
// a relative path could land anywhere, so it counts.
|
|
409
|
+
if (UNFOLLOWABLE_WORKING_DIRECTORY.test(command))
|
|
410
|
+
return true;
|
|
411
|
+
// A path that names this bundle's own folder (x.ouro/psyche) or a glob just above the folder (*/psyche) is this agent's
|
|
412
|
+
// wherever the command works from, so eval, a piped shell, env --chdir, find -path and code that chdir's all count.
|
|
413
|
+
const bundle = path.basename(path.resolve(agentRoot)).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
414
|
+
const anchored = new RegExp(`(?:^|/)(?:${bundle}|[^/]*[*?[][^/]*)/${name}(?:/|$)`);
|
|
415
|
+
if (candidates.some((candidate) => anchored.test(candidate)))
|
|
416
|
+
return true;
|
|
417
|
+
const folders = [lexicalFolder, path.join(canonicalize(path.resolve(agentRoot)), name)];
|
|
418
|
+
// Every directory the command could be working in: where the shell starts, and each cd target, from each earlier one.
|
|
419
|
+
const cwds = [process.cwd()];
|
|
420
|
+
for (const [index, word] of words.entries()) {
|
|
421
|
+
// `git -C <dir>` works in <dir> just as `cd <dir>` does.
|
|
422
|
+
if (words[index - 1] !== "cd" && words[index - 1] !== "-C")
|
|
423
|
+
continue;
|
|
424
|
+
const target = expandHome(word);
|
|
425
|
+
cwds.push(...(path.isAbsolute(target) ? [target] : cwds.map((cwd) => path.resolve(cwd, target))));
|
|
426
|
+
}
|
|
427
|
+
return candidates.some((candidate) => {
|
|
428
|
+
const word = expandHome(candidate);
|
|
429
|
+
const lexicals = path.isAbsolute(word) ? [path.resolve(word)] : cwds.map((cwd) => path.resolve(cwd, word));
|
|
430
|
+
return lexicals.flatMap((lexical) => [lexical, canonicalize(lexical)])
|
|
431
|
+
.some((target) => folders.some((dir) => target === dir || target.startsWith(`${dir}${path.sep}`)));
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
// --- the product rules in shell commands ---
|
|
435
|
+
// product/PRODUCT.md is the package's standing rules for this agent; releases replace it, and nothing in a conversation does.
|
|
436
|
+
// Unlike the friends store, a bare relative "product/" is an ordinary project folder almost everywhere, so it counts as this
|
|
437
|
+
// agent's only when the command is plainly working in the agent root.
|
|
438
|
+
function productShellWriteRefused(command, agentRoot) {
|
|
439
|
+
if (!agentRoot)
|
|
440
|
+
return false;
|
|
441
|
+
try {
|
|
442
|
+
return protectedFolderShellWriteRefused(command, (words) => commandMentionsAgentFolder(command, words, agentRoot, "product"));
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// The filesystem could not say where a path that names product/ lands: refuse rather than guess.
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
102
449
|
// --- structural guardrail checks (always on, all trust levels) ---
|
|
103
450
|
function checkReadBeforeWrite(toolName, args, context) {
|
|
104
451
|
if (toolName === "edit_file") {
|
|
@@ -127,21 +474,47 @@ function checkDestructiveShellPatterns(toolName, args) {
|
|
|
127
474
|
}
|
|
128
475
|
return allow;
|
|
129
476
|
}
|
|
130
|
-
function checkProtectedPaths(toolName, args) {
|
|
477
|
+
function checkProtectedPaths(toolName, args, context) {
|
|
131
478
|
if (toolName === "write_file" || toolName === "edit_file") {
|
|
132
479
|
const filePath = args.path || "";
|
|
480
|
+
// The file tools resolve a relative path against the repo root and follow symlinks, so the guard checks every form.
|
|
481
|
+
// A path whose landing place the filesystem cannot report is refused rather than checked lexically.
|
|
482
|
+
const agentRoot = filePath ? context.agentRoot : undefined;
|
|
483
|
+
if (agentRoot) {
|
|
484
|
+
try {
|
|
485
|
+
const psycheClass = psycheFileClass(filePath, agentRoot);
|
|
486
|
+
if (psycheClass && !psycheWriteAllowed(psycheClass, filePath, context))
|
|
487
|
+
return deny(exports.PSYCHE_REFUSAL);
|
|
488
|
+
if (insideAgentFolder(filePath, agentRoot, "friends"))
|
|
489
|
+
return deny(REASONS.protectedPath);
|
|
490
|
+
if (insideAgentFolder(filePath, agentRoot, "product"))
|
|
491
|
+
return deny(REASONS.protectedPath);
|
|
492
|
+
// A link can carry a write that reads as harmless to a protected file (state/policy, agent.json).
|
|
493
|
+
if (canonicalTarget(filePath, agentRoot).some(isProtectedPath))
|
|
494
|
+
return deny(REASONS.protectedPath);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return deny(REASONS.protectedPath);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
133
500
|
if (isProtectedPath(filePath))
|
|
134
501
|
return deny(REASONS.protectedPath);
|
|
135
502
|
}
|
|
136
503
|
if (toolName === "shell") {
|
|
137
504
|
const command = args.command || "";
|
|
505
|
+
if (psycheShellWriteRefused(command, context.agentRoot))
|
|
506
|
+
return deny(exports.PSYCHE_REFUSAL);
|
|
507
|
+
if (friendsShellWriteRefused(command, context.agentRoot))
|
|
508
|
+
return deny(REASONS.protectedPath);
|
|
509
|
+
if (productShellWriteRefused(command, context.agentRoot))
|
|
510
|
+
return deny(REASONS.protectedPath);
|
|
138
511
|
if (shellWritesToProtectedPath(command))
|
|
139
512
|
return deny(REASONS.protectedPath);
|
|
140
513
|
}
|
|
141
514
|
return allow;
|
|
142
515
|
}
|
|
143
516
|
function checkStructuralGuardrails(toolName, args, context) {
|
|
144
|
-
const protectedResult = checkProtectedPaths(toolName, args);
|
|
517
|
+
const protectedResult = checkProtectedPaths(toolName, args, context);
|
|
145
518
|
if (!protectedResult.allowed)
|
|
146
519
|
return protectedResult;
|
|
147
520
|
const destructiveResult = checkDestructiveShellPatterns(toolName, args);
|
|
@@ -155,18 +528,15 @@ exports.OURO_CLI_TRUST_MANIFEST = {
|
|
|
155
528
|
whoami: "acquaintance",
|
|
156
529
|
changelog: "acquaintance",
|
|
157
530
|
"session list": "acquaintance",
|
|
158
|
-
"task board": "friend",
|
|
159
|
-
"task create": "friend",
|
|
160
|
-
"task update": "friend",
|
|
161
|
-
"task show": "friend",
|
|
162
|
-
"task actionable": "friend",
|
|
163
|
-
"task deps": "friend",
|
|
164
|
-
"task sessions": "friend",
|
|
165
531
|
"friend list": "friend",
|
|
166
532
|
"friend show": "friend",
|
|
167
533
|
"friend create": "friend",
|
|
168
534
|
"friend update": "family",
|
|
169
|
-
|
|
535
|
+
// Linking and merging change who the agent believes someone is: family only.
|
|
536
|
+
"friend link": "family",
|
|
537
|
+
"friend unlink": "family",
|
|
538
|
+
"friend merge": "family",
|
|
539
|
+
"friend dedupe": "family",
|
|
170
540
|
"config model": "friend",
|
|
171
541
|
"config models": "friend",
|
|
172
542
|
"mcp list": "acquaintance",
|
|
@@ -201,7 +571,8 @@ function resolveOuroSubcommand(command) {
|
|
|
201
571
|
/* v8 ignore next -- bare "ouro" is caught upstream by checkShellTrustGuardrails @preserve */
|
|
202
572
|
if (!afterOuro)
|
|
203
573
|
return null;
|
|
204
|
-
|
|
574
|
+
// `--agent <name>` may come before the subcommand; it never changes which subcommand runs.
|
|
575
|
+
const tokens = afterOuro.split(/\s+/).filter((token, index, all) => token !== "--agent" && all[index - 1] !== "--agent");
|
|
205
576
|
const twoWord = tokens.length >= 2 ? `${tokens[0]} ${tokens[1]}` : null;
|
|
206
577
|
// Two-word match first (e.g. "task board"), then one-word (e.g. "whoami")
|
|
207
578
|
if (twoWord && exports.OURO_CLI_TRUST_MANIFEST[twoWord])
|
|
@@ -210,6 +581,26 @@ function resolveOuroSubcommand(command) {
|
|
|
210
581
|
return tokens[0];
|
|
211
582
|
return null;
|
|
212
583
|
}
|
|
584
|
+
// --- MCP server-specific trust rules ---
|
|
585
|
+
const MCP_SERVER_TRUST = {
|
|
586
|
+
browser: { minTrust: "friend", blockGroupChat: true },
|
|
587
|
+
};
|
|
588
|
+
function checkMcpServerTrust(command, context) {
|
|
589
|
+
const match = command.match(/^ouro\s+mcp\s+call\s+(\S+)/);
|
|
590
|
+
if (!match)
|
|
591
|
+
return allow;
|
|
592
|
+
const serverName = match[1];
|
|
593
|
+
const rules = MCP_SERVER_TRUST[serverName];
|
|
594
|
+
if (!rules)
|
|
595
|
+
return allow; // no special rules for this server
|
|
596
|
+
if (!trustLevelSatisfied(rules.minTrust, context.trustLevel ?? "friend")) {
|
|
597
|
+
return deny(REASONS.needsTrust);
|
|
598
|
+
}
|
|
599
|
+
if (rules.blockGroupChat && context.isGroupChat) {
|
|
600
|
+
return deny("browser tools are only available in 1:1 conversations, not group chats.");
|
|
601
|
+
}
|
|
602
|
+
return allow;
|
|
603
|
+
}
|
|
213
604
|
function checkSingleShellCommandTrust(command, trustLevel) {
|
|
214
605
|
const trimmed = command.trim();
|
|
215
606
|
const tokens = trimmed.split(/\s+/);
|
|
@@ -261,9 +652,794 @@ function checkWriteTrustGuardrails(toolName, args, context) {
|
|
|
261
652
|
return allow;
|
|
262
653
|
return deny(REASONS.needsTrustForWrite);
|
|
263
654
|
}
|
|
655
|
+
// --- credential tool trust gating ---
|
|
656
|
+
// Credential write tools: family only
|
|
657
|
+
const CREDENTIAL_FAMILY_TOOLS = new Set([
|
|
658
|
+
"credential_generate_password", "credential_store", "credential_delete", "vault_setup",
|
|
659
|
+
// User profile tools: family only
|
|
660
|
+
"user_profile_store", "user_profile_get", "user_profile_delete",
|
|
661
|
+
// Payment tools: family only
|
|
662
|
+
"stripe_create_card", "stripe_deactivate_card", "stripe_list_cards",
|
|
663
|
+
// Booking tools that involve payment: family only
|
|
664
|
+
"flight_book", "flight_hold", "flight_cancel",
|
|
665
|
+
]);
|
|
666
|
+
// Credential read tools: friend+
|
|
667
|
+
const CREDENTIAL_TRUSTED_TOOLS = new Set(["credential_get", "credential_list"]);
|
|
668
|
+
// Travel tools: friend+ (weather_lookup accesses vault credentials indirectly;
|
|
669
|
+
// advisory and geocode are public APIs but gated for consistency)
|
|
670
|
+
// Flight search is also friend+ (read-only, no payment)
|
|
671
|
+
const TRAVEL_TRUSTED_TOOLS = new Set(["weather_lookup", "travel_advisory", "geocode_search", "flight_search"]);
|
|
672
|
+
const A2A_TRUSTED_TOOLS = new Set(["a2a_list_peers", "a2a_send_message", "a2a_get_task"]);
|
|
673
|
+
const COMMERCE_FAMILY_TOOLS = new Set(["commerce_checkout_preview", "commerce_checkout_commit", "commerce_receipt_get", "commerce_access_log"]);
|
|
674
|
+
const COMMERCE_AUTHORITY_TOOLS = new Set(["stripe_create_card", "flight_hold", "flight_book"]);
|
|
675
|
+
// The Shepherd tools read the owner's terminals and speak to them through their desktop, so only family may use them.
|
|
676
|
+
// A call with no known trust level fails closed.
|
|
677
|
+
const SHEPHERD_FAMILY_TOOLS = new Set(["shepherd_overview", "shepherd_read", "shepherd_signal", "shepherd_send"]);
|
|
678
|
+
const MAIL_FAMILY_TOOLS = new Set(["mail_screener", "mail_decide", "mail_access_log", "mail_send", "mail_index_refresh"]);
|
|
679
|
+
const MAIL_DELEGATED_READ_TOOLS = new Set(["mail_recent", "mail_search"]);
|
|
680
|
+
function opaqueAnsiShellAtoms(command) {
|
|
681
|
+
const atoms = [];
|
|
682
|
+
let inAnsiQuote = false;
|
|
683
|
+
for (let index = 0; index < command.length;) {
|
|
684
|
+
if (!inAnsiQuote && command.startsWith("$'", index)) {
|
|
685
|
+
inAnsiQuote = true;
|
|
686
|
+
index += 2;
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
const character = command[index];
|
|
690
|
+
if (inAnsiQuote && character === "'") {
|
|
691
|
+
inAnsiQuote = false;
|
|
692
|
+
index += 1;
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (inAnsiQuote && character === "\\") {
|
|
696
|
+
const escape = command.slice(index).match(/^\\(?:x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{1,4}|U[0-9a-fA-F]{1,8}|[0-7]{1,3}|c.|.)?/s)[0];
|
|
697
|
+
atoms.push(null);
|
|
698
|
+
index += escape.length;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (!inAnsiQuote && (character === "'" || character === '"')) {
|
|
702
|
+
index += 1;
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
if (!inAnsiQuote && character === "\\" && index + 1 < command.length) {
|
|
706
|
+
atoms.push(command[index + 1]);
|
|
707
|
+
index += 2;
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
atoms.push(character);
|
|
711
|
+
index += 1;
|
|
712
|
+
}
|
|
713
|
+
return atoms;
|
|
714
|
+
}
|
|
715
|
+
function shellContainsAmbiguousAnsiMailCacheSync(command) {
|
|
716
|
+
if (!command.includes("$'"))
|
|
717
|
+
return false;
|
|
718
|
+
const expected = ["ouro", "mail", "sync-cache"];
|
|
719
|
+
let states = new Set(["0:0"]);
|
|
720
|
+
const crossTokenBoundary = (current) => {
|
|
721
|
+
const next = new Set(["0:0"]);
|
|
722
|
+
for (const state of current) {
|
|
723
|
+
const [tokenIndex, characterIndex] = state.split(":").map(Number);
|
|
724
|
+
if (characterIndex !== expected[tokenIndex].length)
|
|
725
|
+
continue;
|
|
726
|
+
if (tokenIndex === expected.length - 1)
|
|
727
|
+
return { matched: true, states: next };
|
|
728
|
+
next.add(`${tokenIndex + 1}:0`);
|
|
729
|
+
}
|
|
730
|
+
return { matched: false, states: next };
|
|
731
|
+
};
|
|
732
|
+
for (const atom of opaqueAnsiShellAtoms(command)) {
|
|
733
|
+
if (atom === null) {
|
|
734
|
+
const next = new Set(states);
|
|
735
|
+
for (const state of states) {
|
|
736
|
+
const [tokenIndex, characterIndex] = state.split(":").map(Number);
|
|
737
|
+
if (characterIndex >= 0 && characterIndex < expected[tokenIndex].length) {
|
|
738
|
+
next.add(`${tokenIndex}:${characterIndex + 1}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const boundary = crossTokenBoundary(states);
|
|
742
|
+
if (boundary.matched)
|
|
743
|
+
return true;
|
|
744
|
+
states = new Set([...next, ...boundary.states]);
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (/\s|[;&|()`]/.test(atom)) {
|
|
748
|
+
const boundary = crossTokenBoundary(states);
|
|
749
|
+
if (boundary.matched)
|
|
750
|
+
return true;
|
|
751
|
+
states = boundary.states;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const next = new Set();
|
|
755
|
+
for (const state of states) {
|
|
756
|
+
const [tokenIndex, characterIndex] = state.split(":").map(Number);
|
|
757
|
+
const wanted = expected[tokenIndex]?.[characterIndex];
|
|
758
|
+
next.add(wanted?.toLowerCase() === atom.toLowerCase() ? `${tokenIndex}:${characterIndex + 1}` : `${tokenIndex}:-1`);
|
|
759
|
+
}
|
|
760
|
+
states = next;
|
|
761
|
+
}
|
|
762
|
+
return crossTokenBoundary(states).matched;
|
|
763
|
+
}
|
|
764
|
+
const SHELL_COMMAND_BOUNDARIES = new Set(["||", "&&", ";;", "|&", "&", ";", "(", ")", "|", "<("]);
|
|
765
|
+
const SHELL_INTERPRETERS = new Set(["sh", "bash", "zsh"]);
|
|
766
|
+
const SHELL_TOKEN_BOUNDARIES = new Set(["{", "}", "then", "elif", "else", "do"]);
|
|
767
|
+
const SHELL_GRAMMAR_PREFIXES = new Set(["!", "if", "until", "while"]);
|
|
768
|
+
const SHELL_PARAMETER_MARKER = "__OURO_SHELL_PARAMETER_";
|
|
769
|
+
const SHELL_ARRAY_EXPANSION = "__OURO_ARRAY_EXPANSION__";
|
|
770
|
+
function hasUnescapedShellSubstitution(value) {
|
|
771
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
772
|
+
if (value[index] === "\\") {
|
|
773
|
+
index += 1;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (value.startsWith("$(", index) && !value.startsWith("$((", index))
|
|
777
|
+
return true;
|
|
778
|
+
if (value[index] === "`")
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
function stripHereDocBodies(command) {
|
|
784
|
+
const pending = [];
|
|
785
|
+
const lines = command.split("\n");
|
|
786
|
+
return lines.map((sourceLine) => {
|
|
787
|
+
if (pending.length > 0) {
|
|
788
|
+
const active = pending[0];
|
|
789
|
+
const candidate = active.stripTabs ? sourceLine.replace(/^\t+/, "") : sourceLine;
|
|
790
|
+
if (candidate === active.delimiter) {
|
|
791
|
+
pending.shift();
|
|
792
|
+
return "";
|
|
793
|
+
}
|
|
794
|
+
if (active.quoted || !hasUnescapedShellSubstitution(sourceLine))
|
|
795
|
+
return "";
|
|
796
|
+
return exposeShellSubstitutions(sourceLine).split("\n").slice(1).join("\n");
|
|
797
|
+
}
|
|
798
|
+
const pattern = /<<(?!<)(-)?\s*(?:'([^']*)'|"([^"]*)"|\\([^\s;&|]+)|([^\s;&|]+))/g;
|
|
799
|
+
for (const match of stripShellComments(sourceLine).matchAll(pattern)) {
|
|
800
|
+
pending.push({
|
|
801
|
+
stripTabs: match[1] !== undefined,
|
|
802
|
+
delimiter: match[2] ?? match[3] ?? match[4] ?? match[5],
|
|
803
|
+
quoted: match[2] !== undefined || match[3] !== undefined || match[4] !== undefined,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
return sourceLine;
|
|
807
|
+
}).join("\n");
|
|
808
|
+
}
|
|
809
|
+
function stripShellComments(command) {
|
|
810
|
+
let result = "";
|
|
811
|
+
let quote = null;
|
|
812
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
813
|
+
const character = command[index];
|
|
814
|
+
if (character === "\\" && quote !== "single") {
|
|
815
|
+
result += character + (command[index + 1] ?? "");
|
|
816
|
+
index += 1;
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if (character === "'" && quote !== "double")
|
|
820
|
+
quote = quote === "single" ? null : "single";
|
|
821
|
+
else if (character === '"' && quote !== "single")
|
|
822
|
+
quote = quote === "double" ? null : "double";
|
|
823
|
+
if (character === "#" && quote === null && (index === 0 || /[\s;&|()]/.test(command[index - 1]))) {
|
|
824
|
+
const newline = command.indexOf("\n", index);
|
|
825
|
+
if (newline < 0)
|
|
826
|
+
break;
|
|
827
|
+
result += " ".repeat(newline - index) + "\n";
|
|
828
|
+
index = newline;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
result += character;
|
|
832
|
+
}
|
|
833
|
+
return result;
|
|
834
|
+
}
|
|
835
|
+
const SHELL_ARRAY_ASSIGNMENT = /([A-Za-z_][A-Za-z0-9_]*)(\+?=)\(([^()\n]*)\)/y;
|
|
836
|
+
const SHELL_INDEXED_ASSIGNMENT = /([A-Za-z_][A-Za-z0-9_]*)\[(\d+)\]=("[^"]*"|'[^']*'|[^\s;&|]+)/y;
|
|
837
|
+
const SHELL_SCALAR_ASSIGNMENT = /([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|'[^']*'|[^\s;&|()]+)/y;
|
|
838
|
+
const SHELL_ARRAY_PARAMETER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\[(@|\*)\]\}/y;
|
|
839
|
+
const SHELL_SCALAR_PARAMETER = /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/y;
|
|
840
|
+
function shellMatchAt(pattern, command, index) {
|
|
841
|
+
pattern.lastIndex = index;
|
|
842
|
+
return pattern.exec(command);
|
|
843
|
+
}
|
|
844
|
+
function shellAssignmentPosition(command, index) {
|
|
845
|
+
let previous = index - 1;
|
|
846
|
+
while (previous >= 0 && /[ \t]/.test(command[previous]))
|
|
847
|
+
previous -= 1;
|
|
848
|
+
return previous < 0 || /[;&|()\n]/.test(command[previous]);
|
|
849
|
+
}
|
|
850
|
+
function parseShellWords(value) {
|
|
851
|
+
if (hasUnescapedShellSubstitution(value) || /(^|[^\\])\$(?!['"])/.test(value))
|
|
852
|
+
return undefined;
|
|
853
|
+
try {
|
|
854
|
+
const entries = (0, shell_quote_1.parse)(value);
|
|
855
|
+
if (entries.some((entry) => typeof entry !== "string"))
|
|
856
|
+
return undefined;
|
|
857
|
+
return entries;
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function shellAssignmentPersists(command, endIndex) {
|
|
864
|
+
let next = endIndex;
|
|
865
|
+
while (next < command.length && /[ \t]/.test(command[next]))
|
|
866
|
+
next += 1;
|
|
867
|
+
return next === command.length || /[;&|\n]/.test(command[next]);
|
|
868
|
+
}
|
|
869
|
+
function shellUnquotedFields(values) {
|
|
870
|
+
const fields = values.flatMap((value) => value.split(/\s+/).filter(Boolean));
|
|
871
|
+
return fields.some((field) => /[*?[]/.test(field)) ? SHELL_ARRAY_EXPANSION : (0, shell_quote_1.quote)(fields);
|
|
872
|
+
}
|
|
873
|
+
function doubleQuotedFragment(value) {
|
|
874
|
+
if (hasUnescapedShellSubstitution(value))
|
|
875
|
+
return undefined;
|
|
876
|
+
try {
|
|
877
|
+
const parsed = (0, shell_quote_1.parse)(`"${value}"`);
|
|
878
|
+
return parsed.length === 1 && typeof parsed[0] === "string" ? parsed[0] : undefined;
|
|
879
|
+
}
|
|
880
|
+
catch {
|
|
881
|
+
return undefined;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function closingDoubleQuote(command, startIndex) {
|
|
885
|
+
for (let index = startIndex; index < command.length; index += 1) {
|
|
886
|
+
if (command[index] === "\\")
|
|
887
|
+
index += 1;
|
|
888
|
+
else if (command[index] === '"')
|
|
889
|
+
return index;
|
|
890
|
+
}
|
|
891
|
+
return -1;
|
|
892
|
+
}
|
|
893
|
+
function transformOrderedShellVariables(command) {
|
|
894
|
+
const variables = new Map();
|
|
895
|
+
const result = [];
|
|
896
|
+
let quote = null;
|
|
897
|
+
let doubleQuoteStart = -1;
|
|
898
|
+
let doubleQuoteResultIndex = -1;
|
|
899
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
900
|
+
const character = command[index];
|
|
901
|
+
if (character === "\\" && quote !== "single") {
|
|
902
|
+
result.push(character + (command[index + 1] ?? ""));
|
|
903
|
+
index += 1;
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (character === "'") {
|
|
907
|
+
if (quote !== "double")
|
|
908
|
+
quote = quote === "single" ? null : "single";
|
|
909
|
+
result.push(character);
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (character === '"') {
|
|
913
|
+
if (quote !== "single") {
|
|
914
|
+
if (quote === "double") {
|
|
915
|
+
quote = null;
|
|
916
|
+
doubleQuoteStart = -1;
|
|
917
|
+
doubleQuoteResultIndex = -1;
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
quote = "double";
|
|
921
|
+
doubleQuoteStart = index;
|
|
922
|
+
doubleQuoteResultIndex = result.length;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
result.push(character);
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
if (quote === null && /[A-Za-z_]/.test(character) && shellAssignmentPosition(command, index)) {
|
|
929
|
+
const arrayAssignment = shellMatchAt(SHELL_ARRAY_ASSIGNMENT, command, index);
|
|
930
|
+
if (arrayAssignment) {
|
|
931
|
+
const [, name, operator, body] = arrayAssignment;
|
|
932
|
+
const elements = parseShellWords(body);
|
|
933
|
+
if (elements === undefined)
|
|
934
|
+
variables.delete(name);
|
|
935
|
+
else
|
|
936
|
+
variables.set(name, { array: operator === "+=" ? [...(variables.get(name)?.array ?? []), ...elements] : elements });
|
|
937
|
+
result.push(`${name}=__OURO_ARRAY_LITERAL`);
|
|
938
|
+
index += arrayAssignment[0].length - 1;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
const indexedAssignment = shellMatchAt(SHELL_INDEXED_ASSIGNMENT, command, index);
|
|
942
|
+
if (indexedAssignment) {
|
|
943
|
+
const [, name, elementIndex, rawValue] = indexedAssignment;
|
|
944
|
+
const element = parseShellWords(rawValue);
|
|
945
|
+
if (element?.length !== 1)
|
|
946
|
+
variables.delete(name);
|
|
947
|
+
else {
|
|
948
|
+
const array = [...(variables.get(name)?.array ?? [])];
|
|
949
|
+
array[Number(elementIndex)] = element[0];
|
|
950
|
+
variables.set(name, { array });
|
|
951
|
+
}
|
|
952
|
+
result.push(`${name}=__OURO_ARRAY_LITERAL`);
|
|
953
|
+
index += indexedAssignment[0].length - 1;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
const scalarAssignment = shellMatchAt(SHELL_SCALAR_ASSIGNMENT, command, index);
|
|
957
|
+
if (scalarAssignment) {
|
|
958
|
+
const [, name, rawValue] = scalarAssignment;
|
|
959
|
+
const scalar = parseShellWords(rawValue);
|
|
960
|
+
if (shellAssignmentPersists(command, index + scalarAssignment[0].length)) {
|
|
961
|
+
if (scalar?.length === 1)
|
|
962
|
+
variables.set(name, { scalar: scalar[0] });
|
|
963
|
+
else
|
|
964
|
+
variables.delete(name);
|
|
965
|
+
}
|
|
966
|
+
result.push(scalarAssignment[0]);
|
|
967
|
+
index += scalarAssignment[0].length - 1;
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (quote !== "single" && character === "$") {
|
|
972
|
+
const arrayExpansion = shellMatchAt(SHELL_ARRAY_PARAMETER, command, index);
|
|
973
|
+
if (arrayExpansion) {
|
|
974
|
+
const [, name, mode] = arrayExpansion;
|
|
975
|
+
const elements = variables.get(name)?.array;
|
|
976
|
+
if (elements === undefined)
|
|
977
|
+
result.push(SHELL_ARRAY_EXPANSION);
|
|
978
|
+
else if (quote === "double" && mode === "@") {
|
|
979
|
+
const closing = closingDoubleQuote(command, index + arrayExpansion[0].length);
|
|
980
|
+
const prefix = doubleQuotedFragment(command.slice(doubleQuoteStart + 1, index));
|
|
981
|
+
const suffix = closing < 0 ? undefined : doubleQuotedFragment(command.slice(index + arrayExpansion[0].length, closing));
|
|
982
|
+
if (closing < 0 || prefix === undefined || suffix === undefined)
|
|
983
|
+
result.push(SHELL_ARRAY_EXPANSION);
|
|
984
|
+
else {
|
|
985
|
+
const presentElements = elements.filter((element) => element !== undefined);
|
|
986
|
+
const expanded = presentElements.length === 0
|
|
987
|
+
? (prefix.length + suffix.length === 0 ? [] : [`${prefix}${suffix}`])
|
|
988
|
+
: presentElements.map((element, elementIndex) => `${elementIndex === 0 ? prefix : ""}${element}${elementIndex === presentElements.length - 1 ? suffix : ""}`);
|
|
989
|
+
result.splice(doubleQuoteResultIndex);
|
|
990
|
+
result.push((0, shell_quote_1.quote)(expanded));
|
|
991
|
+
quote = null;
|
|
992
|
+
doubleQuoteStart = -1;
|
|
993
|
+
doubleQuoteResultIndex = -1;
|
|
994
|
+
index = closing;
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
else if (quote === "double")
|
|
999
|
+
result.push(elements.join(" "));
|
|
1000
|
+
else
|
|
1001
|
+
result.push(shellUnquotedFields(elements));
|
|
1002
|
+
index += arrayExpansion[0].length - 1;
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
const scalarExpansion = shellMatchAt(SHELL_SCALAR_PARAMETER, command, index);
|
|
1006
|
+
if (scalarExpansion) {
|
|
1007
|
+
const name = scalarExpansion[1] ?? scalarExpansion[2];
|
|
1008
|
+
const scalar = variables.get(name)?.scalar;
|
|
1009
|
+
if (scalar !== undefined) {
|
|
1010
|
+
if (quote === "double")
|
|
1011
|
+
result.push(scalar.replace(/["\\$`]/g, "\\$&"));
|
|
1012
|
+
else
|
|
1013
|
+
result.push(shellUnquotedFields([scalar]));
|
|
1014
|
+
index += scalarExpansion[0].length - 1;
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (quote === null) {
|
|
1018
|
+
result.push(SHELL_ARRAY_EXPANSION);
|
|
1019
|
+
index += scalarExpansion[0].length - 1;
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
result.push(character);
|
|
1025
|
+
}
|
|
1026
|
+
return result.join("");
|
|
1027
|
+
}
|
|
1028
|
+
function findCommandSubstitutionEnd(command, start) {
|
|
1029
|
+
let depth = 1;
|
|
1030
|
+
let quote = null;
|
|
1031
|
+
for (let index = start; index < command.length; index += 1) {
|
|
1032
|
+
const character = command[index];
|
|
1033
|
+
if (character === "\\" && quote !== "single") {
|
|
1034
|
+
index += 1;
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
if (character === "'" && quote !== "double") {
|
|
1038
|
+
quote = quote === "single" ? null : "single";
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (character === '"' && quote !== "single") {
|
|
1042
|
+
quote = quote === "double" ? null : "double";
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
if (quote !== null)
|
|
1046
|
+
continue;
|
|
1047
|
+
if (character === "(")
|
|
1048
|
+
depth += 1;
|
|
1049
|
+
else if (character === ")") {
|
|
1050
|
+
depth -= 1;
|
|
1051
|
+
if (depth === 0)
|
|
1052
|
+
return index;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return -1;
|
|
1056
|
+
}
|
|
1057
|
+
function exposeShellSubstitutions(command, depth = 0) {
|
|
1058
|
+
if (depth >= 32)
|
|
1059
|
+
return "ouro mail sync-cache";
|
|
1060
|
+
let outer = "";
|
|
1061
|
+
const nested = [];
|
|
1062
|
+
let quote = null;
|
|
1063
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
1064
|
+
const character = command[index];
|
|
1065
|
+
if (character === "\\" && quote !== "single") {
|
|
1066
|
+
outer += character + (command[index + 1] ?? "");
|
|
1067
|
+
index += 1;
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
if (character === "'" && quote !== "double") {
|
|
1071
|
+
quote = quote === "single" ? null : "single";
|
|
1072
|
+
outer += character;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
if (character === '"' && quote !== "single") {
|
|
1076
|
+
quote = quote === "double" ? null : "double";
|
|
1077
|
+
outer += character;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (quote !== "single" && command.startsWith("$(", index) && !command.startsWith("$((", index)) {
|
|
1081
|
+
const end = findCommandSubstitutionEnd(command, index + 2);
|
|
1082
|
+
if (end >= 0) {
|
|
1083
|
+
nested.push(exposeShellSubstitutions(command.slice(index + 2, end), depth + 1));
|
|
1084
|
+
outer += "${__OURO_COMMAND_SUBSTITUTION}";
|
|
1085
|
+
index = end;
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (quote !== "single" && character === "`") {
|
|
1090
|
+
let end = index + 1;
|
|
1091
|
+
while (end < command.length && command[end] !== "`") {
|
|
1092
|
+
if (command[end] === "\\")
|
|
1093
|
+
end += 1;
|
|
1094
|
+
end += 1;
|
|
1095
|
+
}
|
|
1096
|
+
if (end < command.length) {
|
|
1097
|
+
nested.push(exposeShellSubstitutions(command.slice(index + 1, end), depth + 1));
|
|
1098
|
+
outer += "${__OURO_COMMAND_SUBSTITUTION}";
|
|
1099
|
+
index = end;
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
outer += character;
|
|
1104
|
+
}
|
|
1105
|
+
return [outer, ...nested].join("\n");
|
|
1106
|
+
}
|
|
1107
|
+
function normalizeShellNewlines(command) {
|
|
1108
|
+
let result = "";
|
|
1109
|
+
let quote = null;
|
|
1110
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
1111
|
+
const character = command[index];
|
|
1112
|
+
if (character === "\\" && quote !== "single") {
|
|
1113
|
+
result += character + (command[index + 1] ?? "");
|
|
1114
|
+
index += 1;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (character === "'" && quote !== "double")
|
|
1118
|
+
quote = quote === "single" ? null : "single";
|
|
1119
|
+
else if (character === '"' && quote !== "single")
|
|
1120
|
+
quote = quote === "double" ? null : "double";
|
|
1121
|
+
result += character === "\n" && quote === null ? " ; " : character;
|
|
1122
|
+
}
|
|
1123
|
+
return result;
|
|
1124
|
+
}
|
|
1125
|
+
function executableShellView(command) {
|
|
1126
|
+
const withoutData = stripShellComments(stripHereDocBodies(command));
|
|
1127
|
+
return normalizeShellNewlines(transformOrderedShellVariables(exposeShellSubstitutions(withoutData)));
|
|
1128
|
+
}
|
|
1129
|
+
function parseShellCommands(command) {
|
|
1130
|
+
let parameterIndex = 0;
|
|
1131
|
+
const parameterNames = new Map();
|
|
1132
|
+
const entries = (0, shell_quote_1.parse)(command, (name) => {
|
|
1133
|
+
const marker = `${SHELL_PARAMETER_MARKER}${parameterIndex}__`;
|
|
1134
|
+
parameterIndex += 1;
|
|
1135
|
+
parameterNames.set(marker, name);
|
|
1136
|
+
return marker;
|
|
1137
|
+
});
|
|
1138
|
+
const commands = [];
|
|
1139
|
+
let current = [];
|
|
1140
|
+
let skipRedirectionTarget = false;
|
|
1141
|
+
const flush = () => {
|
|
1142
|
+
if (current.length > 0)
|
|
1143
|
+
commands.push(current);
|
|
1144
|
+
current = [];
|
|
1145
|
+
skipRedirectionTarget = false;
|
|
1146
|
+
};
|
|
1147
|
+
for (const entry of entries) {
|
|
1148
|
+
if (typeof entry === "string") {
|
|
1149
|
+
if (skipRedirectionTarget)
|
|
1150
|
+
skipRedirectionTarget = false;
|
|
1151
|
+
else if (SHELL_TOKEN_BOUNDARIES.has(entry.toLowerCase()))
|
|
1152
|
+
flush();
|
|
1153
|
+
else
|
|
1154
|
+
current.push(entry);
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
const operation = entry;
|
|
1158
|
+
if (operation.op === "glob") {
|
|
1159
|
+
current.push(operation.pattern);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
if (SHELL_COMMAND_BOUNDARIES.has(operation.op)) {
|
|
1163
|
+
flush();
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
// Every remaining control operator is a redirection. Its next string is
|
|
1167
|
+
// a path or descriptor, not an argv token.
|
|
1168
|
+
skipRedirectionTarget = true;
|
|
1169
|
+
}
|
|
1170
|
+
flush();
|
|
1171
|
+
return { commands, parameterNames };
|
|
1172
|
+
}
|
|
1173
|
+
function resolveKnownShellParameters(token, knownVariables, parameterNames) {
|
|
1174
|
+
let resolved = token;
|
|
1175
|
+
for (const [marker, name] of parameterNames) {
|
|
1176
|
+
const known = knownVariables.get(name);
|
|
1177
|
+
if (known !== undefined)
|
|
1178
|
+
resolved = resolved.replaceAll(marker, known);
|
|
1179
|
+
}
|
|
1180
|
+
return resolved;
|
|
1181
|
+
}
|
|
1182
|
+
function shellTokenCanEqual(token, expected) {
|
|
1183
|
+
const escaped = token
|
|
1184
|
+
.split(new RegExp(`(?:${SHELL_PARAMETER_MARKER}\\d+__|${SHELL_ARRAY_EXPANSION})`, "g"))
|
|
1185
|
+
.map((literal) => literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
1186
|
+
.join(".*");
|
|
1187
|
+
return new RegExp(`^${escaped}$`, "i").test(expected);
|
|
1188
|
+
}
|
|
1189
|
+
function shellExecutableCanEqual(token, expected) {
|
|
1190
|
+
const basenameToken = token.slice(token.lastIndexOf("/") + 1);
|
|
1191
|
+
return shellTokenCanEqual(basenameToken, expected);
|
|
1192
|
+
}
|
|
1193
|
+
function exactShellExecutable(token) {
|
|
1194
|
+
if (token === undefined || token.includes(SHELL_PARAMETER_MARKER))
|
|
1195
|
+
return undefined;
|
|
1196
|
+
return token.slice(token.lastIndexOf("/") + 1).toLowerCase();
|
|
1197
|
+
}
|
|
1198
|
+
const SUDO_SHORT_OPERANDS = new Set(["-C", "-D", "-g", "-h", "-p", "-R", "-r", "-T", "-t", "-U", "-u"]);
|
|
1199
|
+
const SUDO_LONG_OPERANDS = new Set(["--chdir", "--chroot", "--close-from", "--command-timeout", "--group", "--host", "--other-user", "--prompt", "--role", "--type", "--user"]);
|
|
1200
|
+
const TIME_SHORT_OPERANDS = new Set(["-f", "-o"]);
|
|
1201
|
+
const TIME_LONG_OPERANDS = new Set(["--format", "--output"]);
|
|
1202
|
+
function wrapperOptionAdvance(option, shortOperands, longOperands) {
|
|
1203
|
+
if (shortOperands.has(option) || longOperands.has(option))
|
|
1204
|
+
return 2;
|
|
1205
|
+
if ([...shortOperands].some((candidate) => option.startsWith(candidate) && option.length > candidate.length))
|
|
1206
|
+
return 1;
|
|
1207
|
+
if ([...longOperands].some((candidate) => option.startsWith(`${candidate}=`)))
|
|
1208
|
+
return 1;
|
|
1209
|
+
return 0;
|
|
1210
|
+
}
|
|
1211
|
+
function resolveShellCommand(tokens, startIndex) {
|
|
1212
|
+
let index = startIndex;
|
|
1213
|
+
while (index < tokens.length) {
|
|
1214
|
+
const executable = exactShellExecutable(tokens[index]);
|
|
1215
|
+
if (SHELL_GRAMMAR_PREFIXES.has(executable ?? "")) {
|
|
1216
|
+
index += 1;
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
if (executable === "coproc") {
|
|
1220
|
+
index += 1;
|
|
1221
|
+
const candidate = tokens[index] ?? "";
|
|
1222
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(candidate) && !shellExecutableCanEqual(candidate, "ouro") && tokens[index + 1] !== undefined)
|
|
1223
|
+
index += 1;
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (executable === "command" || executable === "nohup") {
|
|
1227
|
+
index += 1;
|
|
1228
|
+
if (executable === "command" && (tokens[index] === "-v" || tokens[index] === "-V"))
|
|
1229
|
+
return { index: tokens.length };
|
|
1230
|
+
while (tokens[index]?.startsWith("-"))
|
|
1231
|
+
index += 1;
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
if (executable === "env") {
|
|
1235
|
+
index += 1;
|
|
1236
|
+
while (index < tokens.length) {
|
|
1237
|
+
const option = tokens[index];
|
|
1238
|
+
if (option === "-S" || option === "--split-string")
|
|
1239
|
+
return { index, script: `env ${tokens.slice(index + 1).join(" ")}` };
|
|
1240
|
+
if (option.startsWith("--split-string="))
|
|
1241
|
+
return { index, script: `env ${[option.slice("--split-string=".length), ...tokens.slice(index + 1)].join(" ")}` };
|
|
1242
|
+
if (["-u", "-C", "--unset", "--chdir"].includes(option))
|
|
1243
|
+
index += 2;
|
|
1244
|
+
else if (option.startsWith("-") || /^[A-Za-z_][A-Za-z0-9_]*=/.test(option))
|
|
1245
|
+
index += 1;
|
|
1246
|
+
else
|
|
1247
|
+
break;
|
|
1248
|
+
}
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
if (executable === "sudo") {
|
|
1252
|
+
index += 1;
|
|
1253
|
+
while (tokens[index]?.startsWith("-")) {
|
|
1254
|
+
const advance = wrapperOptionAdvance(tokens[index], SUDO_SHORT_OPERANDS, SUDO_LONG_OPERANDS);
|
|
1255
|
+
index += advance === 0 ? 1 : advance;
|
|
1256
|
+
}
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
if (executable === "time") {
|
|
1260
|
+
index += 1;
|
|
1261
|
+
while (tokens[index]?.startsWith("-")) {
|
|
1262
|
+
const advance = wrapperOptionAdvance(tokens[index], TIME_SHORT_OPERANDS, TIME_LONG_OPERANDS);
|
|
1263
|
+
index += advance === 0 ? 1 : advance;
|
|
1264
|
+
}
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
if (executable === "exec") {
|
|
1268
|
+
index += 1;
|
|
1269
|
+
while (tokens[index]?.startsWith("-"))
|
|
1270
|
+
index += tokens[index] === "-a" ? 2 : 1;
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
break;
|
|
1274
|
+
}
|
|
1275
|
+
return { index };
|
|
1276
|
+
}
|
|
1277
|
+
function shellScriptRequiresFamily(script, variables, depth) {
|
|
1278
|
+
if (script.includes(SHELL_PARAMETER_MARKER) || script.includes(SHELL_ARRAY_EXPANSION))
|
|
1279
|
+
return true;
|
|
1280
|
+
return shellCommandsContainMailCacheSync(script, variables, depth + 1);
|
|
1281
|
+
}
|
|
1282
|
+
function shellTokensContainMailCacheSync(tokens, startIndex, variables, depth) {
|
|
1283
|
+
const resolved = resolveShellCommand(tokens, startIndex);
|
|
1284
|
+
if (resolved.script !== undefined)
|
|
1285
|
+
return shellScriptRequiresFamily(resolved.script, variables, depth);
|
|
1286
|
+
const executableToken = tokens[resolved.index] ?? "";
|
|
1287
|
+
const executable = exactShellExecutable(executableToken);
|
|
1288
|
+
if (executableToken.includes(SHELL_ARRAY_EXPANSION))
|
|
1289
|
+
return true;
|
|
1290
|
+
if (SHELL_INTERPRETERS.has(executable ?? "")) {
|
|
1291
|
+
const commandOption = tokens.findIndex((token, index) => index > resolved.index && /^-[^-]*c/.test(token));
|
|
1292
|
+
return commandOption >= 0
|
|
1293
|
+
&& shellScriptRequiresFamily(tokens[commandOption + 1] ?? "", variables, depth);
|
|
1294
|
+
}
|
|
1295
|
+
if (executable === "eval")
|
|
1296
|
+
return shellScriptRequiresFamily(tokens.slice(resolved.index + 1).join(" "), variables, depth);
|
|
1297
|
+
return shellExecutableCanEqual(executableToken, "ouro")
|
|
1298
|
+
&& shellTokenCanEqual(tokens[resolved.index + 1] ?? "", "mail")
|
|
1299
|
+
&& shellTokenCanEqual(tokens[resolved.index + 2] ?? "", "sync-cache");
|
|
1300
|
+
}
|
|
1301
|
+
function shellCommandsContainMailCacheSync(command, inheritedVariables = new Map(), depth = 0) {
|
|
1302
|
+
if (depth >= 32)
|
|
1303
|
+
return true;
|
|
1304
|
+
let parsed;
|
|
1305
|
+
try {
|
|
1306
|
+
parsed = parseShellCommands(command);
|
|
1307
|
+
}
|
|
1308
|
+
catch {
|
|
1309
|
+
return false;
|
|
1310
|
+
}
|
|
1311
|
+
const knownVariables = new Map(inheritedVariables);
|
|
1312
|
+
for (const rawTokens of parsed.commands) {
|
|
1313
|
+
const tokens = rawTokens.map((token) => resolveKnownShellParameters(token, knownVariables, parsed.parameterNames));
|
|
1314
|
+
const invocationVariables = new Map(knownVariables);
|
|
1315
|
+
let commandIndex = 0;
|
|
1316
|
+
while (commandIndex < tokens.length) {
|
|
1317
|
+
const assignment = tokens[commandIndex].match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s);
|
|
1318
|
+
if (!assignment)
|
|
1319
|
+
break;
|
|
1320
|
+
if (assignment[2].includes(SHELL_PARAMETER_MARKER))
|
|
1321
|
+
invocationVariables.delete(assignment[1]);
|
|
1322
|
+
else
|
|
1323
|
+
invocationVariables.set(assignment[1], assignment[2]);
|
|
1324
|
+
commandIndex += 1;
|
|
1325
|
+
}
|
|
1326
|
+
if (commandIndex === tokens.length) {
|
|
1327
|
+
knownVariables.clear();
|
|
1328
|
+
for (const [name, value] of invocationVariables)
|
|
1329
|
+
knownVariables.set(name, value);
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
if (shellTokensContainMailCacheSync(tokens, commandIndex, invocationVariables, depth))
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
function shellContainsMailCacheSync(command) {
|
|
1338
|
+
const executableCommand = executableShellView(command);
|
|
1339
|
+
if (shellContainsAmbiguousAnsiMailCacheSync(executableCommand))
|
|
1340
|
+
return true;
|
|
1341
|
+
return shellCommandsContainMailCacheSync(executableCommand);
|
|
1342
|
+
}
|
|
1343
|
+
function mailCacheSyncShellGuardrail(toolName, args, context) {
|
|
1344
|
+
if (toolName !== "shell" || !shellContainsMailCacheSync(args.command ?? ""))
|
|
1345
|
+
return allow;
|
|
1346
|
+
if (context.trustLevel === undefined || context.trustLevel === "family")
|
|
1347
|
+
return allow;
|
|
1348
|
+
return deny("hosted mail cache convergence requires family trust.");
|
|
1349
|
+
}
|
|
1350
|
+
function mailTrustGuardrail(toolName, args, context) {
|
|
1351
|
+
if (MAIL_FAMILY_TOOLS.has(toolName)) {
|
|
1352
|
+
if (context.trustLevel === undefined || context.trustLevel === "family")
|
|
1353
|
+
return allow;
|
|
1354
|
+
if (toolName === "mail_send")
|
|
1355
|
+
return deny("outbound mail sends require family trust.");
|
|
1356
|
+
return deny(toolName === "mail_decide"
|
|
1357
|
+
? "mail screener decisions require family trust."
|
|
1358
|
+
: "delegated human mail requires family trust.");
|
|
1359
|
+
}
|
|
1360
|
+
if (MAIL_DELEGATED_READ_TOOLS.has(toolName)) {
|
|
1361
|
+
const scope = (args.scope ?? "").trim().toLowerCase();
|
|
1362
|
+
if (scope === "delegated" || scope === "all") {
|
|
1363
|
+
if (context.trustLevel === undefined || context.trustLevel === "family")
|
|
1364
|
+
return allow;
|
|
1365
|
+
return deny("delegated human mail requires family trust.");
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return allow;
|
|
1369
|
+
}
|
|
1370
|
+
function checkCredentialTrustGuardrails(toolName, context) {
|
|
1371
|
+
if (CREDENTIAL_FAMILY_TOOLS.has(toolName) || COMMERCE_FAMILY_TOOLS.has(toolName)) {
|
|
1372
|
+
if (context.trustLevel === "family")
|
|
1373
|
+
return allow;
|
|
1374
|
+
return deny(REASONS.needsTrust);
|
|
1375
|
+
}
|
|
1376
|
+
if (CREDENTIAL_TRUSTED_TOOLS.has(toolName) || TRAVEL_TRUSTED_TOOLS.has(toolName) || A2A_TRUSTED_TOOLS.has(toolName)) {
|
|
1377
|
+
if ((0, friends_1.isTrustedLevel)(context.trustLevel))
|
|
1378
|
+
return allow;
|
|
1379
|
+
return deny(REASONS.needsTrust);
|
|
1380
|
+
}
|
|
1381
|
+
return allow;
|
|
1382
|
+
}
|
|
1383
|
+
function checkCommerceAuthorityGuardrails(toolName, args, context) {
|
|
1384
|
+
if (!COMMERCE_AUTHORITY_TOOLS.has(toolName))
|
|
1385
|
+
return allow;
|
|
1386
|
+
if (!context.agentRoot)
|
|
1387
|
+
return deny("commerce authority unavailable: agent root could not be resolved.");
|
|
1388
|
+
const result = (0, store_1.validateCommerceAuthority)({
|
|
1389
|
+
agentRoot: context.agentRoot,
|
|
1390
|
+
token: args.commerce_authority,
|
|
1391
|
+
toolName,
|
|
1392
|
+
args,
|
|
1393
|
+
friendId: context.friendId,
|
|
1394
|
+
});
|
|
1395
|
+
if (result.ok)
|
|
1396
|
+
return allow;
|
|
1397
|
+
return deny(`commerce authority required: ${result.reason}`);
|
|
1398
|
+
}
|
|
1399
|
+
function checkFirstClassMcpTrust(context) {
|
|
1400
|
+
if (!context.mcpServerName)
|
|
1401
|
+
return allow;
|
|
1402
|
+
const rules = MCP_SERVER_TRUST[context.mcpServerName] ?? { minTrust: "friend", blockGroupChat: false };
|
|
1403
|
+
if (!trustLevelSatisfied(rules.minTrust, context.trustLevel ?? "friend")) {
|
|
1404
|
+
return deny(REASONS.needsTrust);
|
|
1405
|
+
}
|
|
1406
|
+
if (rules.blockGroupChat && context.isGroupChat) {
|
|
1407
|
+
return deny("browser tools are only available in 1:1 conversations, not group chats.");
|
|
1408
|
+
}
|
|
1409
|
+
return allow;
|
|
1410
|
+
}
|
|
264
1411
|
function checkTrustLevelGuardrails(toolName, args, context) {
|
|
265
|
-
|
|
266
|
-
|
|
1412
|
+
if (toolName === "teams_send_image" && context.trustLevel !== "family") {
|
|
1413
|
+
return deny("sending local images in Teams requires family trust.");
|
|
1414
|
+
}
|
|
1415
|
+
const cacheSyncResult = mailCacheSyncShellGuardrail(toolName, args, context);
|
|
1416
|
+
if (!cacheSyncResult.allowed)
|
|
1417
|
+
return cacheSyncResult;
|
|
1418
|
+
const mailResult = mailTrustGuardrail(toolName, args, context);
|
|
1419
|
+
if (!mailResult.allowed)
|
|
1420
|
+
return mailResult;
|
|
1421
|
+
if (SHEPHERD_FAMILY_TOOLS.has(toolName) && context.trustLevel !== "family") {
|
|
1422
|
+
return deny("the owner's terminals require family trust.");
|
|
1423
|
+
}
|
|
1424
|
+
// Credential tools have their own trust rules that apply at all levels
|
|
1425
|
+
const credentialResult = checkCredentialTrustGuardrails(toolName, context);
|
|
1426
|
+
if (!credentialResult.allowed)
|
|
1427
|
+
return credentialResult;
|
|
1428
|
+
const commerceAuthorityResult = checkCommerceAuthorityGuardrails(toolName, args, context);
|
|
1429
|
+
if (!commerceAuthorityResult.allowed)
|
|
1430
|
+
return commerceAuthorityResult;
|
|
1431
|
+
// First-class MCP tool trust (e.g. browser_navigate) — applies at all trust levels
|
|
1432
|
+
const firstClassMcpResult = checkFirstClassMcpTrust(context);
|
|
1433
|
+
if (!firstClassMcpResult.allowed)
|
|
1434
|
+
return firstClassMcpResult;
|
|
1435
|
+
// MCP server-specific trust via shell (e.g. ouro mcp call browser) — applies at all trust levels
|
|
1436
|
+
if (toolName === "shell") {
|
|
1437
|
+
const mcpResult = checkMcpServerTrust(args.command || "", context);
|
|
1438
|
+
if (!mcpResult.allowed)
|
|
1439
|
+
return mcpResult;
|
|
1440
|
+
}
|
|
1441
|
+
// Trusted levels (family/friend) — no further trust guardrails. Undefined defaults to friend.
|
|
1442
|
+
if ((0, friends_1.isTrustedLevel)(context.trustLevel))
|
|
267
1443
|
return allow;
|
|
268
1444
|
if (toolName === "shell") {
|
|
269
1445
|
return checkShellTrustGuardrails(args.command || "", context.trustLevel);
|