@piagent/platform 1.3.1 → 1.4.1
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 +132 -0
- package/README.md +10 -4
- package/README.vi.md +27 -0
- package/architecture/layers.json +96 -1
- package/docs/README.md +1 -0
- package/docs/architecture.md +1 -0
- package/docs/authority-manifest.md +1 -0
- package/docs/auto-delegation-policy.md +1 -0
- package/docs/capability-packs.md +1 -0
- package/docs/command-reference-vietnamese.md +5 -4
- package/docs/context-engine.md +1 -0
- package/docs/context-window-policy.md +1 -0
- package/docs/distribution-standard.md +8 -7
- package/docs/en/architecture.md +13 -0
- package/docs/herdr-workflow.md +1 -0
- package/docs/languages.json +20 -4
- package/docs/mcp-and-tools.md +125 -9
- package/docs/memory-policy.md +1 -0
- package/docs/model-options.md +1 -0
- package/docs/oauth-providers.md +1 -0
- package/docs/operator-manual-vietnamese.md +15 -12
- package/docs/package-architecture-notes.md +1 -0
- package/docs/project-adapters.md +1 -0
- package/docs/project-onboarding.md +1 -0
- package/docs/publishing-for-teams.md +37 -0
- package/docs/quality-benchmark.md +1 -0
- package/docs/quickstart-vietnamese.md +3 -2
- package/docs/release-install-policy.md +22 -8
- package/docs/runtime-harness-standard.md +2 -1
- package/docs/runtime-policy-design.md +1 -0
- package/docs/runtime-quality-baseline.md +1 -0
- package/docs/security-redaction-benchmark.md +1 -0
- package/docs/security-threat-model.md +2 -1
- package/docs/subagent-orchestration-capabilities.md +1 -0
- package/docs/subagents-and-multiagent.md +1 -0
- package/docs/task-implementation-contract.md +12 -4
- package/docs/task-lifecycle-vietnamese.md +1 -0
- package/docs/team-onboarding.md +3 -2
- package/docs/third-party-neutrality.md +26 -0
- package/docs/usage-observability.md +71 -0
- package/docs/vercel-docs-site.md +1 -0
- package/docs/vi/architecture.md +13 -0
- package/docs/workflow-recipes.md +1 -0
- package/docs-site/architecture.html +5 -1
- package/docs-site/benchmark.html +5 -1
- package/docs-site/commands.html +5 -1
- package/docs-site/content/en/index.html +49 -0
- package/docs-site/content/en/runtime.html +13 -0
- package/docs-site/content/index.html +49 -0
- package/docs-site/content/runtime.html +28 -0
- package/docs-site/context-engine.html +5 -1
- package/docs-site/en/architecture.html +5 -1
- package/docs-site/en/benchmark.html +5 -1
- package/docs-site/en/commands.html +5 -1
- package/docs-site/en/context-engine.html +5 -1
- package/docs-site/en/index.html +54 -1
- package/docs-site/en/mcp-approval.html +5 -1
- package/docs-site/en/mcp-auth.html +5 -1
- package/docs-site/en/mcp-servers.html +5 -1
- package/docs-site/en/mcp.html +5 -1
- package/docs-site/en/permissions.html +5 -1
- package/docs-site/en/profiles.html +5 -1
- package/docs-site/en/quickstart.html +5 -1
- package/docs-site/en/runtime.html +18 -1
- package/docs-site/en/scope.html +5 -1
- package/docs-site/en/security.html +5 -1
- package/docs-site/en/team.html +5 -1
- package/docs-site/en/whats-new.html +5 -1
- package/docs-site/en/workflows.html +5 -1
- package/docs-site/index.html +54 -1
- package/docs-site/mcp-approval.html +5 -1
- package/docs-site/mcp-auth.html +5 -1
- package/docs-site/mcp-servers.html +5 -1
- package/docs-site/mcp.html +5 -1
- package/docs-site/permissions.html +5 -1
- package/docs-site/profiles.html +5 -1
- package/docs-site/quickstart.html +5 -1
- package/docs-site/runtime.html +33 -1
- package/docs-site/scope.html +5 -1
- package/docs-site/security.html +5 -1
- package/docs-site/team.html +5 -1
- package/docs-site/whats-new.html +5 -1
- package/docs-site/workflows.html +5 -1
- package/evals/fixtures/mutation-provenance-record.invalid.json +17 -0
- package/evals/fixtures/mutation-provenance-record.valid.json +27 -0
- package/evals/fixtures/piagent-webui/approval-v1.invalid.json +54 -0
- package/evals/fixtures/piagent-webui/approval-v1.valid.json +58 -0
- package/evals/fixtures/piagent-webui/attachment-v1.invalid.json +25 -0
- package/evals/fixtures/piagent-webui/attachment-v1.valid.json +25 -0
- package/evals/fixtures/piagent-webui/capabilities-v1.invalid.json +51 -0
- package/evals/fixtures/piagent-webui/capabilities-v1.valid.json +122 -0
- package/evals/fixtures/piagent-webui/commit-summary-v1.invalid.json +10 -0
- package/evals/fixtures/piagent-webui/commit-summary-v1.valid.json +25 -0
- package/evals/fixtures/piagent-webui/control-command-v1.invalid.json +37 -0
- package/evals/fixtures/piagent-webui/control-command-v1.valid.json +35 -0
- package/evals/fixtures/piagent-webui/diff-v1.invalid.json +13 -0
- package/evals/fixtures/piagent-webui/diff-v1.valid.json +141 -0
- package/evals/fixtures/piagent-webui/gateway-capabilities-v1.invalid.json +35 -0
- package/evals/fixtures/piagent-webui/gateway-capabilities-v1.valid.json +35 -0
- package/evals/fixtures/piagent-webui/gateway-protocol-v1.invalid.json +14 -0
- package/evals/fixtures/piagent-webui/gateway-protocol-v1.valid.json +14 -0
- package/evals/fixtures/piagent-webui/handoff-history-v1.invalid.json +16 -0
- package/evals/fixtures/piagent-webui/handoff-history-v1.valid.json +16 -0
- package/evals/fixtures/piagent-webui/model-catalog-v1.invalid.json +31 -0
- package/evals/fixtures/piagent-webui/model-catalog-v1.valid.json +43 -0
- package/evals/fixtures/piagent-webui/queue-v1.invalid.json +29 -0
- package/evals/fixtures/piagent-webui/queue-v1.valid.json +44 -0
- package/evals/fixtures/piagent-webui/recovery-history-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/recovery-history-v1.valid.json +20 -0
- package/evals/fixtures/piagent-webui/release-monitor-v1.invalid.json +12 -0
- package/evals/fixtures/piagent-webui/release-monitor-v1.valid.json +29 -0
- package/evals/fixtures/piagent-webui/review-state-v1.invalid.json +25 -0
- package/evals/fixtures/piagent-webui/review-state-v1.valid.json +38 -0
- package/evals/fixtures/piagent-webui/runtime-event-v2.invalid.json +49 -0
- package/evals/fixtures/piagent-webui/runtime-event-v2.valid.json +49 -0
- package/evals/fixtures/piagent-webui/session-catalog-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/session-catalog-v1.valid.json +58 -0
- package/evals/fixtures/piagent-webui/session-command-v1.invalid.json +19 -0
- package/evals/fixtures/piagent-webui/session-command-v1.valid.json +19 -0
- package/evals/fixtures/piagent-webui/snapshot-v1.invalid.json +5 -0
- package/evals/fixtures/piagent-webui/snapshot-v1.valid.json +191 -0
- package/evals/fixtures/piagent-webui/source-change-v1.invalid.json +57 -0
- package/evals/fixtures/piagent-webui/source-change-v1.valid.json +98 -0
- package/evals/fixtures/piagent-webui/source-mutation-v1.invalid.json +19 -0
- package/evals/fixtures/piagent-webui/source-mutation-v1.valid.json +38 -0
- package/evals/fixtures/piagent-webui/source-revert-v1.invalid.json +20 -0
- package/evals/fixtures/piagent-webui/source-revert-v1.valid.json +62 -0
- package/evals/fixtures/piagent-webui/subagent-tree-v1.invalid.json +26 -0
- package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +39 -0
- package/evals/fixtures/piagent-webui/task-index-v1.invalid.json +15 -0
- package/evals/fixtures/piagent-webui/task-index-v1.valid.json +15 -0
- package/evals/fixtures/piagent-webui/task-timeline-v1.invalid.json +14 -0
- package/evals/fixtures/piagent-webui/task-timeline-v1.valid.json +16 -0
- package/evals/fixtures/piagent-webui/transcript-v1.invalid.json +35 -0
- package/evals/fixtures/piagent-webui/transcript-v1.valid.json +55 -0
- package/evals/fixtures/task-baseline-manifest.invalid.json +16 -0
- package/evals/fixtures/task-baseline-manifest.valid.json +41 -0
- package/evals/fixtures/verifier-file-snapshot.invalid.json +21 -0
- package/evals/fixtures/verifier-file-snapshot.valid.json +21 -0
- package/package.json +43 -4
- package/packages/piagent-core/README.md +3 -2
- package/packages/piagent-core/benchmark/benchmark-candidate.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-forensics.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-ledger.d.ts +4 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-runtime.d.ts +1 -0
- package/packages/piagent-core/capabilities/capability-core.js +2 -2
- package/packages/piagent-core/capabilities/capability-verification-cache.js +17 -7
- package/packages/piagent-core/capabilities/runtime-integrity.js +190 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +80 -272
- package/packages/piagent-core/extensions/redaction-core.d.ts +2 -0
- package/packages/piagent-core/extensions/runtime-evidence.js +12 -1
- package/packages/piagent-core/extensions/shell-reach.ts +250 -0
- package/packages/piagent-core/extensions/task-journal.js +30 -1
- package/packages/piagent-core/extensions/task-state.js +33 -36
- package/packages/piagent-core/mcp/mcp-command-actions.js +1 -1
- package/packages/piagent-core/mcp/mcp-server-catalog.js +139 -9
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +2 -2
- package/packages/piagent-core/runtime/hooks/tool-call-hook.ts +12 -2
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +20 -19
- package/packages/piagent-core/runtime/hooks/tool-result-metadata.ts +13 -0
- package/packages/piagent-core/runtime/input/chat-images.ts +2 -2
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +94 -0
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +318 -0
- package/packages/piagent-core/runtime/inspection/commit-summary-projection.ts +59 -0
- package/packages/piagent-core/runtime/inspection/context-telemetry-inspection.ts +37 -0
- package/packages/piagent-core/runtime/inspection/criteria-links.ts +295 -0
- package/packages/piagent-core/runtime/inspection/diff-projection.ts +499 -0
- package/packages/piagent-core/runtime/inspection/git-filter-safety.ts +38 -0
- package/packages/piagent-core/runtime/inspection/git-numstat-adapter.ts +71 -0
- package/packages/piagent-core/runtime/inspection/git-status-adapter.ts +493 -0
- package/packages/piagent-core/runtime/inspection/git-tree-adapter.ts +108 -0
- package/packages/piagent-core/runtime/inspection/mcp-control.ts +10 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-contract.ts +131 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-recorder.ts +48 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-store.ts +161 -0
- package/packages/piagent-core/runtime/inspection/project-runtime-inspection.ts +36 -0
- package/packages/piagent-core/runtime/inspection/review-state-contract.ts +108 -0
- package/packages/piagent-core/runtime/inspection/review-state-projection.ts +93 -0
- package/packages/piagent-core/runtime/inspection/review-state-store.ts +112 -0
- package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +262 -0
- package/packages/piagent-core/runtime/inspection/same-process-bridge-proof.ts +103 -0
- package/packages/piagent-core/runtime/inspection/source-change-projection.ts +471 -0
- package/packages/piagent-core/runtime/inspection/source-evidence-contract.ts +139 -0
- package/packages/piagent-core/runtime/inspection/source-evidence-store.ts +315 -0
- package/packages/piagent-core/runtime/inspection/source-handoff-store.ts +70 -0
- package/packages/piagent-core/runtime/inspection/source-inspection-plan.ts +34 -0
- package/packages/piagent-core/runtime/inspection/source-mutation-projection.ts +164 -0
- package/packages/piagent-core/runtime/inspection/source-mutation-store.ts +168 -0
- package/packages/piagent-core/runtime/inspection/source-open-target.ts +38 -0
- package/packages/piagent-core/runtime/inspection/source-revert-projection.ts +147 -0
- package/packages/piagent-core/runtime/inspection/task-baseline-start-capture.ts +22 -0
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +160 -0
- package/packages/piagent-core/runtime/inspection/task-control-journal.ts +131 -0
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +95 -0
- package/packages/piagent-core/runtime/inspection/task-provenance-projection.ts +60 -0
- package/packages/piagent-core/runtime/inspection/task-recovery-timeline.ts +88 -0
- package/packages/piagent-core/runtime/inspection/task-run-index.ts +87 -0
- package/packages/piagent-core/runtime/inspection/task-source-projection.ts +401 -0
- package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +101 -0
- package/packages/piagent-core/runtime/inspection/text-diff.ts +60 -0
- package/packages/piagent-core/runtime/inspection/verifier-snapshot-contract.ts +114 -0
- package/packages/piagent-core/runtime/inspection/verifier-snapshot-store.ts +189 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +320 -0
- package/packages/piagent-core/runtime/inspection/workspace-file-reader.ts +132 -0
- package/packages/piagent-core/runtime/inspection/workspace-source-projection.ts +96 -0
- package/packages/piagent-core/runtime/inspection/zero-turn-conformance.ts +175 -0
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +93 -6
- package/packages/piagent-core/runtime/policy/block-remedy.ts +139 -0
- package/packages/piagent-core/runtime/policy/source-index-transaction.ts +173 -0
- package/packages/piagent-core/runtime/policy/source-mutation-guard-binding.ts +42 -0
- package/packages/piagent-core/runtime/policy/source-mutation-guard.ts +184 -0
- package/packages/piagent-core/runtime/policy/source-worktree-transaction.ts +105 -0
- package/packages/piagent-core/runtime/product/activity-inspector-footer.ts +5 -3
- package/packages/piagent-core/runtime/product/activity-inspector.ts +85 -343
- package/packages/piagent-core/runtime/recovery/resume-state.ts +38 -1
- package/packages/piagent-core/runtime/registration/activity-inspector-command.ts +68 -14
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +22 -27
- package/packages/piagent-core/runtime/session/model-authorship-state.ts +31 -3
- package/packages/piagent-core/runtime/session/runtime-state.ts +11 -1
- package/packages/piagent-core/runtime/session/token-economics.js +34 -0
- package/packages/piagent-core/runtime/tools/phase-tool-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +5 -0
- package/packages/piagent-core/security/sensitive-data.js +7 -0
- package/packages/piagent-webui/dist/client/assets/index-DwKCWbwd.css +1 -0
- package/packages/piagent-webui/dist/client/assets/index-bu7XpD9Q.js +172 -0
- package/packages/piagent-webui/dist/client/index.html +16 -0
- package/packages/piagent-webui/extension/attachment-store.ts +323 -0
- package/packages/piagent-webui/extension/held-message-queue.ts +311 -0
- package/packages/piagent-webui/extension/lifecycle-controller.ts +423 -0
- package/packages/piagent-webui/extension/lifecycle-event-adapter.ts +23 -0
- package/packages/piagent-webui/extension/piagent-webui.ts +470 -0
- package/packages/piagent-webui/extension/review-controller.ts +160 -0
- package/packages/piagent-webui/extension/same-session-bridge.ts +500 -0
- package/packages/piagent-webui/extension/session-options-controller.ts +281 -0
- package/packages/piagent-webui/extension/session-stream-adapter.ts +249 -0
- package/packages/piagent-webui/extension/source-mutation-controller.ts +215 -0
- package/packages/piagent-webui/extension/source-open-controller.ts +136 -0
- package/packages/piagent-webui/extension/source-revert-controller.ts +189 -0
- package/packages/piagent-webui/extension/terminal-session-adapter.ts +73 -0
- package/packages/piagent-webui/extension/vscode-handoff.ts +51 -0
- package/packages/piagent-webui/gateway/control-socket.ts +92 -0
- package/packages/piagent-webui/gateway/extension-authority.ts +47 -0
- package/packages/piagent-webui/gateway/gateway-events.ts +60 -0
- package/packages/piagent-webui/gateway/gateway-protocol-service.ts +64 -0
- package/packages/piagent-webui/gateway/gateway-service.ts +215 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +110 -0
- package/packages/piagent-webui/gateway/mcp-auth-broker.ts +158 -0
- package/packages/piagent-webui/gateway/native-project-picker.ts +29 -0
- package/packages/piagent-webui/gateway/pi-host.ts +41 -0
- package/packages/piagent-webui/gateway/profile-state.ts +1 -0
- package/packages/piagent-webui/gateway/project-registry.ts +108 -0
- package/packages/piagent-webui/gateway/provider-auth-broker.ts +172 -0
- package/packages/piagent-webui/gateway/session-catalog.ts +128 -0
- package/packages/piagent-webui/gateway/session-command-controller.ts +237 -0
- package/packages/piagent-webui/gateway/session-command-store.ts +224 -0
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +239 -0
- package/packages/piagent-webui/gateway/session-lease-store.ts +1 -0
- package/packages/piagent-webui/gateway/session-metadata-store.ts +152 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +489 -0
- package/packages/piagent-webui/ownership/gateway-runtime-context.ts +8 -0
- package/packages/piagent-webui/ownership/profile-state.ts +117 -0
- package/packages/piagent-webui/ownership/session-lease-store.ts +329 -0
- package/packages/piagent-webui/ownership/session-refs.ts +8 -0
- package/packages/piagent-webui/package.json +33 -0
- package/packages/piagent-webui/server/README.md +4 -0
- package/packages/piagent-webui/server/benchmark-release-monitor.ts +230 -0
- package/packages/piagent-webui/server/core-inspection-provider.ts +403 -0
- package/packages/piagent-webui/server/gateway-websocket.ts +143 -0
- package/packages/piagent-webui/server/http-security.ts +27 -0
- package/packages/piagent-webui/server/ipc-read-model-client.ts +89 -0
- package/packages/piagent-webui/server/loopback-server.ts +368 -0
- package/packages/piagent-webui/server/read-model-provider.ts +37 -0
- package/packages/piagent-webui/server/read-only-router.ts +105 -0
- package/packages/piagent-webui/server/session-auth.ts +88 -0
- package/packages/piagent-webui/server/sidecar-main.ts +86 -0
- package/packages/piagent-webui/server/sse-hub.ts +76 -0
- package/packages/piagent-webui/server/static-bundle.ts +40 -0
- package/packages/piagent-webui/server/transcript-projection.ts +125 -0
- package/schemas/mutation-provenance-record.schema.json +100 -0
- package/schemas/piagent-webui/approval-v1.schema.json +654 -0
- package/schemas/piagent-webui/attachment-v1.schema.json +267 -0
- package/schemas/piagent-webui/capabilities-v1.schema.json +729 -0
- package/schemas/piagent-webui/catalog-v1.json +240 -0
- package/schemas/piagent-webui/commit-summary-v1.schema.json +63 -0
- package/schemas/piagent-webui/common-v1.schema.json +479 -0
- package/schemas/piagent-webui/control-command-v1.schema.json +1780 -0
- package/schemas/piagent-webui/diff-v1.schema.json +826 -0
- package/schemas/piagent-webui/gateway-capabilities-v1.schema.json +136 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +456 -0
- package/schemas/piagent-webui/handoff-history-v1.schema.json +92 -0
- package/schemas/piagent-webui/model-catalog-v1.schema.json +95 -0
- package/schemas/piagent-webui/queue-v1.schema.json +99 -0
- package/schemas/piagent-webui/recovery-history-v1.schema.json +144 -0
- package/schemas/piagent-webui/release-monitor-v1.schema.json +173 -0
- package/schemas/piagent-webui/review-state-v1.schema.json +206 -0
- package/schemas/piagent-webui/runtime-event-v2.schema.json +5206 -0
- package/schemas/piagent-webui/session-catalog-v1.schema.json +222 -0
- package/schemas/piagent-webui/session-command-v1.schema.json +390 -0
- package/schemas/piagent-webui/snapshot-v1.schema.json +2622 -0
- package/schemas/piagent-webui/source-change-v1.schema.json +763 -0
- package/schemas/piagent-webui/source-mutation-v1.schema.json +96 -0
- package/schemas/piagent-webui/source-revert-v1.schema.json +130 -0
- package/schemas/piagent-webui/subagent-tree-v1.schema.json +88 -0
- package/schemas/piagent-webui/task-index-v1.schema.json +74 -0
- package/schemas/piagent-webui/task-timeline-v1.schema.json +49 -0
- package/schemas/piagent-webui/transcript-v1.schema.json +243 -0
- package/schemas/task-baseline-manifest.schema.json +122 -0
- package/schemas/verifier-file-snapshot.schema.json +81 -0
- package/scripts/build-docs-site.mjs +21 -1
- package/scripts/check-architecture.mjs +1 -0
- package/scripts/check-doc-languages.mjs +19 -2
- package/scripts/check-third-party-neutrality.mjs +43 -0
- package/scripts/explain-command.mjs +240 -0
- package/scripts/install-global.sh +105 -4
- package/scripts/mcp-manage.mjs +17 -4
- package/scripts/pi-session-stats.sh +10 -2
- package/scripts/piagent-cli.mjs +30 -3
- package/scripts/piagent-dashboard.mjs +261 -0
- package/scripts/piagent-webui-launcher.mjs +121 -0
- package/scripts/setup.sh +69 -33
- package/scripts/traffic-snapshot.mjs +214 -0
- package/scripts/typescript-loader.mjs +42 -1
- package/scripts/uninstall-global.sh +53 -2
- package/scripts/update-global.mjs +33 -1
- package/scripts/verify-local.sh +257 -0
- package/scripts/verify-release-identity.mjs +4 -1
- package/scripts/verify-translation-pairing.mjs +121 -0
|
@@ -11,18 +11,14 @@ import {
|
|
|
11
11
|
evaluateExecPolicyCore, extractShellGlobCandidates, extractShellPathCandidates, findProtectedPathInCommand, unresolvedPathExpansions,
|
|
12
12
|
globMatchesPath, matchesProtectedPath, normalizePathCandidate, matchesAnyPath
|
|
13
13
|
} from "./policy-core.js";
|
|
14
|
-
import { extractShellWritePathCandidates, shellHasFileWriteRedirection } from "./shell-write-targets.js";
|
|
15
|
-
import {
|
|
16
|
-
commandMatchesVerifyPlan,
|
|
17
|
-
createBashResultLedger,
|
|
18
|
-
findMatchingObservedBashResult,
|
|
19
|
-
readObservedBashResults
|
|
20
|
-
} from "./runtime-evidence.js";
|
|
21
14
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from "./
|
|
15
|
+
expandSimpleGlobAlternatives, findResolvedProtectedPathInCommand, normalizeRelative, protectedPatternExamples,
|
|
16
|
+
resolveRepositoryPathCandidate, shellGlobMatchesPath, shellGlobSegmentMatches, shellGlobTargetsProtectedPath,
|
|
17
|
+
unresolvedExpansionReason
|
|
18
|
+
} from "./shell-reach.ts";
|
|
19
|
+
import { extractShellWritePathCandidates, shellHasFileWriteRedirection } from "./shell-write-targets.js";
|
|
20
|
+
import { commandMatchesVerifyPlan, createBashResultLedger, findMatchingObservedBashResult, readObservedBashResults } from "./runtime-evidence.js";
|
|
21
|
+
import { findPackageRoot, findPlatformRoot, readJsonFile } from "./guard-io.js";
|
|
26
22
|
import {
|
|
27
23
|
DOCUMENT_EXTENSIONS,
|
|
28
24
|
extractDocument,
|
|
@@ -94,6 +90,9 @@ import { completeTaskDigestRefresh } from "./task-digest-migration.js";
|
|
|
94
90
|
import { WORKING_TREE_DIGEST_ALGORITHM, isCurrentWorkingTreeDigest, workingTreeObservation, workingTreeSnapshotUsesCurrentAlgorithm } from "./working-tree-digest.js";
|
|
95
91
|
import { appendJsonlBounded } from "./state-retention.js";
|
|
96
92
|
import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
|
|
93
|
+
import { piApprovalBroker, type ApprovalActionDraft } from "../runtime/inspection/approval-broker.ts";
|
|
94
|
+
import { inspectTaskControlState } from "../runtime/inspection/task-control-journal.ts";
|
|
95
|
+
import { createSourceMutationGuardBindings } from "../runtime/policy/source-mutation-guard-binding.ts";
|
|
97
96
|
import {
|
|
98
97
|
TOOL_RESULT_CAPTURE_MAX_CHARS,
|
|
99
98
|
TOOL_RESULT_COMPACT_CHAR_THRESHOLD,
|
|
@@ -160,6 +159,7 @@ import { buildLiveTaskStatus, formatLiveTaskStatus } from "../runtime/product/op
|
|
|
160
159
|
import { buildTaskEfficiencyMetrics } from "../runtime/product/efficiency-metrics.ts";
|
|
161
160
|
import { performanceReviewToolDecision, performanceReviewToolKind } from "../runtime/quality/performance-assurance.ts";
|
|
162
161
|
import { expectedModelMutationProof } from "../runtime/quality/model-mutation-proof.ts";
|
|
162
|
+
import { captureVerifierFileSnapshot } from "../runtime/inspection/verifier-snapshot-store.ts";
|
|
163
163
|
import { prefixCompletions, registerPiagentTool, registerRuntimeCommand, registerRuntimeTool } from "../runtime/registration/extension-registration.ts";
|
|
164
164
|
import { FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP, ONBOARDING_COMMAND_ACTIONS, WORKFLOW_COMMAND_EXCLUSIONS } from "../runtime/registration/operator-catalogs.ts";
|
|
165
165
|
import { registerPiagentStatusCommand } from "../runtime/registration/runtime-model-status.ts";
|
|
@@ -379,10 +379,16 @@ const SHELL_TOOL_NAMES = new Set(["bash", "shell", "exec"]);
|
|
|
379
379
|
const MAX_MCP_PROXY_ARGS_CHARS = 131_072;
|
|
380
380
|
const SESSION_PERMISSION_OVERRIDES = new Map<string, PermissionProfileMode>();
|
|
381
381
|
|
|
382
|
+
// The policy used when `policies/base-policy.json` cannot be read. It runs in
|
|
383
|
+
// exactly the case where something is already wrong, so it must never be the
|
|
384
|
+
// looser of the two: this copy had drifted behind the file and silently dropped
|
|
385
|
+
// `.pi/piagent-state/**` -- the guard's own state -- along with the `sudo ` and
|
|
386
|
+
// `chmod -R 777` blocks. A missing policy file is a reason to be stricter, not a
|
|
387
|
+
// quiet downgrade. `tests/policy-core.test.mjs` fails if this drifts again.
|
|
382
388
|
const DEFAULT_POLICY: BasePolicy = {
|
|
383
|
-
protectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
|
|
384
|
-
shellProtectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
|
|
385
|
-
blockedCommandPatterns: ["rm -rf /", "rm -rf ~", "rm -rf $HOME", "git reset --hard", "git clean -fd"],
|
|
389
|
+
protectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", "**/node_modules/**", "**/dist/**", ".pi/piagent-state/**", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
|
|
390
|
+
shellProtectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", ".pi/piagent-state/**", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
|
|
391
|
+
blockedCommandPatterns: ["rm -rf /", "rm -rf ~", "rm -rf $HOME", "git reset --hard", "git clean -fd", "sudo ", "chmod -R 777"],
|
|
386
392
|
requireConfirmationPatterns: ["deploy", "release", "publish", "migration", "gh pr merge", "git push"],
|
|
387
393
|
defaultRequiredContext: ["AGENTS.md", "README.md"],
|
|
388
394
|
permissionProfiles: {
|
|
@@ -651,26 +657,6 @@ function legacyProjectStateWarning(cwd: string): string | undefined {
|
|
|
651
657
|
: `${detail} No .pi/piagent-profile.json exists, so this project is running unprofiled and its protected paths and verify commands are NOT enforced. Convert it with \`piagent-migrate . --apply\`.`;
|
|
652
658
|
}
|
|
653
659
|
|
|
654
|
-
function normalizeRelative(cwd: string, candidate: unknown): string | undefined {
|
|
655
|
-
if (typeof candidate !== "string" || candidate.trim().length === 0) return undefined;
|
|
656
|
-
let raw = candidate.trim();
|
|
657
|
-
try {
|
|
658
|
-
raw = decodeURIComponent(raw);
|
|
659
|
-
} catch {
|
|
660
|
-
return undefined;
|
|
661
|
-
}
|
|
662
|
-
if (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(raw)) {
|
|
663
|
-
if (!raw.toLowerCase().startsWith("file://")) return undefined;
|
|
664
|
-
try {
|
|
665
|
-
raw = fileURLToPath(raw);
|
|
666
|
-
} catch {
|
|
667
|
-
return undefined;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
const absolute = path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
|
|
671
|
-
return path.relative(cwd, absolute).split(path.sep).join("/");
|
|
672
|
-
}
|
|
673
|
-
|
|
674
660
|
function slugify(input: string): string {
|
|
675
661
|
return input
|
|
676
662
|
.trim()
|
|
@@ -962,60 +948,6 @@ function inspectRepositoryPathBoundary(cwd: string, candidate: string): { reason
|
|
|
962
948
|
return {};
|
|
963
949
|
}
|
|
964
950
|
|
|
965
|
-
function resolveRepositoryPathCandidate(cwd: string, candidate: string): string | undefined {
|
|
966
|
-
const normalized = normalizePathCandidate(candidate);
|
|
967
|
-
if (normalized === ".." || normalized.startsWith("../")) return undefined;
|
|
968
|
-
|
|
969
|
-
const relative = path.posix.isAbsolute(normalized)
|
|
970
|
-
? path.relative(cwd, normalized).split(path.sep).join("/")
|
|
971
|
-
: normalized;
|
|
972
|
-
if (relative === ".." || relative.startsWith("../")) return undefined;
|
|
973
|
-
|
|
974
|
-
const pending = relative.split("/").filter((item) => item && item !== ".");
|
|
975
|
-
let current = cwd;
|
|
976
|
-
let resolvedDepth = 0;
|
|
977
|
-
for (let index = 0; index < pending.length; index += 1) {
|
|
978
|
-
const next = path.join(current, pending[index]);
|
|
979
|
-
try {
|
|
980
|
-
fs.lstatSync(next);
|
|
981
|
-
current = next;
|
|
982
|
-
resolvedDepth = index + 1;
|
|
983
|
-
} catch (error) {
|
|
984
|
-
const code = error && typeof error === "object" && "code" in error ? String((error as { code?: unknown }).code) : "";
|
|
985
|
-
if (code !== "ENOENT") return undefined;
|
|
986
|
-
break;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
let canonicalBase: string;
|
|
991
|
-
try {
|
|
992
|
-
canonicalBase = fs.realpathSync.native(current);
|
|
993
|
-
} catch {
|
|
994
|
-
return undefined;
|
|
995
|
-
}
|
|
996
|
-
const canonical = path.resolve(canonicalBase, ...pending.slice(resolvedDepth));
|
|
997
|
-
const canonicalRoot = fs.realpathSync.native(cwd);
|
|
998
|
-
const canonicalRelative = path.relative(canonicalRoot, canonical).split(path.sep).join("/");
|
|
999
|
-
if (canonicalRelative === ".." || canonicalRelative.startsWith("../") || path.isAbsolute(canonicalRelative)) return undefined;
|
|
1000
|
-
return canonicalRelative || ".";
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
function findResolvedProtectedPathInCommand(
|
|
1004
|
-
cwd: string,
|
|
1005
|
-
command: string,
|
|
1006
|
-
protectedPatterns: string[]
|
|
1007
|
-
): { candidate: string; resolved: string; pattern: string } | undefined {
|
|
1008
|
-
for (const candidate of extractShellPathCandidates(command)) {
|
|
1009
|
-
const relative = normalizeRelative(cwd, candidate);
|
|
1010
|
-
if (!relative) continue;
|
|
1011
|
-
const resolved = resolveRepositoryPathCandidate(cwd, relative);
|
|
1012
|
-
if (!resolved || resolved === relative) continue;
|
|
1013
|
-
const pattern = matchesProtectedPath(resolved, protectedPatterns);
|
|
1014
|
-
if (pattern) return { candidate, resolved, pattern };
|
|
1015
|
-
}
|
|
1016
|
-
return undefined;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
951
|
const MAX_TOOL_INPUT_INSPECTION_DEPTH = 32;
|
|
1020
952
|
|
|
1021
953
|
type StringInputWalkResult = {
|
|
@@ -1093,130 +1025,6 @@ function extractLikelyPathFromInput(cwd: string, input: Record<string, unknown>)
|
|
|
1093
1025
|
return extractPathInputsFromInput(cwd, input)[0]?.path;
|
|
1094
1026
|
}
|
|
1095
1027
|
|
|
1096
|
-
function expandSimpleGlobAlternatives(pattern: string, max = 24): { values: string[]; complete: boolean } {
|
|
1097
|
-
let results = [pattern];
|
|
1098
|
-
let changed = true;
|
|
1099
|
-
let complete = true;
|
|
1100
|
-
|
|
1101
|
-
while (changed) {
|
|
1102
|
-
changed = false;
|
|
1103
|
-
const expanded: string[] = [];
|
|
1104
|
-
for (const item of results) {
|
|
1105
|
-
const match = item.match(/\{([^{}]+)\}/);
|
|
1106
|
-
if (!match) {
|
|
1107
|
-
expanded.push(item);
|
|
1108
|
-
continue;
|
|
1109
|
-
}
|
|
1110
|
-
changed = true;
|
|
1111
|
-
const options = match[1].split(",").map((option) => option.trim());
|
|
1112
|
-
for (const option of options) {
|
|
1113
|
-
expanded.push(`${item.slice(0, match.index)}${option}${item.slice((match.index ?? 0) + match[0].length)}`);
|
|
1114
|
-
if (expanded.length >= max) {
|
|
1115
|
-
complete = false;
|
|
1116
|
-
break;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
if (expanded.length >= max) break;
|
|
1120
|
-
}
|
|
1121
|
-
results = expanded.slice(0, max);
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
return { values: results, complete };
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
function shellGlobSegmentMatches(patternSegment: string, candidateSegment: string): boolean {
|
|
1128
|
-
if (candidateSegment.startsWith(".") && !patternSegment.startsWith(".")) return false;
|
|
1129
|
-
let source = "";
|
|
1130
|
-
for (let index = 0; index < patternSegment.length; index += 1) {
|
|
1131
|
-
const char = patternSegment[index];
|
|
1132
|
-
if (char === "*") {
|
|
1133
|
-
source += "[^/]*";
|
|
1134
|
-
continue;
|
|
1135
|
-
}
|
|
1136
|
-
if (char === "?") {
|
|
1137
|
-
source += "[^/]";
|
|
1138
|
-
continue;
|
|
1139
|
-
}
|
|
1140
|
-
if (char === "[") {
|
|
1141
|
-
const end = patternSegment.indexOf("]", index + 1);
|
|
1142
|
-
const body = end > index + 1 ? patternSegment.slice(index + 1, end) : "";
|
|
1143
|
-
if (body && /^[!^A-Za-z0-9_-]+$/.test(body)) {
|
|
1144
|
-
const negated = body.startsWith("!") ? `^${body.slice(1)}` : body;
|
|
1145
|
-
source += `[${negated}]`;
|
|
1146
|
-
index = end;
|
|
1147
|
-
continue;
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
source += char.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
1151
|
-
}
|
|
1152
|
-
return new RegExp(`^${source}$`, "i").test(candidateSegment);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
function shellGlobMatchesPath(pattern: string, candidate: string): boolean {
|
|
1156
|
-
const patternSegments = normalizePathCandidate(pattern).split("/").filter(Boolean);
|
|
1157
|
-
const candidateSegments = normalizePathCandidate(candidate).split("/").filter(Boolean);
|
|
1158
|
-
|
|
1159
|
-
function match(patternIndex: number, candidateIndex: number): boolean {
|
|
1160
|
-
if (patternIndex === patternSegments.length) return candidateIndex === candidateSegments.length;
|
|
1161
|
-
const patternSegment = patternSegments[patternIndex];
|
|
1162
|
-
if (patternSegment === "**") {
|
|
1163
|
-
if (match(patternIndex + 1, candidateIndex)) return true;
|
|
1164
|
-
for (let next = candidateIndex; next < candidateSegments.length; next += 1) {
|
|
1165
|
-
if (match(patternIndex + 1, next + 1)) return true;
|
|
1166
|
-
}
|
|
1167
|
-
return false;
|
|
1168
|
-
}
|
|
1169
|
-
if (candidateIndex >= candidateSegments.length) return false;
|
|
1170
|
-
return shellGlobSegmentMatches(patternSegment, candidateSegments[candidateIndex])
|
|
1171
|
-
&& match(patternIndex + 1, candidateIndex + 1);
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
return patternSegments.length > 0 && candidateSegments.length > 0 && match(0, 0);
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
function protectedPatternExamples(pattern: string): string[] {
|
|
1178
|
-
const normalized = normalizePathCandidate(pattern);
|
|
1179
|
-
if (!normalized) return [];
|
|
1180
|
-
|
|
1181
|
-
const examples = new Set<string>();
|
|
1182
|
-
const add = (value: string | undefined) => {
|
|
1183
|
-
const normalizedValue = normalizePathCandidate(value ?? "");
|
|
1184
|
-
if (normalizedValue) examples.add(normalizedValue);
|
|
1185
|
-
};
|
|
1186
|
-
|
|
1187
|
-
add(normalized);
|
|
1188
|
-
|
|
1189
|
-
if (normalized.endsWith("/**")) {
|
|
1190
|
-
const base = normalized.slice(0, -3);
|
|
1191
|
-
add(base);
|
|
1192
|
-
add(`${base}/probe`);
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
if (normalized.startsWith("**/")) {
|
|
1196
|
-
const tail = normalized.slice(3);
|
|
1197
|
-
const concreteTail = tail
|
|
1198
|
-
.replace(/\*\*/g, "nested")
|
|
1199
|
-
.replace(/\*/g, tail.includes(".env.") ? "local" : "probe");
|
|
1200
|
-
add(tail);
|
|
1201
|
-
add(concreteTail);
|
|
1202
|
-
add(`nested/${concreteTail}`);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
const concrete = normalized
|
|
1206
|
-
.replace(/^\*\*\//, "")
|
|
1207
|
-
.replace(/\/\*\*$/, "/probe")
|
|
1208
|
-
.replace(/\*\*/g, "nested")
|
|
1209
|
-
.replace(/\*/g, normalized.includes(".env.") ? "local" : "probe");
|
|
1210
|
-
add(concrete);
|
|
1211
|
-
|
|
1212
|
-
for (const example of [...examples]) {
|
|
1213
|
-
const base = path.posix.basename(example);
|
|
1214
|
-
if (base && base !== "probe") examples.add(base);
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
return [...examples];
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
1028
|
function protectedLiteralHints(pattern: string): string[] {
|
|
1221
1029
|
const normalized = normalizePathCandidate(pattern).toLowerCase();
|
|
1222
1030
|
if (!normalized) return [];
|
|
@@ -1276,32 +1084,6 @@ function globTargetsProtectedPath(glob: unknown, protectedPatterns: string[]): {
|
|
|
1276
1084
|
return undefined;
|
|
1277
1085
|
}
|
|
1278
1086
|
|
|
1279
|
-
function shellGlobTargetsProtectedPath(
|
|
1280
|
-
command: string,
|
|
1281
|
-
protectedPatterns: string[]
|
|
1282
|
-
): { glob: string; pattern: string; example: string } | undefined {
|
|
1283
|
-
for (const candidate of extractShellGlobCandidates(command)) {
|
|
1284
|
-
if (!/[*?{\[]/.test(candidate)) continue;
|
|
1285
|
-
const expanded = expandSimpleGlobAlternatives(candidate);
|
|
1286
|
-
if (!expanded.complete) return { glob: candidate, pattern: "bounded glob expansion", example: "a protected path" };
|
|
1287
|
-
for (const candidateGlob of expanded.values) {
|
|
1288
|
-
for (const pattern of protectedPatterns) {
|
|
1289
|
-
for (const example of protectedPatternExamples(pattern)) {
|
|
1290
|
-
if (/[*?{\[\]]/.test(example)) continue;
|
|
1291
|
-
if (
|
|
1292
|
-
shellGlobMatchesPath(candidateGlob, example)
|
|
1293
|
-
|| shellGlobMatchesPath(`**/${candidateGlob}`, example)
|
|
1294
|
-
|| shellGlobMatchesPath(candidateGlob, path.posix.basename(example))
|
|
1295
|
-
) {
|
|
1296
|
-
return { glob: candidate, pattern, example };
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
return undefined;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
1087
|
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
1306
1088
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1307
1089
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -2188,10 +1970,12 @@ function recordObservedTaskVerification(
|
|
|
2188
1970
|
isError?: boolean;
|
|
2189
1971
|
recordedAt?: string;
|
|
2190
1972
|
outputText?: string;
|
|
1973
|
+
toolCallId?: string;
|
|
2191
1974
|
},
|
|
2192
1975
|
pendingContext: ObservedTaskContext[],
|
|
2193
1976
|
maxManifestFiles: number, shellSnapshotBefore?: Record<string, string>,
|
|
2194
|
-
eventTree?: ReturnType<typeof workingTreeObservation
|
|
1977
|
+
eventTree?: ReturnType<typeof workingTreeObservation>,
|
|
1978
|
+
readProtectedPaths: string[] = []
|
|
2195
1979
|
): TaskContract | undefined {
|
|
2196
1980
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
2197
1981
|
if (!task || task.trace.outcome !== "pending") return;
|
|
@@ -2220,19 +2004,31 @@ function recordObservedTaskVerification(
|
|
|
2220
2004
|
));
|
|
2221
2005
|
const contextAdded = mergeObservedTaskContext(task, pendingContext, maxManifestFiles, redactText);
|
|
2222
2006
|
if (!duplicate) {
|
|
2007
|
+
const evidenceRecordedAt = nowIso();
|
|
2008
|
+
const observedAt = observed.recordedAt ?? evidenceRecordedAt;
|
|
2223
2009
|
task.verifyEvidence.push({
|
|
2224
2010
|
command: redactText(command),
|
|
2225
2011
|
exitCode,
|
|
2226
2012
|
summary: `Runtime observed configured verifier exit ${exitCode} (${classification.category}${classification.retryable ? ", retryable" : ""}).`,
|
|
2227
|
-
recordedAt:
|
|
2013
|
+
recordedAt: evidenceRecordedAt,
|
|
2228
2014
|
observed: true,
|
|
2229
|
-
observedAt
|
|
2015
|
+
observedAt,
|
|
2230
2016
|
isError: observed.isError === true,
|
|
2231
2017
|
matchedProfileCommand: true,
|
|
2232
2018
|
preWorkingTreeDigest,
|
|
2233
2019
|
workingTreeDigest: currentDigest
|
|
2234
2020
|
});
|
|
2235
2021
|
task.verifyEvidence = task.verifyEvidence.slice(-100);
|
|
2022
|
+
try {
|
|
2023
|
+
captureVerifierFileSnapshot({
|
|
2024
|
+
projectRoot: ctx.cwd, taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId,
|
|
2025
|
+
toolCallId: observed.toolCallId ?? "", commandHash: observed.commandHash, observedAt,
|
|
2026
|
+
capturedAt: evidenceRecordedAt, exitCode, treeDigest: currentDigest, snapshot: currentDigests,
|
|
2027
|
+
protectedPaths: readProtectedPaths
|
|
2028
|
+
});
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
ctx.ui.notify(`Piagent could not persist verifier file snapshot: ${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
2031
|
+
}
|
|
2236
2032
|
}
|
|
2237
2033
|
|
|
2238
2034
|
const hasChanges = taskChangedFileEvidence(ctx.cwd, task, currentDigests).expected.length > 0;
|
|
@@ -2541,13 +2337,6 @@ function evaluateMcpApproval(
|
|
|
2541
2337
|
// running the substitution. Matching the literal half against the protected
|
|
2542
2338
|
// patterns answers a different question than the one being asked, so refuse
|
|
2543
2339
|
// rather than let an unknown filename through unchecked.
|
|
2544
|
-
function unresolvedExpansionReason(subject: string, words: string[]): string {
|
|
2545
|
-
const listed = words.map((word) => `\`${word}\``).join(", ");
|
|
2546
|
-
return `${subject} builds a filename this guard cannot resolve: ${listed}. `
|
|
2547
|
-
+ "The literal text around the expansion makes it a path, but its value is only known at run time, "
|
|
2548
|
-
+ "so it cannot be checked against the protected paths. Write the path out, or put the expansion in its own argument.";
|
|
2549
|
-
}
|
|
2550
|
-
|
|
2551
2340
|
function evaluateMcpProxyShellProtectedAccess(
|
|
2552
2341
|
cwd: string,
|
|
2553
2342
|
prepared: PreparedToolInput,
|
|
@@ -4034,6 +3823,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4034
3823
|
const runtimeState = new RuntimeSessionState({
|
|
4035
3824
|
maxObservedContext: contextBudgetConfig(policy).maxManifestFiles
|
|
4036
3825
|
});
|
|
3826
|
+
const sourceMutationGuardBindings = createSourceMutationGuardBindings(policy, loadProfileFromContext);
|
|
4037
3827
|
const runtimeSnapshotCapture = new RuntimeSnapshotCapture(), runtimeVersions = readRuntimeVersionMetadata(PLATFORM_ROOT);
|
|
4038
3828
|
const solverShadow = solverMode === "off" ? undefined : new SolverShadowRuntime(solverMode);
|
|
4039
3829
|
const modelRouteRuntime = new ModelRouteRuntime(parentRoutingMode, routingObjective);
|
|
@@ -4242,6 +4032,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4242
4032
|
const activityInspector = registerActivityInspector(pi, {
|
|
4243
4033
|
activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
|
|
4244
4034
|
readEvents: (cwd) => readContextTelemetry(cwd, { limit: 5_000 }) as any[],
|
|
4035
|
+
protectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfile(ctx.cwd)).readProtectedPaths,
|
|
4245
4036
|
selectAction: selectRuntimeAction,
|
|
4246
4037
|
emit: emitRuntimeMessage
|
|
4247
4038
|
});
|
|
@@ -4268,7 +4059,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4268
4059
|
mcpReadinessNotice,
|
|
4269
4060
|
updateAvailabilityNotice,
|
|
4270
4061
|
contextExcludePatterns: (profile) => contextIndexExcludePatterns(policy, profile),
|
|
4271
|
-
inspectResume: inspectTaskResumeState,
|
|
4062
|
+
inspectResume: (cwd, task, sessionId) => inspectTaskResumeState(cwd, task, sessionId, undefined, {
|
|
4063
|
+
protectedPaths: effectiveProtectedPaths(policy, loadProfile(cwd)).readProtectedPaths
|
|
4064
|
+
}),
|
|
4272
4065
|
syncTrajectory: (ctx, task) => {
|
|
4273
4066
|
const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
|
|
4274
4067
|
const resume = workingTreeSnapshotHasUnavailableEvidence(snapshot)
|
|
@@ -4286,7 +4079,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4286
4079
|
})));
|
|
4287
4080
|
},
|
|
4288
4081
|
telemetry,
|
|
4289
|
-
afterStart: activityInspector.refresh
|
|
4082
|
+
afterStart: async (ctx) => { sourceMutationGuardBindings.bind(ctx); await activityInspector.refresh(ctx); }
|
|
4290
4083
|
});
|
|
4291
4084
|
|
|
4292
4085
|
registerSessionHooks(pi, {
|
|
@@ -4301,6 +4094,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4301
4094
|
onTurnEnd: activityInspector.refresh,
|
|
4302
4095
|
onAgentSettled: activityInspector.refresh,
|
|
4303
4096
|
beforeShutdown: (ctx) => {
|
|
4097
|
+
sourceMutationGuardBindings.unbind(ctx);
|
|
4304
4098
|
activityInspector.dispose(ctx);
|
|
4305
4099
|
sessionCapabilityDigests.delete(`${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`);
|
|
4306
4100
|
}
|
|
@@ -4497,6 +4291,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4497
4291
|
if (reviewDecision) runtimeState.denyPerformanceReviewTool(task.taskRunId);
|
|
4498
4292
|
return reviewDecision;
|
|
4499
4293
|
},
|
|
4294
|
+
beforeStart: (_event, ctx) => {
|
|
4295
|
+
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4296
|
+
const control = task?.trace.outcome === "pending" ? inspectTaskControlState(ctx.cwd, task) : null;
|
|
4297
|
+
if (control?.dispatchBlocked) return { block: true, reason: `Task lifecycle control blocks tool start while state is ${control.state}.` };
|
|
4298
|
+
},
|
|
4500
4299
|
afterDecision: (_event, ctx, metadata) => {
|
|
4501
4300
|
if (metadata.allowed) return;
|
|
4502
4301
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
@@ -4631,13 +4430,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4631
4430
|
if (approvalDecision.block) {
|
|
4632
4431
|
return { block: true, reason: approvalDecision.reason };
|
|
4633
4432
|
}
|
|
4634
|
-
|
|
4635
4433
|
let shellProjectMutation = false;
|
|
4636
4434
|
let shellMutationTargets: string[] = [];
|
|
4637
4435
|
let shellMutationTargetBounded = false;
|
|
4638
4436
|
let configuredVerifierShell = false;
|
|
4639
4437
|
let authorizedShellCommand = "";
|
|
4640
4438
|
let authorizedShellSegments: Array<{ words: string[] }> = [];
|
|
4439
|
+
let pendingHumanApproval: { prompt: string; title: string; action: Omit<ApprovalActionDraft, "treePrecondition"> } | null = null;
|
|
4641
4440
|
if (SHELL_TOOL_NAMES.has(event.toolName)) {
|
|
4642
4441
|
const shellInput = extractShellCommandInput(toolInput);
|
|
4643
4442
|
if (!shellInput.command) {
|
|
@@ -4695,17 +4494,14 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4695
4494
|
return { block: true, reason: unresolvedExpansionReason("Command", unresolvedExpansions) };
|
|
4696
4495
|
}
|
|
4697
4496
|
|
|
4698
|
-
const confirmationReasons = execDecision.mode !== "off" && execDecision.decision === "prompt"
|
|
4699
|
-
? [...execDecision.reasons]
|
|
4700
|
-
: [];
|
|
4497
|
+
const confirmationReasons = execDecision.mode !== "off" && execDecision.decision === "prompt" ? [...execDecision.reasons] : [];
|
|
4701
4498
|
const externalReason = findShellExternalConfirmationReason(execDecision.segments, externalActionPolicyConfig(policy));
|
|
4702
4499
|
if (externalReason) confirmationReasons.push(externalReason);
|
|
4703
4500
|
if (confirmationReasons.length > 0) {
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
if (!ok) return { block: true, reason: `User denied command: ${confirmationReasons.join("; ")}` };
|
|
4501
|
+
pendingHumanApproval = { prompt: `Command requires confirmation.\n\n${confirmationReasons.join("\n")}\n\nAllow?`, title: "Piagent exec policy confirmation",
|
|
4502
|
+
action: { kind: externalReason ? "external-provider-action" : "workspace-patch", preconditionClass: externalReason ? "runtime-only" : "workspace-tree", toolName: event.toolName, rawAction: { toolName: event.toolName, command, input: preparedInput.input },
|
|
4503
|
+
commandPreview: command, parameterPreview: preparedInput.confirmationSummary ?? "Shell command", targetPaths: shellMutationTargets, targetSummaries: confirmationReasons, provider: externalReason ? "shell-external" : null, urlOrigin: null,
|
|
4504
|
+
requestedScope: "one-shell-command", reason: confirmationReasons.join("; "), riskClass: shellProjectMutation ? "high" : "medium", allowConsequence: "Run this exact command once after the guard rechecks the task and working tree.", denyConsequence: "Block this command and return the denial to the active Pi operation." } };
|
|
4709
4505
|
}
|
|
4710
4506
|
}
|
|
4711
4507
|
|
|
@@ -4717,7 +4513,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4717
4513
|
if (proxyShellDecision.block) {
|
|
4718
4514
|
return { block: true, reason: proxyShellDecision.reason };
|
|
4719
4515
|
}
|
|
4720
|
-
|
|
4721
4516
|
const policyToolIdentity = preparedInput.proxyToolName ?? event.toolName;
|
|
4722
4517
|
const usesKnownExternalProvider = externalActionPolicyConfig(policy).providerKeywords
|
|
4723
4518
|
.some((provider) => actionTextMatchesAny(policyToolIdentity, [provider]));
|
|
@@ -4754,16 +4549,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4754
4549
|
}
|
|
4755
4550
|
: classifiedExternalAction;
|
|
4756
4551
|
if (externalAction.decision === "confirm") {
|
|
4757
|
-
const inputSummary = preparedInput.confirmationSummary
|
|
4758
|
-
|
|
4759
|
-
: ""
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
"Piagent external action confirmation"
|
|
4763
|
-
);
|
|
4764
|
-
if (!ok) {
|
|
4765
|
-
return { block: true, reason: `User denied external provider action: ${event.toolName}` };
|
|
4766
|
-
}
|
|
4552
|
+
const inputSummary = preparedInput.confirmationSummary ? `\ninput: ${preparedInput.confirmationSummary}` : "";
|
|
4553
|
+
pendingHumanApproval = { prompt: `External provider action requires confirmation.\n\nprovider: ${externalAction.provider}\naction: ${externalAction.action}\ntool: ${event.toolName}${inputSummary}\n\nAllow?`, title: "Piagent external action confirmation",
|
|
4554
|
+
action: { kind: "external-provider-action", preconditionClass: "runtime-only", toolName: event.toolName, rawAction: { toolName: event.toolName, input: preparedInput.input, provider: externalAction.provider, action: externalAction.action },
|
|
4555
|
+
commandPreview: null, parameterPreview: preparedInput.confirmationSummary ?? String(externalAction.action), targetPaths: [], targetSummaries: [], provider: String(externalAction.provider),
|
|
4556
|
+
urlOrigin: null, requestedScope: "one-external-action", reason: `External provider action ${String(externalAction.action)} requires confirmation`, riskClass: "high", allowConsequence: "Release this exact provider action once after the guard rechecks runtime authority.", denyConsequence: "Block the provider action without changing external state." } };
|
|
4767
4557
|
}
|
|
4768
4558
|
}
|
|
4769
4559
|
|
|
@@ -4805,6 +4595,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4805
4595
|
verificationCarrier: SHELL_TOOL_NAMES.has(event.toolName)
|
|
4806
4596
|
});
|
|
4807
4597
|
if (phaseMutationDecision) return phaseMutationDecision;
|
|
4598
|
+
if (pendingHumanApproval && !sessionTask) {
|
|
4599
|
+
const approval = await piApprovalBroker.request({ cwd: ctx.cwd, rawSessionId: ctx.sessionManager.getSessionId(), toolCallId: String(event.toolCallId), expectedTask: null, action: { ...pendingHumanApproval.action, preconditionClass: "runtime-only", treePrecondition: null }, terminalConfirm: () => ctx.ui.confirm(pendingHumanApproval!.prompt, pendingHumanApproval!.title) });
|
|
4600
|
+
if (!approval.allowed) return { block: true, reason: pendingHumanApproval.action.kind === "external-provider-action" && !SHELL_TOOL_NAMES.has(event.toolName) ? `User denied external provider action: ${event.toolName}` : `User denied command: ${pendingHumanApproval.action.reason}` };
|
|
4601
|
+
pendingHumanApproval = null;
|
|
4602
|
+
}
|
|
4808
4603
|
if (sessionTask && sessionTask.trace.outcome !== "pending" && (shellProjectMutation || directProjectMutation)) {
|
|
4809
4604
|
return {
|
|
4810
4605
|
block: true,
|
|
@@ -4814,7 +4609,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4814
4609
|
if (!sessionTask && taskContractRequired && (shellProjectMutation || directProjectMutation)) {
|
|
4815
4610
|
return {
|
|
4816
4611
|
block: true,
|
|
4817
|
-
|
|
4612
|
+
// Naming the tool matters: this is the first wall a new operator hits,
|
|
4613
|
+
// and "start the task first" does not say with what.
|
|
4614
|
+
reason: "A session-bound Task Implementation Contract is required before project files can be mutated."
|
|
4615
|
+
+ " Call `piagent_task_start` once with explicit project-relative scope, then retry."
|
|
4818
4616
|
};
|
|
4819
4617
|
}
|
|
4820
4618
|
if (sessionTask?.trace.outcome === "pending" && sessionTask.changeMode === "source-change") {
|
|
@@ -4855,6 +4653,17 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4855
4653
|
}
|
|
4856
4654
|
}
|
|
4857
4655
|
|
|
4656
|
+
if (pendingHumanApproval) {
|
|
4657
|
+
const currentTreeDigest = semanticSnapshot && !workingTreeSnapshotHasUnavailableEvidence(semanticSnapshot) ? workingTreeEvidenceDigest(semanticSnapshot) : null;
|
|
4658
|
+
const treePrecondition = pendingHumanApproval.action.preconditionClass === "runtime-only" || !currentTreeDigest ? null
|
|
4659
|
+
: { workspaceRevision: `workspace-rev.${crypto.createHash("sha256").update(currentTreeDigest).digest("hex")}`, indexRevision: null, preimageDigest: currentTreeDigest };
|
|
4660
|
+
const approval = await piApprovalBroker.request({ cwd: ctx.cwd, rawSessionId: ctx.sessionManager.getSessionId(), toolCallId: String(event.toolCallId), expectedTask: sessionTask ? { taskId: sessionTask.taskId, taskRunId: sessionTask.taskRunId } : null,
|
|
4661
|
+
action: { ...pendingHumanApproval.action, treePrecondition }, terminalConfirm: () => ctx.ui.confirm(pendingHumanApproval!.prompt, pendingHumanApproval!.title),
|
|
4662
|
+
recheck: () => !treePrecondition || (() => { const current = workingTreeSnapshot(ctx.cwd);
|
|
4663
|
+
return !workingTreeSnapshotHasUnavailableEvidence(current) && workingTreeEvidenceDigest(current) === treePrecondition.preimageDigest; })() });
|
|
4664
|
+
if (!approval.allowed) return { block: true, reason: pendingHumanApproval.action.kind === "external-provider-action" && !SHELL_TOOL_NAMES.has(event.toolName) ? `User denied external provider action: ${event.toolName}` : `User denied command: ${pendingHumanApproval.action.reason}` };
|
|
4665
|
+
if (!approval.consume()) return { block: true, reason: "Approval became stale before tool start; the action was blocked." };
|
|
4666
|
+
}
|
|
4858
4667
|
if (
|
|
4859
4668
|
sessionTask?.trace.outcome === "pending"
|
|
4860
4669
|
&& sessionTask.changeMode === "source-change"
|
|
@@ -4864,7 +4673,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4864
4673
|
}
|
|
4865
4674
|
}
|
|
4866
4675
|
});
|
|
4867
|
-
|
|
4868
4676
|
const registrationDeps = {
|
|
4869
4677
|
CONTEXT_INDEX_EDGE_KINDS, CONTEXT_INDEX_NODE_KINDS, DEFAULT_MAX_TASK_ATTEMPTS, FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP,
|
|
4870
4678
|
ONBOARDING_COMMAND_ACTIONS, ORCHESTRATION_ROLES, PIAGENT_TOOL_NAMES, READ_ONLY_TOOL_NAMES, REVIEW_LENSES,
|
|
@@ -58,11 +58,22 @@ function numericExitCode(value) {
|
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// `isError: false` is Pi stating that the command succeeded, so it is evidence
|
|
62
|
+
// and a claim of zero matches it. An observation that states neither an exit code
|
|
63
|
+
// nor an error flag says nothing at all, and nothing is not evidence of success:
|
|
64
|
+
// without this an empty record satisfied a claim of zero, which is the one
|
|
65
|
+
// direction a ledger built to catch forged verification must never fail in.
|
|
66
|
+
//
|
|
67
|
+
// Both shapes are unreachable through `observedBashResultFromToolResultEvent` and
|
|
68
|
+
// `readObservedBashResults`, which always set a boolean flag. The guard is here
|
|
69
|
+
// because a reader of this function should not have to prove that to know the
|
|
70
|
+
// answer is safe.
|
|
61
71
|
export function claimedExitMatchesObserved(exitCode, observed) {
|
|
62
72
|
const claimed = numericExitCode(exitCode);
|
|
63
73
|
if (claimed === undefined) return false;
|
|
64
74
|
if (typeof observed?.exitCode === "number") return observed.exitCode === claimed;
|
|
65
|
-
|
|
75
|
+
if (typeof observed?.isError !== "boolean") return false;
|
|
76
|
+
return observed.isError ? claimed !== 0 : claimed === 0;
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
export function extractBashCommandFromToolResultEvent(event) {
|