@ouro.bot/cli 0.1.0-alpha.56 → 0.1.0-alpha.560
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/README.md +127 -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/changelog.json +3596 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +117 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +237 -0
- package/dist/arc/packets.js +193 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +65 -0
- package/dist/heart/active-work.js +837 -26
- package/dist/heart/agent-entry.js +58 -3
- package/dist/heart/attachments/image-normalize.js +194 -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/adapter.js +2 -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 +16 -0
- package/dist/heart/attachments/store.js +103 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +479 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +111 -0
- package/dist/heart/config-registry.js +322 -0
- package/dist/heart/config.js +114 -118
- package/dist/heart/core.js +913 -246
- package/dist/heart/cross-chat-delivery.js +3 -18
- package/dist/heart/daemon/agent-config-check.js +419 -0
- package/dist/heart/daemon/agent-discovery.js +102 -3
- package/dist/heart/daemon/agent-service.js +522 -0
- package/dist/heart/daemon/agentic-repair.js +547 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/boot-sync-probe.js +197 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +776 -0
- package/dist/heart/daemon/cli-exec.js +7457 -0
- package/dist/heart/daemon/cli-help.js +498 -0
- package/dist/heart/daemon/cli-parse.js +1592 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +763 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/connect-bay.js +323 -0
- package/dist/heart/daemon/daemon-cli.js +29 -1698
- package/dist/heart/daemon/daemon-entry.js +387 -2
- package/dist/heart/daemon/daemon-health.js +176 -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 +796 -71
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +826 -0
- package/dist/heart/daemon/health-monitor.js +122 -1
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +115 -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 +89 -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 +82 -12
- package/dist/heart/daemon/logs-prune.js +110 -0
- package/dist/heart/daemon/mcp-canary.js +297 -0
- package/dist/heart/daemon/message-router.js +2 -2
- package/dist/heart/daemon/os-cron-deps.js +135 -0
- package/dist/heart/daemon/os-cron.js +14 -12
- package/dist/heart/daemon/ouro-bot-entry.js +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/process-manager.js +375 -33
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/provider-ping-progress.js +83 -0
- package/dist/heart/daemon/pulse.js +475 -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 +67 -16
- package/dist/heart/daemon/runtime-metadata.js +3 -31
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +389 -38
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +158 -11
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +330 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/terminal-ui.js +499 -0
- package/dist/heart/daemon/thoughts.js +162 -17
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +1 -1
- package/dist/heart/habits/habit-migration.js +189 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-runtime-state.js +100 -0
- package/dist/heart/habits/habit-scheduler.js +372 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +32 -56
- 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 +35 -12
- package/dist/heart/identity.js +203 -57
- package/dist/heart/kept-notes.js +357 -0
- package/dist/heart/kicks.js +1 -1
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mail-import-discovery.js +353 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +66 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +246 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +31 -0
- package/dist/heart/mailbox/mailbox-types.js +27 -0
- package/dist/heart/mailbox/mailbox-view.js +195 -0
- package/dist/heart/mailbox/readers/agent-machine.js +382 -0
- package/dist/heart/mailbox/readers/continuity-readers.js +338 -0
- package/dist/heart/mailbox/readers/mail.js +362 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +651 -0
- package/dist/heart/mailbox/readers/sessions.js +232 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +683 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +19 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/provider-attempt.js +134 -0
- package/dist/heart/provider-binding-resolver.js +267 -0
- package/dist/heart/provider-credentials.js +425 -0
- package/dist/heart/provider-failover.js +301 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +262 -0
- package/dist/heart/provider-readiness-cache.js +40 -0
- package/dist/heart/provider-visibility.js +188 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +139 -52
- package/dist/heart/providers/azure.js +97 -13
- package/dist/heart/providers/error-classification.js +127 -0
- package/dist/heart/providers/github-copilot.js +145 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +26 -8
- package/dist/heart/providers/openai-codex.js +55 -40
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +367 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +13 -4
- package/dist/heart/session-activity.js +43 -22
- package/dist/heart/session-events.js +1149 -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-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +182 -0
- package/dist/heart/session-transcript.js +243 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +44 -27
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +9 -5
- 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-description.js +139 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +389 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +426 -0
- package/dist/heart/versioning/ouro-version-manager.js +295 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mailbox-ui/assets/index-B-461hes.js +61 -0
- package/dist/mailbox-ui/assets/index-BPr5vNuM.css +1 -0
- package/dist/mailbox-ui/index.html +15 -0
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +674 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/core.js +720 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +430 -0
- package/dist/mailroom/mbox-import.js +383 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +233 -0
- package/dist/mailroom/search-cache.js +256 -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 +7 -1
- package/dist/mind/context.js +165 -101
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +62 -75
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/friends/channel.js +39 -0
- package/dist/mind/friends/resolver.js +54 -2
- package/dist/mind/friends/store-file.js +39 -3
- package/dist/mind/friends/types.js +2 -2
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/note-search.js +268 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +4 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1011 -123
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -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 +129 -5
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +224 -4
- 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 +152 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +11 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +963 -0
- package/dist/repertoire/bundle-templates.js +72 -0
- package/dist/repertoire/bw-installer.js +180 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +330 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +158 -9
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +170 -7
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +178 -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 +396 -0
- package/dist/repertoire/mcp-client.js +295 -0
- package/dist/repertoire/mcp-manager.js +362 -0
- package/dist/repertoire/mcp-tools.js +63 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +15 -24
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tasks/board.js +31 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +16 -4
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +16 -78
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +317 -0
- package/dist/repertoire/tools-base.js +47 -1075
- package/dist/repertoire/tools-bluebubbles.js +1 -0
- package/dist/repertoire/tools-bridge.js +142 -0
- package/dist/repertoire/tools-bundle.js +984 -0
- package/dist/repertoire/tools-config.js +185 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-credential.js +381 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flight.js +224 -0
- package/dist/repertoire/tools-flow.js +119 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-mail.js +1857 -0
- package/dist/repertoire/tools-notes.js +421 -0
- package/dist/repertoire/tools-session.js +750 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-surface.js +243 -0
- package/dist/repertoire/tools-teams.js +9 -39
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +604 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools.js +108 -100
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +594 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +116 -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} +219 -18
- package/dist/senses/bluebubbles/entry.js +77 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
- package/dist/senses/bluebubbles/index.js +2305 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/replay.js +137 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- 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 +240 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +520 -209
- package/dist/senses/commands.js +66 -3
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +175 -21
- package/dist/senses/inner-dialog.js +330 -27
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +379 -0
- package/dist/senses/pipeline.js +549 -181
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/shared-turn.js +248 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +387 -98
- package/dist/senses/trust-gate.js +100 -5
- package/dist/senses/voice/elevenlabs.js +125 -0
- package/dist/senses/voice/index.js +22 -0
- package/dist/senses/voice/transcript.js +70 -0
- package/dist/senses/voice/turn.js +85 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +133 -0
- package/dist/senses/voice-entry.js +80 -0
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +146 -0
- package/package.json +38 -7
- package/skills/agent-commerce.md +106 -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 +101 -0
- package/skills/travel-planning.md +138 -0
- package/dist/heart/daemon/auth-flow.js +0 -351
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/daemon/subagent-installer.js +0 -166
- package/dist/heart/session-recall.js +0 -116
- package/dist/mind/associative-recall.js +0 -209
- package/dist/senses/bluebubbles-entry.js +0 -13
- package/dist/senses/bluebubbles.js +0 -1177
- package/dist/senses/debug-activity.js +0 -148
- package/subagents/README.md +0 -86
- package/subagents/work-doer.md +0 -237
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -390
- /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/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
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.OURO_CLI_TRUST_MANIFEST = void 0;
|
|
37
|
+
exports.guardInvocation = guardInvocation;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const types_1 = require("../mind/friends/types");
|
|
41
|
+
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
const deny = (reason) => ({ allowed: false, reason });
|
|
43
|
+
const allow = { allowed: true };
|
|
44
|
+
// --- reason templates ---
|
|
45
|
+
// Structural reasons (always-on, apply to everyone)
|
|
46
|
+
const REASONS = {
|
|
47
|
+
readBeforeEdit: "i need to read that file first before i can edit it.",
|
|
48
|
+
readBeforeOverwrite: "i need to read that file first before i can overwrite it.",
|
|
49
|
+
protectedPath: "that path is protected — i can read it but not modify it.",
|
|
50
|
+
destructiveCommand: "that command is too dangerous to run — it could cause irreversible damage.",
|
|
51
|
+
// Trust reasons (vary by relationship)
|
|
52
|
+
needsTrust: "i'd need a closer friend to vouch for you before i can do that.",
|
|
53
|
+
needsTrustForWrite: "i'd need a closer friend to vouch for you before i can write files outside my home.",
|
|
54
|
+
};
|
|
55
|
+
// --- read-only tools that never need guardrails ---
|
|
56
|
+
const READ_ONLY_TOOLS = new Set(["read_file", "glob", "grep"]);
|
|
57
|
+
// --- protected path detection ---
|
|
58
|
+
const PROTECTED_PATH_SEGMENTS = [
|
|
59
|
+
".git/",
|
|
60
|
+
".ouro-cli/vault-unlock/",
|
|
61
|
+
".ouro-cli/vault-unlock-dpapi/",
|
|
62
|
+
];
|
|
63
|
+
const PROTECTED_FILENAMES = ["agent.json"];
|
|
64
|
+
function isProtectedPath(filePath) {
|
|
65
|
+
for (const segment of PROTECTED_PATH_SEGMENTS) {
|
|
66
|
+
if (filePath.includes(`/${segment}`) || filePath.startsWith(segment))
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
for (const name of PROTECTED_FILENAMES) {
|
|
70
|
+
if (path.basename(filePath) === name)
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
// --- destructive shell patterns ---
|
|
76
|
+
const DESTRUCTIVE_PATTERNS = [
|
|
77
|
+
/\brm\s+(-\w*\s+)*-\w*r\w*\s+(-\w+\s+)*[/~]/, // rm -rf / or rm -rf ~
|
|
78
|
+
/\bchmod\s+(-\w*\s+)*-\w*R\w*\s+\d+\s+\//, // chmod -R 777 /
|
|
79
|
+
/\bmkfs\b/, // mkfs.*
|
|
80
|
+
/\bdd\s+if=/, // dd if=
|
|
81
|
+
];
|
|
82
|
+
function isDestructiveShellCommand(command) {
|
|
83
|
+
return DESTRUCTIVE_PATTERNS.some((p) => p.test(command));
|
|
84
|
+
}
|
|
85
|
+
// --- compound command splitting ---
|
|
86
|
+
// Shell operators that chain commands: &&, ||, ;, |, $(), backticks
|
|
87
|
+
const COMPOUND_SEPARATORS = /\s*(?:&&|\|\||;|\|)\s*/;
|
|
88
|
+
const SUBSHELL_PATTERN = /\$\(|`/;
|
|
89
|
+
function splitShellCommands(command) {
|
|
90
|
+
if (SUBSHELL_PATTERN.test(command))
|
|
91
|
+
return [command];
|
|
92
|
+
return command.split(COMPOUND_SEPARATORS).filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
// --- shell commands that write to protected paths ---
|
|
95
|
+
function shellWritesToProtectedPath(command) {
|
|
96
|
+
const redirectMatch = command.match(/>\s*(\S+)/);
|
|
97
|
+
if (redirectMatch && isProtectedPath(redirectMatch[1]))
|
|
98
|
+
return true;
|
|
99
|
+
const teeMatch = command.match(/tee\s+(?:-\w+\s+)*(\S+)/);
|
|
100
|
+
if (teeMatch && isProtectedPath(teeMatch[1]))
|
|
101
|
+
return true;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
// --- structural guardrail checks (always on, all trust levels) ---
|
|
105
|
+
function checkReadBeforeWrite(toolName, args, context) {
|
|
106
|
+
if (toolName === "edit_file") {
|
|
107
|
+
const filePath = args.path || "";
|
|
108
|
+
if (!context.readPaths.has(filePath))
|
|
109
|
+
return deny(REASONS.readBeforeEdit);
|
|
110
|
+
}
|
|
111
|
+
if (toolName === "write_file") {
|
|
112
|
+
const filePath = args.path || "";
|
|
113
|
+
if (context.readPaths.has(filePath))
|
|
114
|
+
return allow;
|
|
115
|
+
if (!fs.existsSync(filePath))
|
|
116
|
+
return allow;
|
|
117
|
+
return deny(REASONS.readBeforeOverwrite);
|
|
118
|
+
}
|
|
119
|
+
return allow;
|
|
120
|
+
}
|
|
121
|
+
function checkDestructiveShellPatterns(toolName, args) {
|
|
122
|
+
if (toolName !== "shell")
|
|
123
|
+
return allow;
|
|
124
|
+
const command = args.command || "";
|
|
125
|
+
// Check each subcommand in compound commands for destructive patterns
|
|
126
|
+
for (const sub of splitShellCommands(command)) {
|
|
127
|
+
if (isDestructiveShellCommand(sub))
|
|
128
|
+
return deny(REASONS.destructiveCommand);
|
|
129
|
+
}
|
|
130
|
+
return allow;
|
|
131
|
+
}
|
|
132
|
+
function checkProtectedPaths(toolName, args) {
|
|
133
|
+
if (toolName === "write_file" || toolName === "edit_file") {
|
|
134
|
+
const filePath = args.path || "";
|
|
135
|
+
if (isProtectedPath(filePath))
|
|
136
|
+
return deny(REASONS.protectedPath);
|
|
137
|
+
}
|
|
138
|
+
if (toolName === "shell") {
|
|
139
|
+
const command = args.command || "";
|
|
140
|
+
if (shellWritesToProtectedPath(command))
|
|
141
|
+
return deny(REASONS.protectedPath);
|
|
142
|
+
}
|
|
143
|
+
return allow;
|
|
144
|
+
}
|
|
145
|
+
function checkStructuralGuardrails(toolName, args, context) {
|
|
146
|
+
const protectedResult = checkProtectedPaths(toolName, args);
|
|
147
|
+
if (!protectedResult.allowed)
|
|
148
|
+
return protectedResult;
|
|
149
|
+
const destructiveResult = checkDestructiveShellPatterns(toolName, args);
|
|
150
|
+
if (!destructiveResult.allowed)
|
|
151
|
+
return destructiveResult;
|
|
152
|
+
return checkReadBeforeWrite(toolName, args, context);
|
|
153
|
+
}
|
|
154
|
+
// --- ouro CLI trust manifest ---
|
|
155
|
+
/** Minimum trust level required for each ouro CLI subcommand. */
|
|
156
|
+
exports.OURO_CLI_TRUST_MANIFEST = {
|
|
157
|
+
whoami: "acquaintance",
|
|
158
|
+
changelog: "acquaintance",
|
|
159
|
+
"session list": "acquaintance",
|
|
160
|
+
"task board": "friend",
|
|
161
|
+
"task create": "friend",
|
|
162
|
+
"task update": "friend",
|
|
163
|
+
"task show": "friend",
|
|
164
|
+
"task actionable": "friend",
|
|
165
|
+
"task deps": "friend",
|
|
166
|
+
"task sessions": "friend",
|
|
167
|
+
"task fix": "friend",
|
|
168
|
+
"friend list": "friend",
|
|
169
|
+
"friend show": "friend",
|
|
170
|
+
"friend create": "friend",
|
|
171
|
+
"friend update": "family",
|
|
172
|
+
"reminder create": "friend",
|
|
173
|
+
"config model": "friend",
|
|
174
|
+
"config models": "friend",
|
|
175
|
+
"mcp list": "acquaintance",
|
|
176
|
+
"mcp call": "friend",
|
|
177
|
+
auth: "family",
|
|
178
|
+
"auth verify": "family",
|
|
179
|
+
"auth switch": "family",
|
|
180
|
+
rollback: "family",
|
|
181
|
+
versions: "acquaintance",
|
|
182
|
+
};
|
|
183
|
+
// --- trust level comparison ---
|
|
184
|
+
const LEVEL_ORDER = {
|
|
185
|
+
stranger: 0,
|
|
186
|
+
acquaintance: 1,
|
|
187
|
+
friend: 2,
|
|
188
|
+
family: 3,
|
|
189
|
+
};
|
|
190
|
+
function trustLevelSatisfied(required, actual) {
|
|
191
|
+
return LEVEL_ORDER[actual] >= LEVEL_ORDER[required];
|
|
192
|
+
}
|
|
193
|
+
// --- general CLI allowlists for acquaintance ---
|
|
194
|
+
const ACQUAINTANCE_SHELL_ALLOWLIST = new Set([
|
|
195
|
+
"cat", "ls", "head", "tail", "wc", "file", "stat", "which", "echo",
|
|
196
|
+
"pwd", "env", "printenv", "whoami", "date", "uname",
|
|
197
|
+
]);
|
|
198
|
+
const ACQUAINTANCE_GIT_ALLOWLIST = new Set([
|
|
199
|
+
"status", "log", "show", "diff", "branch",
|
|
200
|
+
]);
|
|
201
|
+
// --- trust-level shell guardrails ---
|
|
202
|
+
function resolveOuroSubcommand(command) {
|
|
203
|
+
const afterOuro = command.replace(/^ouro\s+/, "").trim();
|
|
204
|
+
/* v8 ignore next -- bare "ouro" is caught upstream by checkShellTrustGuardrails @preserve */
|
|
205
|
+
if (!afterOuro)
|
|
206
|
+
return null;
|
|
207
|
+
const tokens = afterOuro.split(/\s+/);
|
|
208
|
+
const twoWord = tokens.length >= 2 ? `${tokens[0]} ${tokens[1]}` : null;
|
|
209
|
+
// Two-word match first (e.g. "task board"), then one-word (e.g. "whoami")
|
|
210
|
+
if (twoWord && exports.OURO_CLI_TRUST_MANIFEST[twoWord])
|
|
211
|
+
return twoWord;
|
|
212
|
+
if (exports.OURO_CLI_TRUST_MANIFEST[tokens[0]])
|
|
213
|
+
return tokens[0];
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
// --- MCP server-specific trust rules ---
|
|
217
|
+
const MCP_SERVER_TRUST = {
|
|
218
|
+
browser: { minTrust: "friend", blockGroupChat: true },
|
|
219
|
+
};
|
|
220
|
+
function checkMcpServerTrust(command, context) {
|
|
221
|
+
const match = command.match(/^ouro\s+mcp\s+call\s+(\S+)/);
|
|
222
|
+
if (!match)
|
|
223
|
+
return allow;
|
|
224
|
+
const serverName = match[1];
|
|
225
|
+
const rules = MCP_SERVER_TRUST[serverName];
|
|
226
|
+
if (!rules)
|
|
227
|
+
return allow; // no special rules for this server
|
|
228
|
+
if (!trustLevelSatisfied(rules.minTrust, context.trustLevel ?? "friend")) {
|
|
229
|
+
return deny(REASONS.needsTrust);
|
|
230
|
+
}
|
|
231
|
+
if (rules.blockGroupChat && context.isGroupChat) {
|
|
232
|
+
return deny("browser tools are only available in 1:1 conversations, not group chats.");
|
|
233
|
+
}
|
|
234
|
+
return allow;
|
|
235
|
+
}
|
|
236
|
+
function checkSingleShellCommandTrust(command, trustLevel) {
|
|
237
|
+
const trimmed = command.trim();
|
|
238
|
+
const tokens = trimmed.split(/\s+/);
|
|
239
|
+
const firstToken = tokens[0] || "";
|
|
240
|
+
// ouro CLI — check per-subcommand trust manifest
|
|
241
|
+
if (firstToken === "ouro") {
|
|
242
|
+
const subcommand = resolveOuroSubcommand(trimmed);
|
|
243
|
+
const requiredLevel = subcommand ? exports.OURO_CLI_TRUST_MANIFEST[subcommand] : "friend";
|
|
244
|
+
if (trustLevelSatisfied(requiredLevel, trustLevel))
|
|
245
|
+
return allow;
|
|
246
|
+
return deny(REASONS.needsTrust);
|
|
247
|
+
}
|
|
248
|
+
// git — check subcommand allowlist
|
|
249
|
+
if (firstToken === "git") {
|
|
250
|
+
const gitSub = tokens[1] || "";
|
|
251
|
+
if (ACQUAINTANCE_GIT_ALLOWLIST.has(gitSub))
|
|
252
|
+
return allow;
|
|
253
|
+
return deny(REASONS.needsTrust);
|
|
254
|
+
}
|
|
255
|
+
// General CLI — check allowlist
|
|
256
|
+
if (ACQUAINTANCE_SHELL_ALLOWLIST.has(firstToken))
|
|
257
|
+
return allow;
|
|
258
|
+
return deny(REASONS.needsTrust);
|
|
259
|
+
}
|
|
260
|
+
function checkShellTrustGuardrails(command, trustLevel) {
|
|
261
|
+
// Subshell patterns ($(), backticks) can't be reliably split — check as single command
|
|
262
|
+
/* v8 ignore next -- subshell branch: tested via guardrails.test.ts @preserve */
|
|
263
|
+
if (SUBSHELL_PATTERN.test(command)) {
|
|
264
|
+
return checkSingleShellCommandTrust(command, trustLevel);
|
|
265
|
+
}
|
|
266
|
+
// Compound commands: check each subcommand individually
|
|
267
|
+
const subcommands = splitShellCommands(command);
|
|
268
|
+
if (subcommands.length === 0)
|
|
269
|
+
return checkSingleShellCommandTrust(command, trustLevel);
|
|
270
|
+
for (const sub of subcommands) {
|
|
271
|
+
const result = checkSingleShellCommandTrust(sub, trustLevel);
|
|
272
|
+
if (!result.allowed)
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
return allow;
|
|
276
|
+
}
|
|
277
|
+
function checkWriteTrustGuardrails(toolName, args, context) {
|
|
278
|
+
if (toolName !== "write_file" && toolName !== "edit_file")
|
|
279
|
+
return allow;
|
|
280
|
+
const filePath = args.path || "";
|
|
281
|
+
if (context.agentRoot && filePath.startsWith(context.agentRoot))
|
|
282
|
+
return allow;
|
|
283
|
+
if (!context.agentRoot)
|
|
284
|
+
return allow;
|
|
285
|
+
return deny(REASONS.needsTrustForWrite);
|
|
286
|
+
}
|
|
287
|
+
// --- credential tool trust gating ---
|
|
288
|
+
// Credential write tools: family only
|
|
289
|
+
const CREDENTIAL_FAMILY_TOOLS = new Set([
|
|
290
|
+
"credential_generate_password", "credential_store", "credential_delete", "vault_setup",
|
|
291
|
+
// User profile tools: family only
|
|
292
|
+
"user_profile_store", "user_profile_get", "user_profile_delete",
|
|
293
|
+
// Payment tools: family only
|
|
294
|
+
"stripe_create_card", "stripe_deactivate_card", "stripe_list_cards",
|
|
295
|
+
// Booking tools that involve payment: family only
|
|
296
|
+
"flight_book", "flight_hold", "flight_cancel",
|
|
297
|
+
]);
|
|
298
|
+
// Credential read tools: friend+
|
|
299
|
+
const CREDENTIAL_TRUSTED_TOOLS = new Set(["credential_get", "credential_list"]);
|
|
300
|
+
// Travel tools: friend+ (weather_lookup accesses vault credentials indirectly;
|
|
301
|
+
// advisory and geocode are public APIs but gated for consistency)
|
|
302
|
+
// Flight search is also friend+ (read-only, no payment)
|
|
303
|
+
const TRAVEL_TRUSTED_TOOLS = new Set(["weather_lookup", "travel_advisory", "geocode_search", "flight_search"]);
|
|
304
|
+
const MAIL_FAMILY_TOOLS = new Set(["mail_screener", "mail_decide", "mail_access_log", "mail_send", "mail_index_refresh"]);
|
|
305
|
+
const MAIL_DELEGATED_READ_TOOLS = new Set(["mail_recent", "mail_search"]);
|
|
306
|
+
function mailTrustGuardrail(toolName, args, context) {
|
|
307
|
+
if (MAIL_FAMILY_TOOLS.has(toolName)) {
|
|
308
|
+
if (context.trustLevel === undefined || context.trustLevel === "family")
|
|
309
|
+
return allow;
|
|
310
|
+
if (toolName === "mail_send")
|
|
311
|
+
return deny("outbound mail sends require family trust.");
|
|
312
|
+
return deny(toolName === "mail_decide"
|
|
313
|
+
? "mail screener decisions require family trust."
|
|
314
|
+
: "delegated human mail requires family trust.");
|
|
315
|
+
}
|
|
316
|
+
if (MAIL_DELEGATED_READ_TOOLS.has(toolName)) {
|
|
317
|
+
const scope = (args.scope ?? "").trim().toLowerCase();
|
|
318
|
+
if (scope === "delegated" || scope === "all") {
|
|
319
|
+
if (context.trustLevel === undefined || context.trustLevel === "family")
|
|
320
|
+
return allow;
|
|
321
|
+
return deny("delegated human mail requires family trust.");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return allow;
|
|
325
|
+
}
|
|
326
|
+
function checkCredentialTrustGuardrails(toolName, context) {
|
|
327
|
+
if (CREDENTIAL_FAMILY_TOOLS.has(toolName)) {
|
|
328
|
+
if (context.trustLevel === "family")
|
|
329
|
+
return allow;
|
|
330
|
+
return deny(REASONS.needsTrust);
|
|
331
|
+
}
|
|
332
|
+
if (CREDENTIAL_TRUSTED_TOOLS.has(toolName) || TRAVEL_TRUSTED_TOOLS.has(toolName)) {
|
|
333
|
+
if ((0, types_1.isTrustedLevel)(context.trustLevel))
|
|
334
|
+
return allow;
|
|
335
|
+
return deny(REASONS.needsTrust);
|
|
336
|
+
}
|
|
337
|
+
return allow;
|
|
338
|
+
}
|
|
339
|
+
function checkFirstClassMcpTrust(context) {
|
|
340
|
+
if (!context.mcpServerName)
|
|
341
|
+
return allow;
|
|
342
|
+
const rules = MCP_SERVER_TRUST[context.mcpServerName];
|
|
343
|
+
if (!rules)
|
|
344
|
+
return allow;
|
|
345
|
+
if (!trustLevelSatisfied(rules.minTrust, context.trustLevel ?? "friend")) {
|
|
346
|
+
return deny(REASONS.needsTrust);
|
|
347
|
+
}
|
|
348
|
+
if (rules.blockGroupChat && context.isGroupChat) {
|
|
349
|
+
return deny("browser tools are only available in 1:1 conversations, not group chats.");
|
|
350
|
+
}
|
|
351
|
+
return allow;
|
|
352
|
+
}
|
|
353
|
+
function checkTrustLevelGuardrails(toolName, args, context) {
|
|
354
|
+
const mailResult = mailTrustGuardrail(toolName, args, context);
|
|
355
|
+
if (!mailResult.allowed)
|
|
356
|
+
return mailResult;
|
|
357
|
+
// Credential tools have their own trust rules that apply at all levels
|
|
358
|
+
const credentialResult = checkCredentialTrustGuardrails(toolName, context);
|
|
359
|
+
if (!credentialResult.allowed)
|
|
360
|
+
return credentialResult;
|
|
361
|
+
// First-class MCP tool trust (e.g. browser_navigate) — applies at all trust levels
|
|
362
|
+
const firstClassMcpResult = checkFirstClassMcpTrust(context);
|
|
363
|
+
if (!firstClassMcpResult.allowed)
|
|
364
|
+
return firstClassMcpResult;
|
|
365
|
+
// MCP server-specific trust via shell (e.g. ouro mcp call browser) — applies at all trust levels
|
|
366
|
+
if (toolName === "shell") {
|
|
367
|
+
const mcpResult = checkMcpServerTrust(args.command || "", context);
|
|
368
|
+
if (!mcpResult.allowed)
|
|
369
|
+
return mcpResult;
|
|
370
|
+
}
|
|
371
|
+
// Trusted levels (family/friend) — no further trust guardrails. Undefined defaults to friend.
|
|
372
|
+
if ((0, types_1.isTrustedLevel)(context.trustLevel))
|
|
373
|
+
return allow;
|
|
374
|
+
if (toolName === "shell") {
|
|
375
|
+
return checkShellTrustGuardrails(args.command || "", context.trustLevel);
|
|
376
|
+
}
|
|
377
|
+
return checkWriteTrustGuardrails(toolName, args, context);
|
|
378
|
+
}
|
|
379
|
+
// --- main entry point ---
|
|
380
|
+
function guardInvocation(toolName, args, context) {
|
|
381
|
+
(0, runtime_1.emitNervesEvent)({
|
|
382
|
+
component: "tools",
|
|
383
|
+
event: "tools.guard_check",
|
|
384
|
+
message: "guardrail check",
|
|
385
|
+
meta: { toolName },
|
|
386
|
+
});
|
|
387
|
+
// Read-only tools are always allowed (no structural or trust guardrails)
|
|
388
|
+
if (READ_ONLY_TOOLS.has(toolName))
|
|
389
|
+
return allow;
|
|
390
|
+
// Layer 1: structural guardrails (always on)
|
|
391
|
+
const structuralResult = checkStructuralGuardrails(toolName, args, context);
|
|
392
|
+
if (!structuralResult.allowed)
|
|
393
|
+
return structuralResult;
|
|
394
|
+
// Layer 2: trust-level guardrails (varies by friend's trust)
|
|
395
|
+
return checkTrustLevelGuardrails(toolName, args, context);
|
|
396
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.McpClient = void 0;
|
|
4
|
+
exports.isMcpTransportError = isMcpTransportError;
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
const readline_1 = require("readline");
|
|
7
|
+
const runtime_1 = require("../nerves/runtime");
|
|
8
|
+
const runtime_cwd_1 = require("../heart/runtime-cwd");
|
|
9
|
+
const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
10
|
+
const DEFAULT_REQUEST_TIMEOUT = 10_000;
|
|
11
|
+
const DEFAULT_TOOL_CALL_TIMEOUT = 30_000;
|
|
12
|
+
function isMcpTransportError(error) {
|
|
13
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14
|
+
const normalized = message.toLowerCase();
|
|
15
|
+
return normalized.includes("disconnected")
|
|
16
|
+
|| normalized.includes("transport")
|
|
17
|
+
|| normalized.includes("closed")
|
|
18
|
+
|| normalized.includes("econnreset")
|
|
19
|
+
|| normalized.includes("econnrefused")
|
|
20
|
+
|| normalized.includes("enoent")
|
|
21
|
+
|| normalized.includes("epipe")
|
|
22
|
+
|| normalized.includes("broken pipe")
|
|
23
|
+
|| normalized.includes("not writable");
|
|
24
|
+
}
|
|
25
|
+
class McpClient {
|
|
26
|
+
config;
|
|
27
|
+
process = null;
|
|
28
|
+
nextId = 1;
|
|
29
|
+
pending = new Map();
|
|
30
|
+
connected = false;
|
|
31
|
+
cachedTools = null;
|
|
32
|
+
onCloseCallback = null;
|
|
33
|
+
constructor(config) {
|
|
34
|
+
this.config = config;
|
|
35
|
+
}
|
|
36
|
+
async connect() {
|
|
37
|
+
if (this.connected)
|
|
38
|
+
return;
|
|
39
|
+
this.shutdownProcessOnly();
|
|
40
|
+
(0, runtime_1.emitNervesEvent)({
|
|
41
|
+
event: "mcp.connect_start",
|
|
42
|
+
component: "repertoire",
|
|
43
|
+
message: "starting MCP server connection",
|
|
44
|
+
meta: { command: this.config.command },
|
|
45
|
+
});
|
|
46
|
+
const env = { ...process.env, ...this.config.env };
|
|
47
|
+
const spawnCwd = this.config.cwd ?? (0, runtime_cwd_1.recoverRuntimeCwd)();
|
|
48
|
+
this.process = (0, child_process_1.spawn)(this.config.command, this.config.args ?? [], {
|
|
49
|
+
env,
|
|
50
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
51
|
+
cwd: spawnCwd,
|
|
52
|
+
});
|
|
53
|
+
this.setupLineReader();
|
|
54
|
+
this.setupProcessHandlers();
|
|
55
|
+
try {
|
|
56
|
+
await this.initialize();
|
|
57
|
+
this.connected = true;
|
|
58
|
+
(0, runtime_1.emitNervesEvent)({
|
|
59
|
+
event: "mcp.connect_end",
|
|
60
|
+
component: "repertoire",
|
|
61
|
+
message: "MCP server connected",
|
|
62
|
+
meta: { command: this.config.command },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
this.connected = false;
|
|
67
|
+
this.shutdownProcessOnly();
|
|
68
|
+
(0, runtime_1.emitNervesEvent)({
|
|
69
|
+
level: "error",
|
|
70
|
+
event: "mcp.connect_error",
|
|
71
|
+
component: "repertoire",
|
|
72
|
+
message: "MCP server connection failed",
|
|
73
|
+
meta: {
|
|
74
|
+
command: this.config.command,
|
|
75
|
+
/* v8 ignore next -- defensive: spawn errors are always Error instances @preserve */
|
|
76
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async listTools() {
|
|
83
|
+
if (this.cachedTools) {
|
|
84
|
+
return this.cachedTools;
|
|
85
|
+
}
|
|
86
|
+
const allTools = [];
|
|
87
|
+
let cursor;
|
|
88
|
+
do {
|
|
89
|
+
const params = {};
|
|
90
|
+
if (cursor) {
|
|
91
|
+
params.cursor = cursor;
|
|
92
|
+
}
|
|
93
|
+
const result = await this.sendRequest("tools/list", params);
|
|
94
|
+
allTools.push(...result.tools);
|
|
95
|
+
cursor = result.nextCursor;
|
|
96
|
+
} while (cursor);
|
|
97
|
+
this.cachedTools = allTools;
|
|
98
|
+
return allTools;
|
|
99
|
+
}
|
|
100
|
+
async refreshTools() {
|
|
101
|
+
this.cachedTools = null;
|
|
102
|
+
return this.listTools();
|
|
103
|
+
}
|
|
104
|
+
async callTool(name, args, timeout = DEFAULT_TOOL_CALL_TIMEOUT) {
|
|
105
|
+
(0, runtime_1.emitNervesEvent)({
|
|
106
|
+
event: "mcp.tool_call_start",
|
|
107
|
+
component: "repertoire",
|
|
108
|
+
message: `calling MCP tool: ${name}`,
|
|
109
|
+
meta: { tool: name },
|
|
110
|
+
});
|
|
111
|
+
try {
|
|
112
|
+
const result = await this.sendRequest("tools/call", {
|
|
113
|
+
name,
|
|
114
|
+
arguments: args,
|
|
115
|
+
}, timeout);
|
|
116
|
+
(0, runtime_1.emitNervesEvent)({
|
|
117
|
+
event: "mcp.tool_call_end",
|
|
118
|
+
component: "repertoire",
|
|
119
|
+
message: `MCP tool call completed: ${name}`,
|
|
120
|
+
meta: { tool: name },
|
|
121
|
+
});
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
(0, runtime_1.emitNervesEvent)({
|
|
126
|
+
level: "error",
|
|
127
|
+
event: "mcp.tool_call_error",
|
|
128
|
+
component: "repertoire",
|
|
129
|
+
message: `MCP tool call failed: ${name}`,
|
|
130
|
+
meta: {
|
|
131
|
+
tool: name,
|
|
132
|
+
/* v8 ignore next -- defensive: callTool errors are always Error instances @preserve */
|
|
133
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
shutdown() {
|
|
140
|
+
this.connected = false;
|
|
141
|
+
this.rejectAllPending(new Error("Client shutdown"));
|
|
142
|
+
/* v8 ignore next -- defensive: process always exists during normal shutdown @preserve */
|
|
143
|
+
if (this.process && !this.process.killed) {
|
|
144
|
+
this.process.kill();
|
|
145
|
+
}
|
|
146
|
+
this.process = null;
|
|
147
|
+
}
|
|
148
|
+
isConnected() {
|
|
149
|
+
return this.connected;
|
|
150
|
+
}
|
|
151
|
+
onClose(callback) {
|
|
152
|
+
this.onCloseCallback = callback;
|
|
153
|
+
}
|
|
154
|
+
async initialize() {
|
|
155
|
+
const result = await this.sendRequest("initialize", {
|
|
156
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
157
|
+
clientInfo: { name: "ouroboros", version: "1.0" },
|
|
158
|
+
capabilities: {},
|
|
159
|
+
});
|
|
160
|
+
// Send initialized notification (no id, no response expected)
|
|
161
|
+
this.writeMessage({
|
|
162
|
+
jsonrpc: "2.0",
|
|
163
|
+
method: "initialized",
|
|
164
|
+
});
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
sendRequest(method, params, timeout = DEFAULT_REQUEST_TIMEOUT) {
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
if (!this.process || !this.connected && method !== "initialize") {
|
|
170
|
+
reject(new Error("MCP client is disconnected"));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const id = this.nextId++;
|
|
174
|
+
const pending = { resolve, reject };
|
|
175
|
+
if (timeout) {
|
|
176
|
+
pending.timer = setTimeout(() => {
|
|
177
|
+
this.pending.delete(id);
|
|
178
|
+
reject(new Error(`MCP request timeout after ${timeout}ms: ${method}`));
|
|
179
|
+
}, timeout);
|
|
180
|
+
}
|
|
181
|
+
this.pending.set(id, pending);
|
|
182
|
+
const request = {
|
|
183
|
+
jsonrpc: "2.0",
|
|
184
|
+
id,
|
|
185
|
+
method,
|
|
186
|
+
params,
|
|
187
|
+
};
|
|
188
|
+
if (!this.writeMessage(request)) {
|
|
189
|
+
this.pending.delete(id);
|
|
190
|
+
if (pending.timer) {
|
|
191
|
+
clearTimeout(pending.timer);
|
|
192
|
+
}
|
|
193
|
+
reject(new Error(`MCP transport is not writable for request: ${method}`));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
writeMessage(message) {
|
|
198
|
+
if (this.process?.stdin?.writable) {
|
|
199
|
+
this.process.stdin.write(JSON.stringify(message) + "\n");
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
setupLineReader() {
|
|
205
|
+
/* v8 ignore next -- defensive: stdout always exists after spawn @preserve */
|
|
206
|
+
if (!this.process?.stdout)
|
|
207
|
+
return;
|
|
208
|
+
const rl = (0, readline_1.createInterface)({ input: this.process.stdout });
|
|
209
|
+
rl.on("line", (line) => {
|
|
210
|
+
this.handleLine(line);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
handleLine(line) {
|
|
214
|
+
let response;
|
|
215
|
+
try {
|
|
216
|
+
response = JSON.parse(line);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
(0, runtime_1.emitNervesEvent)({
|
|
220
|
+
level: "warn",
|
|
221
|
+
event: "mcp.connect_error",
|
|
222
|
+
component: "repertoire",
|
|
223
|
+
message: "received malformed JSON from MCP server",
|
|
224
|
+
meta: { line },
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (response.id === undefined || response.id === null) {
|
|
229
|
+
// Notification or invalid — ignore
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const pending = this.pending.get(response.id);
|
|
233
|
+
if (!pending)
|
|
234
|
+
return;
|
|
235
|
+
this.pending.delete(response.id);
|
|
236
|
+
if (pending.timer) {
|
|
237
|
+
clearTimeout(pending.timer);
|
|
238
|
+
}
|
|
239
|
+
if (response.error) {
|
|
240
|
+
pending.reject(new Error(response.error.message));
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
pending.resolve(response.result);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
setupProcessHandlers() {
|
|
247
|
+
/* v8 ignore next -- defensive: process always exists after spawn @preserve */
|
|
248
|
+
if (!this.process)
|
|
249
|
+
return;
|
|
250
|
+
this.process.on("error", (error) => {
|
|
251
|
+
(0, runtime_1.emitNervesEvent)({
|
|
252
|
+
level: "error",
|
|
253
|
+
event: "mcp.connect_error",
|
|
254
|
+
component: "repertoire",
|
|
255
|
+
message: "MCP server process error",
|
|
256
|
+
meta: { reason: error.message },
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
this.process.on("close", (code) => {
|
|
260
|
+
const wasConnected = this.connected;
|
|
261
|
+
this.connected = false;
|
|
262
|
+
this.rejectAllPending(new Error(`MCP server process closed with code ${code}`));
|
|
263
|
+
if (wasConnected) {
|
|
264
|
+
(0, runtime_1.emitNervesEvent)({
|
|
265
|
+
level: "error",
|
|
266
|
+
event: "mcp.connect_error",
|
|
267
|
+
component: "repertoire",
|
|
268
|
+
message: "MCP server process exited unexpectedly",
|
|
269
|
+
meta: { exitCode: code },
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (this.onCloseCallback) {
|
|
273
|
+
this.onCloseCallback();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
rejectAllPending(error) {
|
|
278
|
+
for (const [id, pending] of this.pending) {
|
|
279
|
+
if (pending.timer) {
|
|
280
|
+
clearTimeout(pending.timer);
|
|
281
|
+
}
|
|
282
|
+
pending.reject(error);
|
|
283
|
+
this.pending.delete(id);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
shutdownProcessOnly() {
|
|
287
|
+
this.rejectAllPending(new Error("MCP transport closed during reconnect"));
|
|
288
|
+
/* v8 ignore next -- defensive: process may already be absent @preserve */
|
|
289
|
+
if (this.process && !this.process.killed) {
|
|
290
|
+
this.process.kill();
|
|
291
|
+
}
|
|
292
|
+
this.process = null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
exports.McpClient = McpClient;
|