gentle-pi 4.0.0 → 4.2.0
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 +157 -18
- package/assets/agents/gentle-ai-explore.md +11 -4
- package/assets/agents/gentle-ai-verify.md +23 -4
- package/assets/agents/gentle-ai-worker.md +6 -6
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +70 -163
- package/assets/orchestrator-memory.md +11 -4
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +29 -25
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +708 -22
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +399 -0
- package/docs/delegated-verification.md +3 -5
- package/docs/gentle-agents-activity.md +513 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +90 -33
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +127 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +310 -47
- package/docs/render-cache-performance.md +28 -0
- package/docs/session-profile-format.md +39 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/yolo-mode.md +11 -10
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/child-context.ts +11 -2
- package/extensions/child-safety.ts +4 -2
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +718 -87
- package/extensions/gentle-ai.ts +991 -263
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +271 -80
- package/extensions/gentle-todo.ts +123 -18
- package/extensions/history/index.ts +11 -6
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +91 -21
- package/extensions/skill-registry.ts +175 -46
- package/extensions/startup-banner.ts +11 -2
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +40 -1
- package/lib/agents-runner.ts +90 -15
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +459 -0
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +484 -0
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +83 -10
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +14 -4
- package/lib/command-palette-catalog.ts +3 -2
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +104 -28
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +535 -0
- package/lib/gentle-shell-launcher.ts +75 -24
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/nan-provider.ts +33 -11
- package/lib/native-review-cli.ts +194 -73
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase.ts +1 -1
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/review-candidate-view-owner.ts +8 -6
- package/lib/review-candidate-view.ts +3 -2
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-risk-assessment.ts +77 -51
- package/lib/review-sidebar-state.ts +2 -1
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +22 -7
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +261 -0
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-worktree-registry.ts +74 -8
- package/lib/shell-bar.ts +41 -11
- package/lib/shell-changes-view.ts +40 -13
- package/lib/shell-changes.ts +88 -71
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +81 -26
- package/lib/shell-usage.ts +4 -3
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/vim-editor-adapter.ts +27 -5
- package/lib/visual-customization-policy.ts +20 -9
- package/lib/visual-customize-view.ts +171 -20
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +198 -121
- package/package.json +5 -5
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +536 -0
- package/runtime/gentle-shell-launcher.mjs +74 -23
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +191 -70
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +77 -51
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +7 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +79 -32
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/pi-update.mjs +223 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +22 -8
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +23 -5
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +82 -18
- package/skills/gentle-ai/SKILL.md +12 -11
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +12 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-protocol.test.ts +57 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +89 -4
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +276 -0
- package/tests/auto-mode-sdk.test.ts +84 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +634 -0
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +454 -0
- package/tests/autonomous-guard.test.ts +86 -16
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +52 -1
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +34 -2
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +12 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +107 -2
- package/tests/command-palette.test.ts +5 -5
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -2
- package/tests/devbinary/native-review-parity.devtest.ts +1 -1
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +316 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1895 -76
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +19 -2
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-installer.test.ts +548 -58
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +6 -6
- package/tests/gentle-ai.test.ts +999 -31
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +263 -19
- package/tests/gentle-shell-bundled.test.ts +1036 -0
- package/tests/gentle-shell-launcher.test.ts +251 -25
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +611 -100
- package/tests/gentle-todo.test.ts +317 -14
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/inprocess-reviewer.test.ts +20 -0
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2159 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +395 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +28 -18
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +236 -1
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +1 -1
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +14 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
- package/tests/odd-routing-contract.test.ts +80 -23
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +29 -6
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +6 -5
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/package-manifest.test.ts +105 -21
- package/tests/packed-runner-owned-path.test.ts +35 -1
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +4 -12
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/pi-update-launcher.test.ts +87 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/provider-defect-handoff.test.ts +4 -3
- package/tests/quiet-tool-rendering.test.ts +160 -3
- package/tests/rdd-aware-verification-contract.test.ts +29 -41
- package/tests/rdd-status-line.test.ts +52 -0
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/review-agent-end-preflight.test.ts +62 -10
- package/tests/review-candidate-view.test.ts +101 -12
- package/tests/review-contract-prompt.test.ts +33 -4
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +255 -1
- package/tests/review-controller-workspace-root.test.ts +43 -2
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +5 -3
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +21 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +5 -4
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-risk-assessment.test.ts +109 -33
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +83 -5
- package/tests/runtime-harness.mjs +42 -15
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +65 -5
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +394 -0
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +57 -2
- package/tests/shell-bar.test.ts +40 -0
- package/tests/shell-changes-view.test.ts +85 -34
- package/tests/shell-changes.test.ts +79 -62
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +27 -2
- package/tests/shell-todo.test.ts +132 -11
- package/tests/shell-usage-view.test.ts +1 -1
- package/tests/shell-usage.test.ts +35 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +311 -5
- package/tests/startup-banner.test.ts +117 -3
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +27 -1
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter.test.ts +47 -21
- package/tests/visual-customization-policy.test.ts +32 -3
- package/tests/visual-customize-view.test.ts +288 -10
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +18 -3
- package/tests/yolo-mode-runtime.test.ts +37 -10
- package/tests/yolo-mode.test.ts +38 -5
- package/tests/yolo-session-policy.test.ts +1 -1
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext, InputEvent, ToolCallEvent, ToolCallEventResult, ToolResultEvent } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { gentlePiConfigHome } from "./agent-home.ts";
|
|
5
|
+
import { AutoPermissionMemory } from "./auto-permission-memory.ts";
|
|
6
|
+
import { projectToolContext } from "./auto-permission-tool-context.ts";
|
|
7
|
+
import { subscribeHumanAnswers, type HumanAnswerReceipt } from "./auto-permission-answers.ts";
|
|
8
|
+
import { AUTO_PERMISSION_EVIDENCE_BYTES, AUTO_PERMISSION_EVIDENCE_LIMITS, resolveAutoPermissionEvidencePath } from "./auto-permission-evidence.ts";
|
|
9
|
+
import { AUTO_PERMISSION_DIAGNOSTIC_PATHS, type AutoPermissionDiagnostic } from "./auto-permission-classifier.ts";
|
|
10
|
+
import { appendSystemPromptOnce, type AppendableSystemPromptOptions } from "./append-system-prompt.ts";
|
|
11
|
+
|
|
12
|
+
export const AUTO_DIRECTIVE = `<gentle-auto-permissions>
|
|
13
|
+
Auto permission mode is active. Continue ordinary authorized work without routine permission questions. An independent classifier checks eligible tool actions before execution. A denial is a boundary, not an obstacle to evade: choose a materially safer alternative within the request; never hide the same effect behind another tool, script, or subagent. Ask the human only for unresolved consequential intent, explicit required consent, or when safe progress is blocked. Existing hard safety, explicit user restrictions, and native review/maintenance consent still apply. Never answer human consent tools yourself. Auto is not a sandbox, does not grant new task scope, and does not change Gentle Shell delegation.
|
|
14
|
+
</gentle-auto-permissions>`;
|
|
15
|
+
export function updateAutoPrompt(options: AppendableSystemPromptOptions | undefined, active: boolean): void {
|
|
16
|
+
if (!options) return;
|
|
17
|
+
options.appendSystemPrompt = (options.appendSystemPrompt ?? "").replace(`\n\n${AUTO_DIRECTIVE}`, "").replace(AUTO_DIRECTIVE, "");
|
|
18
|
+
if (active) appendSystemPromptOnce(options, AUTO_DIRECTIVE);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ModeController = {mode(ctx: ExtensionContext): Promise<string>; epoch(): number};
|
|
22
|
+
type Verdict = {status: string; decision?: string; reason?: string; code?: string; usage?: unknown; usageKnown?: boolean; actualModel?: unknown; requestedModel?: unknown; diagnostic?: unknown};
|
|
23
|
+
/** Project a closed metadata schema; injected classifiers cannot add payloads to diagnostics. */
|
|
24
|
+
function safeDiagnostic(value: unknown): AutoPermissionDiagnostic | undefined {
|
|
25
|
+
try {
|
|
26
|
+
if(!value || typeof value!=="object")return undefined;
|
|
27
|
+
const {implementation,elapsedMs,deadlineMs,timerFired,returnPath,evidenceLimit}=value as AutoPermissionDiagnostic;
|
|
28
|
+
if(implementation!=="auto-classifier-diagnostics-v1" || !Number.isSafeInteger(elapsedMs) || elapsedMs<0 ||
|
|
29
|
+
!Number.isFinite(deadlineMs) || deadlineMs<1 || deadlineMs>20000 || typeof timerFired!=="boolean" ||
|
|
30
|
+
!AUTO_PERMISSION_DIAGNOSTIC_PATHS.includes(returnPath) ||
|
|
31
|
+
(evidenceLimit!==undefined && !AUTO_PERMISSION_EVIDENCE_LIMITS.includes(evidenceLimit)))return undefined;
|
|
32
|
+
return {implementation,elapsedMs,deadlineMs,timerFired,returnPath,...(evidenceLimit ? {evidenceLimit} : {})};
|
|
33
|
+
} catch { return undefined; }
|
|
34
|
+
}
|
|
35
|
+
export type AutoRuntimeClassifier = {run(request: any): Promise<Verdict>; cancel(): void};
|
|
36
|
+
type Classifier = AutoRuntimeClassifier;
|
|
37
|
+
type BranchItem = {id: string; type: string; message?: {role: string; content: unknown}};
|
|
38
|
+
type AuthoredInput = {text: string; order:number; before: Set<string>; entryId?: string; prompt?: string};
|
|
39
|
+
// These are independent authority/dialog/delegation surfaces, not Auto approvals.
|
|
40
|
+
const OWNED_CONTROL_TOOLS = new Set([
|
|
41
|
+
"ask_user_choice", "ask_user_question", "gentle_review", "gentle_review_capture", "gentle_review_capture_group",
|
|
42
|
+
"subagent_run", "subagent_continue", "subagent_reply", "subagent_cancel", "subagent_send_message",
|
|
43
|
+
"subagent_status", "subagent_result", "subagent_list_tasks", "subagent_list_agents",
|
|
44
|
+
"gentle_odd_phase", "todo",
|
|
45
|
+
]);
|
|
46
|
+
const DENIAL_LIMIT = 3, TOTAL_DENIAL_LIMIT = 20;
|
|
47
|
+
const MAX_AUTHORED_BYTES = 24_000;
|
|
48
|
+
// Answered prompts are context, not authority: keep each tail short and the total (prompt plus repeated reply) well inside the classifier budget.
|
|
49
|
+
const PROMPT_TAIL_BYTES = 1_200, PROMPT_TOTAL_BYTES = 4_000;
|
|
50
|
+
// Codex-style window for long sessions: never block every action on size. Oversized messages keep
|
|
51
|
+
// their start and end; the first (task) and latest messages are always kept, recent ones fill the rest.
|
|
52
|
+
// Sizes here are JSON-serialized bytes, as the classifier measures them; the reserve leaves room
|
|
53
|
+
// for the omission policy that is only sent once history is omitted.
|
|
54
|
+
const MESSAGE_WINDOW_BYTES = 6_000, OMISSION_RESERVE_BYTES = 1_024;
|
|
55
|
+
const serialized = (text: string): number => Buffer.byteLength(JSON.stringify(text))-2;
|
|
56
|
+
function codePointBytes(text: string, max: number, fromEnd: boolean): string {
|
|
57
|
+
let bytes=0;const points=Array.from(text);const kept:string[]=[];
|
|
58
|
+
for(const point of fromEnd ? points.reverse() : points) {
|
|
59
|
+
const size=serialized(point);
|
|
60
|
+
if(bytes+size>max)break;
|
|
61
|
+
bytes+=size;kept.push(point);
|
|
62
|
+
}
|
|
63
|
+
return (fromEnd ? kept.reverse() : kept).join("");
|
|
64
|
+
}
|
|
65
|
+
function middleTruncate(text: string): string {
|
|
66
|
+
if(serialized(text)<=MESSAGE_WINDOW_BYTES)return text;
|
|
67
|
+
const size=Buffer.byteLength(text);
|
|
68
|
+
const keep=Math.floor((MESSAGE_WINDOW_BYTES-64)/2);
|
|
69
|
+
const head=codePointBytes(text,keep,false), tailText=codePointBytes(text,keep,true);
|
|
70
|
+
return `${head} [${size-Buffer.byteLength(head)-Buffer.byteLength(tailText)} bytes omitted by host] ${tailText}`;
|
|
71
|
+
}
|
|
72
|
+
const PROVIDER_RETRY_DELAYS_MS = [500, 1_500];
|
|
73
|
+
const HISTORY_BYTES = 4_000, HISTORY_ENTRIES = 12;
|
|
74
|
+
const UNAVAILABLE_REASONS: Record<string,string> = {
|
|
75
|
+
busy: "The Auto classifier is still settling a previous request. Wait for it to finish before retrying; this is not a safety denial.",
|
|
76
|
+
timeout: "The Auto classifier timed out. A timeout is not evidence that the action is unsafe. Retry after the pending request settles, or use manual permissions.",
|
|
77
|
+
cancelled: "The Auto classifier was cancelled. Retry only in the current live context.",
|
|
78
|
+
revoked: "The Auto decision expired. Retry only in the current live context.",
|
|
79
|
+
"missing-trusted-request": "Auto has no live human request for this branch. Ask the user for a specific instruction.",
|
|
80
|
+
"input-too-large": "The Auto context exceeds its input budget. Narrow the current action, or use manual permissions; repeated retries cannot fix oversized input.",
|
|
81
|
+
"redacted-context": "The Auto context contains potentially sensitive evidence. Use non-sensitive evidence or manual review; do not expose it to obtain approval.",
|
|
82
|
+
"invalid-input": "The Auto action context is invalid. Correct the tool input before retrying.",
|
|
83
|
+
"invalid-output": "The Auto classifier returned an invalid verdict. Retry later or use manual permissions.",
|
|
84
|
+
"truncated-output": "The Auto classifier verdict was truncated. Retry later or use manual permissions.",
|
|
85
|
+
"output-too-large": "The Auto classifier verdict exceeded its output budget. Retry later or use manual permissions.",
|
|
86
|
+
"tool-call": "The Auto classifier requested an unsupported tool. No usable verdict was produced; use manual permissions.",
|
|
87
|
+
"evidence-unavailable": "The Auto classifier could not obtain the required evidence. Provide a relevant local file or use manual review.",
|
|
88
|
+
"evidence-limit": "The Auto classifier exhausted its evidence budget. Narrow the action or use manual review.",
|
|
89
|
+
};
|
|
90
|
+
function block(reason: string): ToolCallEventResult { return {block:true,reason}; }
|
|
91
|
+
function within(path: string, root: string): boolean {
|
|
92
|
+
const rel = relative(root, path);
|
|
93
|
+
return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`));
|
|
94
|
+
}
|
|
95
|
+
/** Resolve existing ancestors too, so a new file under a symlink is not local by spelling. */
|
|
96
|
+
function canonical(path: string): string {
|
|
97
|
+
const absolute = resolve(path);
|
|
98
|
+
let ancestor = absolute;
|
|
99
|
+
while (!existsSync(ancestor)) { const parent = dirname(ancestor); if (parent === ancestor) break; ancestor = parent; }
|
|
100
|
+
return resolve(realpathSync(ancestor), relative(ancestor, absolute));
|
|
101
|
+
}
|
|
102
|
+
/** True only for an existing symlink-free in-project regular file larger than the reader accepts.
|
|
103
|
+
* lstat walks each component from cwd and stops at any symlink, so no outside path is ever examined.
|
|
104
|
+
* Anything else keeps its existing candidate path; the reader still re-checks everything. */
|
|
105
|
+
function oversizedLocalFile(cwd: string, path: string): boolean {
|
|
106
|
+
try {
|
|
107
|
+
const parts=path.split("/");
|
|
108
|
+
let current=realpathSync(resolve(cwd));
|
|
109
|
+
for(let i=0;i<parts.length;i++) {
|
|
110
|
+
current=resolve(current,parts[i]);
|
|
111
|
+
const stat=lstatSync(current,{throwIfNoEntry:false});
|
|
112
|
+
if(!stat || stat.isSymbolicLink())return false;
|
|
113
|
+
if(i===parts.length-1)return stat.isFile() && stat.size>AUTO_PERMISSION_EVIDENCE_BYTES;
|
|
114
|
+
if(!stat.isDirectory())return false;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
} catch { return false; }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Select explicit file operands, never evaluate a shell or crawl prior read paths.
|
|
121
|
+
* Unsupported shell syntax stays tool-free rather than inspecting a file in the wrong cwd. */
|
|
122
|
+
function evidencePaths(toolName: string, input: unknown, cwd: string): string[] {
|
|
123
|
+
if(!["bash","bash_background","monitor"].includes(toolName))return [];
|
|
124
|
+
const command=(input as {command?:unknown})?.command;
|
|
125
|
+
if(typeof command!=="string")return [];
|
|
126
|
+
if(command.includes("&&")) {
|
|
127
|
+
// Evidence only: validate the whole narrow expression before selecting operands.
|
|
128
|
+
// No shell evaluation, cwd changes, quoting, redirection or inherited approval.
|
|
129
|
+
const segments=command.split("&&").map(part=>part.trim());
|
|
130
|
+
if(segments.length>4 || segments.some(part=>!part || /[$`;&|<>(){}*?\\\r\n\"']/.test(part)))return [];
|
|
131
|
+
const valid=segments.every(part=>part==="git diff --check" ||
|
|
132
|
+
/^node(?:\s+(?:--experimental-strip-types|--test))*\s+[\w./-]+\.(?:[cm]?[jt]s)(?:\s+[\w./-]+\.(?:[cm]?[jt]s))*$/.test(part));
|
|
133
|
+
if(!valid)return [];
|
|
134
|
+
return [...new Set(segments.flatMap(part=>evidencePaths(toolName,{command:part},cwd)))].slice(0,12);
|
|
135
|
+
}
|
|
136
|
+
if(/[$`;&|<>(){}*?\\\r\n]/.test(command))return [];
|
|
137
|
+
const tokens:string[]=[];
|
|
138
|
+
let token="",quote="";
|
|
139
|
+
for(const char of command) {
|
|
140
|
+
if(quote) {if(char===quote)quote="";else token+=char;}
|
|
141
|
+
else if(char==='"' || char==="'")quote=char;
|
|
142
|
+
else if(/\s/.test(char)) {if(token){tokens.push(token);token="";}}
|
|
143
|
+
else token+=char;
|
|
144
|
+
}
|
|
145
|
+
if(quote)return [];
|
|
146
|
+
if(token)tokens.push(token);
|
|
147
|
+
if(!tokens.length || tokens.some(t=>["-e","--eval","-c","--command","-p","--print"].includes(t)))return [];
|
|
148
|
+
// Interpreter/package-manager cwd switches change which file a relative operand means.
|
|
149
|
+
// Do not offer root decoys as evidence when we cannot bind the effective directory.
|
|
150
|
+
if(tokens.some(t=>/^(?:--(?:cwd|dir|directory|prefix|workspace|workspaces)(?:=|$)|-C|-w)/.test(t)))return [];
|
|
151
|
+
let candidates:string[]=[];
|
|
152
|
+
if(["npm","pnpm","yarn","bun"].includes(tokens[0]) && ["run","run-script","test","test:unit","lint"].includes(tokens[1])) candidates=["package.json"];
|
|
153
|
+
else if(/^(?:node|nodejs|python(?:3(?:\.\d+)?)?|bash|sh|zsh|ruby|perl|deno|bun|tsx|ts-node)$/.test(tokens[0]))
|
|
154
|
+
candidates=tokens.slice(1).filter(t=>!t.startsWith("-") && /\.(?:[cm]?[jt]s|tsx|jsx|py|sh|bash|zsh|rb|pl)$/.test(t));
|
|
155
|
+
else if(tokens[0].startsWith("./") && /\.(?:sh|bash|zsh|py|[cm]?[jt]s)$/.test(tokens[0]))candidates=[tokens[0]];
|
|
156
|
+
const result:string[]=[];
|
|
157
|
+
for(const candidate of candidates) {
|
|
158
|
+
const path=isAbsolute(candidate) ? relative(resolve(cwd),candidate).split(sep).join("/") : candidate.replace(/^\.\//,"");
|
|
159
|
+
try {
|
|
160
|
+
resolveAutoPermissionEvidencePath(resolve(cwd),[path],path);
|
|
161
|
+
// Never offer a file the reader must reject: the classifier then judges without it instead of
|
|
162
|
+
// failing technically. Missing files keep their existing path; the reader still re-checks size.
|
|
163
|
+
if(oversizedLocalFile(cwd,path))continue;
|
|
164
|
+
if(!result.includes(path))result.push(path);
|
|
165
|
+
} catch { /* Unsupported/credential paths are not inspection candidates. */ }
|
|
166
|
+
}
|
|
167
|
+
return result.slice(0,12);
|
|
168
|
+
}
|
|
169
|
+
function inputText(content: unknown): string | undefined {
|
|
170
|
+
if (typeof content === "string") return content;
|
|
171
|
+
if (!Array.isArray(content) || content.some(p => p?.type !== "text")) return undefined;
|
|
172
|
+
return content.map(p=>p.text).join("");
|
|
173
|
+
}
|
|
174
|
+
/** The host appends image hints after a blank line, so attached images keep the typed text as a prefix. */
|
|
175
|
+
function matchesInput(content: unknown, text: string): boolean {
|
|
176
|
+
if (!Array.isArray(content) || !content.some(p => p?.type === "image")) return inputText(content) === text;
|
|
177
|
+
if (content.some(p => p?.type !== "text" && p?.type !== "image")) return false;
|
|
178
|
+
const typed = content.filter(p => p.type === "text").map(p => p.text).join("");
|
|
179
|
+
return typed === text || typed.startsWith(`${text}\n\n`);
|
|
180
|
+
}
|
|
181
|
+
function assistantText(content: unknown): string {
|
|
182
|
+
if (typeof content === "string") return content;
|
|
183
|
+
if (!Array.isArray(content)) return "";
|
|
184
|
+
return content.filter(p => p?.type === "text" && typeof p.text === "string").map(p => p.text).join("\n");
|
|
185
|
+
}
|
|
186
|
+
/** Resolves false when aborted, so a revoked check never sleeps into another attempt. */
|
|
187
|
+
function pause(ms: number, signal: AbortSignal): Promise<boolean> {
|
|
188
|
+
if (signal.aborted) return Promise.resolve(false);
|
|
189
|
+
return new Promise(done => {
|
|
190
|
+
const timer = setTimeout(() => { signal.removeEventListener("abort", stop); done(true); }, ms);
|
|
191
|
+
const stop = () => { clearTimeout(timer); done(false); };
|
|
192
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function tail(text: string, bytes: number): string {
|
|
196
|
+
const points = Array.from(text.trim());
|
|
197
|
+
let size = 0, start = points.length;
|
|
198
|
+
while (start > 0 && size + Buffer.byteLength(points[start-1]) <= bytes) size += Buffer.byteLength(points[--start]);
|
|
199
|
+
return points.slice(start).join("");
|
|
200
|
+
}
|
|
201
|
+
function numericUsage(usage: unknown): Record<string,number> {
|
|
202
|
+
if (!usage || typeof usage !== "object") return {};
|
|
203
|
+
const safe: Record<string,number> = {};
|
|
204
|
+
for (const key of ["input","output","cacheRead","cacheWrite","totalTokens","costTotal"]) {
|
|
205
|
+
const value = (usage as Record<string,unknown>)[key];
|
|
206
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) safe[key]=value;
|
|
207
|
+
}
|
|
208
|
+
return safe;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Live host provenance is held outside model text and never restored from a transcript. */
|
|
212
|
+
export class AutoPermissionRuntime {
|
|
213
|
+
private owner?: {manager: ExtensionContext["sessionManager"]; id: string; cwd: string};
|
|
214
|
+
private generation = 0;
|
|
215
|
+
private requests: AuthoredInput[] = [];
|
|
216
|
+
private hostAnswers: {text:string;prompt:string;anchor:string;epoch:number;order:number}[] = [];
|
|
217
|
+
private inputOrder=0;
|
|
218
|
+
private unsubscribeAnswers?: () => void;
|
|
219
|
+
private answerEpoch?: number;
|
|
220
|
+
private priorActions: {toolName:string;input:unknown}[] = [];
|
|
221
|
+
private consecutive = 0;
|
|
222
|
+
private total = 0;
|
|
223
|
+
private unavailable = 0;
|
|
224
|
+
private calls = 0;
|
|
225
|
+
private tokens = 0;
|
|
226
|
+
private cost = 0;
|
|
227
|
+
private active = new Set<AbortController>();
|
|
228
|
+
private configHome: string;
|
|
229
|
+
private pi: Pick<ExtensionAPI,"appendEntry"> & Partial<Pick<ExtensionAPI,"getAllTools">>;
|
|
230
|
+
private memory: AutoPermissionMemory;
|
|
231
|
+
private modes: ModeController;
|
|
232
|
+
private classifier: Classifier;
|
|
233
|
+
constructor(pi: Pick<ExtensionAPI,"appendEntry"> & Partial<Pick<ExtensionAPI,"getAllTools">>, modes: ModeController, classifier: Classifier,
|
|
234
|
+
options: {configHome?: string; retryDelaysMs?: number[]} = {}) {
|
|
235
|
+
this.pi=pi;this.modes=modes;this.classifier=classifier;
|
|
236
|
+
this.memory=new AutoPermissionMemory(()=>pi.getAllTools?.() ?? []);
|
|
237
|
+
this.configHome = resolve(options.configHome ?? gentlePiConfigHome());
|
|
238
|
+
this.retryDelaysMs = options.retryDelaysMs ?? PROVIDER_RETRY_DELAYS_MS;
|
|
239
|
+
}
|
|
240
|
+
private retryDelaysMs: number[];
|
|
241
|
+
private toolContext(name:string) {
|
|
242
|
+
try { return projectToolContext(this.pi.getAllTools?.().find(tool=>tool.name===name),name); }
|
|
243
|
+
catch { return undefined; }
|
|
244
|
+
}
|
|
245
|
+
toolResult(event: ToolResultEvent, ctx: ExtensionContext): void {
|
|
246
|
+
this.own(ctx);
|
|
247
|
+
this.memory.result(event,ctx,this.modes.epoch());
|
|
248
|
+
}
|
|
249
|
+
cancelPending(): void {
|
|
250
|
+
this.memory.clear();
|
|
251
|
+
this.unsubscribeAnswers?.();this.unsubscribeAnswers=undefined;
|
|
252
|
+
this.hostAnswers=[];this.answerEpoch=undefined;
|
|
253
|
+
this.generation++;
|
|
254
|
+
for (const controller of this.active) controller.abort();
|
|
255
|
+
this.classifier.cancel();
|
|
256
|
+
}
|
|
257
|
+
reset(): void {
|
|
258
|
+
this.cancelPending();
|
|
259
|
+
this.active.clear();this.owner=undefined;this.requests=[];this.priorActions=[];this.inputOrder=0;
|
|
260
|
+
this.consecutive=0;this.total=0;this.unavailable=0;this.calls=0;this.tokens=0;this.cost=0;
|
|
261
|
+
}
|
|
262
|
+
private own(ctx: ExtensionContext): void {
|
|
263
|
+
const id=ctx.sessionManager.getSessionId();
|
|
264
|
+
if (this.owner?.manager === ctx.sessionManager && this.owner.id === id && this.owner.cwd === ctx.cwd) return;
|
|
265
|
+
this.reset();this.owner={manager:ctx.sessionManager,id,cwd:ctx.cwd};
|
|
266
|
+
}
|
|
267
|
+
private observeAnswers(ctx: ExtensionContext): void {
|
|
268
|
+
const epoch=this.modes.epoch();
|
|
269
|
+
if(this.unsubscribeAnswers && this.answerEpoch===epoch)return;
|
|
270
|
+
this.unsubscribeAnswers?.();this.answerEpoch=epoch;
|
|
271
|
+
this.hostAnswers=this.hostAnswers.filter(answer=>answer.epoch===epoch);
|
|
272
|
+
this.unsubscribeAnswers=subscribeHumanAnswers(ctx,receipt=>{
|
|
273
|
+
if(epoch!==this.modes.epoch())return;
|
|
274
|
+
this.captureAnswer(receipt,ctx,epoch);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
private captureAnswer(receipt: HumanAnswerReceipt, ctx: ExtensionContext, epoch: number): void {
|
|
278
|
+
if(receipt.sessionManager!==this.owner?.manager || receipt.sessionId!==this.owner.id ||
|
|
279
|
+
receipt.cwd!==this.owner.cwd || receipt.branchAnchor===null || ctx.signal?.aborted ||
|
|
280
|
+
!this.branch(ctx).some(entry=>entry.id===receipt.branchAnchor))return;
|
|
281
|
+
const answer=receipt.answer;
|
|
282
|
+
const text=answer.kind==="multi" ? answer.selected.join("; ") :
|
|
283
|
+
[answer.text,...answer.selected ?? []].join("; ");
|
|
284
|
+
if(!text.trim())return;
|
|
285
|
+
// Preserve the entire referent: cutting option descriptions can change what "yes" means.
|
|
286
|
+
const prompt=receipt.prompt;
|
|
287
|
+
// Size never blocks: the reply joins the bounded window and its prompt is kept only while it fits.
|
|
288
|
+
if(typeof prompt!=="string")return;
|
|
289
|
+
this.generation++;
|
|
290
|
+
for(const controller of this.active)controller.abort();
|
|
291
|
+
this.classifier.cancel();
|
|
292
|
+
this.consecutive=0;this.total=0;this.unavailable=0;this.priorActions=[];
|
|
293
|
+
this.hostAnswers.push({text,prompt,anchor:receipt.branchAnchor,epoch,order:++this.inputOrder});
|
|
294
|
+
}
|
|
295
|
+
private liveHostAnswers(ctx: ExtensionContext) {
|
|
296
|
+
const ids=new Set(this.branch(ctx).map(entry=>entry.id));
|
|
297
|
+
return this.hostAnswers.filter(answer=>answer.epoch===this.modes.epoch() && ids.has(answer.anchor));
|
|
298
|
+
}
|
|
299
|
+
private branch(ctx: ExtensionContext): BranchItem[] {
|
|
300
|
+
try { return ctx.sessionManager.getBranch() as BranchItem[]; } catch { return []; }
|
|
301
|
+
}
|
|
302
|
+
input(event: Pick<InputEvent,"text"|"source">, ctx: ExtensionContext): void {
|
|
303
|
+
this.own(ctx);
|
|
304
|
+
// RPC clients and extension messages are not evidence of an interactive human decision.
|
|
305
|
+
if (event.source !== "interactive") return;
|
|
306
|
+
this.unsubscribeAnswers?.();this.unsubscribeAnswers=undefined;
|
|
307
|
+
this.generation++;
|
|
308
|
+
for (const controller of this.active) controller.abort();
|
|
309
|
+
this.classifier.cancel();
|
|
310
|
+
this.consecutive=0;this.total=0;this.unavailable=0;
|
|
311
|
+
// Optional action history is not authority. A new request starts a fresh evidence window;
|
|
312
|
+
// all live human restrictions below remain intact.
|
|
313
|
+
this.priorActions=[];
|
|
314
|
+
const branch=this.branch(ctx);
|
|
315
|
+
// Captured now, before any later assistant text exists, so a reply never inherits a question asked after it.
|
|
316
|
+
const last=branch.findLast(e=>e.type==="message" && (e.message?.role==="assistant" || e.message?.role==="user"));
|
|
317
|
+
const prompt=last?.message?.role==="assistant" ? tail(assistantText(last.message.content),PROMPT_TAIL_BYTES) : "";
|
|
318
|
+
this.requests.push({text:event.text,order:++this.inputOrder,before:new Set(branch.map(e=>e.id)),...(prompt ? {prompt} : {})});
|
|
319
|
+
}
|
|
320
|
+
private trusted(ctx: ExtensionContext): {messages: string[]; kept: Set<number>; omitted: number} {
|
|
321
|
+
const branch=this.branch(ctx), ids=new Set(branch.map(e=>e.id));
|
|
322
|
+
for(const request of this.requests) {
|
|
323
|
+
if(request.entryId) continue;
|
|
324
|
+
const match=branch.find(e=>!request.before.has(e.id) && e.type==="message" && e.message?.role==="user" && matchesInput(e.message.content,request.text));
|
|
325
|
+
if(match) request.entryId=match.id;
|
|
326
|
+
}
|
|
327
|
+
// Switching branches cannot reuse an approval whose actual input entry is absent.
|
|
328
|
+
const all=[...this.live(ids),...this.liveHostAnswers(ctx)].sort((a,b)=>a.order-b.order);
|
|
329
|
+
const full=all.map(r=>r.text);
|
|
330
|
+
if(Buffer.byteLength(full.join(""))<=MAX_AUTHORED_BYTES)return {messages:full,kept:new Set(all.map(r=>r.order)),omitted:0};
|
|
331
|
+
// Over budget: keep the task (first) and the latest message, then the most recent contiguous ones.
|
|
332
|
+
const texts=full.map(middleTruncate);
|
|
333
|
+
const keep=new Set([0,all.length-1]);
|
|
334
|
+
// +3 per entry covers the array's quotes and separator.
|
|
335
|
+
const cost=(i:number)=>serialized(texts[i])+3;
|
|
336
|
+
let budget=MAX_AUTHORED_BYTES-OMISSION_RESERVE_BYTES-2-[...keep].reduce((sum,i)=>sum+cost(i),0);
|
|
337
|
+
for(let i=all.length-2;i>0;i--) {
|
|
338
|
+
const size=cost(i);
|
|
339
|
+
if(size>budget)break;
|
|
340
|
+
budget-=size;keep.add(i);
|
|
341
|
+
}
|
|
342
|
+
const order=[...keep].sort((a,b)=>a-b);
|
|
343
|
+
return {messages:order.map(i=>texts[i]),kept:new Set(order.map(i=>all[i].order)),omitted:all.length-keep.size};
|
|
344
|
+
}
|
|
345
|
+
private live(ids: Set<string>): AuthoredInput[] { return this.requests.filter(r=>r.entryId && ids.has(r.entryId)); }
|
|
346
|
+
/** Newest first within a fixed budget that also counts the repeated reply; dropped pairs leave their replies trusted. */
|
|
347
|
+
private answered(ctx: ExtensionContext, window: {messages: string[]; kept: Set<number>}): {prompt:string;reply:string}[] {
|
|
348
|
+
// Only pairs whose reply is in the window; omitted replies must not return through their prompt.
|
|
349
|
+
const live=this.live(new Set(this.branch(ctx).map(e=>e.id))).filter(r=>window.kept.has(r.order));
|
|
350
|
+
const kept: {prompt:string;reply:string}[]=[];
|
|
351
|
+
// Host answers keep their full question, newest first within their own budget, as before the window existed.
|
|
352
|
+
let hostBudget=PROMPT_TOTAL_BYTES;
|
|
353
|
+
for(const answer of this.liveHostAnswers(ctx).filter(r=>window.kept.has(r.order)).reverse()) {
|
|
354
|
+
const size=Buffer.byteLength(answer.prompt)+Buffer.byteLength(answer.text);
|
|
355
|
+
if(size>hostBudget)continue;
|
|
356
|
+
hostBudget-=size;kept.unshift({prompt:answer.prompt,reply:answer.text});
|
|
357
|
+
}
|
|
358
|
+
// Typed-reply pairs use what live requests and host pairs leave, so the classifier input never grows past its previous ceiling.
|
|
359
|
+
let budget=Math.min(PROMPT_TOTAL_BYTES,MAX_AUTHORED_BYTES-live.reduce((sum,r)=>sum+Buffer.byteLength(r.text),0))-(PROMPT_TOTAL_BYTES-hostBudget);
|
|
360
|
+
for(const request of live.reverse()) {
|
|
361
|
+
if(!request.prompt) continue;
|
|
362
|
+
const size=Buffer.byteLength(request.prompt)+Buffer.byteLength(request.text);
|
|
363
|
+
if(size>budget) continue;
|
|
364
|
+
budget-=size;kept.unshift({prompt:request.prompt,reply:request.text});
|
|
365
|
+
}
|
|
366
|
+
return kept;
|
|
367
|
+
}
|
|
368
|
+
private protectedMutation(event: Pick<ToolCallEvent,"toolName"|"input">, ctx: ExtensionContext): boolean {
|
|
369
|
+
const args=event.input as Record<string,unknown>;
|
|
370
|
+
if (!args || typeof args!=="object") return false;
|
|
371
|
+
if ((event.toolName==="write" || event.toolName==="edit") && typeof args.path==="string") {
|
|
372
|
+
try { return within(canonical(resolve(ctx.cwd,args.path)),canonical(this.configHome)); } catch { return true; }
|
|
373
|
+
}
|
|
374
|
+
if (["bash","bash_background","monitor"].includes(event.toolName) && typeof args.command==="string") {
|
|
375
|
+
// Best-effort shell recognition supplements classification, not OS containment.
|
|
376
|
+
return args.command.includes(this.configHome) || /(?:permission-mode|runtime-guardrails)\.json/.test(args.command);
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
private record(ctx: ExtensionContext, event: Pick<ToolCallEvent,"toolName"|"toolCallId">, result: Verdict, late=false, memoryContextAttached?: boolean): void {
|
|
381
|
+
const usage=numericUsage(result.usage), diagnostic=safeDiagnostic(result.diagnostic);
|
|
382
|
+
// Receipts carry no raw command, input, transcript, or provider-generated rationale.
|
|
383
|
+
try {this.pi.appendEntry("gentle-auto-permission",{schema:1,toolCallId:event.toolCallId,toolName:event.toolName,
|
|
384
|
+
status:result.status,decision:result.decision,code:result.code,usage,usageKnown:result.usageKnown,late,actualModel:result.actualModel,...(memoryContextAttached === undefined ? {} : {memoryContextAttached}),...(diagnostic ? {diagnostic} : {})});} catch { /* Missing persistence never creates permission. */ }
|
|
385
|
+
if(!late)this.calls++;
|
|
386
|
+
this.tokens+=usage.totalTokens ?? 0;this.cost+=usage.costTotal ?? 0;
|
|
387
|
+
try {ctx.ui.setStatus("gentle:auto-usage",`Auto checks ${this.calls} · ${this.tokens} tokens · $${this.cost.toFixed(4)} (separate)`);} catch { /* Headless. */ }
|
|
388
|
+
}
|
|
389
|
+
guard(event: Pick<ToolCallEvent,"toolName"|"input">, ctx: ExtensionContext): ToolCallEventResult | undefined {
|
|
390
|
+
return this.protectedMutation(event,ctx) ? block("Permission configuration is human-controlled. Use the mode settings UI; model tools cannot change it.") : undefined;
|
|
391
|
+
}
|
|
392
|
+
async check(event: Pick<ToolCallEvent,"toolName"|"input"|"toolCallId">, ctx: ExtensionContext, primary=true): Promise<ToolCallEventResult|undefined> {
|
|
393
|
+
if(!primary)return undefined;
|
|
394
|
+
this.own(ctx);
|
|
395
|
+
if(this.protectedMutation(event,ctx))return block("Permission configuration is human-controlled. Use the mode settings UI; model tools cannot change it.");
|
|
396
|
+
// Revocation while identity lookup is pending must not become a fresh grant.
|
|
397
|
+
const generation=this.generation, epoch=this.modes.epoch(), owner=this.owner;
|
|
398
|
+
const mode=await this.modes.mode(ctx);
|
|
399
|
+
if(generation!==this.generation || epoch!==this.modes.epoch() || owner!==this.owner || ctx.signal?.aborted)
|
|
400
|
+
return block("Auto decision expired during mode lookup. The action was not run.");
|
|
401
|
+
if(mode!=="auto") { this.memory.admitted(event,ctx,epoch);return undefined; }
|
|
402
|
+
this.observeAnswers(ctx);
|
|
403
|
+
// Independent executors retain their own consent and delegation authorities.
|
|
404
|
+
if(OWNED_CONTROL_TOOLS.has(event.toolName))return undefined;
|
|
405
|
+
if(this.consecutive>=DENIAL_LIMIT || this.total>=TOTAL_DENIAL_LIMIT || this.unavailable>=DENIAL_LIMIT)
|
|
406
|
+
return block("Auto mode is paused after repeated denials or unavailable checks. Stop this attempt and ask the user for a safer, specific instruction; do not route around the boundary.");
|
|
407
|
+
// Local reads can disclose explicitly forbidden data; they need the same check.
|
|
408
|
+
if(!ctx.model)return block("Auto could not evaluate this action: no session model is available. The action was not run.");
|
|
409
|
+
// Copy identity values: a host can update its model object in place.
|
|
410
|
+
const model={id:ctx.model.id,provider:ctx.model.provider};
|
|
411
|
+
const window=this.trusted(ctx), trustedMessages=window.messages, answeredPrompts=this.answered(ctx,window);
|
|
412
|
+
const omittedTrustedMessages=window.omitted || undefined;
|
|
413
|
+
const branchIds=new Set(this.branch(ctx).map(entry=>entry.id));
|
|
414
|
+
const trustedEntryIds=[...this.requests.flatMap(request=>request.entryId && branchIds.has(request.entryId) ? [request.entryId] : []),
|
|
415
|
+
...this.liveHostAnswers(ctx).map(answer=>answer.anchor)];
|
|
416
|
+
let serialized: string;
|
|
417
|
+
try {serialized=JSON.stringify(event.input);}catch{return block("Auto could not evaluate invalid tool input. The action was not run.");}
|
|
418
|
+
const memoryContext=this.memory.context(event,ctx,epoch);
|
|
419
|
+
const memoryContextAttached=["mem_save","mem_session_summary"].includes(event.toolName) ? memoryContext !== undefined : undefined;
|
|
420
|
+
const memoryBinding=JSON.stringify(memoryContext);
|
|
421
|
+
const toolContext=this.toolContext(event.toolName),toolBinding=JSON.stringify(toolContext);
|
|
422
|
+
const current=()=>{ try {
|
|
423
|
+
// SDK branch changes must revoke removed input even before a host tree event.
|
|
424
|
+
const currentBranchIds=new Set(this.branch(ctx).map(entry=>entry.id));
|
|
425
|
+
return generation===this.generation && epoch===this.modes.epoch() && owner===this.owner &&
|
|
426
|
+
owner?.manager===ctx.sessionManager && owner.id===ctx.sessionManager.getSessionId() && owner.cwd===ctx.cwd &&
|
|
427
|
+
model.id===ctx.model?.id && model.provider===ctx.model?.provider && JSON.stringify(event.input)===serialized &&
|
|
428
|
+
trustedEntryIds.every(id=>currentBranchIds.has(id)) &&
|
|
429
|
+
JSON.stringify(this.memory.context(event,ctx,epoch))===memoryBinding &&
|
|
430
|
+
JSON.stringify(this.toolContext(event.toolName))===toolBinding;
|
|
431
|
+
} catch {return false;} };
|
|
432
|
+
const paths=evidencePaths(event.toolName,JSON.parse(serialized),ctx.cwd);
|
|
433
|
+
// Canonical cwd allows safe inspection in workspaces reached through /tmp-like aliases.
|
|
434
|
+
const classifierCwd=paths.length ? canonical(ctx.cwd) : ctx.cwd;
|
|
435
|
+
const controller=new AbortController();this.active.add(controller);
|
|
436
|
+
const onAbort=()=>controller.abort();ctx.signal?.addEventListener("abort",onAbort,{once:true});
|
|
437
|
+
if(ctx.signal?.aborted)controller.abort();
|
|
438
|
+
let result: Verdict = {status:"unavailable",code:"provider-error"};
|
|
439
|
+
try {
|
|
440
|
+
// Classification has no side effects, so a transient provider failure is retried within the same live binding.
|
|
441
|
+
for(let attempt=0;attempt<=this.retryDelaysMs.length;attempt++){
|
|
442
|
+
if(attempt>0 && (!(await pause(this.retryDelaysMs[attempt-1],controller.signal)) || !current()))break;
|
|
443
|
+
try {
|
|
444
|
+
result=await this.classifier.run({trustedMessages,...(omittedTrustedMessages ? {omittedTrustedMessages} : {}),answeredPrompts,memoryContext,toolContext,action:{toolName:event.toolName,input:JSON.parse(serialized)},
|
|
445
|
+
priorActions:this.priorActions,evidencePaths:paths,cwd:classifierCwd,model:ctx.model,modelRegistry:ctx.modelRegistry,isCurrent:current,signal:controller.signal,
|
|
446
|
+
onUsage:(late: Omit<Verdict,"status">)=>{
|
|
447
|
+
if(owner?.manager===ctx.sessionManager && owner.id===ctx.sessionManager.getSessionId()) this.record(ctx,event,{...late,status:"settled"},true,memoryContextAttached);
|
|
448
|
+
}});
|
|
449
|
+
}catch{result={status:"unavailable",code:"provider-error"};}
|
|
450
|
+
// Record attempts even when revoked; never transplant old receipts into a replacement session.
|
|
451
|
+
if(owner?.manager===ctx.sessionManager && owner.id===ctx.sessionManager.getSessionId())this.record(ctx,event,result,false,memoryContextAttached);
|
|
452
|
+
if(result.status==="available" || result.code!=="provider-error" || controller.signal.aborted)break;
|
|
453
|
+
}
|
|
454
|
+
}finally{ctx.signal?.removeEventListener("abort",onAbort);this.active.delete(controller);}
|
|
455
|
+
const stillAuto=await this.modes.mode(ctx)==="auto";
|
|
456
|
+
if(!current() || controller.signal.aborted || !stillAuto)return block("Auto decision expired because the action, mode, request or session changed. The action was not run.");
|
|
457
|
+
if(result.status!=="available" || !["allow","deny"].includes(result.decision ?? "")){
|
|
458
|
+
// Contention and revocation are not failed safety judgements or provider outages.
|
|
459
|
+
if(!["busy","cancelled","revoked"].includes(result.code ?? ""))this.unavailable++;
|
|
460
|
+
if(result.code==="provider-error")return block(`Auto could not reach its classifier model (provider error after ${this.retryDelaysMs.length+1} attempts). The action was not run. Retry later, switch the session model, or continue with manual permissions; do not treat a missing verdict as permission or evade this check.`);
|
|
461
|
+
const diagnostic=["timeout","evidence-limit"].includes(result.code ?? "") ? safeDiagnostic(result.diagnostic) : undefined;
|
|
462
|
+
const detail=diagnostic ? ` Diagnostic: ${JSON.stringify(diagnostic)}.` : "";
|
|
463
|
+
return block(`${UNAVAILABLE_REASONS[result.code ?? ""] ?? "Auto received no usable classifier verdict. Retry later or use manual permissions."} The action was not run. A missing verdict is not permission; do not evade this check.${detail}`);
|
|
464
|
+
}
|
|
465
|
+
this.unavailable=0;
|
|
466
|
+
if(result.decision==="deny"){
|
|
467
|
+
this.consecutive++;this.total++;
|
|
468
|
+
// Model reasoning is shown as advice only, never authorization or executable instructions.
|
|
469
|
+
const reason=(result.reason ?? "Outside the authorized safe scope").slice(0,600);
|
|
470
|
+
return block(`Auto denied this action: ${reason}. Find a materially safer alternative within the request, or ask the user; do not pursue the same effect through another tool.`);
|
|
471
|
+
}
|
|
472
|
+
this.consecutive=0;
|
|
473
|
+
this.memory.admitted(event,ctx,epoch);
|
|
474
|
+
// Keep only complete recent records: truncated source could hide side effects. Missing
|
|
475
|
+
// script evidence can be inspected by the reviewer, never inferred from an omitted record.
|
|
476
|
+
const action={toolName:event.toolName,input:JSON.parse(serialized)};
|
|
477
|
+
if(Buffer.byteLength(JSON.stringify([action]))<=HISTORY_BYTES) {
|
|
478
|
+
const history=[...this.priorActions,action];
|
|
479
|
+
while(history.length>HISTORY_ENTRIES || Buffer.byteLength(JSON.stringify(history))>HISTORY_BYTES)history.shift();
|
|
480
|
+
this.priorActions=history;
|
|
481
|
+
}
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Descriptive registry metadata, never authorization or a callable reviewer tool. */
|
|
2
|
+
export interface AutoPermissionToolContext {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: Record<string, unknown> | boolean;
|
|
6
|
+
}
|
|
7
|
+
const MAX_BYTES = 8192;
|
|
8
|
+
/** Snapshot plain schema data without invoking getters/serializers. Drop unusable metadata whole. */
|
|
9
|
+
export function projectToolContext(value: unknown, name: string, unsafeText: (text:string)=>boolean = ()=>false): AutoPermissionToolContext | undefined {
|
|
10
|
+
try {
|
|
11
|
+
let nodes=0, bytes=0;
|
|
12
|
+
const text=(s:string)=>{
|
|
13
|
+
bytes+=Buffer.byteLength(s);
|
|
14
|
+
if(bytes>MAX_BYTES || /[\p{Cf}\p{Cs}]/u.test(s) || unsafeText(s))throw Error("unusable-metadata");
|
|
15
|
+
return s;
|
|
16
|
+
};
|
|
17
|
+
const json=(input:unknown,depth=0,declarations=false,propertyName?:string):unknown=>{
|
|
18
|
+
if(++nodes>512 || depth>12)throw Error("unusable-metadata");
|
|
19
|
+
if(typeof input==="string")return text(input);
|
|
20
|
+
if(input===null || typeof input==="boolean" || (typeof input==="number" && Number.isFinite(input)))return input;
|
|
21
|
+
if(!input || typeof input!=="object")throw Error("unusable-metadata");
|
|
22
|
+
const array=Array.isArray(input), proto=Object.getPrototypeOf(input);
|
|
23
|
+
if(array ? proto!==Array.prototype : proto!==Object.prototype && proto!==null)throw Error("unusable-metadata");
|
|
24
|
+
const descriptors=Object.getOwnPropertyDescriptors(input);
|
|
25
|
+
const entries:[string,unknown][]=[];
|
|
26
|
+
for(const key of Object.keys(descriptors)) {
|
|
27
|
+
if(array && key==="length")continue;
|
|
28
|
+
const descriptor=descriptors[key];
|
|
29
|
+
if(!descriptor.enumerable)continue; // TypeBox also carries non-enumerable string annotations.
|
|
30
|
+
if(!Object.hasOwn(descriptor,"value") || key==="toJSON")throw Error("unusable-metadata");
|
|
31
|
+
if(array && !/^(0|[1-9][0-9]*)$/.test(key))throw Error("unusable-metadata");
|
|
32
|
+
const child=json(descriptor.value,depth+1,key==="properties" || key==="patternProperties",declarations ? key : propertyName);
|
|
33
|
+
// Property declarations describe slots; examples/defaults/const/enum contain values.
|
|
34
|
+
const literal=["examples","example","default","const","enum"].includes(key);
|
|
35
|
+
const assignment=(field:string)=>child!==null && child!=="" && unsafeText(`${field}=${typeof child==="number" ? child : "value"}`);
|
|
36
|
+
if((!declarations && assignment(key)) || (literal &&
|
|
37
|
+
(unsafeText(JSON.stringify(child)) || (propertyName!==undefined && assignment(propertyName)))))throw Error("unusable-metadata");
|
|
38
|
+
entries.push([text(key),child]);
|
|
39
|
+
}
|
|
40
|
+
// TypeBox's symbol annotations are not JSON Schema and are not sent to the reviewer.
|
|
41
|
+
if(array) {
|
|
42
|
+
if(descriptors.length.value!==entries.length)throw Error("unusable-metadata");
|
|
43
|
+
return entries.map(([,item])=>item);
|
|
44
|
+
}
|
|
45
|
+
return Object.fromEntries(entries);
|
|
46
|
+
};
|
|
47
|
+
if(!value || typeof value!=="object")return undefined;
|
|
48
|
+
const descriptors=Object.getOwnPropertyDescriptors(value);
|
|
49
|
+
const field=(key:string)=>{
|
|
50
|
+
const descriptor=descriptors[key];
|
|
51
|
+
if(!descriptor || !Object.hasOwn(descriptor,"value"))throw Error("unusable-metadata");
|
|
52
|
+
return descriptor.value;
|
|
53
|
+
};
|
|
54
|
+
const toolName=field("name"), description=field("description"), parameters=json(field("parameters"));
|
|
55
|
+
if(toolName!==name || typeof toolName!=="string" || !name || name.length>256 || typeof description!=="string" ||
|
|
56
|
+
!(typeof parameters==="boolean" || (parameters!==null && typeof parameters==="object" && !Array.isArray(parameters))))return undefined;
|
|
57
|
+
const result={name:text(toolName),description:text(description),parameters} as AutoPermissionToolContext;
|
|
58
|
+
if(Buffer.byteLength(JSON.stringify(result))>MAX_BYTES)return undefined;
|
|
59
|
+
return result;
|
|
60
|
+
} catch { return undefined; }
|
|
61
|
+
}
|