@piagent/platform 1.3.1 → 1.4.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/CHANGELOG.md +39 -0
- package/README.md +3 -3
- 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/herdr-workflow.md +1 -0
- package/docs/mcp-and-tools.md +14 -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 +1 -0
- package/docs/quality-benchmark.md +1 -0
- package/docs/quickstart-vietnamese.md +3 -2
- package/docs/release-install-policy.md +7 -6
- package/docs/runtime-harness-standard.md +1 -0
- 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 +1 -0
- 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 +1 -0
- package/docs/vercel-docs-site.md +1 -0
- package/docs/workflow-recipes.md +1 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +1 -1
- package/docs-site/workflows.html +1 -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 +30 -3
- 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/runtime-integrity.js +143 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +62 -37
- package/packages/piagent-core/extensions/redaction-core.d.ts +2 -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-server-catalog.js +5 -5
- 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 +5 -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 +68 -4
- 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 +13 -14
- 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-webui/dist/client/assets/index-DjMntaK5.js +172 -0
- package/packages/piagent-webui/dist/client/assets/index-DwKCWbwd.css +1 -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 +408 -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/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 +204 -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 +485 -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 +110 -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/check-architecture.mjs +1 -0
- package/scripts/check-doc-languages.mjs +19 -2
- package/scripts/check-third-party-neutrality.mjs +43 -0
- package/scripts/install-global.sh +1 -1
- package/scripts/piagent-cli.mjs +17 -3
- package/scripts/piagent-dashboard.mjs +208 -0
- package/scripts/piagent-webui-launcher.mjs +121 -0
- package/scripts/typescript-loader.mjs +26 -1
- package/scripts/verify-local.sh +253 -0
- package/scripts/verify-release-identity.mjs +4 -1
|
@@ -12,17 +12,8 @@ import {
|
|
|
12
12
|
globMatchesPath, matchesProtectedPath, normalizePathCandidate, matchesAnyPath
|
|
13
13
|
} from "./policy-core.js";
|
|
14
14
|
import { extractShellWritePathCandidates, shellHasFileWriteRedirection } from "./shell-write-targets.js";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
createBashResultLedger,
|
|
18
|
-
findMatchingObservedBashResult,
|
|
19
|
-
readObservedBashResults
|
|
20
|
-
} from "./runtime-evidence.js";
|
|
21
|
-
import {
|
|
22
|
-
findPackageRoot,
|
|
23
|
-
findPlatformRoot,
|
|
24
|
-
readJsonFile
|
|
25
|
-
} from "./guard-io.js";
|
|
15
|
+
import { commandMatchesVerifyPlan, createBashResultLedger, findMatchingObservedBashResult, readObservedBashResults } from "./runtime-evidence.js";
|
|
16
|
+
import { findPackageRoot, findPlatformRoot, readJsonFile } from "./guard-io.js";
|
|
26
17
|
import {
|
|
27
18
|
DOCUMENT_EXTENSIONS,
|
|
28
19
|
extractDocument,
|
|
@@ -94,6 +85,9 @@ import { completeTaskDigestRefresh } from "./task-digest-migration.js";
|
|
|
94
85
|
import { WORKING_TREE_DIGEST_ALGORITHM, isCurrentWorkingTreeDigest, workingTreeObservation, workingTreeSnapshotUsesCurrentAlgorithm } from "./working-tree-digest.js";
|
|
95
86
|
import { appendJsonlBounded } from "./state-retention.js";
|
|
96
87
|
import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
|
|
88
|
+
import { piApprovalBroker, type ApprovalActionDraft } from "../runtime/inspection/approval-broker.ts";
|
|
89
|
+
import { inspectTaskControlState } from "../runtime/inspection/task-control-journal.ts";
|
|
90
|
+
import { createSourceMutationGuardBindings } from "../runtime/policy/source-mutation-guard-binding.ts";
|
|
97
91
|
import {
|
|
98
92
|
TOOL_RESULT_CAPTURE_MAX_CHARS,
|
|
99
93
|
TOOL_RESULT_COMPACT_CHAR_THRESHOLD,
|
|
@@ -160,6 +154,7 @@ import { buildLiveTaskStatus, formatLiveTaskStatus } from "../runtime/product/op
|
|
|
160
154
|
import { buildTaskEfficiencyMetrics } from "../runtime/product/efficiency-metrics.ts";
|
|
161
155
|
import { performanceReviewToolDecision, performanceReviewToolKind } from "../runtime/quality/performance-assurance.ts";
|
|
162
156
|
import { expectedModelMutationProof } from "../runtime/quality/model-mutation-proof.ts";
|
|
157
|
+
import { captureVerifierFileSnapshot } from "../runtime/inspection/verifier-snapshot-store.ts";
|
|
163
158
|
import { prefixCompletions, registerPiagentTool, registerRuntimeCommand, registerRuntimeTool } from "../runtime/registration/extension-registration.ts";
|
|
164
159
|
import { FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP, ONBOARDING_COMMAND_ACTIONS, WORKFLOW_COMMAND_EXCLUSIONS } from "../runtime/registration/operator-catalogs.ts";
|
|
165
160
|
import { registerPiagentStatusCommand } from "../runtime/registration/runtime-model-status.ts";
|
|
@@ -2188,10 +2183,12 @@ function recordObservedTaskVerification(
|
|
|
2188
2183
|
isError?: boolean;
|
|
2189
2184
|
recordedAt?: string;
|
|
2190
2185
|
outputText?: string;
|
|
2186
|
+
toolCallId?: string;
|
|
2191
2187
|
},
|
|
2192
2188
|
pendingContext: ObservedTaskContext[],
|
|
2193
2189
|
maxManifestFiles: number, shellSnapshotBefore?: Record<string, string>,
|
|
2194
|
-
eventTree?: ReturnType<typeof workingTreeObservation
|
|
2190
|
+
eventTree?: ReturnType<typeof workingTreeObservation>,
|
|
2191
|
+
readProtectedPaths: string[] = []
|
|
2195
2192
|
): TaskContract | undefined {
|
|
2196
2193
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
2197
2194
|
if (!task || task.trace.outcome !== "pending") return;
|
|
@@ -2220,19 +2217,31 @@ function recordObservedTaskVerification(
|
|
|
2220
2217
|
));
|
|
2221
2218
|
const contextAdded = mergeObservedTaskContext(task, pendingContext, maxManifestFiles, redactText);
|
|
2222
2219
|
if (!duplicate) {
|
|
2220
|
+
const evidenceRecordedAt = nowIso();
|
|
2221
|
+
const observedAt = observed.recordedAt ?? evidenceRecordedAt;
|
|
2223
2222
|
task.verifyEvidence.push({
|
|
2224
2223
|
command: redactText(command),
|
|
2225
2224
|
exitCode,
|
|
2226
2225
|
summary: `Runtime observed configured verifier exit ${exitCode} (${classification.category}${classification.retryable ? ", retryable" : ""}).`,
|
|
2227
|
-
recordedAt:
|
|
2226
|
+
recordedAt: evidenceRecordedAt,
|
|
2228
2227
|
observed: true,
|
|
2229
|
-
observedAt
|
|
2228
|
+
observedAt,
|
|
2230
2229
|
isError: observed.isError === true,
|
|
2231
2230
|
matchedProfileCommand: true,
|
|
2232
2231
|
preWorkingTreeDigest,
|
|
2233
2232
|
workingTreeDigest: currentDigest
|
|
2234
2233
|
});
|
|
2235
2234
|
task.verifyEvidence = task.verifyEvidence.slice(-100);
|
|
2235
|
+
try {
|
|
2236
|
+
captureVerifierFileSnapshot({
|
|
2237
|
+
projectRoot: ctx.cwd, taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId,
|
|
2238
|
+
toolCallId: observed.toolCallId ?? "", commandHash: observed.commandHash, observedAt,
|
|
2239
|
+
capturedAt: evidenceRecordedAt, exitCode, treeDigest: currentDigest, snapshot: currentDigests,
|
|
2240
|
+
protectedPaths: readProtectedPaths
|
|
2241
|
+
});
|
|
2242
|
+
} catch (error) {
|
|
2243
|
+
ctx.ui.notify(`Piagent could not persist verifier file snapshot: ${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
2244
|
+
}
|
|
2236
2245
|
}
|
|
2237
2246
|
|
|
2238
2247
|
const hasChanges = taskChangedFileEvidence(ctx.cwd, task, currentDigests).expected.length > 0;
|
|
@@ -4034,6 +4043,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4034
4043
|
const runtimeState = new RuntimeSessionState({
|
|
4035
4044
|
maxObservedContext: contextBudgetConfig(policy).maxManifestFiles
|
|
4036
4045
|
});
|
|
4046
|
+
const sourceMutationGuardBindings = createSourceMutationGuardBindings(policy, loadProfileFromContext);
|
|
4037
4047
|
const runtimeSnapshotCapture = new RuntimeSnapshotCapture(), runtimeVersions = readRuntimeVersionMetadata(PLATFORM_ROOT);
|
|
4038
4048
|
const solverShadow = solverMode === "off" ? undefined : new SolverShadowRuntime(solverMode);
|
|
4039
4049
|
const modelRouteRuntime = new ModelRouteRuntime(parentRoutingMode, routingObjective);
|
|
@@ -4242,6 +4252,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4242
4252
|
const activityInspector = registerActivityInspector(pi, {
|
|
4243
4253
|
activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
|
|
4244
4254
|
readEvents: (cwd) => readContextTelemetry(cwd, { limit: 5_000 }) as any[],
|
|
4255
|
+
protectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfile(ctx.cwd)).readProtectedPaths,
|
|
4245
4256
|
selectAction: selectRuntimeAction,
|
|
4246
4257
|
emit: emitRuntimeMessage
|
|
4247
4258
|
});
|
|
@@ -4268,7 +4279,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4268
4279
|
mcpReadinessNotice,
|
|
4269
4280
|
updateAvailabilityNotice,
|
|
4270
4281
|
contextExcludePatterns: (profile) => contextIndexExcludePatterns(policy, profile),
|
|
4271
|
-
inspectResume: inspectTaskResumeState,
|
|
4282
|
+
inspectResume: (cwd, task, sessionId) => inspectTaskResumeState(cwd, task, sessionId, undefined, {
|
|
4283
|
+
protectedPaths: effectiveProtectedPaths(policy, loadProfile(cwd)).readProtectedPaths
|
|
4284
|
+
}),
|
|
4272
4285
|
syncTrajectory: (ctx, task) => {
|
|
4273
4286
|
const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
|
|
4274
4287
|
const resume = workingTreeSnapshotHasUnavailableEvidence(snapshot)
|
|
@@ -4286,7 +4299,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4286
4299
|
})));
|
|
4287
4300
|
},
|
|
4288
4301
|
telemetry,
|
|
4289
|
-
afterStart: activityInspector.refresh
|
|
4302
|
+
afterStart: async (ctx) => { sourceMutationGuardBindings.bind(ctx); await activityInspector.refresh(ctx); }
|
|
4290
4303
|
});
|
|
4291
4304
|
|
|
4292
4305
|
registerSessionHooks(pi, {
|
|
@@ -4301,6 +4314,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4301
4314
|
onTurnEnd: activityInspector.refresh,
|
|
4302
4315
|
onAgentSettled: activityInspector.refresh,
|
|
4303
4316
|
beforeShutdown: (ctx) => {
|
|
4317
|
+
sourceMutationGuardBindings.unbind(ctx);
|
|
4304
4318
|
activityInspector.dispose(ctx);
|
|
4305
4319
|
sessionCapabilityDigests.delete(`${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`);
|
|
4306
4320
|
}
|
|
@@ -4497,6 +4511,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4497
4511
|
if (reviewDecision) runtimeState.denyPerformanceReviewTool(task.taskRunId);
|
|
4498
4512
|
return reviewDecision;
|
|
4499
4513
|
},
|
|
4514
|
+
beforeStart: (_event, ctx) => {
|
|
4515
|
+
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4516
|
+
const control = task?.trace.outcome === "pending" ? inspectTaskControlState(ctx.cwd, task) : null;
|
|
4517
|
+
if (control?.dispatchBlocked) return { block: true, reason: `Task lifecycle control blocks tool start while state is ${control.state}.` };
|
|
4518
|
+
},
|
|
4500
4519
|
afterDecision: (_event, ctx, metadata) => {
|
|
4501
4520
|
if (metadata.allowed) return;
|
|
4502
4521
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
@@ -4631,13 +4650,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4631
4650
|
if (approvalDecision.block) {
|
|
4632
4651
|
return { block: true, reason: approvalDecision.reason };
|
|
4633
4652
|
}
|
|
4634
|
-
|
|
4635
4653
|
let shellProjectMutation = false;
|
|
4636
4654
|
let shellMutationTargets: string[] = [];
|
|
4637
4655
|
let shellMutationTargetBounded = false;
|
|
4638
4656
|
let configuredVerifierShell = false;
|
|
4639
4657
|
let authorizedShellCommand = "";
|
|
4640
4658
|
let authorizedShellSegments: Array<{ words: string[] }> = [];
|
|
4659
|
+
let pendingHumanApproval: { prompt: string; title: string; action: Omit<ApprovalActionDraft, "treePrecondition"> } | null = null;
|
|
4641
4660
|
if (SHELL_TOOL_NAMES.has(event.toolName)) {
|
|
4642
4661
|
const shellInput = extractShellCommandInput(toolInput);
|
|
4643
4662
|
if (!shellInput.command) {
|
|
@@ -4695,17 +4714,14 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4695
4714
|
return { block: true, reason: unresolvedExpansionReason("Command", unresolvedExpansions) };
|
|
4696
4715
|
}
|
|
4697
4716
|
|
|
4698
|
-
const confirmationReasons = execDecision.mode !== "off" && execDecision.decision === "prompt"
|
|
4699
|
-
? [...execDecision.reasons]
|
|
4700
|
-
: [];
|
|
4717
|
+
const confirmationReasons = execDecision.mode !== "off" && execDecision.decision === "prompt" ? [...execDecision.reasons] : [];
|
|
4701
4718
|
const externalReason = findShellExternalConfirmationReason(execDecision.segments, externalActionPolicyConfig(policy));
|
|
4702
4719
|
if (externalReason) confirmationReasons.push(externalReason);
|
|
4703
4720
|
if (confirmationReasons.length > 0) {
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
if (!ok) return { block: true, reason: `User denied command: ${confirmationReasons.join("; ")}` };
|
|
4721
|
+
pendingHumanApproval = { prompt: `Command requires confirmation.\n\n${confirmationReasons.join("\n")}\n\nAllow?`, title: "Piagent exec policy confirmation",
|
|
4722
|
+
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 },
|
|
4723
|
+
commandPreview: command, parameterPreview: preparedInput.confirmationSummary ?? "Shell command", targetPaths: shellMutationTargets, targetSummaries: confirmationReasons, provider: externalReason ? "shell-external" : null, urlOrigin: null,
|
|
4724
|
+
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
4725
|
}
|
|
4710
4726
|
}
|
|
4711
4727
|
|
|
@@ -4717,7 +4733,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4717
4733
|
if (proxyShellDecision.block) {
|
|
4718
4734
|
return { block: true, reason: proxyShellDecision.reason };
|
|
4719
4735
|
}
|
|
4720
|
-
|
|
4721
4736
|
const policyToolIdentity = preparedInput.proxyToolName ?? event.toolName;
|
|
4722
4737
|
const usesKnownExternalProvider = externalActionPolicyConfig(policy).providerKeywords
|
|
4723
4738
|
.some((provider) => actionTextMatchesAny(policyToolIdentity, [provider]));
|
|
@@ -4754,16 +4769,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4754
4769
|
}
|
|
4755
4770
|
: classifiedExternalAction;
|
|
4756
4771
|
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
|
-
}
|
|
4772
|
+
const inputSummary = preparedInput.confirmationSummary ? `\ninput: ${preparedInput.confirmationSummary}` : "";
|
|
4773
|
+
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",
|
|
4774
|
+
action: { kind: "external-provider-action", preconditionClass: "runtime-only", toolName: event.toolName, rawAction: { toolName: event.toolName, input: preparedInput.input, provider: externalAction.provider, action: externalAction.action },
|
|
4775
|
+
commandPreview: null, parameterPreview: preparedInput.confirmationSummary ?? String(externalAction.action), targetPaths: [], targetSummaries: [], provider: String(externalAction.provider),
|
|
4776
|
+
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
4777
|
}
|
|
4768
4778
|
}
|
|
4769
4779
|
|
|
@@ -4805,6 +4815,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4805
4815
|
verificationCarrier: SHELL_TOOL_NAMES.has(event.toolName)
|
|
4806
4816
|
});
|
|
4807
4817
|
if (phaseMutationDecision) return phaseMutationDecision;
|
|
4818
|
+
if (pendingHumanApproval && !sessionTask) {
|
|
4819
|
+
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) });
|
|
4820
|
+
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}` };
|
|
4821
|
+
pendingHumanApproval = null;
|
|
4822
|
+
}
|
|
4808
4823
|
if (sessionTask && sessionTask.trace.outcome !== "pending" && (shellProjectMutation || directProjectMutation)) {
|
|
4809
4824
|
return {
|
|
4810
4825
|
block: true,
|
|
@@ -4855,6 +4870,17 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4855
4870
|
}
|
|
4856
4871
|
}
|
|
4857
4872
|
|
|
4873
|
+
if (pendingHumanApproval) {
|
|
4874
|
+
const currentTreeDigest = semanticSnapshot && !workingTreeSnapshotHasUnavailableEvidence(semanticSnapshot) ? workingTreeEvidenceDigest(semanticSnapshot) : null;
|
|
4875
|
+
const treePrecondition = pendingHumanApproval.action.preconditionClass === "runtime-only" || !currentTreeDigest ? null
|
|
4876
|
+
: { workspaceRevision: `workspace-rev.${crypto.createHash("sha256").update(currentTreeDigest).digest("hex")}`, indexRevision: null, preimageDigest: currentTreeDigest };
|
|
4877
|
+
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,
|
|
4878
|
+
action: { ...pendingHumanApproval.action, treePrecondition }, terminalConfirm: () => ctx.ui.confirm(pendingHumanApproval!.prompt, pendingHumanApproval!.title),
|
|
4879
|
+
recheck: () => !treePrecondition || (() => { const current = workingTreeSnapshot(ctx.cwd);
|
|
4880
|
+
return !workingTreeSnapshotHasUnavailableEvidence(current) && workingTreeEvidenceDigest(current) === treePrecondition.preimageDigest; })() });
|
|
4881
|
+
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}` };
|
|
4882
|
+
if (!approval.consume()) return { block: true, reason: "Approval became stale before tool start; the action was blocked." };
|
|
4883
|
+
}
|
|
4858
4884
|
if (
|
|
4859
4885
|
sessionTask?.trace.outcome === "pending"
|
|
4860
4886
|
&& sessionTask.changeMode === "source-change"
|
|
@@ -4864,7 +4890,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4864
4890
|
}
|
|
4865
4891
|
}
|
|
4866
4892
|
});
|
|
4867
|
-
|
|
4868
4893
|
const registrationDeps = {
|
|
4869
4894
|
CONTEXT_INDEX_EDGE_KINDS, CONTEXT_INDEX_NODE_KINDS, DEFAULT_MAX_TASK_ATTEMPTS, FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP,
|
|
4870
4895
|
ONBOARDING_COMMAND_ACTIONS, ORCHESTRATION_ROLES, PIAGENT_TOOL_NAMES, READ_ONLY_TOOL_NAMES, REVIEW_LENSES,
|
|
@@ -282,6 +282,23 @@ export function appendTaskJournalEventAtMost(cwd, event, options = {}) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
export function transactTaskJournal(cwd, transaction) {
|
|
286
|
+
if (typeof transaction !== "function") throw new TypeError("Task journal transaction must be a function");
|
|
287
|
+
const safePaths = writableJournalPaths(cwd);
|
|
288
|
+
const release = acquireLock(safePaths);
|
|
289
|
+
try {
|
|
290
|
+
const journal = recoverIncompleteJournalTail(cwd, safePaths, readTaskJournal(cwd));
|
|
291
|
+
if (journal.corruptions.length > 0) throw new Error(`Task journal chain is corrupt: ${journal.corruptions[0]}`);
|
|
292
|
+
const decision = transaction({ events: [...journal.events], head: journal.head ? { ...journal.head } : undefined });
|
|
293
|
+
if (!decision || typeof decision !== "object" || Array.isArray(decision)) throw new Error("Task journal transaction returned an invalid decision");
|
|
294
|
+
if (!decision.event) return { appended: false, record: undefined, result: decision.result };
|
|
295
|
+
const record = appendJournalRecord(safePaths, journal, decision.event, decision.recordedAt);
|
|
296
|
+
return { appended: true, record, result: decision.result };
|
|
297
|
+
} finally {
|
|
298
|
+
release();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
285
302
|
export function readTaskJournal(cwd, options = {}) {
|
|
286
303
|
const paths = taskJournalPaths(cwd);
|
|
287
304
|
let safePath;
|
|
@@ -290,7 +307,17 @@ export function readTaskJournal(cwd, options = {}) {
|
|
|
290
307
|
} catch {
|
|
291
308
|
return { events: [], corruptions: ["journal path is unsafe"], head: undefined };
|
|
292
309
|
}
|
|
293
|
-
if (!fs.existsSync(safePath)) return { events: [], corruptions: [], head: undefined };
|
|
310
|
+
if (!fs.existsSync(safePath)) return { events: [], corruptions: [], head: undefined, inputTruncated: false, omittedEvents: 0 };
|
|
311
|
+
if (Number.isInteger(options.maximumBytes) && options.maximumBytes > 0) {
|
|
312
|
+
try {
|
|
313
|
+
const stat = fs.lstatSync(safePath);
|
|
314
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > options.maximumBytes) {
|
|
315
|
+
return { events: [], corruptions: ["journal exceeds bounded inspection size"], head: undefined, inputTruncated: true, omittedEvents: 0 };
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
return { events: [], corruptions: ["journal size could not be inspected"], head: undefined, inputTruncated: false, omittedEvents: 0 };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
294
321
|
const content = fs.readFileSync(safePath);
|
|
295
322
|
const finalNewline = content.length === 0 || content.at(-1) === 0x0a;
|
|
296
323
|
const lastNewline = finalNewline ? content.length - 1 : content.lastIndexOf(0x0a);
|
|
@@ -329,6 +356,8 @@ export function readTaskJournal(cwd, options = {}) {
|
|
|
329
356
|
return {
|
|
330
357
|
events: visibleEvents,
|
|
331
358
|
corruptions,
|
|
359
|
+
inputTruncated: visibleEvents.length < events.length,
|
|
360
|
+
omittedEvents: Math.max(0, events.length - visibleEvents.length),
|
|
332
361
|
recoverableTailBytes,
|
|
333
362
|
validBytes,
|
|
334
363
|
head: events.at(-1)
|
|
@@ -66,17 +66,9 @@ export function safeTaskId(value) {
|
|
|
66
66
|
return normalized || "task";
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function stateRoot(cwd) {
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function tasksRoot(cwd) {
|
|
74
|
-
return path.join(stateRoot(cwd), "tasks");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function sessionsRoot(cwd) {
|
|
78
|
-
return path.join(stateRoot(cwd), "session-tasks");
|
|
79
|
-
}
|
|
69
|
+
function stateRoot(cwd) { return path.join(cwd, ".pi", "piagent-state"); }
|
|
70
|
+
function tasksRoot(cwd) { return path.join(stateRoot(cwd), "tasks"); }
|
|
71
|
+
function sessionsRoot(cwd) { return path.join(stateRoot(cwd), "session-tasks"); }
|
|
80
72
|
|
|
81
73
|
function taskRunPath(cwd, taskRunId) {
|
|
82
74
|
return path.join(tasksRoot(cwd), `${safeTaskId(taskRunId)}.json`);
|
|
@@ -98,7 +90,12 @@ function readJson(cwd, filePath) {
|
|
|
98
90
|
const safePath = resolveLocalStatePath(cwd, filePath, { label: "Task state", kind: "file" });
|
|
99
91
|
const descriptor = fs.openSync(safePath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
100
92
|
try {
|
|
101
|
-
|
|
93
|
+
const before = fs.fstatSync(descriptor, { bigint: true });
|
|
94
|
+
if (!before.isFile() || before.size > BigInt(8 * 1024 * 1024)) return undefined;
|
|
95
|
+
const value = JSON.parse(fs.readFileSync(descriptor, "utf8"));
|
|
96
|
+
const after = fs.fstatSync(descriptor, { bigint: true });
|
|
97
|
+
const fields = ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"];
|
|
98
|
+
return fields.every((field) => before[field] === after[field]) ? value : undefined;
|
|
102
99
|
} finally {
|
|
103
100
|
fs.closeSync(descriptor);
|
|
104
101
|
}
|
|
@@ -586,7 +583,6 @@ function gitOutput(cwd, args, options = {}) {
|
|
|
586
583
|
stdio: ["ignore", "pipe", "ignore"]
|
|
587
584
|
});
|
|
588
585
|
}
|
|
589
|
-
|
|
590
586
|
function gitHasHead(cwd) {
|
|
591
587
|
try {
|
|
592
588
|
gitOutput(cwd, ["rev-parse", "--verify", "HEAD"]);
|
|
@@ -595,7 +591,6 @@ function gitHasHead(cwd) {
|
|
|
595
591
|
return false;
|
|
596
592
|
}
|
|
597
593
|
}
|
|
598
|
-
|
|
599
594
|
export function isGitWorkingTree(cwd) {
|
|
600
595
|
try {
|
|
601
596
|
return gitOutput(cwd, ["rev-parse", "--is-inside-work-tree"]).trim() === "true";
|
|
@@ -603,20 +598,18 @@ export function isGitWorkingTree(cwd) {
|
|
|
603
598
|
return false;
|
|
604
599
|
}
|
|
605
600
|
}
|
|
606
|
-
|
|
607
601
|
function normalizedGitPath(value) {
|
|
608
602
|
return String(value ?? "").replaceAll("\\", "/").replace(/^\/+/, "");
|
|
609
603
|
}
|
|
610
|
-
|
|
611
604
|
function prefixedGitPath(prefix, file) {
|
|
612
605
|
const normalized = normalizedGitPath(file);
|
|
613
606
|
return prefix ? `${prefix}/${normalized}` : normalized;
|
|
614
607
|
}
|
|
615
608
|
|
|
616
|
-
function workingTreeFilesForGitRoot(cwd) {
|
|
609
|
+
function workingTreeFilesForGitRoot(cwd, observeRename) {
|
|
617
610
|
if (!isGitWorkingTree(cwd)) return [];
|
|
618
611
|
const changed = gitHasHead(cwd)
|
|
619
|
-
? changedPathsFromNameStatus(gitOutput(cwd, ["diff", "--name-status", "-z", "--find-renames", "--diff-filter=ACMRD", "HEAD", "--"]))
|
|
612
|
+
? changedPathsFromNameStatus(gitOutput(cwd, ["diff", "--name-status", "-z", "--find-renames", "--diff-filter=ACMRD", "HEAD", "--"]), observeRename)
|
|
620
613
|
: gitOutput(cwd, ["ls-files", "-z"]).split("\0").filter(Boolean);
|
|
621
614
|
const untracked = gitOutput(cwd, ["ls-files", "--others", "--exclude-standard", "-z"]).split("\0").filter(Boolean);
|
|
622
615
|
return [...new Set([...changed, ...untracked].map(normalizedGitPath).filter((item) => item && !item.startsWith(".pi/piagent-state/")))].sort();
|
|
@@ -694,9 +687,7 @@ function nonGitWorkspaceFiles(cwd, gitRootPrefixes) {
|
|
|
694
687
|
return files.sort();
|
|
695
688
|
}
|
|
696
689
|
|
|
697
|
-
function nonGitWorkspaceFileDigest(cwd, file) {
|
|
698
|
-
return streamedWorkingTreeFileDigest(cwd, file, false);
|
|
699
|
-
}
|
|
690
|
+
function nonGitWorkspaceFileDigest(cwd, file, protectedProjectPath = null) { return streamedWorkingTreeFileDigest(cwd, file, false, protectedProjectPath); }
|
|
700
691
|
|
|
701
692
|
function pathWithinNonGitWorkspaceEvidenceRoot(candidate, gitRootPrefixes) {
|
|
702
693
|
const topSegment = candidate.split("/")[0];
|
|
@@ -777,7 +768,7 @@ export function repositoryFileManifest(cwd, maxFiles = 10_000) {
|
|
|
777
768
|
return [...new Set(files)].sort();
|
|
778
769
|
}
|
|
779
770
|
|
|
780
|
-
function changedPathsFromNameStatus(output) {
|
|
771
|
+
function changedPathsFromNameStatus(output, observeRename) {
|
|
781
772
|
const fields = String(output ?? "").split("\0").filter(Boolean);
|
|
782
773
|
const files = [];
|
|
783
774
|
for (let index = 0; index < fields.length;) {
|
|
@@ -787,7 +778,7 @@ function changedPathsFromNameStatus(output) {
|
|
|
787
778
|
files.push(source);
|
|
788
779
|
if (/^[RC]/.test(status)) {
|
|
789
780
|
const destination = fields[index++];
|
|
790
|
-
if (destination) files.push(destination);
|
|
781
|
+
if (destination) { files.push(destination); observeRename?.(source, destination); }
|
|
791
782
|
}
|
|
792
783
|
}
|
|
793
784
|
return files;
|
|
@@ -804,8 +795,12 @@ export function workingTreeSnapshotHasUnavailableEvidence(snapshot) {
|
|
|
804
795
|
return Object.values(snapshot ?? {}).some((digest) => isUnavailableWorkingTreeDigest(digest));
|
|
805
796
|
}
|
|
806
797
|
|
|
807
|
-
function streamedWorkingTreeFileDigest(root, file, hasHead) {
|
|
798
|
+
function streamedWorkingTreeFileDigest(root, file, hasHead, protectedProjectPath = null) {
|
|
808
799
|
const absolute = path.resolve(root, file);
|
|
800
|
+
if (protectedProjectPath) {
|
|
801
|
+
try { return unavailableWorkingTreeDigest(protectedProjectPath, "protected-path", fs.lstatSync(absolute)); }
|
|
802
|
+
catch { return unavailableWorkingTreeDigest(protectedProjectPath, "protected-path"); }
|
|
803
|
+
}
|
|
809
804
|
const relative = path.relative(root, absolute);
|
|
810
805
|
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
811
806
|
return unavailableWorkingTreeDigest(file, "outside-root");
|
|
@@ -866,22 +861,24 @@ function streamedWorkingTreeFileDigest(root, file, hasHead) {
|
|
|
866
861
|
}
|
|
867
862
|
}
|
|
868
863
|
|
|
869
|
-
export function workingTreeSnapshot(cwd) {
|
|
864
|
+
export function workingTreeSnapshot(cwd, options = {}) {
|
|
870
865
|
const snapshot = {};
|
|
866
|
+
const isProtectedProjectPath = typeof options.isProtectedProjectPath === "function" ? options.isProtectedProjectPath : () => false;
|
|
871
867
|
const roots = gitEvidenceRoots(cwd);
|
|
872
868
|
for (const root of roots) {
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
869
|
+
const protectedAliases = new Set();
|
|
870
|
+
const observeRename = (source, destination) => {
|
|
871
|
+
const paths = [source, destination];
|
|
872
|
+
if (paths.some((file) => file === ".pi/piagent-state" || file.startsWith(".pi/piagent-state/") || isProtectedProjectPath(prefixedGitPath(root.prefix, file))))
|
|
873
|
+
paths.forEach((file) => protectedAliases.add(file));
|
|
874
|
+
};
|
|
875
|
+
const hasHead = gitHasHead(root.cwd); for (const file of workingTreeFilesForGitRoot(root.cwd, observeRename)) {
|
|
876
|
+
const projectPath = prefixedGitPath(root.prefix, file);
|
|
877
|
+
snapshot[projectPath] = streamedWorkingTreeFileDigest(root.cwd, file, hasHead, isProtectedProjectPath(projectPath) || protectedAliases.has(file) ? projectPath : null);
|
|
877
878
|
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
snapshot[file] = nonGitWorkspaceFileDigest(cwd, file);
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
return snapshot;
|
|
879
|
+
} if (!isGitWorkingTree(cwd) && roots.length > 0) for (const file of nonGitWorkspaceFiles(cwd, roots.map((root) => root.prefix))) {
|
|
880
|
+
snapshot[file] = nonGitWorkspaceFileDigest(cwd, file, isProtectedProjectPath(file) ? file : null);
|
|
881
|
+
} return snapshot;
|
|
885
882
|
}
|
|
886
883
|
|
|
887
884
|
export function taskStateMigrationStatus(cwd) {
|
|
@@ -68,7 +68,7 @@ export const CATALOG_SERVERS = {
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
figma: {
|
|
71
|
-
description: "Figma remote MCP server.
|
|
71
|
+
description: "Figma remote MCP server. OAuth works only for clients approved in the Figma MCP Catalog.",
|
|
72
72
|
config: {
|
|
73
73
|
url: "https://mcp.figma.com/mcp",
|
|
74
74
|
auth: "oauth",
|
|
@@ -99,7 +99,7 @@ export const CATALOG_PREREQUISITES = {
|
|
|
99
99
|
"export GITHUB_PERSONAL_ACCESS_TOKEN with the scopes you intend to use"
|
|
100
100
|
],
|
|
101
101
|
playwright: ["browsers installed on first use (npx playwright install)"],
|
|
102
|
-
figma: ["
|
|
102
|
+
figma: ["Piagent approved in the Figma MCP Catalog before remote OAuth can register"],
|
|
103
103
|
"figma-desktop": ["Figma desktop running with Dev Mode MCP enabled"]
|
|
104
104
|
};
|
|
105
105
|
|
|
@@ -119,12 +119,12 @@ export const CATALOG_PRESETS = {
|
|
|
119
119
|
docs: ["context7"],
|
|
120
120
|
browser: ["chrome-devtools", "playwright"],
|
|
121
121
|
github: ["github"],
|
|
122
|
-
design: ["figma"],
|
|
122
|
+
design: ["figma-desktop"],
|
|
123
123
|
"design-local": ["figma-desktop"],
|
|
124
124
|
web: ["context7", "chrome-devtools", "playwright"],
|
|
125
125
|
core: ["context7", "chrome-devtools", "github"],
|
|
126
|
-
popular: ["context7", "chrome-devtools", "playwright", "github", "figma"],
|
|
127
|
-
all: ["context7", "chrome-devtools", "playwright", "github", "figma", "figma
|
|
126
|
+
popular: ["context7", "chrome-devtools", "playwright", "github", "figma-desktop"],
|
|
127
|
+
all: ["context7", "chrome-devtools", "playwright", "github", "figma-desktop", "figma"]
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
/** @param {string} name @returns {boolean} */
|
|
@@ -49,7 +49,7 @@ type SessionStartHookDependencies = {
|
|
|
49
49
|
inspectResume: (cwd: string, task: TaskContract, sessionId: string) => ResumeState;
|
|
50
50
|
telemetry: (ctx: ExtensionContext, payload: Record<string, unknown>) => void;
|
|
51
51
|
syncTrajectory?: (ctx: ExtensionContext, task: TaskContract) => TrajectorySyncResult;
|
|
52
|
-
afterStart?: (ctx: ExtensionContext) => void
|
|
52
|
+
afterStart?: (ctx: ExtensionContext) => void | Promise<void>;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
export function registerSessionStartHook(pi: ExtensionAPI, dependencies: SessionStartHookDependencies): void {
|
|
@@ -227,6 +227,6 @@ export function registerSessionStartHook(pi: ExtensionAPI, dependencies: Session
|
|
|
227
227
|
taskRecovery,
|
|
228
228
|
executionBackend
|
|
229
229
|
});
|
|
230
|
-
dependencies.afterStart?.(ctx);
|
|
230
|
+
await dependencies.afterStart?.(ctx);
|
|
231
231
|
});
|
|
232
232
|
}
|
|
@@ -17,6 +17,7 @@ type ToolCallHookDependencies = {
|
|
|
17
17
|
beforeAuthorize?: (event: ToolCallEvent, ctx: ExtensionContext) => Promise<ToolCallDecision> | ToolCallDecision;
|
|
18
18
|
authorize: (event: ToolCallEvent, ctx: ExtensionContext) => Promise<ToolCallDecision> | ToolCallDecision;
|
|
19
19
|
reviewBudgetDecision?: (event: ToolCallEvent, ctx: ExtensionContext) => ToolCallDecision;
|
|
20
|
+
beforeStart?: (event: ToolCallEvent, ctx: ExtensionContext) => ToolCallDecision;
|
|
20
21
|
afterDecision?: (
|
|
21
22
|
event: ToolCallEvent,
|
|
22
23
|
ctx: ExtensionContext,
|
|
@@ -72,7 +73,9 @@ export function registerToolCallHook(pi: ExtensionAPI, dependencies: ToolCallHoo
|
|
|
72
73
|
const decision = authorizationDecision && typeof authorizationDecision === "object" && authorizationDecision.block
|
|
73
74
|
? authorizationDecision
|
|
74
75
|
: dependencies.reviewBudgetDecision?.(resolvedEvent, ctx);
|
|
75
|
-
const
|
|
76
|
+
const startDecision = !(decision && typeof decision === "object" && decision.block) ? dependencies.beforeStart?.(resolvedEvent, ctx) : undefined;
|
|
77
|
+
const finalDecision = startDecision && typeof startDecision === "object" && startDecision.block ? startDecision : decision;
|
|
78
|
+
const blockedDecision = finalDecision && typeof finalDecision === "object" ? finalDecision : undefined;
|
|
76
79
|
record(ctx, {
|
|
77
80
|
activityId: `decision:${toolCallId}`,
|
|
78
81
|
event: "tool_decision",
|
|
@@ -88,6 +91,6 @@ export function registerToolCallHook(pi: ExtensionAPI, dependencies: ToolCallHoo
|
|
|
88
91
|
observeTrajectorySync(ctx, dependencies.afterAuthorized?.(resolvedEvent, ctx, { toolCallId }), dependencies.telemetry);
|
|
89
92
|
}
|
|
90
93
|
dependencies.afterDecision?.(resolvedEvent, ctx, { toolCallId, allowed: !blockedDecision?.block, reason: blockedDecision?.reason });
|
|
91
|
-
return
|
|
94
|
+
return finalDecision;
|
|
92
95
|
});
|
|
93
96
|
}
|
|
@@ -11,6 +11,7 @@ import { redactForStorage, redactSensitiveText } from "../../extensions/redactio
|
|
|
11
11
|
import { appendObservedBashResult, hashEvidenceCommand, observedBashResultFromToolResultEvent } from "../../extensions/runtime-evidence.js";
|
|
12
12
|
import { workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence } from "../../extensions/task-state.js";
|
|
13
13
|
import { workingTreeObservation } from "../../extensions/working-tree-digest.js";
|
|
14
|
+
import { recordMutationResult } from "../inspection/mutation-provenance-recorder.ts";
|
|
14
15
|
import { boundedGitDiffReview } from "../quality/performance-assurance.ts";
|
|
15
16
|
import { currentFileContentDigests } from "../quality/model-mutation-proof.ts";
|
|
16
17
|
import { boundedPerformanceReviewResultText } from "../quality/performance-review-evidence.ts";
|
|
@@ -18,6 +19,7 @@ import { attachToolResultCompactionDetails, compactToolResultDetails, compactToo
|
|
|
18
19
|
import { RuntimeSessionState, type ObservedTaskContext } from "../session/runtime-state.ts";
|
|
19
20
|
import { observeTrajectorySync } from "../trajectory/trajectory-observability.ts";
|
|
20
21
|
import type { TrajectorySyncResult } from "../trajectory/trajectory-runtime.ts";
|
|
22
|
+
import { patchLineStats } from "./tool-result-metadata.ts";
|
|
21
23
|
|
|
22
24
|
type ToolResultEvent = { toolCallId?: string; toolName: string; input?: unknown; content?: unknown; details?: unknown; isError?: boolean; usage?: unknown };
|
|
23
25
|
type ObservedBashResult = NonNullable<ReturnType<typeof observedBashResultFromToolResultEvent>>;
|
|
@@ -50,7 +52,8 @@ type ToolResultHookDependencies = {
|
|
|
50
52
|
ctx: ExtensionContext,
|
|
51
53
|
observed: ObservedVerificationResult,
|
|
52
54
|
pendingContext: ObservedTaskContext[],
|
|
53
|
-
maxManifestFiles: number, shellSnapshotBefore?: Record<string, string>, eventTree?: WorkingTreeObservation
|
|
55
|
+
maxManifestFiles: number, shellSnapshotBefore?: Record<string, string>, eventTree?: WorkingTreeObservation,
|
|
56
|
+
readProtectedPaths?: string[]
|
|
54
57
|
) => unknown;
|
|
55
58
|
extractLikelyPath: (cwd: string, input: Record<string, unknown>) => string | undefined;
|
|
56
59
|
mutationTargets: (cwd: string, toolName: string, input: Record<string, unknown>) => string[];
|
|
@@ -124,20 +127,6 @@ function boundedToolResultText(content: unknown): string {
|
|
|
124
127
|
return text.slice(-20_000);
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
function patchLineStats(details: unknown): { additions?: number; deletions?: number } {
|
|
128
|
-
if (!details || typeof details !== "object") return {};
|
|
129
|
-
const patch = (details as Record<string, unknown>).patch;
|
|
130
|
-
if (typeof patch !== "string" || !patch.trim()) return {};
|
|
131
|
-
let additions = 0;
|
|
132
|
-
let deletions = 0;
|
|
133
|
-
for (const line of patch.split(/\r?\n/)) {
|
|
134
|
-
if (line.startsWith("+++ ") || line.startsWith("--- ")) continue;
|
|
135
|
-
if (line.startsWith("+")) additions += 1;
|
|
136
|
-
else if (line.startsWith("-")) deletions += 1;
|
|
137
|
-
}
|
|
138
|
-
return { additions, deletions };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
130
|
function countChangedStringLeaves(before: unknown, after: unknown): number {
|
|
142
131
|
if (typeof before === "string" && typeof after === "string") return before === after ? 0 : 1;
|
|
143
132
|
if (Array.isArray(before) && Array.isArray(after)) {
|
|
@@ -270,9 +259,9 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
270
259
|
dependencies.recordObservedTaskVerification(
|
|
271
260
|
pi,
|
|
272
261
|
ctx,
|
|
273
|
-
{ ...observed, outputText: boundedToolResultText(event.content) },
|
|
262
|
+
{ ...observed, toolCallId: observed.toolCallId ?? resultToolCallId, outputText: boundedToolResultText(event.content) },
|
|
274
263
|
pendingContext,
|
|
275
|
-
dependencies.maxManifestFiles, shellSnapshotBefore, eventTree
|
|
264
|
+
dependencies.maxManifestFiles, shellSnapshotBefore, eventTree, readProtectedPaths
|
|
276
265
|
);
|
|
277
266
|
}
|
|
278
267
|
|
|
@@ -287,20 +276,32 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
287
276
|
const currentSnapshot = taskIdentity ? eventTree?.snapshot : undefined;
|
|
288
277
|
const currentTreeDigest = taskIdentity ? eventTree?.digest : undefined;
|
|
289
278
|
const directMutationResult = modelMutationIdentity && currentSnapshot
|
|
290
|
-
? dependencies.state.
|
|
279
|
+
? dependencies.state.completeAuthorizedModelMutationEvidence(
|
|
291
280
|
modelMutationIdentity,
|
|
292
281
|
resultToolCallId,
|
|
293
282
|
successfulToolResult(event),
|
|
294
283
|
currentSnapshot,
|
|
295
284
|
currentFileContentDigests(ctx.cwd, directMutationTargets)
|
|
296
285
|
)
|
|
297
|
-
: { changedPaths: [] as string[], recordedDigests: {} as Record<string, string
|
|
286
|
+
: { changedPaths: [] as string[], recordedDigests: {} as Record<string, string>, beforeSnapshot: null,
|
|
287
|
+
targetPaths: [] as string[], recordedContentDigests: {} as Record<string, string>, proofModes: {} as Record<string, "full-content" | "exact-replacement"> };
|
|
298
288
|
const shellChangedPaths = taskIdentity && currentSnapshot && shellSnapshotBefore
|
|
299
289
|
? changedSnapshotFiles(shellSnapshotBefore, currentSnapshot)
|
|
300
290
|
: [];
|
|
301
291
|
if (modelMutationIdentity && shellChangedPaths.length > 0) {
|
|
302
292
|
dependencies.state.invalidateSuccessfulModelMutationPaths(modelMutationIdentity, shellChangedPaths);
|
|
303
293
|
}
|
|
294
|
+
if (modelMutationIdentity && currentSnapshot) {
|
|
295
|
+
try {
|
|
296
|
+
recordMutationResult({
|
|
297
|
+
projectRoot: ctx.cwd, identity: modelMutationIdentity, toolCallId: resultToolCallId, toolName: event.toolName,
|
|
298
|
+
recordedAt: dependencies.now(), successful: successfulToolResult(event), currentSnapshot,
|
|
299
|
+
completion: directMutationResult, shellSnapshotBefore, shellChangedPaths, protectedPaths: readProtectedPaths
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
ctx.ui.notify(`Piagent could not persist mutation provenance: ${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
304
305
|
const outputText = boundedToolResultText(event.content);
|
|
305
306
|
const performanceReviewOutputText = boundedPerformanceReviewResultText(event.content);
|
|
306
307
|
const observedExitCode = observed?.exitCode
|