apex-code 0.0.1-alpha.1 → 0.0.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +66 -681
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +45 -6
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-command.d.ts.map +1 -1
- package/dist/cli/auth-command.js +3 -3
- package/dist/cli/auth-command.js.map +1 -1
- package/dist/cli/cost-command.d.ts +19 -0
- package/dist/cli/cost-command.d.ts.map +1 -0
- package/dist/cli/cost-command.js +107 -0
- package/dist/cli/cost-command.js.map +1 -0
- package/dist/cli/startup-ui.d.ts +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -2
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +45 -16
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -7
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +2 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +9 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +30 -4
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +46 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +137 -19
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +6 -6
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +1 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/context/deferred-schemas.d.ts +92 -0
- package/dist/core/context/deferred-schemas.d.ts.map +1 -0
- package/dist/core/context/deferred-schemas.js +74 -0
- package/dist/core/context/deferred-schemas.js.map +1 -0
- package/dist/core/context/eviction.d.ts +47 -0
- package/dist/core/context/eviction.d.ts.map +1 -0
- package/dist/core/context/eviction.js +83 -0
- package/dist/core/context/eviction.js.map +1 -0
- package/dist/core/context/pipeline.d.ts +90 -0
- package/dist/core/context/pipeline.d.ts.map +1 -0
- package/dist/core/context/pipeline.js +109 -0
- package/dist/core/context/pipeline.js.map +1 -0
- package/dist/core/credential-failover.d.ts +30 -0
- package/dist/core/credential-failover.d.ts.map +1 -0
- package/dist/core/credential-failover.js +46 -0
- package/dist/core/credential-failover.js.map +1 -0
- package/dist/core/credential-pool.d.ts +60 -0
- package/dist/core/credential-pool.d.ts.map +1 -0
- package/dist/core/credential-pool.js +84 -0
- package/dist/core/credential-pool.js.map +1 -0
- package/dist/core/delegation/agents.d.ts +15 -0
- package/dist/core/delegation/agents.d.ts.map +1 -0
- package/dist/core/delegation/agents.js +55 -0
- package/dist/core/delegation/agents.js.map +1 -0
- package/dist/core/delegation/ceiling.d.ts +29 -0
- package/dist/core/delegation/ceiling.d.ts.map +1 -0
- package/dist/core/delegation/ceiling.js +34 -0
- package/dist/core/delegation/ceiling.js.map +1 -0
- package/dist/core/delegation/runtime.d.ts +94 -0
- package/dist/core/delegation/runtime.d.ts.map +1 -0
- package/dist/core/delegation/runtime.js +117 -0
- package/dist/core/delegation/runtime.js.map +1 -0
- package/dist/core/durable-state/daemon.d.ts +37 -0
- package/dist/core/durable-state/daemon.d.ts.map +1 -0
- package/dist/core/durable-state/daemon.js +62 -0
- package/dist/core/durable-state/daemon.js.map +1 -0
- package/dist/core/durable-state/provenance.d.ts +7 -0
- package/dist/core/durable-state/provenance.d.ts.map +1 -0
- package/dist/core/durable-state/provenance.js +24 -0
- package/dist/core/durable-state/provenance.js.map +1 -0
- package/dist/core/durable-state/sqlite.d.ts +84 -0
- package/dist/core/durable-state/sqlite.d.ts.map +1 -0
- package/dist/core/durable-state/sqlite.js +331 -0
- package/dist/core/durable-state/sqlite.js.map +1 -0
- package/dist/core/environment.d.ts +12 -0
- package/dist/core/environment.d.ts.map +1 -0
- package/dist/core/environment.js +69 -0
- package/dist/core/environment.js.map +1 -0
- package/dist/core/evidence-policy.d.ts +17 -0
- package/dist/core/evidence-policy.d.ts.map +1 -0
- package/dist/core/evidence-policy.js +25 -0
- package/dist/core/evidence-policy.js.map +1 -0
- package/dist/core/evidence.d.ts +16 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +51 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +2 -1
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-config.d.ts +8 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +21 -5
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts +20 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +59 -0
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +73 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +225 -8
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/observability/aggregate.d.ts +21 -0
- package/dist/core/observability/aggregate.d.ts.map +1 -0
- package/dist/core/observability/aggregate.js +36 -0
- package/dist/core/observability/aggregate.js.map +1 -0
- package/dist/core/observability/otlp.d.ts +66 -0
- package/dist/core/observability/otlp.d.ts.map +1 -0
- package/dist/core/observability/otlp.js +95 -0
- package/dist/core/observability/otlp.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +3 -2
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/permissions/gate.d.ts +35 -0
- package/dist/core/permissions/gate.d.ts.map +1 -0
- package/dist/core/permissions/gate.js +72 -0
- package/dist/core/permissions/gate.js.map +1 -0
- package/dist/core/permissions/modes.d.ts +18 -0
- package/dist/core/permissions/modes.d.ts.map +1 -0
- package/dist/core/permissions/modes.js +57 -0
- package/dist/core/permissions/modes.js.map +1 -0
- package/dist/core/permissions/responder.d.ts +30 -0
- package/dist/core/permissions/responder.d.ts.map +1 -0
- package/dist/core/permissions/responder.js +32 -0
- package/dist/core/permissions/responder.js.map +1 -0
- package/dist/core/permissions/rules.d.ts +34 -0
- package/dist/core/permissions/rules.d.ts.map +1 -0
- package/dist/core/permissions/rules.js +74 -0
- package/dist/core/permissions/rules.js.map +1 -0
- package/dist/core/permissions/startup.d.ts +33 -0
- package/dist/core/permissions/startup.d.ts.map +1 -0
- package/dist/core/permissions/startup.js +55 -0
- package/dist/core/permissions/startup.js.map +1 -0
- package/dist/core/permissions/store.d.ts +114 -0
- package/dist/core/permissions/store.d.ts.map +1 -0
- package/dist/core/permissions/store.js +229 -0
- package/dist/core/permissions/store.js.map +1 -0
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js +1 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/provider-attribution.d.ts.map +1 -1
- package/dist/core/provider-attribution.js +6 -7
- package/dist/core/provider-attribution.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +2 -2
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +20 -16
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/sandbox/child-entry.d.ts +2 -0
- package/dist/core/sandbox/child-entry.d.ts.map +1 -0
- package/dist/core/sandbox/child-entry.js +11 -0
- package/dist/core/sandbox/child-entry.js.map +1 -0
- package/dist/core/sandbox/cli-launch.d.ts +27 -0
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -0
- package/dist/core/sandbox/cli-launch.js +133 -0
- package/dist/core/sandbox/cli-launch.js.map +1 -0
- package/dist/core/sandbox/cli-supervisor.d.ts +25 -0
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -0
- package/dist/core/sandbox/cli-supervisor.js +57 -0
- package/dist/core/sandbox/cli-supervisor.js.map +1 -0
- package/dist/core/sandbox/linux-backend.d.ts +18 -0
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -0
- package/dist/core/sandbox/linux-backend.js +186 -0
- package/dist/core/sandbox/linux-backend.js.map +1 -0
- package/dist/core/sandbox/macos-backend.d.ts +18 -0
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -0
- package/dist/core/sandbox/macos-backend.js +195 -0
- package/dist/core/sandbox/macos-backend.js.map +1 -0
- package/dist/core/sandbox/network-proxy.d.ts +19 -0
- package/dist/core/sandbox/network-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/network-proxy.js +79 -0
- package/dist/core/sandbox/network-proxy.js.map +1 -0
- package/dist/core/sandbox/policy.d.ts +32 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -0
- package/dist/core/sandbox/policy.js +38 -0
- package/dist/core/sandbox/policy.js.map +1 -0
- package/dist/core/sandbox/supervisor.d.ts +31 -0
- package/dist/core/sandbox/supervisor.d.ts.map +1 -0
- package/dist/core/sandbox/supervisor.js +18 -0
- package/dist/core/sandbox/supervisor.js.map +1 -0
- package/dist/core/sandbox/violations.d.ts +23 -0
- package/dist/core/sandbox/violations.d.ts.map +1 -0
- package/dist/core/sandbox/violations.js +30 -0
- package/dist/core/sandbox/violations.js.map +1 -0
- package/dist/core/sdk.d.ts +20 -36
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +129 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +41 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +57 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-share.d.ts +16 -0
- package/dist/core/session-share.d.ts.map +1 -0
- package/dist/core/session-share.js +22 -0
- package/dist/core/session-share.js.map +1 -0
- package/dist/core/settings-manager.d.ts +38 -9
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +39 -24
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -5
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/timings.d.ts +1 -1
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +3 -2
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/ask-user.d.ts +25 -0
- package/dist/core/tools/ask-user.d.ts.map +1 -0
- package/dist/core/tools/ask-user.js +49 -0
- package/dist/core/tools/ask-user.js.map +1 -0
- package/dist/core/tools/bash-command-segments.d.ts +22 -0
- package/dist/core/tools/bash-command-segments.d.ts.map +1 -0
- package/dist/core/tools/bash-command-segments.js +128 -0
- package/dist/core/tools/bash-command-segments.js.map +1 -0
- package/dist/core/tools/bash.d.ts +26 -3
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +105 -16
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/contract.d.ts +156 -0
- package/dist/core/tools/contract.d.ts.map +1 -0
- package/dist/core/tools/contract.js +48 -0
- package/dist/core/tools/contract.js.map +1 -0
- package/dist/core/tools/delegate.d.ts +38 -0
- package/dist/core/tools/delegate.d.ts.map +1 -0
- package/dist/core/tools/delegate.js +66 -0
- package/dist/core/tools/delegate.js.map +1 -0
- package/dist/core/tools/edit.d.ts +2 -2
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +24 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +2 -2
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +12 -0
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +2 -2
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +12 -0
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +29 -4
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +118 -2
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +2 -2
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +12 -0
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +1 -1
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/path-permission.d.ts +18 -0
- package/dist/core/tools/path-permission.d.ts.map +1 -0
- package/dist/core/tools/path-permission.js +28 -0
- package/dist/core/tools/path-permission.js.map +1 -0
- package/dist/core/tools/plan-present.d.ts +27 -0
- package/dist/core/tools/plan-present.d.ts.map +1 -0
- package/dist/core/tools/plan-present.js +55 -0
- package/dist/core/tools/plan-present.js.map +1 -0
- package/dist/core/tools/read.d.ts +2 -2
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +12 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/test.d.ts +34 -0
- package/dist/core/tools/test.d.ts.map +1 -0
- package/dist/core/tools/test.js +75 -0
- package/dist/core/tools/test.js.map +1 -0
- package/dist/core/tools/todo-write.d.ts +31 -0
- package/dist/core/tools/todo-write.d.ts.map +1 -0
- package/dist/core/tools/todo-write.js +51 -0
- package/dist/core/tools/todo-write.js.map +1 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts +13 -2
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +11 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/tool-schema.d.ts +22 -0
- package/dist/core/tools/tool-schema.d.ts.map +1 -0
- package/dist/core/tools/tool-schema.js +45 -0
- package/dist/core/tools/tool-schema.js.map +1 -0
- package/dist/core/tools/web-fetch.d.ts +26 -0
- package/dist/core/tools/web-fetch.d.ts.map +1 -0
- package/dist/core/tools/web-fetch.js +62 -0
- package/dist/core/tools/web-fetch.js.map +1 -0
- package/dist/core/tools/web-search.d.ts +26 -0
- package/dist/core/tools/web-search.d.ts.map +1 -0
- package/dist/core/tools/web-search.js +53 -0
- package/dist/core/tools/web-search.js.map +1 -0
- package/dist/core/tools/write.d.ts +6 -2
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +32 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/usage-performance-store.d.ts +56 -0
- package/dist/core/usage-performance-store.d.ts.map +1 -0
- package/dist/core/usage-performance-store.js +85 -0
- package/dist/core/usage-performance-store.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +48 -6
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +2 -2
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +9 -6
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -4
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +11 -39
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +8 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +49 -20
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +6 -6
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +2 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +58 -9
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -3
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +92 -104
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +2 -2
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +15 -10
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/rpc-entry.d.ts.map +1 -1
- package/dist/rpc-entry.js +2 -1
- package/dist/rpc-entry.js.map +1 -1
- package/dist/server/create-harness.d.ts +1 -1
- package/dist/server/create-harness.d.ts.map +1 -1
- package/dist/server/create-harness.js +6 -5
- package/dist/server/create-harness.js.map +1 -1
- package/dist/testing/replay/metrics.d.ts +24 -0
- package/dist/testing/replay/metrics.d.ts.map +1 -0
- package/dist/testing/replay/metrics.js +35 -0
- package/dist/testing/replay/metrics.js.map +1 -0
- package/dist/testing/replay/recorded-provider.d.ts +10 -0
- package/dist/testing/replay/recorded-provider.d.ts.map +1 -0
- package/dist/testing/replay/recorded-provider.js +108 -0
- package/dist/testing/replay/recorded-provider.js.map +1 -0
- package/dist/testing/replay/runner.d.ts +56 -0
- package/dist/testing/replay/runner.d.ts.map +1 -0
- package/dist/testing/replay/runner.js +406 -0
- package/dist/testing/replay/runner.js.map +1 -0
- package/dist/utils/apex-code-user-agent.d.ts +2 -0
- package/dist/utils/apex-code-user-agent.d.ts.map +1 -0
- package/dist/utils/apex-code-user-agent.js +5 -0
- package/dist/utils/apex-code-user-agent.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +32 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +198 -100
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +5 -5
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +23 -14
- package/dist/utils/version-check.js.map +1 -1
- package/dist/utils/windows-self-update.d.ts.map +1 -1
- package/dist/utils/windows-self-update.js +1 -1
- package/dist/utils/windows-self-update.js.map +1 -1
- package/docs/compaction.md +21 -21
- package/docs/containerization.md +24 -21
- package/docs/custom-provider.md +9 -9
- package/docs/development.md +11 -11
- package/docs/environment-variables.md +36 -77
- package/docs/extensions.md +47 -47
- package/docs/index.md +12 -24
- package/docs/json.md +6 -6
- package/docs/keybindings.md +3 -3
- package/docs/llama-cpp.md +7 -7
- package/docs/models.md +11 -11
- package/docs/packages.md +46 -42
- package/docs/prompt-templates.md +3 -3
- package/docs/providers.md +10 -10
- package/docs/quickstart.md +49 -32
- package/docs/rpc.md +11 -11
- package/docs/sdk.md +4 -4
- package/docs/security.md +18 -18
- package/docs/session-format.md +9 -9
- package/docs/sessions.md +13 -13
- package/docs/settings.md +10 -14
- package/docs/shell-aliases.md +1 -1
- package/docs/skills.md +8 -8
- package/docs/terminal-setup.md +12 -12
- package/docs/termux.md +4 -4
- package/docs/themes.md +8 -8
- package/docs/tmux.md +4 -4
- package/docs/tui.md +3 -3
- package/docs/usage.md +54 -51
- package/docs/windows.md +1 -1
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -3
- package/dist/core/telemetry.d.ts +0 -3
- package/dist/core/telemetry.d.ts.map +0 -1
- package/dist/core/telemetry.js +0 -9
- package/dist/core/telemetry.js.map +0 -1
- package/dist/utils/pi-user-agent.d.ts +0 -2
- package/dist/utils/pi-user-agent.d.ts.map +0 -1
- package/dist/utils/pi-user-agent.js +0 -5
- package/dist/utils/pi-user-agent.js.map +0 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission gate: composes rule resolution (rules.ts), mode overlay
|
|
3
|
+
* (modes.ts), and interactive escalation (responder.ts) into a single decision,
|
|
4
|
+
* and adapts that decision to the `beforeToolCall` seam `agent-core` already
|
|
5
|
+
* exposes (packages/agent/src/agent-loop.ts). No new hook, no agent-core change.
|
|
6
|
+
*
|
|
7
|
+
* Tool execution has exactly one call site downstream of this hook
|
|
8
|
+
* (`executePreparedToolCall`, fed only by the function that runs `beforeToolCall`),
|
|
9
|
+
* so wiring this in once at session setup is sufficient for "every registered tool
|
|
10
|
+
* passes the gate" to be true by construction — the universal-gate test (invariant
|
|
11
|
+
* 2) verifies this property holds, it does not have to work to make it hold.
|
|
12
|
+
*/
|
|
13
|
+
import type { BeforeToolCallContext, BeforeToolCallResult } from "apex-code-agent-core";
|
|
14
|
+
import { type ToolContract } from "../tools/contract.ts";
|
|
15
|
+
import type { PermissionResponder } from "./responder.ts";
|
|
16
|
+
import type { PermissionMode, PermissionRuleStore } from "./store.ts";
|
|
17
|
+
export interface PermissionGateOptions {
|
|
18
|
+
/** Resolve a tool's contract by name. Foreign tools with no contract get UNCLASSIFIED, never rejected or silently defaulted (ADR 0010). */
|
|
19
|
+
getContract: (toolName: string) => ToolContract | undefined;
|
|
20
|
+
store: PermissionRuleStore;
|
|
21
|
+
getMode: () => PermissionMode | Promise<PermissionMode>;
|
|
22
|
+
/** Absent in a non-interactive session: an `ask` resolution then fails closed (deny). */
|
|
23
|
+
responder?: PermissionResponder;
|
|
24
|
+
/** Resolves a responder at call time because the interactive UI binds after session construction. */
|
|
25
|
+
getResponder?: () => PermissionResponder | undefined;
|
|
26
|
+
}
|
|
27
|
+
export interface GateDecision {
|
|
28
|
+
block: boolean;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Pure decision function, independent of the beforeToolCall adapter shape below — the part under direct test. */
|
|
32
|
+
export declare function evaluateToolCall(toolName: string, params: unknown, options: PermissionGateOptions): Promise<GateDecision>;
|
|
33
|
+
/** Adapts evaluateToolCall() to the beforeToolCall seam agent-core already exposes. */
|
|
34
|
+
export declare function createPermissionGate(options: PermissionGateOptions): (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
|
|
35
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,qBAAqB;IACrC,2IAA2I;IAC3I,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,YAAY,GAAG,SAAS,CAAC;IAC5D,KAAK,EAAE,mBAAmB,CAAC;IAC3B,OAAO,EAAE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,yFAAyF;IACzF,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,qGAAqG;IACrG,YAAY,CAAC,EAAE,MAAM,mBAAmB,GAAG,SAAS,CAAC;CACrD;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,oHAAkH;AAClH,wBAAsB,gBAAgB,CACrC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,YAAY,CAAC,CA4CvB;AAED,uFAAuF;AACvF,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,qBAAqB,GAC5B,CAAC,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAKrG","sourcesContent":["/**\n * The permission gate: composes rule resolution (rules.ts), mode overlay\n * (modes.ts), and interactive escalation (responder.ts) into a single decision,\n * and adapts that decision to the `beforeToolCall` seam `agent-core` already\n * exposes (packages/agent/src/agent-loop.ts). No new hook, no agent-core change.\n *\n * Tool execution has exactly one call site downstream of this hook\n * (`executePreparedToolCall`, fed only by the function that runs `beforeToolCall`),\n * so wiring this in once at session setup is sufficient for \"every registered tool\n * passes the gate\" to be true by construction — the universal-gate test (invariant\n * 2) verifies this property holds, it does not have to work to make it hold.\n */\n\nimport type { BeforeToolCallContext, BeforeToolCallResult } from \"apex-code-agent-core\";\nimport { resolveToolContract, type ToolContract } from \"../tools/contract.ts\";\nimport { resolveWithMode } from \"./modes.ts\";\nimport type { PermissionResponder } from \"./responder.ts\";\nimport { resolvePermission } from \"./rules.ts\";\nimport type { PermissionMode, PermissionRuleStore } from \"./store.ts\";\n\nexport interface PermissionGateOptions {\n\t/** Resolve a tool's contract by name. Foreign tools with no contract get UNCLASSIFIED, never rejected or silently defaulted (ADR 0010). */\n\tgetContract: (toolName: string) => ToolContract | undefined;\n\tstore: PermissionRuleStore;\n\tgetMode: () => PermissionMode | Promise<PermissionMode>;\n\t/** Absent in a non-interactive session: an `ask` resolution then fails closed (deny). */\n\tresponder?: PermissionResponder;\n\t/** Resolves a responder at call time because the interactive UI binds after session construction. */\n\tgetResponder?: () => PermissionResponder | undefined;\n}\n\nexport interface GateDecision {\n\tblock: boolean;\n\treason?: string;\n}\n\nfunction describeDecision(contract: ToolContract, toolName: string, ruleContent: string | undefined): string {\n\tif (ruleContent !== undefined) return contract.permission.describe(ruleContent);\n\treturn `${toolName} is not permitted by the current permission configuration.`;\n}\n\n/** Pure decision function, independent of the beforeToolCall adapter shape below — the part under direct test. */\nexport async function evaluateToolCall(\n\ttoolName: string,\n\tparams: unknown,\n\toptions: PermissionGateOptions,\n): Promise<GateDecision> {\n\tconst contract = resolveToolContract(options.getContract, toolName);\n\tconst spec = contract.permission;\n\tconst snapshot = await options.store.snapshot();\n\tif (snapshot.errors.length > 0) {\n\t\tconst sources = [...new Set(snapshot.errors.map((entry) => entry.source))].join(\", \");\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: `Permission configuration could not be loaded (${sources}); refusing to run ${toolName}.`,\n\t\t};\n\t}\n\tconst ruleResolution = resolvePermission(snapshot.rules, toolName, spec, params as never);\n\tconst resolution = resolveWithMode(await options.getMode(), ruleResolution, contract.capabilities);\n\n\tif (resolution.behavior === \"allow\") return { block: false };\n\n\tif (resolution.behavior === \"deny\") {\n\t\treturn { block: true, reason: describeDecision(contract, toolName, resolution.rule?.ruleContent) };\n\t}\n\n\t// ask\n\tconst responder = options.getResponder?.() ?? options.responder;\n\tif (!responder) {\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: `${toolName} requires approval, and no responder is available in this session.`,\n\t\t};\n\t}\n\tconst ruleForCall = spec.ruleForCall(params as never);\n\tconst answer = await responder.ask({\n\t\ttoolName,\n\t\tdescription: ruleForCall !== null ? spec.describe(ruleForCall) : `Run ${toolName}`,\n\t});\n\tif (!answer.allow) {\n\t\treturn { block: true, reason: `${toolName} was declined.` };\n\t}\n\tif (answer.persist && ruleForCall !== null) {\n\t\tawait options.store.apply({\n\t\t\ttype: \"addRules\",\n\t\t\tdestination: \"session\",\n\t\t\trules: [{ toolName, behavior: \"allow\", ruleContent: ruleForCall }],\n\t\t});\n\t}\n\treturn { block: false };\n}\n\n/** Adapts evaluateToolCall() to the beforeToolCall seam agent-core already exposes. */\nexport function createPermissionGate(\n\toptions: PermissionGateOptions,\n): (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined> {\n\treturn async ({ toolCall, args }) => {\n\t\tconst decision = await evaluateToolCall(toolCall.name, args, options);\n\t\treturn decision.block ? { block: true, reason: decision.reason } : undefined;\n\t};\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission gate: composes rule resolution (rules.ts), mode overlay
|
|
3
|
+
* (modes.ts), and interactive escalation (responder.ts) into a single decision,
|
|
4
|
+
* and adapts that decision to the `beforeToolCall` seam `agent-core` already
|
|
5
|
+
* exposes (packages/agent/src/agent-loop.ts). No new hook, no agent-core change.
|
|
6
|
+
*
|
|
7
|
+
* Tool execution has exactly one call site downstream of this hook
|
|
8
|
+
* (`executePreparedToolCall`, fed only by the function that runs `beforeToolCall`),
|
|
9
|
+
* so wiring this in once at session setup is sufficient for "every registered tool
|
|
10
|
+
* passes the gate" to be true by construction — the universal-gate test (invariant
|
|
11
|
+
* 2) verifies this property holds, it does not have to work to make it hold.
|
|
12
|
+
*/
|
|
13
|
+
import { resolveToolContract } from "../tools/contract.js";
|
|
14
|
+
import { resolveWithMode } from "./modes.js";
|
|
15
|
+
import { resolvePermission } from "./rules.js";
|
|
16
|
+
function describeDecision(contract, toolName, ruleContent) {
|
|
17
|
+
if (ruleContent !== undefined)
|
|
18
|
+
return contract.permission.describe(ruleContent);
|
|
19
|
+
return `${toolName} is not permitted by the current permission configuration.`;
|
|
20
|
+
}
|
|
21
|
+
/** Pure decision function, independent of the beforeToolCall adapter shape below — the part under direct test. */
|
|
22
|
+
export async function evaluateToolCall(toolName, params, options) {
|
|
23
|
+
const contract = resolveToolContract(options.getContract, toolName);
|
|
24
|
+
const spec = contract.permission;
|
|
25
|
+
const snapshot = await options.store.snapshot();
|
|
26
|
+
if (snapshot.errors.length > 0) {
|
|
27
|
+
const sources = [...new Set(snapshot.errors.map((entry) => entry.source))].join(", ");
|
|
28
|
+
return {
|
|
29
|
+
block: true,
|
|
30
|
+
reason: `Permission configuration could not be loaded (${sources}); refusing to run ${toolName}.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const ruleResolution = resolvePermission(snapshot.rules, toolName, spec, params);
|
|
34
|
+
const resolution = resolveWithMode(await options.getMode(), ruleResolution, contract.capabilities);
|
|
35
|
+
if (resolution.behavior === "allow")
|
|
36
|
+
return { block: false };
|
|
37
|
+
if (resolution.behavior === "deny") {
|
|
38
|
+
return { block: true, reason: describeDecision(contract, toolName, resolution.rule?.ruleContent) };
|
|
39
|
+
}
|
|
40
|
+
// ask
|
|
41
|
+
const responder = options.getResponder?.() ?? options.responder;
|
|
42
|
+
if (!responder) {
|
|
43
|
+
return {
|
|
44
|
+
block: true,
|
|
45
|
+
reason: `${toolName} requires approval, and no responder is available in this session.`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const ruleForCall = spec.ruleForCall(params);
|
|
49
|
+
const answer = await responder.ask({
|
|
50
|
+
toolName,
|
|
51
|
+
description: ruleForCall !== null ? spec.describe(ruleForCall) : `Run ${toolName}`,
|
|
52
|
+
});
|
|
53
|
+
if (!answer.allow) {
|
|
54
|
+
return { block: true, reason: `${toolName} was declined.` };
|
|
55
|
+
}
|
|
56
|
+
if (answer.persist && ruleForCall !== null) {
|
|
57
|
+
await options.store.apply({
|
|
58
|
+
type: "addRules",
|
|
59
|
+
destination: "session",
|
|
60
|
+
rules: [{ toolName, behavior: "allow", ruleContent: ruleForCall }],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return { block: false };
|
|
64
|
+
}
|
|
65
|
+
/** Adapts evaluateToolCall() to the beforeToolCall seam agent-core already exposes. */
|
|
66
|
+
export function createPermissionGate(options) {
|
|
67
|
+
return async ({ toolCall, args }) => {
|
|
68
|
+
const decision = await evaluateToolCall(toolCall.name, args, options);
|
|
69
|
+
return decision.block ? { block: true, reason: decision.reason } : undefined;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../src/core/permissions/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,mBAAmB,EAAqB,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAmB/C,SAAS,gBAAgB,CAAC,QAAsB,EAAE,QAAgB,EAAE,WAA+B,EAAU;IAC5G,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAChF,OAAO,GAAG,QAAQ,4DAA4D,CAAC;AAAA,CAC/E;AAED,oHAAkH;AAClH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,QAAgB,EAChB,MAAe,EACf,OAA8B,EACN;IACxB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC;IACjC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IAChD,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtF,OAAO;YACN,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,iDAAiD,OAAO,sBAAsB,QAAQ,GAAG;SACjG,CAAC;IACH,CAAC;IACD,MAAM,cAAc,GAAG,iBAAiB,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAe,CAAC,CAAC;IAC1F,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;IAEnG,IAAI,UAAU,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAE7D,IAAI,UAAU,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QACpC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;IACpG,CAAC;IAED,MAAM;IACN,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,EAAE,IAAI,OAAO,CAAC,SAAS,CAAC;IAChE,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,OAAO;YACN,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,GAAG,QAAQ,oEAAoE;SACvF,CAAC;IACH,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,MAAe,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC;QAClC,QAAQ;QACR,WAAW,EAAE,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,QAAQ,EAAE;KAClF,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACnB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,gBAAgB,EAAE,CAAC;IAC7D,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QAC5C,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;YACzB,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,SAAS;YACtB,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;SAClE,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAAA,CACxB;AAED,uFAAuF;AACvF,MAAM,UAAU,oBAAoB,CACnC,OAA8B,EACwE;IACtG,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACtE,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAAA,CAC7E,CAAC;AAAA,CACF","sourcesContent":["/**\n * The permission gate: composes rule resolution (rules.ts), mode overlay\n * (modes.ts), and interactive escalation (responder.ts) into a single decision,\n * and adapts that decision to the `beforeToolCall` seam `agent-core` already\n * exposes (packages/agent/src/agent-loop.ts). No new hook, no agent-core change.\n *\n * Tool execution has exactly one call site downstream of this hook\n * (`executePreparedToolCall`, fed only by the function that runs `beforeToolCall`),\n * so wiring this in once at session setup is sufficient for \"every registered tool\n * passes the gate\" to be true by construction — the universal-gate test (invariant\n * 2) verifies this property holds, it does not have to work to make it hold.\n */\n\nimport type { BeforeToolCallContext, BeforeToolCallResult } from \"apex-code-agent-core\";\nimport { resolveToolContract, type ToolContract } from \"../tools/contract.ts\";\nimport { resolveWithMode } from \"./modes.ts\";\nimport type { PermissionResponder } from \"./responder.ts\";\nimport { resolvePermission } from \"./rules.ts\";\nimport type { PermissionMode, PermissionRuleStore } from \"./store.ts\";\n\nexport interface PermissionGateOptions {\n\t/** Resolve a tool's contract by name. Foreign tools with no contract get UNCLASSIFIED, never rejected or silently defaulted (ADR 0010). */\n\tgetContract: (toolName: string) => ToolContract | undefined;\n\tstore: PermissionRuleStore;\n\tgetMode: () => PermissionMode | Promise<PermissionMode>;\n\t/** Absent in a non-interactive session: an `ask` resolution then fails closed (deny). */\n\tresponder?: PermissionResponder;\n\t/** Resolves a responder at call time because the interactive UI binds after session construction. */\n\tgetResponder?: () => PermissionResponder | undefined;\n}\n\nexport interface GateDecision {\n\tblock: boolean;\n\treason?: string;\n}\n\nfunction describeDecision(contract: ToolContract, toolName: string, ruleContent: string | undefined): string {\n\tif (ruleContent !== undefined) return contract.permission.describe(ruleContent);\n\treturn `${toolName} is not permitted by the current permission configuration.`;\n}\n\n/** Pure decision function, independent of the beforeToolCall adapter shape below — the part under direct test. */\nexport async function evaluateToolCall(\n\ttoolName: string,\n\tparams: unknown,\n\toptions: PermissionGateOptions,\n): Promise<GateDecision> {\n\tconst contract = resolveToolContract(options.getContract, toolName);\n\tconst spec = contract.permission;\n\tconst snapshot = await options.store.snapshot();\n\tif (snapshot.errors.length > 0) {\n\t\tconst sources = [...new Set(snapshot.errors.map((entry) => entry.source))].join(\", \");\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: `Permission configuration could not be loaded (${sources}); refusing to run ${toolName}.`,\n\t\t};\n\t}\n\tconst ruleResolution = resolvePermission(snapshot.rules, toolName, spec, params as never);\n\tconst resolution = resolveWithMode(await options.getMode(), ruleResolution, contract.capabilities);\n\n\tif (resolution.behavior === \"allow\") return { block: false };\n\n\tif (resolution.behavior === \"deny\") {\n\t\treturn { block: true, reason: describeDecision(contract, toolName, resolution.rule?.ruleContent) };\n\t}\n\n\t// ask\n\tconst responder = options.getResponder?.() ?? options.responder;\n\tif (!responder) {\n\t\treturn {\n\t\t\tblock: true,\n\t\t\treason: `${toolName} requires approval, and no responder is available in this session.`,\n\t\t};\n\t}\n\tconst ruleForCall = spec.ruleForCall(params as never);\n\tconst answer = await responder.ask({\n\t\ttoolName,\n\t\tdescription: ruleForCall !== null ? spec.describe(ruleForCall) : `Run ${toolName}`,\n\t});\n\tif (!answer.allow) {\n\t\treturn { block: true, reason: `${toolName} was declined.` };\n\t}\n\tif (answer.persist && ruleForCall !== null) {\n\t\tawait options.store.apply({\n\t\t\ttype: \"addRules\",\n\t\t\tdestination: \"session\",\n\t\t\trules: [{ toolName, behavior: \"allow\", ruleContent: ruleForCall }],\n\t\t});\n\t}\n\treturn { block: false };\n}\n\n/** Adapts evaluateToolCall() to the beforeToolCall seam agent-core already exposes. */\nexport function createPermissionGate(\n\toptions: PermissionGateOptions,\n): (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined> {\n\treturn async ({ toolCall, args }) => {\n\t\tconst decision = await evaluateToolCall(toolCall.name, args, options);\n\t\treturn decision.block ? { block: true, reason: decision.reason } : undefined;\n\t};\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission modes (roadmap Phase 2a). A mode is a resolution layer applied on top
|
|
3
|
+
* of `resolvePermission()`'s rule-based result, driven entirely by a tool's
|
|
4
|
+
* declared `capabilities` — never by tool name, so a mode behaves correctly for a
|
|
5
|
+
* tool that does not exist yet.
|
|
6
|
+
*
|
|
7
|
+
* `plan` and `bypassPermissions` are absolute: they override even an explicit
|
|
8
|
+
* matching rule, because that is their entire purpose (a hard safety floor, and an
|
|
9
|
+
* explicit "trust everything" escape hatch, respectively). `acceptEdits` and
|
|
10
|
+
* `dontAsk` are conveniences that only ever act on the *absence* of an explicit
|
|
11
|
+
* rule — they change what a bare tool default resolves to, but never overrule a
|
|
12
|
+
* rule the user actually set.
|
|
13
|
+
*/
|
|
14
|
+
import type { Capability } from "../tools/contract.ts";
|
|
15
|
+
import type { PermissionResolution } from "./rules.ts";
|
|
16
|
+
import type { PermissionMode } from "./store.ts";
|
|
17
|
+
export declare function resolveWithMode(mode: PermissionMode, ruleResolution: PermissionResolution, capabilities: ReadonlySet<Capability>): PermissionResolution;
|
|
18
|
+
//# sourceMappingURL=modes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modes.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/modes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAyBjD,wBAAgB,eAAe,CAC9B,IAAI,EAAE,cAAc,EACpB,cAAc,EAAE,oBAAoB,EACpC,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,GACnC,oBAAoB,CAwBtB","sourcesContent":["/**\n * Permission modes (roadmap Phase 2a). A mode is a resolution layer applied on top\n * of `resolvePermission()`'s rule-based result, driven entirely by a tool's\n * declared `capabilities` — never by tool name, so a mode behaves correctly for a\n * tool that does not exist yet.\n *\n * `plan` and `bypassPermissions` are absolute: they override even an explicit\n * matching rule, because that is their entire purpose (a hard safety floor, and an\n * explicit \"trust everything\" escape hatch, respectively). `acceptEdits` and\n * `dontAsk` are conveniences that only ever act on the *absence* of an explicit\n * rule — they change what a bare tool default resolves to, but never overrule a\n * rule the user actually set.\n */\n\nimport type { Capability } from \"../tools/contract.ts\";\nimport type { PermissionResolution } from \"./rules.ts\";\nimport type { PermissionMode } from \"./store.ts\";\n\n/**\n * Plan mode's hard floor. `state` is deliberately excluded: an explicit harness-state\n * tool such as `todo_write` is how plan mode maintains the plan it is presenting, so\n * denying it outright would make plan mode unable to do its own job (spec\n * `2026-08-13-tool-surface.md`, \"The problem\" item 4).\n */\nconst PLAN_MODE_DENIED_CAPABILITIES: readonly Capability[] = [\"fs.write\", \"exec\", \"delegate\"];\n\nfunction isPlanModeDenied(capabilities: ReadonlySet<Capability>): boolean {\n\treturn PLAN_MODE_DENIED_CAPABILITIES.some((capability) => capabilities.has(capability));\n}\n\n/** fs.write only, with no exec/net/delegate/state — the shape acceptEdits auto-allows. */\nfunction isEditShaped(capabilities: ReadonlySet<Capability>): boolean {\n\treturn (\n\t\tcapabilities.has(\"fs.write\") &&\n\t\t!capabilities.has(\"exec\") &&\n\t\t!capabilities.has(\"net\") &&\n\t\t!capabilities.has(\"delegate\") &&\n\t\t!capabilities.has(\"state\")\n\t);\n}\n\nexport function resolveWithMode(\n\tmode: PermissionMode,\n\truleResolution: PermissionResolution,\n\tcapabilities: ReadonlySet<Capability>,\n): PermissionResolution {\n\t// Plan mode is a hard mutating safety floor. Managed policy is otherwise\n\t// explicitly non-overridable (ADR 0004); bypass remains an escape hatch only\n\t// for lower-precedence sources.\n\tif (mode === \"plan\" && isPlanModeDenied(capabilities)) {\n\t\treturn { behavior: \"deny\" };\n\t}\n\tif (ruleResolution.rule?.source === \"policy\") {\n\t\treturn ruleResolution;\n\t}\n\tif (mode === \"bypassPermissions\") {\n\t\treturn { behavior: \"allow\" };\n\t}\n\tif (ruleResolution.rule) {\n\t\t// An explicit matching rule is authoritative for every remaining mode.\n\t\treturn ruleResolution;\n\t}\n\tif (mode === \"dontAsk\" && ruleResolution.behavior === \"ask\") {\n\t\treturn { behavior: \"deny\" };\n\t}\n\tif (mode === \"acceptEdits\" && ruleResolution.behavior === \"ask\" && isEditShaped(capabilities)) {\n\t\treturn { behavior: \"allow\" };\n\t}\n\treturn ruleResolution;\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission modes (roadmap Phase 2a). A mode is a resolution layer applied on top
|
|
3
|
+
* of `resolvePermission()`'s rule-based result, driven entirely by a tool's
|
|
4
|
+
* declared `capabilities` — never by tool name, so a mode behaves correctly for a
|
|
5
|
+
* tool that does not exist yet.
|
|
6
|
+
*
|
|
7
|
+
* `plan` and `bypassPermissions` are absolute: they override even an explicit
|
|
8
|
+
* matching rule, because that is their entire purpose (a hard safety floor, and an
|
|
9
|
+
* explicit "trust everything" escape hatch, respectively). `acceptEdits` and
|
|
10
|
+
* `dontAsk` are conveniences that only ever act on the *absence* of an explicit
|
|
11
|
+
* rule — they change what a bare tool default resolves to, but never overrule a
|
|
12
|
+
* rule the user actually set.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Plan mode's hard floor. `state` is deliberately excluded: an explicit harness-state
|
|
16
|
+
* tool such as `todo_write` is how plan mode maintains the plan it is presenting, so
|
|
17
|
+
* denying it outright would make plan mode unable to do its own job (spec
|
|
18
|
+
* `2026-08-13-tool-surface.md`, "The problem" item 4).
|
|
19
|
+
*/
|
|
20
|
+
const PLAN_MODE_DENIED_CAPABILITIES = ["fs.write", "exec", "delegate"];
|
|
21
|
+
function isPlanModeDenied(capabilities) {
|
|
22
|
+
return PLAN_MODE_DENIED_CAPABILITIES.some((capability) => capabilities.has(capability));
|
|
23
|
+
}
|
|
24
|
+
/** fs.write only, with no exec/net/delegate/state — the shape acceptEdits auto-allows. */
|
|
25
|
+
function isEditShaped(capabilities) {
|
|
26
|
+
return (capabilities.has("fs.write") &&
|
|
27
|
+
!capabilities.has("exec") &&
|
|
28
|
+
!capabilities.has("net") &&
|
|
29
|
+
!capabilities.has("delegate") &&
|
|
30
|
+
!capabilities.has("state"));
|
|
31
|
+
}
|
|
32
|
+
export function resolveWithMode(mode, ruleResolution, capabilities) {
|
|
33
|
+
// Plan mode is a hard mutating safety floor. Managed policy is otherwise
|
|
34
|
+
// explicitly non-overridable (ADR 0004); bypass remains an escape hatch only
|
|
35
|
+
// for lower-precedence sources.
|
|
36
|
+
if (mode === "plan" && isPlanModeDenied(capabilities)) {
|
|
37
|
+
return { behavior: "deny" };
|
|
38
|
+
}
|
|
39
|
+
if (ruleResolution.rule?.source === "policy") {
|
|
40
|
+
return ruleResolution;
|
|
41
|
+
}
|
|
42
|
+
if (mode === "bypassPermissions") {
|
|
43
|
+
return { behavior: "allow" };
|
|
44
|
+
}
|
|
45
|
+
if (ruleResolution.rule) {
|
|
46
|
+
// An explicit matching rule is authoritative for every remaining mode.
|
|
47
|
+
return ruleResolution;
|
|
48
|
+
}
|
|
49
|
+
if (mode === "dontAsk" && ruleResolution.behavior === "ask") {
|
|
50
|
+
return { behavior: "deny" };
|
|
51
|
+
}
|
|
52
|
+
if (mode === "acceptEdits" && ruleResolution.behavior === "ask" && isEditShaped(capabilities)) {
|
|
53
|
+
return { behavior: "allow" };
|
|
54
|
+
}
|
|
55
|
+
return ruleResolution;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=modes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modes.js","sourceRoot":"","sources":["../../../src/core/permissions/modes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH;;;;;GAKG;AACH,MAAM,6BAA6B,GAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;AAE9F,SAAS,gBAAgB,CAAC,YAAqC,EAAW;IACzE,OAAO,6BAA6B,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;AAAA,CACxF;AAED,4FAA0F;AAC1F,SAAS,YAAY,CAAC,YAAqC,EAAW;IACrE,OAAO,CACN,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC;QAC5B,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;QACzB,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;QACxB,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC;QAC7B,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAC1B,CAAC;AAAA,CACF;AAED,MAAM,UAAU,eAAe,CAC9B,IAAoB,EACpB,cAAoC,EACpC,YAAqC,EACd;IACvB,yEAAyE;IACzE,6EAA6E;IAC7E,gCAAgC;IAChC,IAAI,IAAI,KAAK,MAAM,IAAI,gBAAgB,CAAC,YAAY,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC7B,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC9C,OAAO,cAAc,CAAC;IACvB,CAAC;IACD,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;QAClC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC9B,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,EAAE,CAAC;QACzB,uEAAuE;QACvE,OAAO,cAAc,CAAC;IACvB,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,IAAI,cAAc,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC7B,CAAC;IACD,IAAI,IAAI,KAAK,aAAa,IAAI,cAAc,CAAC,QAAQ,KAAK,KAAK,IAAI,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC;QAC/F,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC9B,CAAC;IACD,OAAO,cAAc,CAAC;AAAA,CACtB","sourcesContent":["/**\n * Permission modes (roadmap Phase 2a). A mode is a resolution layer applied on top\n * of `resolvePermission()`'s rule-based result, driven entirely by a tool's\n * declared `capabilities` — never by tool name, so a mode behaves correctly for a\n * tool that does not exist yet.\n *\n * `plan` and `bypassPermissions` are absolute: they override even an explicit\n * matching rule, because that is their entire purpose (a hard safety floor, and an\n * explicit \"trust everything\" escape hatch, respectively). `acceptEdits` and\n * `dontAsk` are conveniences that only ever act on the *absence* of an explicit\n * rule — they change what a bare tool default resolves to, but never overrule a\n * rule the user actually set.\n */\n\nimport type { Capability } from \"../tools/contract.ts\";\nimport type { PermissionResolution } from \"./rules.ts\";\nimport type { PermissionMode } from \"./store.ts\";\n\n/**\n * Plan mode's hard floor. `state` is deliberately excluded: an explicit harness-state\n * tool such as `todo_write` is how plan mode maintains the plan it is presenting, so\n * denying it outright would make plan mode unable to do its own job (spec\n * `2026-08-13-tool-surface.md`, \"The problem\" item 4).\n */\nconst PLAN_MODE_DENIED_CAPABILITIES: readonly Capability[] = [\"fs.write\", \"exec\", \"delegate\"];\n\nfunction isPlanModeDenied(capabilities: ReadonlySet<Capability>): boolean {\n\treturn PLAN_MODE_DENIED_CAPABILITIES.some((capability) => capabilities.has(capability));\n}\n\n/** fs.write only, with no exec/net/delegate/state — the shape acceptEdits auto-allows. */\nfunction isEditShaped(capabilities: ReadonlySet<Capability>): boolean {\n\treturn (\n\t\tcapabilities.has(\"fs.write\") &&\n\t\t!capabilities.has(\"exec\") &&\n\t\t!capabilities.has(\"net\") &&\n\t\t!capabilities.has(\"delegate\") &&\n\t\t!capabilities.has(\"state\")\n\t);\n}\n\nexport function resolveWithMode(\n\tmode: PermissionMode,\n\truleResolution: PermissionResolution,\n\tcapabilities: ReadonlySet<Capability>,\n): PermissionResolution {\n\t// Plan mode is a hard mutating safety floor. Managed policy is otherwise\n\t// explicitly non-overridable (ADR 0004); bypass remains an escape hatch only\n\t// for lower-precedence sources.\n\tif (mode === \"plan\" && isPlanModeDenied(capabilities)) {\n\t\treturn { behavior: \"deny\" };\n\t}\n\tif (ruleResolution.rule?.source === \"policy\") {\n\t\treturn ruleResolution;\n\t}\n\tif (mode === \"bypassPermissions\") {\n\t\treturn { behavior: \"allow\" };\n\t}\n\tif (ruleResolution.rule) {\n\t\t// An explicit matching rule is authoritative for every remaining mode.\n\t\treturn ruleResolution;\n\t}\n\tif (mode === \"dontAsk\" && ruleResolution.behavior === \"ask\") {\n\t\treturn { behavior: \"deny\" };\n\t}\n\tif (mode === \"acceptEdits\" && ruleResolution.behavior === \"ask\" && isEditShaped(capabilities)) {\n\t\treturn { behavior: \"allow\" };\n\t}\n\treturn ruleResolution;\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Interactive escalation for an `ask`-resolved permission decision. */
|
|
2
|
+
export interface PermissionAskRequest {
|
|
3
|
+
toolName: string;
|
|
4
|
+
/** Human-readable rendering of what would be persisted, from the tool's own describe(). */
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PermissionAnswer {
|
|
8
|
+
allow: boolean;
|
|
9
|
+
/** "Always allow this" — persist a session-source rule via the tool's own ruleForCall(). */
|
|
10
|
+
persist?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface PermissionResponder {
|
|
13
|
+
ask(request: PermissionAskRequest): Promise<PermissionAnswer>;
|
|
14
|
+
}
|
|
15
|
+
/** The subset of ExtensionUIContext (core/extensions/types.ts) this responder needs. */
|
|
16
|
+
export interface SelectUI {
|
|
17
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Builds a PermissionResponder over the same live, mid-session prompt primitive
|
|
21
|
+
* extensions already use (ExtensionUIContext.select) — no new rendering
|
|
22
|
+
* dependency, per the roadmap's "Explicitly not building" a second TUI stack.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately answers only `{allow, persist}`. It never constructs a rule string
|
|
25
|
+
* itself: generating one from `ruleForCall()` and persisting it is the gate's job
|
|
26
|
+
* (permissions/gate.ts), so the tool that owns the grammar is the only thing that
|
|
27
|
+
* ever writes a rule (ADR 0010).
|
|
28
|
+
*/
|
|
29
|
+
export declare function createInteractiveResponder(ui: SelectUI): PermissionResponder;
|
|
30
|
+
//# sourceMappingURL=responder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responder.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/responder.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAExE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,8FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IACnC,GAAG,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC9D;AAMD,wFAAwF;AACxF,MAAM,WAAW,QAAQ;IACxB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACtE;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,QAAQ,GAAG,mBAAmB,CAc5E","sourcesContent":["/** Interactive escalation for an `ask`-resolved permission decision. */\n\nexport interface PermissionAskRequest {\n\ttoolName: string;\n\t/** Human-readable rendering of what would be persisted, from the tool's own describe(). */\n\tdescription: string;\n}\n\nexport interface PermissionAnswer {\n\tallow: boolean;\n\t/** \"Always allow this\" — persist a session-source rule via the tool's own ruleForCall(). */\n\tpersist?: boolean;\n}\n\nexport interface PermissionResponder {\n\task(request: PermissionAskRequest): Promise<PermissionAnswer>;\n}\n\nconst ALLOW_ONCE = \"Allow once\";\nconst ALWAYS_ALLOW = \"Always allow\";\nconst DENY = \"Deny\";\n\n/** The subset of ExtensionUIContext (core/extensions/types.ts) this responder needs. */\nexport interface SelectUI {\n\tselect(title: string, options: string[]): Promise<string | undefined>;\n}\n\n/**\n * Builds a PermissionResponder over the same live, mid-session prompt primitive\n * extensions already use (ExtensionUIContext.select) — no new rendering\n * dependency, per the roadmap's \"Explicitly not building\" a second TUI stack.\n *\n * Deliberately answers only `{allow, persist}`. It never constructs a rule string\n * itself: generating one from `ruleForCall()` and persisting it is the gate's job\n * (permissions/gate.ts), so the tool that owns the grammar is the only thing that\n * ever writes a rule (ADR 0010).\n */\nexport function createInteractiveResponder(ui: SelectUI): PermissionResponder {\n\treturn {\n\t\tasync ask({ toolName, description }) {\n\t\t\tconst choice = await ui.select(`Permission required — ${toolName}: ${description}`, [\n\t\t\t\tALLOW_ONCE,\n\t\t\t\tALWAYS_ALLOW,\n\t\t\t\tDENY,\n\t\t\t]);\n\t\t\tif (choice === ALLOW_ONCE) return { allow: true };\n\t\t\tif (choice === ALWAYS_ALLOW) return { allow: true, persist: true };\n\t\t\t// DENY, or no selection (cancelled/dismissed) — fail closed.\n\t\t\treturn { allow: false };\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Interactive escalation for an `ask`-resolved permission decision. */
|
|
2
|
+
const ALLOW_ONCE = "Allow once";
|
|
3
|
+
const ALWAYS_ALLOW = "Always allow";
|
|
4
|
+
const DENY = "Deny";
|
|
5
|
+
/**
|
|
6
|
+
* Builds a PermissionResponder over the same live, mid-session prompt primitive
|
|
7
|
+
* extensions already use (ExtensionUIContext.select) — no new rendering
|
|
8
|
+
* dependency, per the roadmap's "Explicitly not building" a second TUI stack.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately answers only `{allow, persist}`. It never constructs a rule string
|
|
11
|
+
* itself: generating one from `ruleForCall()` and persisting it is the gate's job
|
|
12
|
+
* (permissions/gate.ts), so the tool that owns the grammar is the only thing that
|
|
13
|
+
* ever writes a rule (ADR 0010).
|
|
14
|
+
*/
|
|
15
|
+
export function createInteractiveResponder(ui) {
|
|
16
|
+
return {
|
|
17
|
+
async ask({ toolName, description }) {
|
|
18
|
+
const choice = await ui.select(`Permission required — ${toolName}: ${description}`, [
|
|
19
|
+
ALLOW_ONCE,
|
|
20
|
+
ALWAYS_ALLOW,
|
|
21
|
+
DENY,
|
|
22
|
+
]);
|
|
23
|
+
if (choice === ALLOW_ONCE)
|
|
24
|
+
return { allow: true };
|
|
25
|
+
if (choice === ALWAYS_ALLOW)
|
|
26
|
+
return { allow: true, persist: true };
|
|
27
|
+
// DENY, or no selection (cancelled/dismissed) — fail closed.
|
|
28
|
+
return { allow: false };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=responder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responder.js","sourceRoot":"","sources":["../../../src/core/permissions/responder.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAkBxE,MAAM,UAAU,GAAG,YAAY,CAAC;AAChC,MAAM,YAAY,GAAG,cAAc,CAAC;AACpC,MAAM,IAAI,GAAG,MAAM,CAAC;AAOpB;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,EAAY,EAAuB;IAC7E,OAAO;QACN,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE;YACpC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,2BAAyB,QAAQ,KAAK,WAAW,EAAE,EAAE;gBACnF,UAAU;gBACV,YAAY;gBACZ,IAAI;aACJ,CAAC,CAAC;YACH,IAAI,MAAM,KAAK,UAAU;gBAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YAClD,IAAI,MAAM,KAAK,YAAY;gBAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YACnE,+DAA6D;YAC7D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAAA,CACxB;KACD,CAAC;AAAA,CACF","sourcesContent":["/** Interactive escalation for an `ask`-resolved permission decision. */\n\nexport interface PermissionAskRequest {\n\ttoolName: string;\n\t/** Human-readable rendering of what would be persisted, from the tool's own describe(). */\n\tdescription: string;\n}\n\nexport interface PermissionAnswer {\n\tallow: boolean;\n\t/** \"Always allow this\" — persist a session-source rule via the tool's own ruleForCall(). */\n\tpersist?: boolean;\n}\n\nexport interface PermissionResponder {\n\task(request: PermissionAskRequest): Promise<PermissionAnswer>;\n}\n\nconst ALLOW_ONCE = \"Allow once\";\nconst ALWAYS_ALLOW = \"Always allow\";\nconst DENY = \"Deny\";\n\n/** The subset of ExtensionUIContext (core/extensions/types.ts) this responder needs. */\nexport interface SelectUI {\n\tselect(title: string, options: string[]): Promise<string | undefined>;\n}\n\n/**\n * Builds a PermissionResponder over the same live, mid-session prompt primitive\n * extensions already use (ExtensionUIContext.select) — no new rendering\n * dependency, per the roadmap's \"Explicitly not building\" a second TUI stack.\n *\n * Deliberately answers only `{allow, persist}`. It never constructs a rule string\n * itself: generating one from `ruleForCall()` and persisting it is the gate's job\n * (permissions/gate.ts), so the tool that owns the grammar is the only thing that\n * ever writes a rule (ADR 0010).\n */\nexport function createInteractiveResponder(ui: SelectUI): PermissionResponder {\n\treturn {\n\t\tasync ask({ toolName, description }) {\n\t\t\tconst choice = await ui.select(`Permission required — ${toolName}: ${description}`, [\n\t\t\t\tALLOW_ONCE,\n\t\t\t\tALWAYS_ALLOW,\n\t\t\t\tDENY,\n\t\t\t]);\n\t\t\tif (choice === ALLOW_ONCE) return { allow: true };\n\t\t\tif (choice === ALWAYS_ALLOW) return { allow: true, persist: true };\n\t\t\t// DENY, or no selection (cancelled/dismissed) — fail closed.\n\t\t\treturn { allow: false };\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission rule model (ADR 0004). Pure and tool-agnostic: this module never
|
|
3
|
+
* inspects a `ruleContent` grammar itself — it only calls the tool's own
|
|
4
|
+
* `PermissionSpec.matches()`, per ADR 0010's rule that a tool owns both directions
|
|
5
|
+
* of its grammar and the engine never accumulates tool-specific matching.
|
|
6
|
+
*/
|
|
7
|
+
import type { Static, TSchema } from "typebox";
|
|
8
|
+
import type { PermissionBehavior, PermissionSpec } from "../tools/contract.ts";
|
|
9
|
+
/**
|
|
10
|
+
* Highest to lowest precedence. `flag` outranks config because it is a deliberate
|
|
11
|
+
* per-run operator override; `cliArg` sits below config because it is a per-run
|
|
12
|
+
* *default*, not an override. See ADR 0004 for why each source sits where it does.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PERMISSION_SOURCES: readonly ["policy", "flag", "local", "project", "user", "cliArg", "command", "session"];
|
|
15
|
+
export type PermissionSource = (typeof PERMISSION_SOURCES)[number];
|
|
16
|
+
export interface PermissionRule {
|
|
17
|
+
source: PermissionSource;
|
|
18
|
+
behavior: PermissionBehavior;
|
|
19
|
+
toolName: string;
|
|
20
|
+
/** Undefined matches every call to this tool — a tool-wide rule. */
|
|
21
|
+
ruleContent?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface PermissionResolution {
|
|
24
|
+
behavior: PermissionBehavior;
|
|
25
|
+
/** The winning rule, or undefined when no rule matched and the tool's own default applied. */
|
|
26
|
+
rule?: PermissionRule;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the effective behavior for one call. The highest-precedence *matching*
|
|
30
|
+
* rule wins, whatever its behavior — `deny` does not automatically outrank `allow`.
|
|
31
|
+
* With no matching rule, the tool's own `defaultBehavior` applies.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolvePermission<TParams extends TSchema>(rules: readonly PermissionRule[], toolName: string, spec: PermissionSpec<TParams>, params: Static<TParams>): PermissionResolution;
|
|
34
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/rules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,yFASrB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,8FAA8F;IAC9F,IAAI,CAAC,EAAE,cAAc,CAAC;CACtB;AA8BD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,OAAO,EACxD,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,EAC7B,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,GACrB,oBAAoB,CAkBtB","sourcesContent":["/**\n * The permission rule model (ADR 0004). Pure and tool-agnostic: this module never\n * inspects a `ruleContent` grammar itself — it only calls the tool's own\n * `PermissionSpec.matches()`, per ADR 0010's rule that a tool owns both directions\n * of its grammar and the engine never accumulates tool-specific matching.\n */\n\nimport type { Static, TSchema } from \"typebox\";\nimport type { PermissionBehavior, PermissionSpec } from \"../tools/contract.ts\";\n\n/**\n * Highest to lowest precedence. `flag` outranks config because it is a deliberate\n * per-run operator override; `cliArg` sits below config because it is a per-run\n * *default*, not an override. See ADR 0004 for why each source sits where it does.\n */\nexport const PERMISSION_SOURCES = [\n\t\"policy\",\n\t\"flag\",\n\t\"local\",\n\t\"project\",\n\t\"user\",\n\t\"cliArg\",\n\t\"command\",\n\t\"session\",\n] as const;\n\nexport type PermissionSource = (typeof PERMISSION_SOURCES)[number];\n\nexport interface PermissionRule {\n\tsource: PermissionSource;\n\tbehavior: PermissionBehavior;\n\ttoolName: string;\n\t/** Undefined matches every call to this tool — a tool-wide rule. */\n\truleContent?: string;\n}\n\nexport interface PermissionResolution {\n\tbehavior: PermissionBehavior;\n\t/** The winning rule, or undefined when no rule matched and the tool's own default applied. */\n\trule?: PermissionRule;\n}\n\nconst SOURCE_RANK = new Map<PermissionSource, number>(PERMISSION_SOURCES.map((source, index) => [source, index]));\n\nfunction ruleMatchesCall<TParams extends TSchema>(\n\trule: PermissionRule,\n\tspec: PermissionSpec<TParams>,\n\tparams: Static<TParams>,\n): boolean {\n\tif (rule.ruleContent === undefined) return true;\n\treturn spec.matches(rule.ruleContent, params);\n}\n\n/**\n * Deterministic tie-break for two matching rules at the *same* source, independent\n * of array order: a rule with `ruleContent` (specific) outranks a tool-wide rule\n * with none (blanket); between two specific rules, the longer `ruleContent` wins\n * (a proxy for \"more specific\"); a final lexical comparison guarantees a total,\n * order-independent order for the residual case of equal-length content.\n */\nfunction compareSameSourceRules(a: PermissionRule, b: PermissionRule): number {\n\tconst aSpecific = a.ruleContent !== undefined;\n\tconst bSpecific = b.ruleContent !== undefined;\n\tif (aSpecific !== bSpecific) return aSpecific ? -1 : 1; // specific wins (negative = a first)\n\tif (!aSpecific) return 0; // both blanket at the same source: no further distinction needed\n\tconst lengthDelta = (b.ruleContent as string).length - (a.ruleContent as string).length;\n\tif (lengthDelta !== 0) return lengthDelta;\n\treturn (a.ruleContent as string).localeCompare(b.ruleContent as string);\n}\n\n/**\n * Resolve the effective behavior for one call. The highest-precedence *matching*\n * rule wins, whatever its behavior — `deny` does not automatically outrank `allow`.\n * With no matching rule, the tool's own `defaultBehavior` applies.\n */\nexport function resolvePermission<TParams extends TSchema>(\n\trules: readonly PermissionRule[],\n\ttoolName: string,\n\tspec: PermissionSpec<TParams>,\n\tparams: Static<TParams>,\n): PermissionResolution {\n\tlet winner: PermissionRule | undefined;\n\tfor (const candidate of rules) {\n\t\tif (candidate.toolName !== toolName) continue;\n\t\tif (!ruleMatchesCall(candidate, spec, params)) continue;\n\t\tif (!winner) {\n\t\t\twinner = candidate;\n\t\t\tcontinue;\n\t\t}\n\t\tconst candidateRank = SOURCE_RANK.get(candidate.source) ?? Number.POSITIVE_INFINITY;\n\t\tconst winnerRank = SOURCE_RANK.get(winner.source) ?? Number.POSITIVE_INFINITY;\n\t\tif (candidateRank < winnerRank) {\n\t\t\twinner = candidate;\n\t\t} else if (candidateRank === winnerRank && compareSameSourceRules(candidate, winner) < 0) {\n\t\t\twinner = candidate;\n\t\t}\n\t}\n\treturn winner ? { behavior: winner.behavior, rule: winner } : { behavior: spec.defaultBehavior };\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The permission rule model (ADR 0004). Pure and tool-agnostic: this module never
|
|
3
|
+
* inspects a `ruleContent` grammar itself — it only calls the tool's own
|
|
4
|
+
* `PermissionSpec.matches()`, per ADR 0010's rule that a tool owns both directions
|
|
5
|
+
* of its grammar and the engine never accumulates tool-specific matching.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Highest to lowest precedence. `flag` outranks config because it is a deliberate
|
|
9
|
+
* per-run operator override; `cliArg` sits below config because it is a per-run
|
|
10
|
+
* *default*, not an override. See ADR 0004 for why each source sits where it does.
|
|
11
|
+
*/
|
|
12
|
+
export const PERMISSION_SOURCES = [
|
|
13
|
+
"policy",
|
|
14
|
+
"flag",
|
|
15
|
+
"local",
|
|
16
|
+
"project",
|
|
17
|
+
"user",
|
|
18
|
+
"cliArg",
|
|
19
|
+
"command",
|
|
20
|
+
"session",
|
|
21
|
+
];
|
|
22
|
+
const SOURCE_RANK = new Map(PERMISSION_SOURCES.map((source, index) => [source, index]));
|
|
23
|
+
function ruleMatchesCall(rule, spec, params) {
|
|
24
|
+
if (rule.ruleContent === undefined)
|
|
25
|
+
return true;
|
|
26
|
+
return spec.matches(rule.ruleContent, params);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Deterministic tie-break for two matching rules at the *same* source, independent
|
|
30
|
+
* of array order: a rule with `ruleContent` (specific) outranks a tool-wide rule
|
|
31
|
+
* with none (blanket); between two specific rules, the longer `ruleContent` wins
|
|
32
|
+
* (a proxy for "more specific"); a final lexical comparison guarantees a total,
|
|
33
|
+
* order-independent order for the residual case of equal-length content.
|
|
34
|
+
*/
|
|
35
|
+
function compareSameSourceRules(a, b) {
|
|
36
|
+
const aSpecific = a.ruleContent !== undefined;
|
|
37
|
+
const bSpecific = b.ruleContent !== undefined;
|
|
38
|
+
if (aSpecific !== bSpecific)
|
|
39
|
+
return aSpecific ? -1 : 1; // specific wins (negative = a first)
|
|
40
|
+
if (!aSpecific)
|
|
41
|
+
return 0; // both blanket at the same source: no further distinction needed
|
|
42
|
+
const lengthDelta = b.ruleContent.length - a.ruleContent.length;
|
|
43
|
+
if (lengthDelta !== 0)
|
|
44
|
+
return lengthDelta;
|
|
45
|
+
return a.ruleContent.localeCompare(b.ruleContent);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the effective behavior for one call. The highest-precedence *matching*
|
|
49
|
+
* rule wins, whatever its behavior — `deny` does not automatically outrank `allow`.
|
|
50
|
+
* With no matching rule, the tool's own `defaultBehavior` applies.
|
|
51
|
+
*/
|
|
52
|
+
export function resolvePermission(rules, toolName, spec, params) {
|
|
53
|
+
let winner;
|
|
54
|
+
for (const candidate of rules) {
|
|
55
|
+
if (candidate.toolName !== toolName)
|
|
56
|
+
continue;
|
|
57
|
+
if (!ruleMatchesCall(candidate, spec, params))
|
|
58
|
+
continue;
|
|
59
|
+
if (!winner) {
|
|
60
|
+
winner = candidate;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const candidateRank = SOURCE_RANK.get(candidate.source) ?? Number.POSITIVE_INFINITY;
|
|
64
|
+
const winnerRank = SOURCE_RANK.get(winner.source) ?? Number.POSITIVE_INFINITY;
|
|
65
|
+
if (candidateRank < winnerRank) {
|
|
66
|
+
winner = candidate;
|
|
67
|
+
}
|
|
68
|
+
else if (candidateRank === winnerRank && compareSameSourceRules(candidate, winner) < 0) {
|
|
69
|
+
winner = candidate;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return winner ? { behavior: winner.behavior, rule: winner } : { behavior: spec.defaultBehavior };
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/core/permissions/rules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IACjC,QAAQ;IACR,MAAM;IACN,OAAO;IACP,SAAS;IACT,MAAM;IACN,QAAQ;IACR,SAAS;IACT,SAAS;CACA,CAAC;AAkBX,MAAM,WAAW,GAAG,IAAI,GAAG,CAA2B,kBAAkB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AAElH,SAAS,eAAe,CACvB,IAAoB,EACpB,IAA6B,EAC7B,MAAuB,EACb;IACV,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAChD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AAAA,CAC9C;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,CAAiB,EAAE,CAAiB,EAAU;IAC7E,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC;IAC9C,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC;IAC9C,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,qCAAqC;IAC7F,IAAI,CAAC,SAAS;QAAE,OAAO,CAAC,CAAC,CAAC,iEAAiE;IAC3F,MAAM,WAAW,GAAI,CAAC,CAAC,WAAsB,CAAC,MAAM,GAAI,CAAC,CAAC,WAAsB,CAAC,MAAM,CAAC;IACxF,IAAI,WAAW,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC;IAC1C,OAAQ,CAAC,CAAC,WAAsB,CAAC,aAAa,CAAC,CAAC,CAAC,WAAqB,CAAC,CAAC;AAAA,CACxE;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAChC,KAAgC,EAChC,QAAgB,EAChB,IAA6B,EAC7B,MAAuB,EACA;IACvB,IAAI,MAAkC,CAAC;IACvC,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,SAAS,CAAC,QAAQ,KAAK,QAAQ;YAAE,SAAS;QAC9C,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC;YAAE,SAAS;QACxD,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,GAAG,SAAS,CAAC;YACnB,SAAS;QACV,CAAC;QACD,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,iBAAiB,CAAC;QACpF,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,iBAAiB,CAAC;QAC9E,IAAI,aAAa,GAAG,UAAU,EAAE,CAAC;YAChC,MAAM,GAAG,SAAS,CAAC;QACpB,CAAC;aAAM,IAAI,aAAa,KAAK,UAAU,IAAI,sBAAsB,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1F,MAAM,GAAG,SAAS,CAAC;QACpB,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC;AAAA,CACjG","sourcesContent":["/**\n * The permission rule model (ADR 0004). Pure and tool-agnostic: this module never\n * inspects a `ruleContent` grammar itself — it only calls the tool's own\n * `PermissionSpec.matches()`, per ADR 0010's rule that a tool owns both directions\n * of its grammar and the engine never accumulates tool-specific matching.\n */\n\nimport type { Static, TSchema } from \"typebox\";\nimport type { PermissionBehavior, PermissionSpec } from \"../tools/contract.ts\";\n\n/**\n * Highest to lowest precedence. `flag` outranks config because it is a deliberate\n * per-run operator override; `cliArg` sits below config because it is a per-run\n * *default*, not an override. See ADR 0004 for why each source sits where it does.\n */\nexport const PERMISSION_SOURCES = [\n\t\"policy\",\n\t\"flag\",\n\t\"local\",\n\t\"project\",\n\t\"user\",\n\t\"cliArg\",\n\t\"command\",\n\t\"session\",\n] as const;\n\nexport type PermissionSource = (typeof PERMISSION_SOURCES)[number];\n\nexport interface PermissionRule {\n\tsource: PermissionSource;\n\tbehavior: PermissionBehavior;\n\ttoolName: string;\n\t/** Undefined matches every call to this tool — a tool-wide rule. */\n\truleContent?: string;\n}\n\nexport interface PermissionResolution {\n\tbehavior: PermissionBehavior;\n\t/** The winning rule, or undefined when no rule matched and the tool's own default applied. */\n\trule?: PermissionRule;\n}\n\nconst SOURCE_RANK = new Map<PermissionSource, number>(PERMISSION_SOURCES.map((source, index) => [source, index]));\n\nfunction ruleMatchesCall<TParams extends TSchema>(\n\trule: PermissionRule,\n\tspec: PermissionSpec<TParams>,\n\tparams: Static<TParams>,\n): boolean {\n\tif (rule.ruleContent === undefined) return true;\n\treturn spec.matches(rule.ruleContent, params);\n}\n\n/**\n * Deterministic tie-break for two matching rules at the *same* source, independent\n * of array order: a rule with `ruleContent` (specific) outranks a tool-wide rule\n * with none (blanket); between two specific rules, the longer `ruleContent` wins\n * (a proxy for \"more specific\"); a final lexical comparison guarantees a total,\n * order-independent order for the residual case of equal-length content.\n */\nfunction compareSameSourceRules(a: PermissionRule, b: PermissionRule): number {\n\tconst aSpecific = a.ruleContent !== undefined;\n\tconst bSpecific = b.ruleContent !== undefined;\n\tif (aSpecific !== bSpecific) return aSpecific ? -1 : 1; // specific wins (negative = a first)\n\tif (!aSpecific) return 0; // both blanket at the same source: no further distinction needed\n\tconst lengthDelta = (b.ruleContent as string).length - (a.ruleContent as string).length;\n\tif (lengthDelta !== 0) return lengthDelta;\n\treturn (a.ruleContent as string).localeCompare(b.ruleContent as string);\n}\n\n/**\n * Resolve the effective behavior for one call. The highest-precedence *matching*\n * rule wins, whatever its behavior — `deny` does not automatically outrank `allow`.\n * With no matching rule, the tool's own `defaultBehavior` applies.\n */\nexport function resolvePermission<TParams extends TSchema>(\n\trules: readonly PermissionRule[],\n\ttoolName: string,\n\tspec: PermissionSpec<TParams>,\n\tparams: Static<TParams>,\n): PermissionResolution {\n\tlet winner: PermissionRule | undefined;\n\tfor (const candidate of rules) {\n\t\tif (candidate.toolName !== toolName) continue;\n\t\tif (!ruleMatchesCall(candidate, spec, params)) continue;\n\t\tif (!winner) {\n\t\t\twinner = candidate;\n\t\t\tcontinue;\n\t\t}\n\t\tconst candidateRank = SOURCE_RANK.get(candidate.source) ?? Number.POSITIVE_INFINITY;\n\t\tconst winnerRank = SOURCE_RANK.get(winner.source) ?? Number.POSITIVE_INFINITY;\n\t\tif (candidateRank < winnerRank) {\n\t\t\twinner = candidate;\n\t\t} else if (candidateRank === winnerRank && compareSameSourceRules(candidate, winner) < 0) {\n\t\t\twinner = candidate;\n\t\t}\n\t}\n\treturn winner ? { behavior: winner.behavior, rule: winner } : { behavior: spec.defaultBehavior };\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup-time permission mode resolution (ADR 0004). A misconfigured
|
|
3
|
+
* non-interactive run should fail fast, at startup, with an actionable message —
|
|
4
|
+
* not deny every tool call mid-run in a way that looks like a bug (see the ADR's
|
|
5
|
+
* "ask fails closed, and a session that cannot ask refuses to start").
|
|
6
|
+
*/
|
|
7
|
+
import { type PermissionMode, type WritablePermissionSource } from "./store.ts";
|
|
8
|
+
export type PermissionModeStartupResult = {
|
|
9
|
+
ok: true;
|
|
10
|
+
mode: PermissionMode;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* `requestedMode` is assumed already validated against `PERMISSION_MODES` (args.ts
|
|
17
|
+
* rejects an unknown value at parse time) — this only decides whether a session may
|
|
18
|
+
* start *without* one. Interactive sessions may: an `ask` there has a human to
|
|
19
|
+
* resolve it. Non-interactive sessions may not: with no responder, `ask` fails
|
|
20
|
+
* closed to `deny`, and discovering that per-call, mid-run, is the exact bad
|
|
21
|
+
* failure mode this check exists to avoid.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolvePermissionModeForStartup(options: {
|
|
24
|
+
interactive: boolean;
|
|
25
|
+
requestedMode: PermissionMode | undefined;
|
|
26
|
+
}): PermissionModeStartupResult;
|
|
27
|
+
/**
|
|
28
|
+
* The `--permission-mode` flag outranks every persisted mode, including a project
|
|
29
|
+
* rule a team committed — a deliberate per-run operator decision should not be
|
|
30
|
+
* silently overridden by checked-in config (ADR 0004).
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveEffectiveMode(flagMode: PermissionMode | undefined, modesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>): PermissionMode;
|
|
33
|
+
//# sourceMappingURL=startup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/startup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAElG,MAAM,MAAM,2BAA2B,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9G;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE;IACxD,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,cAAc,GAAG,SAAS,CAAC;CAC1C,GAAG,2BAA2B,CAW9B;AAYD;;;;GAIG;AACH,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,aAAa,EAAE,WAAW,CAAC,wBAAwB,EAAE,cAAc,CAAC,GAClE,cAAc,CAUhB","sourcesContent":["/**\n * Startup-time permission mode resolution (ADR 0004). A misconfigured\n * non-interactive run should fail fast, at startup, with an actionable message —\n * not deny every tool call mid-run in a way that looks like a bug (see the ADR's\n * \"ask fails closed, and a session that cannot ask refuses to start\").\n */\n\nimport { PERMISSION_MODES, type PermissionMode, type WritablePermissionSource } from \"./store.ts\";\n\nexport type PermissionModeStartupResult = { ok: true; mode: PermissionMode } | { ok: false; message: string };\n\n/**\n * `requestedMode` is assumed already validated against `PERMISSION_MODES` (args.ts\n * rejects an unknown value at parse time) — this only decides whether a session may\n * start *without* one. Interactive sessions may: an `ask` there has a human to\n * resolve it. Non-interactive sessions may not: with no responder, `ask` fails\n * closed to `deny`, and discovering that per-call, mid-run, is the exact bad\n * failure mode this check exists to avoid.\n */\nexport function resolvePermissionModeForStartup(options: {\n\tinteractive: boolean;\n\trequestedMode: PermissionMode | undefined;\n}): PermissionModeStartupResult {\n\tif (options.requestedMode !== undefined) {\n\t\treturn { ok: true, mode: options.requestedMode };\n\t}\n\tif (!options.interactive) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `Non-interactive sessions require an explicit --permission-mode. Valid modes: ${PERMISSION_MODES.join(\", \")}.`,\n\t\t};\n\t}\n\treturn { ok: true, mode: \"default\" };\n}\n\n/** Sources a mode can come from, in precedence order — mirrors PERMISSION_SOURCES (rules.ts), restricted to the sources that can plausibly set one. */\nconst MODE_SOURCE_ORDER: readonly [\"flag\", ...WritablePermissionSource[]] = [\n\t\"flag\",\n\t\"local\",\n\t\"project\",\n\t\"user\",\n\t\"command\",\n\t\"session\",\n];\n\n/**\n * The `--permission-mode` flag outranks every persisted mode, including a project\n * rule a team committed — a deliberate per-run operator decision should not be\n * silently overridden by checked-in config (ADR 0004).\n */\nexport function resolveEffectiveMode(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): PermissionMode {\n\tfor (const source of MODE_SOURCE_ORDER) {\n\t\tif (source === \"flag\") {\n\t\t\tif (flagMode !== undefined) return flagMode;\n\t\t\tcontinue;\n\t\t}\n\t\tconst mode = modesBySource.get(source);\n\t\tif (mode !== undefined) return mode;\n\t}\n\treturn \"default\";\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup-time permission mode resolution (ADR 0004). A misconfigured
|
|
3
|
+
* non-interactive run should fail fast, at startup, with an actionable message —
|
|
4
|
+
* not deny every tool call mid-run in a way that looks like a bug (see the ADR's
|
|
5
|
+
* "ask fails closed, and a session that cannot ask refuses to start").
|
|
6
|
+
*/
|
|
7
|
+
import { PERMISSION_MODES } from "./store.js";
|
|
8
|
+
/**
|
|
9
|
+
* `requestedMode` is assumed already validated against `PERMISSION_MODES` (args.ts
|
|
10
|
+
* rejects an unknown value at parse time) — this only decides whether a session may
|
|
11
|
+
* start *without* one. Interactive sessions may: an `ask` there has a human to
|
|
12
|
+
* resolve it. Non-interactive sessions may not: with no responder, `ask` fails
|
|
13
|
+
* closed to `deny`, and discovering that per-call, mid-run, is the exact bad
|
|
14
|
+
* failure mode this check exists to avoid.
|
|
15
|
+
*/
|
|
16
|
+
export function resolvePermissionModeForStartup(options) {
|
|
17
|
+
if (options.requestedMode !== undefined) {
|
|
18
|
+
return { ok: true, mode: options.requestedMode };
|
|
19
|
+
}
|
|
20
|
+
if (!options.interactive) {
|
|
21
|
+
return {
|
|
22
|
+
ok: false,
|
|
23
|
+
message: `Non-interactive sessions require an explicit --permission-mode. Valid modes: ${PERMISSION_MODES.join(", ")}.`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return { ok: true, mode: "default" };
|
|
27
|
+
}
|
|
28
|
+
/** Sources a mode can come from, in precedence order — mirrors PERMISSION_SOURCES (rules.ts), restricted to the sources that can plausibly set one. */
|
|
29
|
+
const MODE_SOURCE_ORDER = [
|
|
30
|
+
"flag",
|
|
31
|
+
"local",
|
|
32
|
+
"project",
|
|
33
|
+
"user",
|
|
34
|
+
"command",
|
|
35
|
+
"session",
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* The `--permission-mode` flag outranks every persisted mode, including a project
|
|
39
|
+
* rule a team committed — a deliberate per-run operator decision should not be
|
|
40
|
+
* silently overridden by checked-in config (ADR 0004).
|
|
41
|
+
*/
|
|
42
|
+
export function resolveEffectiveMode(flagMode, modesBySource) {
|
|
43
|
+
for (const source of MODE_SOURCE_ORDER) {
|
|
44
|
+
if (source === "flag") {
|
|
45
|
+
if (flagMode !== undefined)
|
|
46
|
+
return flagMode;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const mode = modesBySource.get(source);
|
|
50
|
+
if (mode !== undefined)
|
|
51
|
+
return mode;
|
|
52
|
+
}
|
|
53
|
+
return "default";
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=startup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup.js","sourceRoot":"","sources":["../../../src/core/permissions/startup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAsD,MAAM,YAAY,CAAC;AAIlG;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,OAG/C,EAA+B;IAC/B,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACzC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;IAClD,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC1B,OAAO;YACN,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,gFAAgF,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;SACvH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAAA,CACrC;AAED,yJAAuJ;AACvJ,MAAM,iBAAiB,GAAqD;IAC3E,MAAM;IACN,OAAO;IACP,SAAS;IACT,MAAM;IACN,SAAS;IACT,SAAS;CACT,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CACnC,QAAoC,EACpC,aAAoE,EACnD;IACjB,KAAK,MAAM,MAAM,IAAI,iBAAiB,EAAE,CAAC;QACxC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACvB,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,QAAQ,CAAC;YAC5C,SAAS;QACV,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;IACrC,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB","sourcesContent":["/**\n * Startup-time permission mode resolution (ADR 0004). A misconfigured\n * non-interactive run should fail fast, at startup, with an actionable message —\n * not deny every tool call mid-run in a way that looks like a bug (see the ADR's\n * \"ask fails closed, and a session that cannot ask refuses to start\").\n */\n\nimport { PERMISSION_MODES, type PermissionMode, type WritablePermissionSource } from \"./store.ts\";\n\nexport type PermissionModeStartupResult = { ok: true; mode: PermissionMode } | { ok: false; message: string };\n\n/**\n * `requestedMode` is assumed already validated against `PERMISSION_MODES` (args.ts\n * rejects an unknown value at parse time) — this only decides whether a session may\n * start *without* one. Interactive sessions may: an `ask` there has a human to\n * resolve it. Non-interactive sessions may not: with no responder, `ask` fails\n * closed to `deny`, and discovering that per-call, mid-run, is the exact bad\n * failure mode this check exists to avoid.\n */\nexport function resolvePermissionModeForStartup(options: {\n\tinteractive: boolean;\n\trequestedMode: PermissionMode | undefined;\n}): PermissionModeStartupResult {\n\tif (options.requestedMode !== undefined) {\n\t\treturn { ok: true, mode: options.requestedMode };\n\t}\n\tif (!options.interactive) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `Non-interactive sessions require an explicit --permission-mode. Valid modes: ${PERMISSION_MODES.join(\", \")}.`,\n\t\t};\n\t}\n\treturn { ok: true, mode: \"default\" };\n}\n\n/** Sources a mode can come from, in precedence order — mirrors PERMISSION_SOURCES (rules.ts), restricted to the sources that can plausibly set one. */\nconst MODE_SOURCE_ORDER: readonly [\"flag\", ...WritablePermissionSource[]] = [\n\t\"flag\",\n\t\"local\",\n\t\"project\",\n\t\"user\",\n\t\"command\",\n\t\"session\",\n];\n\n/**\n * The `--permission-mode` flag outranks every persisted mode, including a project\n * rule a team committed — a deliberate per-run operator decision should not be\n * silently overridden by checked-in config (ADR 0004).\n */\nexport function resolveEffectiveMode(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): PermissionMode {\n\tfor (const source of MODE_SOURCE_ORDER) {\n\t\tif (source === \"flag\") {\n\t\t\tif (flagMode !== undefined) return flagMode;\n\t\t\tcontinue;\n\t\t}\n\t\tconst mode = modesBySource.get(source);\n\t\tif (mode !== undefined) return mode;\n\t}\n\treturn \"default\";\n}\n"]}
|