@piagent/platform 1.8.0 → 1.9.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/AGENTS.md +7 -0
- package/CHANGELOG.md +97 -1
- package/README.md +45 -70
- package/README.vi.md +34 -61
- package/architecture/layers.json +16 -3
- package/catalog/capabilities.json +14 -7
- package/catalog/claude-model-additions.json +42 -0
- package/docs/auto-delegation-policy.md +2 -0
- package/docs/command-reference-vietnamese.md +68 -90
- package/docs/context-window-policy.md +5 -5
- package/docs/distribution-standard.md +13 -16
- package/docs/en/architecture.md +9 -1
- package/docs/herdr-workflow.md +2 -3
- package/docs/languages.json +4 -4
- package/docs/managed-local-acceptance.md +403 -0
- package/docs/mcp-and-tools.md +5 -7
- package/docs/memory-policy.md +3 -3
- package/docs/model-options.md +2 -2
- package/docs/oauth-providers.md +1 -1
- package/docs/operator-manual-vietnamese.md +82 -127
- package/docs/package-architecture-notes.md +4 -4
- package/docs/piagent-core-improvement-plan.md +3 -5
- package/docs/piagent-core-plan-checkpoint-2026-09-06.json +2 -13
- package/docs/piagent-runtime-benchmark-execution-plan.md +10 -8
- package/docs/project-adapters.md +3 -3
- package/docs/project-onboarding.md +15 -16
- package/docs/publishing-for-teams.md +2 -1
- package/docs/quality-benchmark.md +2 -0
- package/docs/quickstart-vietnamese.md +23 -24
- package/docs/release-install-policy.md +3 -2
- package/docs/research/harness-next-evidence-review.md +7 -7
- package/docs/runtime-harness-standard.md +2 -0
- package/docs/runtime-policy-design.md +2 -0
- package/docs/runtime-quality-baseline.md +2 -0
- package/docs/subagent-orchestration-capabilities.md +1 -1
- package/docs/subagents-and-multiagent.md +10 -8
- package/docs/task-implementation-contract.md +2 -0
- package/docs/task-lifecycle-vietnamese.md +2 -0
- package/docs/team-onboarding.md +28 -59
- package/docs/usage-observability.md +2 -2
- package/docs/vi/architecture.md +9 -1
- package/docs/workflow-recipes.md +6 -10
- package/docs-site/architecture.html +4 -3
- package/docs-site/assets/docs.css +80 -0
- package/docs-site/benchmark.html +7 -1
- package/docs-site/commands.html +37 -23
- package/docs-site/content/architecture.html +2 -2
- package/docs-site/content/benchmark.html +5 -0
- package/docs-site/content/commands.html +35 -22
- package/docs-site/content/ecosystem.html +144 -0
- package/docs-site/content/en/architecture.html +1 -1
- package/docs-site/content/en/benchmark.html +5 -0
- package/docs-site/content/en/commands.html +30 -13
- package/docs-site/content/en/ecosystem.html +144 -0
- package/docs-site/content/en/index.html +24 -9
- package/docs-site/content/en/quickstart.html +10 -12
- package/docs-site/content/en/security.html +1 -1
- package/docs-site/content/en/team.html +2 -2
- package/docs-site/content/en/whats-new.html +77 -1
- package/docs-site/content/en/workflows.html +29 -31
- package/docs-site/content/index.html +24 -9
- package/docs-site/content/quickstart.html +10 -12
- package/docs-site/content/security.html +2 -2
- package/docs-site/content/team.html +2 -2
- package/docs-site/content/whats-new.html +77 -1
- package/docs-site/content/workflows.html +36 -38
- package/docs-site/context-engine.html +2 -1
- package/docs-site/ecosystem.html +283 -0
- package/docs-site/en/architecture.html +3 -2
- package/docs-site/en/benchmark.html +7 -1
- package/docs-site/en/commands.html +32 -14
- package/docs-site/en/context-engine.html +2 -1
- package/docs-site/en/ecosystem.html +283 -0
- package/docs-site/en/index.html +30 -14
- package/docs-site/en/mcp-approval.html +2 -1
- package/docs-site/en/mcp-auth.html +2 -1
- package/docs-site/en/mcp-servers.html +2 -1
- package/docs-site/en/mcp.html +2 -1
- package/docs-site/en/permissions.html +2 -1
- package/docs-site/en/profiles.html +2 -1
- package/docs-site/en/quickstart.html +17 -18
- package/docs-site/en/runtime.html +2 -1
- package/docs-site/en/scope.html +2 -1
- package/docs-site/en/security.html +6 -5
- package/docs-site/en/team.html +4 -3
- package/docs-site/en/whats-new.html +85 -8
- package/docs-site/en/workflows.html +34 -35
- package/docs-site/index.html +30 -14
- package/docs-site/mcp-approval.html +2 -1
- package/docs-site/mcp-auth.html +2 -1
- package/docs-site/mcp-servers.html +2 -1
- package/docs-site/mcp.html +2 -1
- package/docs-site/permissions.html +2 -1
- package/docs-site/profiles.html +2 -1
- package/docs-site/quickstart.html +17 -18
- package/docs-site/runtime.html +2 -1
- package/docs-site/scope.html +2 -1
- package/docs-site/security.html +7 -6
- package/docs-site/team.html +4 -3
- package/docs-site/whats-new.html +85 -8
- package/docs-site/workflows.html +41 -42
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/private-holdout-v1/CURRENT_BOUNDARY.md +17 -0
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +8 -7
- package/packages/piagent-core/README.md +15 -23
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +2 -0
- package/packages/piagent-core/extensions/experiment-loop-policy.ts +75 -0
- package/packages/piagent-core/extensions/local-state-path.js +15 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +108 -960
- package/packages/piagent-core/extensions/policy-core.js +2 -2
- package/packages/piagent-core/extensions/source-checkout-paths.ts +54 -0
- package/packages/piagent-core/managed/broker-client.mjs +49 -0
- package/packages/piagent-core/managed/capabilities.mjs +28 -0
- package/packages/piagent-core/managed/compaction.mjs +63 -0
- package/packages/piagent-core/managed/doctor.mjs +107 -0
- package/packages/piagent-core/managed/native-catalog.mjs +42 -0
- package/packages/piagent-core/managed/patch-snapshot.mjs +48 -0
- package/packages/piagent-core/managed/repository-operation.mjs +84 -0
- package/packages/piagent-core/managed/request-stream.mjs +129 -0
- package/packages/piagent-core/managed/resource-loader.mjs +42 -0
- package/packages/piagent-core/managed/review-snapshot-worker.mjs +27 -0
- package/packages/piagent-core/managed/sandbox-diagnostic.mjs +12 -0
- package/packages/piagent-core/managed/search-fallback.mjs +93 -0
- package/packages/piagent-core/managed/session.mjs +472 -0
- package/packages/piagent-core/managed/store.d.mts +3 -0
- package/packages/piagent-core/managed/store.mjs +64 -0
- package/packages/piagent-core/managed/tool-boundary.mjs +243 -0
- package/packages/piagent-core/managed/tool-worker.mjs +36 -0
- package/packages/piagent-core/managed/toolchain.mjs +120 -0
- package/packages/piagent-core/managed/web-fetch.mjs +105 -0
- package/packages/piagent-core/managed/web-search.mjs +96 -0
- package/packages/piagent-core/managed/workflow.mjs +299 -0
- package/packages/piagent-core/package.json +2 -5
- package/packages/piagent-core/policies/base-policy.json +80 -26
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +3 -3
- package/packages/piagent-core/runtime/hooks/input-hook.ts +35 -196
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +2 -0
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +9 -1
- package/packages/piagent-core/runtime/managed-failure.d.mts +10 -0
- package/packages/piagent-core/runtime/managed-failure.mjs +164 -0
- package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +8 -29
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +1 -1
- package/packages/piagent-core/runtime/policy/block-remedy.ts +5 -3
- package/packages/piagent-core/runtime/registration/context-commands.ts +1 -1
- package/packages/piagent-core/runtime/registration/knowledge-tools.ts +7 -7
- package/packages/piagent-core/runtime/registration/policy-tools.ts +2 -2
- package/packages/piagent-core/runtime/registration/session-commands.ts +11 -26
- package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +1 -1
- package/packages/piagent-core/runtime/session/system-prompt.ts +6 -7
- package/packages/piagent-core/runtime/workflows/webui-runtime-command.ts +6 -6
- package/packages/piagent-core/runtime/workflows/webui-workflow.ts +6 -11
- package/packages/piagent-core/security/sensitive-text.js +9 -0
- package/packages/piagent-core/skills/piagent-ops/SKILL.md +8 -10
- package/packages/piagent-core/skills/test-audit/SKILL.md +101 -0
- package/packages/piagent-core/subagents/piagent-planner.md +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-DMU7Xe_O.js → MarkdownMessage-Dd9wPk-v.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-7I-50Q2U.js +228 -0
- package/packages/piagent-webui/dist/client/assets/{index-DVahUVWy.css → index-DgSmcnP8.css} +1 -1
- package/packages/piagent-webui/dist/client/index.html +2 -2
- package/packages/piagent-webui/extension/piagent-webui.ts +11 -11
- package/packages/piagent-webui/extension/session-stream-adapter.ts +3 -1
- package/packages/piagent-webui/extension/terminal-session-adapter.ts +6 -1
- package/packages/piagent-webui/gateway/company-relay.ts +308 -0
- package/packages/piagent-webui/gateway/control-socket.ts +39 -12
- package/packages/piagent-webui/gateway/gateway-service.ts +109 -28
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +58 -2
- package/packages/piagent-webui/gateway/managed-gateway.mjs +41 -0
- package/packages/piagent-webui/gateway/managed-launch.ts +145 -0
- package/packages/piagent-webui/gateway/managed-projection.ts +20 -0
- package/packages/piagent-webui/gateway/pi-host.ts +22 -4
- package/packages/piagent-webui/gateway/runtime-session-controls.ts +5 -2
- package/packages/piagent-webui/gateway/session-approval-authority.ts +28 -0
- package/packages/piagent-webui/gateway/session-catalog.ts +11 -3
- package/packages/piagent-webui/gateway/session-command-controller.ts +8 -2
- package/packages/piagent-webui/gateway/session-file-facts.ts +25 -0
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +40 -9
- package/packages/piagent-webui/gateway/session-list-cache.ts +68 -0
- package/packages/piagent-webui/gateway/session-runtime-ownership.ts +8 -1
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +20 -27
- package/packages/piagent-webui/ownership/managed-terminal-lease.ts +27 -0
- package/packages/piagent-webui/ownership/profile-state.ts +2 -0
- package/packages/piagent-webui/ownership/session-lease-store.ts +22 -1
- package/packages/piagent-webui/server/approval-projection.ts +9 -0
- package/packages/piagent-webui/server/core-inspection-provider.ts +4 -9
- package/packages/piagent-webui/server/loopback-server.ts +37 -1
- package/packages/piagent-webui/server/message-stop-reason.ts +11 -0
- package/packages/piagent-webui/server/tool-call-summary.ts +185 -0
- package/packages/piagent-webui/server/transcript-projection.ts +86 -3
- package/packs/engineering-base/pack.json +4 -0
- package/schemas/piagent-webui/runtime-command-v1.schema.json +1 -2
- package/schemas/piagent-webui/session-catalog-v1.schema.json +8 -0
- package/schemas/piagent-webui/transcript-v1.schema.json +94 -1
- package/scripts/build-docs-site.mjs +23 -12
- package/scripts/check-public-wording.mjs +5 -6
- package/scripts/check-runtime-advisories.mjs +23 -6
- package/scripts/claude-model-additions.mjs +33 -0
- package/scripts/configure-model-scope.sh +30 -6
- package/scripts/configure-subagents.sh +1 -1
- package/scripts/explain-command.mjs +5 -6
- package/scripts/model-scope-prune.mjs +75 -0
- package/scripts/phase-tools-evaluation.mjs +9 -4
- package/scripts/piagent-cli.mjs +51 -2
- package/scripts/piagent-dashboard.mjs +27 -4
- package/scripts/piagent-installations.mjs +26 -0
- package/scripts/piagent-studio.mjs +155 -0
- package/scripts/runtime-advisory-exceptions.json +29 -0
- package/scripts/team-doctor.sh +20 -1
- package/scripts/typescript-loader.mjs +6 -0
- package/scripts/verify-local.sh +2 -2
- package/templates/global/settings.json +3 -1
- package/templates/project/.pi/project-context.md +3 -3
- package/templates/project/AGENTS.md +4 -5
- package/docs/piagent-core-reasoning-research-2026-09-06.md +0 -225
- package/docs/piagent-core-research-sources-2026-09-06.json +0 -416
- package/packages/piagent-webui/dist/client/assets/index-Cui9AAV_.js +0 -176
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { unresolvedFocusedVerification } from "./acceptance-focused-verification.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
@@ -10,20 +10,21 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
10
10
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
11
11
|
import { Type } from "typebox";
|
|
12
12
|
import {
|
|
13
|
-
evaluateExecPolicyCore,
|
|
13
|
+
evaluateExecPolicyCore, findProtectedPathInCommand, unresolvedPathExpansions,
|
|
14
14
|
globMatchesPath, matchesProtectedPath, normalizePathCandidate, matchesAnyPath
|
|
15
15
|
} from "./policy-core.js";
|
|
16
16
|
import {
|
|
17
17
|
expandSimpleGlobAlternatives, findResolvedProtectedPathInCommand, normalizeRelative, protectedPatternExamples,
|
|
18
|
-
resolveRepositoryPathCandidate,
|
|
18
|
+
resolveRepositoryPathCandidate, shellGlobTargetsProtectedPath,
|
|
19
19
|
unresolvedExpansionReason
|
|
20
20
|
} from "./shell-reach.ts";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { grantedSourceCheckoutRootForPath, shellTouchesGrantedSourceCheckout } from "./source-checkout-paths.ts";
|
|
22
|
+
import { experimentLoopDecision } from "./experiment-loop-policy.ts";
|
|
23
|
+
import { extractShellWritePathCandidates } from "./shell-write-targets.js";
|
|
24
|
+
import { isProjectMutatingShellCommand } from "./readonly-inline-inspection.ts";
|
|
23
25
|
import { commandMatchesVerifyPlan, createBashResultLedger, findMatchingObservedBashResult, readObservedBashResults } from "./runtime-evidence.js";
|
|
24
26
|
import { findPackageRoot, findPlatformRoot, readJsonFile } from "./guard-io.js";
|
|
25
27
|
import {
|
|
26
|
-
DOCUMENT_EXTENSIONS,
|
|
27
28
|
extractDocument,
|
|
28
29
|
resolveDocumentPath,
|
|
29
30
|
resolveDocumentRoots
|
|
@@ -58,7 +59,7 @@ import {
|
|
|
58
59
|
import { resolveCapabilitySourceRoots } from "../capabilities/capability-sources.js";
|
|
59
60
|
import {
|
|
60
61
|
actionTextMatchesAny, actionTokens, classifyActionTokenSequence, classifyExplicitActionValues, classifyToolNameAction,
|
|
61
|
-
|
|
62
|
+
extractShellCommandInput, findShellExternalConfirmationReason, normalizeActionToken, normalizeShellCommandForPolicy
|
|
62
63
|
} from "./guard-shell-analysis.ts";
|
|
63
64
|
import {
|
|
64
65
|
appendContextTelemetry, buildContextEfficiencyReport, buildContextIndexV2, buildContextPack, buildTestImpact, classifyContextTask,
|
|
@@ -69,31 +70,27 @@ import {
|
|
|
69
70
|
effectiveProtectedPaths
|
|
70
71
|
} from "./context-index-policy.js";
|
|
71
72
|
import {
|
|
72
|
-
DEFAULT_MAX_TASK_ATTEMPTS,
|
|
73
|
+
DEFAULT_MAX_TASK_ATTEMPTS, bindSessionTask, createTaskRunId, hasGitEvidenceRoot, listTaskContracts,
|
|
73
74
|
priorTaskAttempts, repositoryFileManifest, repositoryFileManifestDetails, resolveTaskContract, safeTaskId, summarizeAttempt, taskContractValidationErrors, taskDigestMigrationArchiveStatus,
|
|
74
75
|
workPlanDependencyError, workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence, writeTaskContract
|
|
75
76
|
} from "./task-state.js";
|
|
76
|
-
import {
|
|
77
|
+
import { classifyVerificationFailure, meaningfulVerificationCommands, selectVerificationPlan } from "./verification-intelligence.js";
|
|
77
78
|
import { executionBackendToolDecision } from "./execution-backend.js";
|
|
78
79
|
import { replayTaskCheckpoints } from "./task-journal.js";
|
|
79
|
-
import
|
|
80
|
-
import { recordCompletionAudit, recordMutationCheckpoint, recordTaskProgressCheckpoints, recordTaskStartCheckpoint, recordVerificationCheckpoint } from "./task-runtime-audit.js";
|
|
80
|
+
import { recordCompletionAudit, recordTaskProgressCheckpoints, recordTaskStartCheckpoint, recordVerificationCheckpoint } from "./task-runtime-audit.js";
|
|
81
81
|
import {
|
|
82
82
|
applyAcceptanceRecoveryProvenance, acceptanceBaselineGuidance, acceptanceProofGuidance, acceptanceSemanticConflicts,
|
|
83
|
-
buildAcceptanceReceipt,
|
|
83
|
+
buildAcceptanceReceipt, refreshAcceptanceReceipt
|
|
84
84
|
} from "./acceptance-receipt.js";
|
|
85
85
|
import { acceptanceLanguageAdapterForPath, isAcceptanceTestPath } from "./acceptance-language-adapters.js";
|
|
86
|
-
import { allVerifyCommandsPassCurrentTree,
|
|
86
|
+
import { allVerifyCommandsPassCurrentTree, compactTaskDetails, passingVerifyCommandsForDigest, taskDeltaFilesFromSnapshot } from "./task-contract-view.js";
|
|
87
87
|
import { applyRuntimeLifecycleObservation, runtimeLifecycleMode, workingTreeEvidenceDigest } from "./task-lifecycle.js";
|
|
88
|
-
import {
|
|
89
|
-
import { WORKING_TREE_DIGEST_ALGORITHM, isCurrentWorkingTreeDigest, workingTreeObservation, workingTreeSnapshotUsesCurrentAlgorithm } from "./working-tree-digest.js";
|
|
88
|
+
import { WORKING_TREE_DIGEST_ALGORITHM, isCurrentWorkingTreeDigest, workingTreeSnapshotUsesCurrentAlgorithm } from "./working-tree-digest.js";
|
|
90
89
|
import { currentWorkspaceRevisionDigest } from "./workspace-revision.js";
|
|
91
|
-
import { captureWorkspaceVerificationSnapshot } from "./workspace-verification-snapshot.js";
|
|
92
90
|
import { appendJsonlBounded } from "./state-retention.js";
|
|
93
91
|
import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
|
|
94
92
|
import { hasDurableContextEvidence } from "./context-evidence.js";
|
|
95
93
|
import { piApprovalBroker, type ApprovalActionDraft, type ApprovalUnavailableFallback } from "../runtime/inspection/approval-broker.ts";
|
|
96
|
-
import { inspectTaskControlState } from "../runtime/inspection/task-control-journal.ts";
|
|
97
94
|
import { createSourceMutationGuardBindings } from "../runtime/policy/source-mutation-guard-binding.ts";
|
|
98
95
|
import {
|
|
99
96
|
TOOL_RESULT_CAPTURE_MAX_CHARS,
|
|
@@ -111,10 +108,7 @@ import {
|
|
|
111
108
|
} from "../runtime/session/tool-result-compaction.ts";
|
|
112
109
|
import { RuntimeSessionState } from "../runtime/session/runtime-state.ts";
|
|
113
110
|
import type { ObservedTaskContext } from "../runtime/session/runtime-state.ts";
|
|
114
|
-
import { reuseCurrentTreeExactVerifier } from "../runtime/verification/exact-verifier-reuse.ts";
|
|
115
|
-
import { IndependentAcceptanceRuntime } from "../runtime/verification/independent-acceptance-runtime.ts";
|
|
116
111
|
import { independentAcceptanceState } from "./acceptance-independent-registry.js";
|
|
117
|
-
import { independentVerificationRecovery } from "../runtime/recovery/independent-verification-recovery.ts";
|
|
118
112
|
import {
|
|
119
113
|
PIAGENT_TOOL_GROUPS,
|
|
120
114
|
PIAGENT_TOOL_NAMES,
|
|
@@ -138,7 +132,6 @@ import {
|
|
|
138
132
|
import { readChatImage } from "../runtime/input/chat-images.ts";
|
|
139
133
|
import { registerInputHook } from "../runtime/hooks/input-hook.ts";
|
|
140
134
|
import { registerAgentStartHook } from "../runtime/hooks/agent-start-hook.ts";
|
|
141
|
-
import { registerCompletionHook } from "../runtime/hooks/completion-hook.ts";
|
|
142
135
|
import { registerSessionHooks } from "../runtime/hooks/session-hooks.ts";
|
|
143
136
|
import { registerSessionStartHook } from "../runtime/hooks/session-start-hook.ts";
|
|
144
137
|
import { registerToolCallHook } from "../runtime/hooks/tool-call-hook.ts";
|
|
@@ -156,49 +149,36 @@ import type { TaskFeatures } from "../runtime/solver/solver-types.ts";
|
|
|
156
149
|
import { planRetrievalRoute } from "../runtime/context/retrieval-route-policy.ts";
|
|
157
150
|
import { evaluateRuntimeSolver } from "../runtime/solver/runtime-features.ts";
|
|
158
151
|
import { SolverShadowRuntime, solverModeFromEnvironment } from "../runtime/solver/solver-shadow.ts";
|
|
159
|
-
import { observeTrajectorySync } from "../runtime/trajectory/trajectory-observability.ts";
|
|
160
152
|
import { TrajectoryRuntime } from "../runtime/trajectory/trajectory-runtime.ts";
|
|
161
|
-
import { PhaseToolRuntime, phaseToolModeFromEnvironment } from "../runtime/tools/phase-tool-runtime.ts";
|
|
162
153
|
import { parseApplyPatchTargets, registerApplyPatchTool } from "../runtime/tools/apply-patch-tool.ts";
|
|
163
|
-
import { authorityReplacementState, ensureTaskAuthorityResumePolicy } from "../runtime/policy/authority-resume-policy.ts";
|
|
164
154
|
import { taskAuthorityDecision } from "../runtime/policy/task-authority-runtime.ts";
|
|
165
|
-
import { applyProofCapabilityHandoff, selectRecoveryDecision } from "../runtime/recovery/recovery-policy.ts";
|
|
166
|
-
import type { RecoveryDecision } from "../runtime/recovery/recovery-policy.ts";
|
|
167
155
|
import { inspectTaskResumeState } from "../runtime/recovery/resume-state.ts";
|
|
168
|
-
import { SemanticRepairRuntime } from "../runtime/recovery/semantic-repair-runtime.ts";
|
|
169
156
|
import { defaultRolePolicy } from "../runtime/orchestration/role-policy.ts";
|
|
170
157
|
import { helpersMode } from "../runtime/orchestration/helper-lifecycle.ts";
|
|
171
|
-
import { evaluateDirectSubagentDispatch
|
|
158
|
+
import { evaluateDirectSubagentDispatch } from "../runtime/orchestration/subagent-tool-policy.ts";
|
|
172
159
|
import { buildLiveTaskStatus, formatLiveTaskStatus } from "../runtime/product/operator-projections.ts";
|
|
173
160
|
import { buildTaskEfficiencyMetrics } from "../runtime/product/efficiency-metrics.ts";
|
|
174
|
-
import { performanceReviewToolDecision, performanceReviewToolKind } from "../runtime/quality/performance-assurance.ts";
|
|
175
|
-
import { expectedModelMutationProof } from "../runtime/quality/model-mutation-proof.ts";
|
|
176
161
|
import { EditFreshnessGuard, editFreshnessModeFromEnvironment } from "../runtime/quality/edit-freshness-guard.ts";
|
|
177
|
-
import { captureVerifierFileSnapshot } from "../runtime/inspection/verifier-snapshot-store.ts";
|
|
178
162
|
import { finalGateConfig } from "./acceptance-diagnostic-policy.ts";
|
|
179
163
|
import { fallbackBasePolicy } from "./fallback-base-policy.ts";
|
|
180
|
-
import { prefixCompletions, registerPiagentTool, registerRuntimeCommand, registerRuntimeTool } from "../runtime/registration/extension-registration.ts";
|
|
164
|
+
import { prefixCompletions, registerPiagentTool as registerToolDefinition, registerRuntimeCommand, registerRuntimeTool } from "../runtime/registration/extension-registration.ts";
|
|
165
|
+
import { registerTaskEvidenceTools } from "../runtime/registration/task-evidence-tools.ts";
|
|
181
166
|
import { FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP, ONBOARDING_COMMAND_ACTIONS, WORKFLOW_COMMAND_EXCLUSIONS } from "../runtime/registration/operator-catalogs.ts";
|
|
182
167
|
import { registerPiagentStatusCommand } from "../runtime/registration/runtime-model-status.ts";
|
|
183
168
|
import { registerTaskPreflightCommand } from "../runtime/registration/task-preflight.ts";
|
|
184
169
|
import { registerPolicyTools } from "../runtime/registration/policy-tools.ts";
|
|
185
170
|
import { registerKnowledgeTools } from "../runtime/registration/knowledge-tools.ts";
|
|
186
171
|
import { registerOnboardingTools } from "../runtime/registration/onboarding-tools.ts";
|
|
187
|
-
import { registerTaskStartTool } from "../runtime/registration/task-start-tool.ts";
|
|
188
|
-
import { registerTaskEvidenceTools } from "../runtime/registration/task-evidence-tools.ts";
|
|
189
|
-
import { registerTaskCompletionTools } from "../runtime/registration/task-completion-tools.ts";
|
|
190
172
|
import { registerPermissionCommands } from "../runtime/registration/permission-commands.ts";
|
|
191
173
|
import { registerProfileCommands } from "../runtime/registration/profile-commands.ts";
|
|
192
174
|
import { registerMemoryMcpCommands } from "../runtime/registration/memory-mcp-commands.ts";
|
|
193
175
|
import { registerContextCommands } from "../runtime/registration/context-commands.ts";
|
|
194
176
|
import { registerSessionCommands } from "../runtime/registration/session-commands.ts";
|
|
195
|
-
import { registerWorkflowCommands } from "../runtime/registration/workflow-commands.ts";
|
|
196
177
|
import { registerActivityInspector } from "../runtime/registration/activity-inspector-command.ts";
|
|
197
178
|
export { readChatImage };
|
|
198
179
|
import type { ActionClassification } from "./guard-shell-analysis.ts";
|
|
199
180
|
import type {
|
|
200
181
|
BasePolicy,
|
|
201
|
-
CommandRule,
|
|
202
182
|
ContextBudgetConfig,
|
|
203
183
|
ContextIndexCitation,
|
|
204
184
|
ContextIndexEdge,
|
|
@@ -210,7 +190,6 @@ import type {
|
|
|
210
190
|
ExternalActionPolicyConfig,
|
|
211
191
|
MemorySettings,
|
|
212
192
|
OrchestrationMode,
|
|
213
|
-
OrchestrationPolicySettings,
|
|
214
193
|
OrchestrationRole,
|
|
215
194
|
PermissionProfileMode,
|
|
216
195
|
PermissionProfilesConfig,
|
|
@@ -218,7 +197,6 @@ import type {
|
|
|
218
197
|
ProjectContextIndex,
|
|
219
198
|
ProjectOnboardingSnapshot,
|
|
220
199
|
ProjectProfile,
|
|
221
|
-
ProjectTechStackReference,
|
|
222
200
|
ReferenceRepo,
|
|
223
201
|
ResolvedOrchestrationPolicy,
|
|
224
202
|
ResolvedPermissionProfile,
|
|
@@ -236,30 +214,6 @@ import type {
|
|
|
236
214
|
const PIAGENT_TRACE_STATE_TYPE = "piagent-task-trace";
|
|
237
215
|
const BOILERPLATE_COLLAPSE_CHARS = 300;
|
|
238
216
|
const TRACE_MAX_BYTES = 8 * 1024 * 1024;
|
|
239
|
-
type TaskStartParameters = {
|
|
240
|
-
taskId?: string;
|
|
241
|
-
summary: string;
|
|
242
|
-
riskLane: "tiny" | "normal" | "high-risk";
|
|
243
|
-
intakeMode?: "model" | "runtime";
|
|
244
|
-
changeMode?: "source-change" | "read-only";
|
|
245
|
-
mutationPolicy?: "required" | "allowed" | "forbidden";
|
|
246
|
-
verifyGroup?: string;
|
|
247
|
-
maxAttempts?: number;
|
|
248
|
-
expectedOutput: string;
|
|
249
|
-
acceptanceCriteria: string[];
|
|
250
|
-
scope: string[];
|
|
251
|
-
outOfScope?: string[];
|
|
252
|
-
reviewLenses?: ReviewLens[];
|
|
253
|
-
workPlan?: Array<{
|
|
254
|
-
id: string;
|
|
255
|
-
title: string;
|
|
256
|
-
role?: OrchestrationRole;
|
|
257
|
-
mode?: "read-only" | "single-writer" | "review";
|
|
258
|
-
status?: "pending" | "in-progress" | "done" | "skipped" | "failed";
|
|
259
|
-
dependsOn?: string[];
|
|
260
|
-
note?: string;
|
|
261
|
-
}>;
|
|
262
|
-
};
|
|
263
217
|
const ORCHESTRATION_MODES = ["solo-first", "bounded-subagents", "parallel-readonly"] as const;
|
|
264
218
|
const REVIEW_LENSES = ["correctness", "tests", "scope", "security", "docs", "release", "package"] as const;
|
|
265
219
|
const ORCHESTRATION_ROLES = ["parent", "piagent-scout", "piagent-planner", "piagent-worker", "piagent-reviewer", "piagent-oracle"] as const;
|
|
@@ -346,7 +300,7 @@ const DEFAULT_RUNTIME_POLICY: Required<RuntimePolicySettings> = {
|
|
|
346
300
|
};
|
|
347
301
|
const DEFAULT_ORCHESTRATION_POLICY: ResolvedOrchestrationPolicy = {
|
|
348
302
|
defaultMode: "solo-first",
|
|
349
|
-
maxConcurrentSubagents:
|
|
303
|
+
maxConcurrentSubagents: 2,
|
|
350
304
|
defaultReviewLenses: ["correctness", "tests", "scope"],
|
|
351
305
|
roleModelGuidance: {
|
|
352
306
|
planner: "Use the strongest available model for decomposition, architecture, risk, and acceptance criteria.",
|
|
@@ -362,14 +316,13 @@ const DEFAULT_ORCHESTRATION_POLICY: ResolvedOrchestrationPolicy = {
|
|
|
362
316
|
readBeforeTask: true
|
|
363
317
|
},
|
|
364
318
|
rules: [
|
|
365
|
-
"
|
|
366
|
-
"Use at most
|
|
367
|
-
"Never delegate implementation, inherit parent history, retry a deterministic helper failure
|
|
319
|
+
"Work directly on the request; delegate a useful independent research or patch review when enabled.",
|
|
320
|
+
"Use at most two fresh read-only helpers. Token-saving estimates are optional telemetry, not admission requirements.",
|
|
321
|
+
"Never delegate implementation, inherit parent history, spawn nested helpers, or retry a deterministic helper failure.",
|
|
368
322
|
"Treat Field Guide memory as advisory; verify every durable fact against current repository files.",
|
|
369
323
|
"Keep review lenses explicit so cheap review work catches drift before release."
|
|
370
324
|
]
|
|
371
325
|
};
|
|
372
|
-
const PERMISSION_PROFILE_MODES = ["read-only", "workspace-write", "trusted-full-access"] as const;
|
|
373
326
|
const PERMISSION_PROFILE_ALIASES: Record<string, PermissionProfileMode> = {
|
|
374
327
|
readonly: "read-only",
|
|
375
328
|
"read_only": "read-only",
|
|
@@ -386,13 +339,19 @@ const PERMISSION_PROFILE_ALIASES: Record<string, PermissionProfileMode> = {
|
|
|
386
339
|
};
|
|
387
340
|
const READ_ONLY_TOOL_NAMES = new Set(["read", "grep", "find", "ls"]);
|
|
388
341
|
const WRITE_TOOL_NAMES = new Set(["write", "edit"]);
|
|
389
|
-
|
|
342
|
+
// run_experiment (an experiment-loop tool) runs `bash -c <command>` itself: same policy as bash.
|
|
343
|
+
const SHELL_TOOL_NAMES = new Set(["bash", "shell", "exec", "run_experiment"]);
|
|
390
344
|
const MAX_MCP_PROXY_ARGS_CHARS = 131_072;
|
|
391
345
|
const SESSION_PERMISSION_OVERRIDES = new Map<string, PermissionProfileMode>();
|
|
392
346
|
const DEFAULT_POLICY = fallbackBasePolicy(CONTEXT_INDEX_FILE, DEFAULT_ORCHESTRATION_POLICY);
|
|
393
347
|
// Which platform supplies the adapters is fixed by where this file is installed,
|
|
394
348
|
// so it is resolved once here rather than threaded through every profile load.
|
|
395
349
|
const PLATFORM_ROOT = findPlatformRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
350
|
+
// The skills Pi lists for the model are read where they are installed: this
|
|
351
|
+
// package's and the user's. Reading them is allowed; writes stay in the project.
|
|
352
|
+
const SKILL_READ_ROOTS = [path.join(PLATFORM_ROOT, "packages", "piagent-core", "skills"),
|
|
353
|
+
path.join(process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"), "skills")]
|
|
354
|
+
.flatMap((root) => { try { return [fs.realpathSync.native(root)]; } catch { return []; } });
|
|
396
355
|
const UPDATE_CHECK_MODULE = fileURLToPath(new URL("./update-check.js", import.meta.url));
|
|
397
356
|
// The installed version, read from the package this file ships in. A maintainer
|
|
398
357
|
// working in the repository is not running a release and has nothing to update
|
|
@@ -1077,21 +1036,6 @@ function evaluatePathLikeToolAccess(
|
|
|
1077
1036
|
return { block: false };
|
|
1078
1037
|
}
|
|
1079
1038
|
|
|
1080
|
-
function grantedSourceCheckoutRootForPath(cwd: string, candidate: string, roots: string[]): string | undefined {
|
|
1081
|
-
if (roots.length === 0) return undefined;
|
|
1082
|
-
const absolute = path.resolve(cwd, normalizePathCandidate(candidate));
|
|
1083
|
-
let canonical: string;
|
|
1084
|
-
try {
|
|
1085
|
-
canonical = fs.realpathSync.native(absolute);
|
|
1086
|
-
} catch {
|
|
1087
|
-
return undefined;
|
|
1088
|
-
}
|
|
1089
|
-
return roots.find((root) => {
|
|
1090
|
-
const relative = path.relative(root, canonical);
|
|
1091
|
-
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
1039
|
function stateRoot(cwd: string): string {
|
|
1096
1040
|
return path.join(cwd, ".pi", "piagent-state");
|
|
1097
1041
|
}
|
|
@@ -1359,7 +1303,7 @@ function resolveOrchestrationPolicy(profile: ProjectProfile, policy: BasePolicy)
|
|
|
1359
1303
|
};
|
|
1360
1304
|
return {
|
|
1361
1305
|
defaultMode: normalizeOrchestrationMode(configured.defaultMode, defaultPolicy.defaultMode),
|
|
1362
|
-
maxConcurrentSubagents: Math.min(
|
|
1306
|
+
maxConcurrentSubagents: Math.min(2, boundedInteger(configured.maxConcurrentSubagents, defaultPolicy.maxConcurrentSubagents, 0, 2)),
|
|
1363
1307
|
defaultReviewLenses: normalizeReviewLenses(configured.defaultReviewLenses, defaultPolicy.defaultReviewLenses),
|
|
1364
1308
|
roleModelGuidance,
|
|
1365
1309
|
fieldGuide: {
|
|
@@ -1685,48 +1629,7 @@ function isTaskMutationTool(toolName: string, input: Record<string, unknown>): b
|
|
|
1685
1629
|
.some((token) => tokens.has(token));
|
|
1686
1630
|
}
|
|
1687
1631
|
|
|
1688
|
-
function shellTouchesGrantedSourceCheckout(cwd: string, command: string, roots: string[]): boolean {
|
|
1689
|
-
if (roots.length === 0) return false;
|
|
1690
|
-
for (const root of roots) {
|
|
1691
|
-
const relative = path.relative(cwd, root).split(path.sep).join("/");
|
|
1692
|
-
if (command.includes(root) || (relative && command.includes(relative))) return true;
|
|
1693
|
-
}
|
|
1694
|
-
return extractShellPathCandidates(command)
|
|
1695
|
-
.some((candidate) => Boolean(grantedSourceCheckoutRootForPath(cwd, normalizeRelative(cwd, candidate) ?? candidate, roots)));
|
|
1696
|
-
}
|
|
1697
1632
|
|
|
1698
|
-
const EXPLICIT_SHELL_MUTATORS = new Set([
|
|
1699
|
-
"apply_patch", "chmod", "chown", "cp", "dd", "install", "ln", "mkdir", "mv", "patch", "prename", "rename",
|
|
1700
|
-
"rm", "rmdir", "rsync", "scp", "tee", "touch", "truncate"
|
|
1701
|
-
]);
|
|
1702
|
-
|
|
1703
|
-
function opaqueShellMutationNeedsBoundedTarget(
|
|
1704
|
-
command: string,
|
|
1705
|
-
segments: Array<{ words: string[] }>
|
|
1706
|
-
): boolean {
|
|
1707
|
-
if (segments.some((segment) => {
|
|
1708
|
-
const words = segment.words.filter(Boolean);
|
|
1709
|
-
const executable = path.basename(words[0] ?? "").toLowerCase();
|
|
1710
|
-
if (EXPLICIT_SHELL_MUTATORS.has(executable)) return true;
|
|
1711
|
-
if (executable === "find" && words.some((word) => ["-delete", "-exec", "-execdir", "-ok", "-okdir", "-fprint", "-fprint0", "-fprintf", "-fls"].includes(word))) return true;
|
|
1712
|
-
if (executable === "sed" && words.some((word) => /^-[^-]*i/.test(word) || word === "--in-place" || word.startsWith("--in-place="))) return true;
|
|
1713
|
-
if (executable === "git") {
|
|
1714
|
-
return words.some((word) => ["apply", "checkout", "clean", "mv", "reset", "restore", "rm", "switch"].includes(word));
|
|
1715
|
-
}
|
|
1716
|
-
if (["npm", "pnpm", "yarn", "bun"].includes(executable)) {
|
|
1717
|
-
return words.some((word) => ["add", "ci", "install", "link", "remove", "uninstall", "update", "upgrade"].includes(word));
|
|
1718
|
-
}
|
|
1719
|
-
return false;
|
|
1720
|
-
})) return true;
|
|
1721
|
-
// Interpreter and build commands are conservatively snapshotted after the
|
|
1722
|
-
// call, but ordinary tests/checks must remain usable. Only an inline script
|
|
1723
|
-
// with a recognizable write primitive is an opaque pre-call mutation.
|
|
1724
|
-
return shellHasOpaqueWritePrimitive(command);
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
function shellHasOpaqueWritePrimitive(command: string): boolean {
|
|
1728
|
-
return /\b(?:appendFile(?:Sync)?|copyFile(?:Sync)?|mkdir(?:Sync)?|rename(?:Sync)?|rm(?:Sync)?|rmdir(?:Sync)?|truncate(?:Sync)?|unlink(?:Sync)?|writeFile(?:Sync)?)\s*\(|\bopen\s*\([^\n)]*,\s*["'][wax+]/i.test(command);
|
|
1729
|
-
}
|
|
1730
1633
|
|
|
1731
1634
|
function taskMutationTargets(cwd: string, toolName: string, input: Record<string, unknown>): string[] {
|
|
1732
1635
|
if (!isTaskMutationTool(toolName, input) || SHELL_TOOL_NAMES.has(toolName)) return [];
|
|
@@ -1793,247 +1696,6 @@ function observedTaskContextFromToolResult(
|
|
|
1793
1696
|
};
|
|
1794
1697
|
}
|
|
1795
1698
|
|
|
1796
|
-
function recordObservedTaskChanges(
|
|
1797
|
-
pi: ExtensionAPI,
|
|
1798
|
-
ctx: ExtensionContext,
|
|
1799
|
-
event: { toolName: string; input?: unknown; isError?: boolean },
|
|
1800
|
-
pendingContext: ObservedTaskContext[],
|
|
1801
|
-
maxManifestFiles: number,
|
|
1802
|
-
shellSnapshotBefore?: Record<string, string>,
|
|
1803
|
-
eventTree?: ReturnType<typeof workingTreeObservation>
|
|
1804
|
-
): TaskContract | undefined {
|
|
1805
|
-
if (event.isError) return;
|
|
1806
|
-
const input = isPlainRecord(event.input) ? event.input : {};
|
|
1807
|
-
if (!isTaskMutationTool(event.toolName, input)) return;
|
|
1808
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
1809
|
-
if (!task || task.trace.outcome !== "pending") return;
|
|
1810
|
-
|
|
1811
|
-
const targets = taskMutationTargets(ctx.cwd, event.toolName, input);
|
|
1812
|
-
if (!eventTree) return;
|
|
1813
|
-
const eventSnapshot = eventTree.snapshot as Record<string, string>;
|
|
1814
|
-
const shellMutationObserved = SHELL_TOOL_NAMES.has(event.toolName) && shellSnapshotBefore !== undefined;
|
|
1815
|
-
const shellChangedFiles = shellMutationObserved
|
|
1816
|
-
? changedSnapshotFiles(shellSnapshotBefore, eventSnapshot)
|
|
1817
|
-
: [];
|
|
1818
|
-
const nextObserved = uniqueStrings([...task.observedChangedFiles, ...shellChangedFiles, ...targets]).sort();
|
|
1819
|
-
const added = nextObserved.filter((file) => !task.observedChangedFiles.includes(file));
|
|
1820
|
-
const contextAdded = mergeObservedTaskContext(task, pendingContext, maxManifestFiles, redactText);
|
|
1821
|
-
const mutationObserved = targets.length > 0 || shellChangedFiles.length > 0;
|
|
1822
|
-
const lifecycle = mutationObserved
|
|
1823
|
-
? applyRuntimeLifecycleObservation(task, "mutation", nowIso())
|
|
1824
|
-
: { changed: false, mode: runtimeLifecycleMode(task) };
|
|
1825
|
-
const acceptanceInvalidation = mutationObserved
|
|
1826
|
-
? invalidateAcceptanceReceiptAfterMutation(task, nowIso())
|
|
1827
|
-
: { task, changed: false };
|
|
1828
|
-
task.acceptanceReceipt = acceptanceInvalidation.task.acceptanceReceipt;
|
|
1829
|
-
if (added.length === 0 && contextAdded.length === 0 && !lifecycle.changed && !acceptanceInvalidation.changed) return;
|
|
1830
|
-
task.observedChangedFiles = nextObserved;
|
|
1831
|
-
const written = writeTask(ctx.cwd, task);
|
|
1832
|
-
const trace = {
|
|
1833
|
-
event: "task_changes_observed",
|
|
1834
|
-
taskId: written.taskId,
|
|
1835
|
-
taskRunId: written.taskRunId,
|
|
1836
|
-
sessionId: written.sessionId,
|
|
1837
|
-
toolName: event.toolName,
|
|
1838
|
-
files: added,
|
|
1839
|
-
contextFiles: contextAdded,
|
|
1840
|
-
lifecycleMode: lifecycle.mode,
|
|
1841
|
-
lifecycleAdvanced: lifecycle.changed
|
|
1842
|
-
};
|
|
1843
|
-
appendTrace(ctx.cwd, trace);
|
|
1844
|
-
appendSessionTrace(pi, trace);
|
|
1845
|
-
recordMutationCheckpoint(ctx, written, {
|
|
1846
|
-
toolName: event.toolName,
|
|
1847
|
-
files: added,
|
|
1848
|
-
contextFiles: contextAdded,
|
|
1849
|
-
lifecycleMode: lifecycle.mode
|
|
1850
|
-
});
|
|
1851
|
-
return written;
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
function recordObservedTaskVerification(
|
|
1855
|
-
pi: ExtensionAPI,
|
|
1856
|
-
ctx: ExtensionContext,
|
|
1857
|
-
observed: {
|
|
1858
|
-
command?: string;
|
|
1859
|
-
normalizedCommand?: string;
|
|
1860
|
-
commandHash?: string;
|
|
1861
|
-
exitCode?: number;
|
|
1862
|
-
isError?: boolean;
|
|
1863
|
-
recordedAt?: string;
|
|
1864
|
-
outputText?: string;
|
|
1865
|
-
toolCallId?: string;
|
|
1866
|
-
},
|
|
1867
|
-
pendingContext: ObservedTaskContext[],
|
|
1868
|
-
maxManifestFiles: number, shellSnapshotBefore?: Record<string, string>,
|
|
1869
|
-
eventTree?: ReturnType<typeof captureWorkspaceVerificationSnapshot>,
|
|
1870
|
-
readProtectedPaths: string[] = [], preWorkspaceRevisionDigest?: string, diagnosticFocused = false
|
|
1871
|
-
): TaskContract | undefined {
|
|
1872
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
1873
|
-
if (!task || task.trace.outcome !== "pending") return;
|
|
1874
|
-
const command = String(observed.normalizedCommand ?? observed.command ?? "").trim();
|
|
1875
|
-
const observedAtMs = Date.parse(observed.recordedAt ?? "");
|
|
1876
|
-
const taskCreatedAtMs = Date.parse(task.createdAt);
|
|
1877
|
-
const matchedProfileCommand = commandMatchesVerifyPlan(command, task.verifyCommands);
|
|
1878
|
-
const focusedTest = diagnosticFocused && observedFocusedTestSummary(observed.outputText);
|
|
1879
|
-
if (
|
|
1880
|
-
!command
|
|
1881
|
-
|| !observed.commandHash
|
|
1882
|
-
|| (!matchedProfileCommand && !focusedTest)
|
|
1883
|
-
|| !Number.isFinite(observedAtMs)
|
|
1884
|
-
|| !Number.isFinite(taskCreatedAtMs)
|
|
1885
|
-
|| observedAtMs < taskCreatedAtMs
|
|
1886
|
-
) return;
|
|
1887
|
-
|
|
1888
|
-
if (!eventTree?.proofCapable) return;
|
|
1889
|
-
const currentDigests = eventTree.snapshot as Record<string, string>;
|
|
1890
|
-
const currentDigest = eventTree.digest;
|
|
1891
|
-
const workspaceRevisionDigest = eventTree.workspaceRevisionDigest ?? undefined;
|
|
1892
|
-
const preWorkingTreeDigest = shellSnapshotBefore && workingTreeSnapshotUsesCurrentAlgorithm(shellSnapshotBefore) ? workingTreeEvidenceDigest(shellSnapshotBefore) : undefined;
|
|
1893
|
-
const exitCode = Number.isInteger(observed.exitCode) ? observed.exitCode as number : observed.isError ? 1 : 0;
|
|
1894
|
-
const classification = classifyVerificationFailure(observed.outputText, exitCode);
|
|
1895
|
-
const duplicate = task.verifyEvidence.some((evidence) => (
|
|
1896
|
-
evidence.command.trim() === command
|
|
1897
|
-
&& evidence.exitCode === exitCode
|
|
1898
|
-
&& evidence.workingTreeDigest === currentDigest && evidence.observedAt === observed.recordedAt
|
|
1899
|
-
&& evidence.workspaceRevisionDigest === workspaceRevisionDigest && evidence.preWorkspaceRevisionDigest === preWorkspaceRevisionDigest
|
|
1900
|
-
));
|
|
1901
|
-
const contextAdded = mergeObservedTaskContext(task, pendingContext, maxManifestFiles, redactText);
|
|
1902
|
-
if (!duplicate) {
|
|
1903
|
-
const evidenceRecordedAt = nowIso();
|
|
1904
|
-
const observedAt = observed.recordedAt ?? evidenceRecordedAt;
|
|
1905
|
-
task.verifyEvidence.push({
|
|
1906
|
-
command: redactText(command),
|
|
1907
|
-
exitCode,
|
|
1908
|
-
summary: `Runtime observed ${matchedProfileCommand ? "configured" : "focused"} verifier exit ${exitCode} (${classification.category}${classification.retryable ? ", retryable" : ""}).`,
|
|
1909
|
-
recordedAt: evidenceRecordedAt,
|
|
1910
|
-
observed: true,
|
|
1911
|
-
observedAt,
|
|
1912
|
-
isError: observed.isError === true,
|
|
1913
|
-
matchedProfileCommand,
|
|
1914
|
-
preWorkingTreeDigest,
|
|
1915
|
-
workingTreeDigest: currentDigest,
|
|
1916
|
-
preWorkspaceRevisionDigest, workspaceRevisionDigest
|
|
1917
|
-
});
|
|
1918
|
-
task.verifyEvidence = diagnosticFocused ? retainFocusedVerification(task.verifyEvidence, currentDigest, workspaceRevisionDigest) : task.verifyEvidence.slice(-100);
|
|
1919
|
-
try {
|
|
1920
|
-
captureVerifierFileSnapshot({
|
|
1921
|
-
projectRoot: ctx.cwd, taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId,
|
|
1922
|
-
toolCallId: observed.toolCallId ?? "", commandHash: observed.commandHash, observedAt,
|
|
1923
|
-
capturedAt: evidenceRecordedAt, exitCode, treeDigest: currentDigest, snapshot: currentDigests,
|
|
1924
|
-
protectedPaths: readProtectedPaths
|
|
1925
|
-
});
|
|
1926
|
-
} catch (error) {
|
|
1927
|
-
ctx.ui.notify(`Piagent could not persist verifier file snapshot: ${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
const taskLocalDelta = taskChangedFileEvidence(ctx.cwd, task, currentDigests).expected;
|
|
1932
|
-
const hasChanges = taskLocalDelta.length > 0;
|
|
1933
|
-
const verificationCanSettleSourceTask = hasChanges || task.mutationPolicy === "allowed";
|
|
1934
|
-
const allPassing = verificationCanSettleSourceTask && allVerifyCommandsPassCurrentTree(task, currentDigest, workspaceRevisionDigest ?? null);
|
|
1935
|
-
const lifecycle = verificationCanSettleSourceTask
|
|
1936
|
-
? applyRuntimeLifecycleObservation(task, allPassing ? "verification-complete" : "verification-pending", nowIso())
|
|
1937
|
-
: { changed: false, mode: runtimeLifecycleMode(task) };
|
|
1938
|
-
const acceptance = refreshAcceptanceReceipt(task, {
|
|
1939
|
-
cwd: ctx.cwd, apiBaselineEvidence,
|
|
1940
|
-
changedFiles: taskAcceptanceEvidenceFiles(ctx.cwd, task, currentDigests, taskLocalDelta),
|
|
1941
|
-
currentWorkingTreeDigest: currentDigest
|
|
1942
|
-
});
|
|
1943
|
-
task.acceptanceReceipt = acceptance.task.acceptanceReceipt;
|
|
1944
|
-
if (!task.workingTreeDigestMigration || (shellSnapshotBefore && changedSnapshotFiles(shellSnapshotBefore, currentDigests).length === 0)) Object.assign(task, completeTaskDigestRefresh(task, currentDigest, workspaceRevisionDigest ?? null));
|
|
1945
|
-
if (duplicate && contextAdded.length === 0 && !lifecycle.changed) return task;
|
|
1946
|
-
|
|
1947
|
-
const written = writeTask(ctx.cwd, task);
|
|
1948
|
-
const trace = {
|
|
1949
|
-
event: "verify_observed",
|
|
1950
|
-
taskId: written.taskId,
|
|
1951
|
-
taskRunId: written.taskRunId,
|
|
1952
|
-
sessionId: written.sessionId,
|
|
1953
|
-
command: redactText(command),
|
|
1954
|
-
exitCode,
|
|
1955
|
-
workingTreeDigest: currentDigest,
|
|
1956
|
-
contextFiles: contextAdded,
|
|
1957
|
-
lifecycleMode: lifecycle.mode,
|
|
1958
|
-
lifecycleAdvanced: lifecycle.changed,
|
|
1959
|
-
allConfiguredVerifiersPassing: allPassing
|
|
1960
|
-
};
|
|
1961
|
-
appendTrace(ctx.cwd, trace);
|
|
1962
|
-
appendSessionTrace(pi, trace);
|
|
1963
|
-
recordVerificationCheckpoint(ctx, written, {
|
|
1964
|
-
commandHash: observed.commandHash, observedAt: observed.recordedAt,
|
|
1965
|
-
workingTreeDigest: currentDigest,
|
|
1966
|
-
exitCode,
|
|
1967
|
-
evidence: {
|
|
1968
|
-
command: redactText(command),
|
|
1969
|
-
exitCode,
|
|
1970
|
-
category: classification.category,
|
|
1971
|
-
retryable: classification.retryable,
|
|
1972
|
-
failureClassification: classification,
|
|
1973
|
-
preWorkingTreeDigest,
|
|
1974
|
-
workingTreeDigest: currentDigest,
|
|
1975
|
-
preWorkspaceRevisionDigest, workspaceRevisionDigest
|
|
1976
|
-
}
|
|
1977
|
-
});
|
|
1978
|
-
return written;
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
function flushObservedTaskContext(
|
|
1982
|
-
pi: ExtensionAPI,
|
|
1983
|
-
ctx: ExtensionContext,
|
|
1984
|
-
pendingContext: ObservedTaskContext[],
|
|
1985
|
-
maxManifestFiles: number,
|
|
1986
|
-
event: string
|
|
1987
|
-
): TaskContract | undefined {
|
|
1988
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
1989
|
-
if (!task || task.trace.outcome !== "pending") return task;
|
|
1990
|
-
const added = mergeObservedTaskContext(task, pendingContext, maxManifestFiles, redactText);
|
|
1991
|
-
const lifecycle = (task.changeMode === "read-only" || task.mutationPolicy === "forbidden") && hasDurableContextEvidence(task)
|
|
1992
|
-
? applyRuntimeLifecycleObservation(task, "context-complete", nowIso())
|
|
1993
|
-
: { changed: false, mode: runtimeLifecycleMode(task) };
|
|
1994
|
-
if (added.length === 0 && !lifecycle.changed) return task;
|
|
1995
|
-
const written = writeTask(ctx.cwd, task);
|
|
1996
|
-
const trace = {
|
|
1997
|
-
event,
|
|
1998
|
-
taskId: written.taskId,
|
|
1999
|
-
taskRunId: written.taskRunId,
|
|
2000
|
-
sessionId: written.sessionId,
|
|
2001
|
-
files: added,
|
|
2002
|
-
lifecycleMode: lifecycle.mode,
|
|
2003
|
-
lifecycleAdvanced: lifecycle.changed
|
|
2004
|
-
};
|
|
2005
|
-
appendTrace(ctx.cwd, trace);
|
|
2006
|
-
appendSessionTrace(pi, trace);
|
|
2007
|
-
return written;
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
function completionTaskProjection(
|
|
2011
|
-
cwd: string,
|
|
2012
|
-
task: TaskContract,
|
|
2013
|
-
finalFileDigests: Record<string, string> = workingTreeSnapshot(cwd) as Record<string, string>
|
|
2014
|
-
): TaskContract {
|
|
2015
|
-
const changedFiles = taskChangedFileEvidence(cwd, task, finalFileDigests).expected;
|
|
2016
|
-
const projected = {
|
|
2017
|
-
...task,
|
|
2018
|
-
changedFiles,
|
|
2019
|
-
finalWorkingTreeFiles: Object.keys(finalFileDigests).sort(),
|
|
2020
|
-
finalFileDigests,
|
|
2021
|
-
failedAt: undefined,
|
|
2022
|
-
failureReason: undefined,
|
|
2023
|
-
ruledOut: undefined,
|
|
2024
|
-
trace: {
|
|
2025
|
-
outcome: "completed" as const,
|
|
2026
|
-
notes: "Runtime finalized from observed context, working-tree changes, current verification, and completed work-plan evidence.",
|
|
2027
|
-
recordedAt: nowIso()
|
|
2028
|
-
}
|
|
2029
|
-
};
|
|
2030
|
-
return refreshAcceptanceReceipt(projected, {
|
|
2031
|
-
cwd, apiBaselineEvidence,
|
|
2032
|
-
changedFiles: taskAcceptanceEvidenceFiles(cwd, projected, finalFileDigests, changedFiles),
|
|
2033
|
-
currentWorkingTreeDigest: workingTreeEvidenceDigest(finalFileDigests)
|
|
2034
|
-
}).task as TaskContract;
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
1699
|
function prepareToolInputForPolicy(
|
|
2038
1700
|
toolName: string,
|
|
2039
1701
|
input: Record<string, unknown>,
|
|
@@ -3456,14 +3118,6 @@ function taskAcceptanceEvidenceFiles(
|
|
|
3456
3118
|
);
|
|
3457
3119
|
}
|
|
3458
3120
|
|
|
3459
|
-
function exactReviewPathCoverage(expectedPaths: string[], reviewedPaths: string[] | undefined): boolean {
|
|
3460
|
-
const expected = [...new Set(expectedPaths)].sort();
|
|
3461
|
-
const reviewed = [...new Set(reviewedPaths ?? [])].sort();
|
|
3462
|
-
return expected.length > 0
|
|
3463
|
-
&& expected.length === reviewed.length
|
|
3464
|
-
&& expected.every((file, index) => file === reviewed[index]);
|
|
3465
|
-
}
|
|
3466
|
-
|
|
3467
3121
|
function taskScopeIncludesPath(scope: string[], file: string): boolean {
|
|
3468
3122
|
const normalizedFile = normalizePathCandidate(file);
|
|
3469
3123
|
return scope.some((candidate) => {
|
|
@@ -3737,18 +3391,6 @@ function checkoutReferenceRepo(repoRef: string, forceUpdate = false): ReferenceR
|
|
|
3737
3391
|
return { host, owner, repo, cloneUrl, checkoutPath, commit, fetched };
|
|
3738
3392
|
}
|
|
3739
3393
|
|
|
3740
|
-
function sessionTaskReference(ctx: ExtensionContext): { taskId?: string; taskRunId?: string } | undefined {
|
|
3741
|
-
const entries = ctx.sessionManager.getBranch();
|
|
3742
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
3743
|
-
const entry = entries[index] as unknown as { type?: string; customType?: string; data?: Record<string, unknown> };
|
|
3744
|
-
if (entry.type !== "custom" || entry.customType !== PIAGENT_TRACE_STATE_TYPE || !entry.data) continue;
|
|
3745
|
-
const taskId = typeof entry.data.taskId === "string" ? entry.data.taskId : undefined;
|
|
3746
|
-
const taskRunId = typeof entry.data.taskRunId === "string" ? entry.data.taskRunId : undefined;
|
|
3747
|
-
if (taskId || taskRunId) return { taskId, taskRunId };
|
|
3748
|
-
}
|
|
3749
|
-
return undefined;
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
3394
|
function compactSessionTask(cwd: string, sessionId: string): TaskContract | undefined {
|
|
3753
3395
|
return activeSessionTask(cwd, sessionId) as TaskContract | undefined;
|
|
3754
3396
|
}
|
|
@@ -3763,6 +3405,25 @@ function environmentFeatureEnabled(name: string, fallback = true): boolean {
|
|
|
3763
3405
|
return !["0", "false", "no", "off", "disabled"].includes(value);
|
|
3764
3406
|
}
|
|
3765
3407
|
|
|
3408
|
+
// Kept registration modules also power historical inspection. Do not publish
|
|
3409
|
+
// their retired workflow controls to current agent sessions, and never put them
|
|
3410
|
+
// back on the active list through a tool group that still names them.
|
|
3411
|
+
const RETIRED_PIAGENT_TOOLS = new Set(["piagent_task_start", "piagent_task_progress", "piagent_task_gate_check", "piagent_context_record",
|
|
3412
|
+
"piagent_verify_record", "piagent_trace_record", "piagent_memory_citation_record"]);
|
|
3413
|
+
function registerPiagentTool(...args: Parameters<typeof registerToolDefinition>): ReturnType<typeof registerToolDefinition> {
|
|
3414
|
+
if (RETIRED_PIAGENT_TOOLS.has(args[1].name)) return;
|
|
3415
|
+
return registerToolDefinition(...args);
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
/** Workflow records are historical data. They never authorize or gate a freeform turn. */
|
|
3419
|
+
function activeSessionTask(_cwd: string, _sessionId: string): TaskContract | undefined {
|
|
3420
|
+
return undefined;
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// The runtime hooks keep task callbacks for historical records and the
|
|
3424
|
+
// benchmark grader; a freeform session has no task, so they get nothing.
|
|
3425
|
+
const noTask = (..._args: unknown[]): undefined => undefined;
|
|
3426
|
+
|
|
3766
3427
|
export default function piagentGuard(pi: ExtensionAPI) {
|
|
3767
3428
|
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
|
3768
3429
|
const policy = loadPolicy(extensionDir);
|
|
@@ -3774,47 +3435,19 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
3774
3435
|
const parentRoutingMode = parentRoutingModeFromEnvironment(process.env.PIAGENT_PARENT_ROUTING);
|
|
3775
3436
|
const routingObjective = routingObjectiveFromEnvironment(process.env.PIAGENT_ROUTING_OBJECTIVE);
|
|
3776
3437
|
const autoContextEnabled = environmentFeatureEnabled("PIAGENT_AUTO_CONTEXT");
|
|
3777
|
-
const
|
|
3778
|
-
const independentVerificationConfigPath = process.env.PIAGENT_INDEPENDENT_VERIFICATION_CONFIG,
|
|
3779
|
-
runtimeState = new RuntimeSessionState({ maxObservedContext: contextBudgetConfig(policy).maxManifestFiles });
|
|
3780
|
-
const independentAcceptance = new IndependentAcceptanceRuntime({ state: runtimeState, installedRoot: PLATFORM_ROOT,
|
|
3781
|
-
configPath: independentVerificationConfigPath,
|
|
3782
|
-
writeTask,
|
|
3783
|
-
activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
|
|
3784
|
-
authorizeSourceRead: (ctx, sourcePath) => {
|
|
3785
|
-
if (!ctx.isProjectTrusted()) return false;
|
|
3786
|
-
const profile = loadProfileFromContext(ctx), paths = effectiveProtectedPaths(policy, profile);
|
|
3787
|
-
const capabilities = verifyProjectCapabilityState(extensionDir, ctx.cwd, true, { sessionId: ctx.sessionManager.getSessionId() });
|
|
3788
|
-
if (!capabilities.ok) return false;
|
|
3789
|
-
const permission = resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx));
|
|
3790
|
-
return !evaluatePathLikeToolAccess(ctx.cwd, "read", { path: sourcePath }, paths.writeProtectedPaths, paths.readProtectedPaths,
|
|
3791
|
-
paths.readOnlyPaths, permission.mode === "trusted-full-access" ? undefined : capabilities.filesystemRead).block;
|
|
3792
|
-
} });
|
|
3438
|
+
const runtimeState = new RuntimeSessionState({ maxObservedContext: contextBudgetConfig(policy).maxManifestFiles });
|
|
3793
3439
|
const editFreshnessGuard = new EditFreshnessGuard(editFreshnessModeFromEnvironment(process.env.PIAGENT_EDIT_FRESHNESS));
|
|
3440
|
+
// Snapshots were keyed by the task run. A freeform turn has none since the
|
|
3441
|
+
// task-contract retirement, so it uses one session-wide key; the guard itself
|
|
3442
|
+
// is already per session.
|
|
3443
|
+
const FREEFORM_EDIT_SCOPE = "freeform";
|
|
3794
3444
|
const sourceMutationGuardBindings = createSourceMutationGuardBindings(policy, loadProfileFromContext);
|
|
3795
3445
|
const runtimeSnapshotCapture = new RuntimeSnapshotCapture(), runtimeVersions = readRuntimeVersionMetadata(PLATFORM_ROOT);
|
|
3796
3446
|
const solverShadow = solverMode === "off" ? undefined : new SolverShadowRuntime(solverMode);
|
|
3797
3447
|
const modelRouteRuntime = new ModelRouteRuntime(parentRoutingMode, routingObjective);
|
|
3798
3448
|
const modelSelectionProvenance = new ModelSelectionProvenanceTracker();
|
|
3799
3449
|
const serviceTierRuntime = new ServiceTierRuntime({ environmentValue: process.env.PIAGENT_FAST_MODE });
|
|
3800
|
-
const trajectoryRuntime = new TrajectoryRuntime()
|
|
3801
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined; return task && authorityReplacementState(ctx.cwd, task).required ? "new-attempt-required" : task?.workingTreeDigestMigration?.status;
|
|
3802
|
-
}, (ctx) => {
|
|
3803
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
3804
|
-
return !task ? undefined : taskAuthorityDecision(task, "CAP-09", "block").allowed ? "on" : taskAuthorityDecision(task, "CAP-09", "observe").allowed ? "shadow" : "off";
|
|
3805
|
-
});
|
|
3806
|
-
const semanticRepairRuntime = new SemanticRepairRuntime({
|
|
3807
|
-
now: nowIso,
|
|
3808
|
-
trace: (ctx, task, payload) => {
|
|
3809
|
-
const trace = { ...payload, taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId };
|
|
3810
|
-
appendTrace(ctx.cwd, trace); appendSessionTrace(pi, trace); telemetry(ctx, trace);
|
|
3811
|
-
},
|
|
3812
|
-
openRepair: (ctx, task, observedAt) => phaseToolRuntime.apply(ctx, trajectoryRuntime.sync(
|
|
3813
|
-
ctx.cwd, ctx.sessionManager.getSessionId(), task,
|
|
3814
|
-
{ sourceHook: "tool-result", recoveryRequested: true, recoveryMutationAllowed: true, observedAt }
|
|
3815
|
-
))
|
|
3816
|
-
});
|
|
3817
|
-
let maybeStartAutomaticTask: (prompt: string, ctx: ExtensionContext) => Promise<{ started: boolean; text: string; task?: TaskContract } | undefined>;
|
|
3450
|
+
const trajectoryRuntime = new TrajectoryRuntime();
|
|
3818
3451
|
|
|
3819
3452
|
pi.on("model_select", (event, ctx) => {
|
|
3820
3453
|
modelSelectionProvenance.observeModelSelection(ctx.sessionManager.getSessionId(), event.source);
|
|
@@ -3988,7 +3621,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
3988
3621
|
if (!PIAGENT_TOOL_NAMES.has(toolName)) selected.add(toolName);
|
|
3989
3622
|
}
|
|
3990
3623
|
}
|
|
3991
|
-
const normalizedGroups = [...new Set<PiagentToolGroup>(groups)];
|
|
3624
|
+
const normalizedGroups = [...new Set<PiagentToolGroup>(groups)].filter((group) => !["intake", "task", "recovery"].includes(group));
|
|
3992
3625
|
for (const toolName of PIAGENT_TOOL_ORDER) {
|
|
3993
3626
|
if (normalizedGroups.some((group) => PIAGENT_TOOL_GROUPS[group].includes(toolName as never))) {
|
|
3994
3627
|
selected.add(toolName);
|
|
@@ -3996,7 +3629,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
3996
3629
|
}
|
|
3997
3630
|
const ordered = [
|
|
3998
3631
|
...preferApplyPatchBeforeWriters(current.filter((toolName) => selected.has(toolName) && !PIAGENT_TOOL_NAMES.has(toolName))),
|
|
3999
|
-
...PIAGENT_TOOL_ORDER.filter((toolName) => selected.has(toolName))
|
|
3632
|
+
...PIAGENT_TOOL_ORDER.filter((toolName) => selected.has(toolName) && !RETIRED_PIAGENT_TOOLS.has(toolName))
|
|
4000
3633
|
];
|
|
4001
3634
|
const unchanged = ordered.length === current.length && ordered.every((toolName, index) => toolName === current[index]);
|
|
4002
3635
|
if (!unchanged) pi.setActiveTools(ordered);
|
|
@@ -4008,80 +3641,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4008
3641
|
activeCount: ordered.length,
|
|
4009
3642
|
piagentTools: ordered.filter((toolName) => PIAGENT_TOOL_NAMES.has(toolName))
|
|
4010
3643
|
});
|
|
4011
|
-
phaseToolRuntime.reapply(ctx);
|
|
4012
3644
|
return pi.getActiveTools();
|
|
4013
3645
|
}
|
|
4014
3646
|
|
|
4015
|
-
function recoveryDecisionForTask(
|
|
4016
|
-
ctx: ExtensionContext,
|
|
4017
|
-
task: TaskContract,
|
|
4018
|
-
gate?: { missing: string[]; missingVerifyCommands: string[] },
|
|
4019
|
-
currentTreeDigest?: string
|
|
4020
|
-
): RecoveryDecision {
|
|
4021
|
-
const latestExactVerifier = latestObservedVerification(task.verifyEvidence.filter((evidence) => evidence.matchedProfileCommand === true));
|
|
4022
|
-
const failed = latestExactVerifier && latestExactVerifier.exitCode !== 0 ? latestExactVerifier : undefined;
|
|
4023
|
-
const summary = failed?.summary ?? gate?.missing.join("; ") ?? "unknown task recovery evidence";
|
|
4024
|
-
let recordedClassification: FailureClassification | undefined;
|
|
4025
|
-
try {
|
|
4026
|
-
const replay = replayTaskCheckpoints(ctx.cwd, task.taskRunId, task);
|
|
4027
|
-
if (replay.corruptions.length === 0 && failed) recordedClassification = recordedFailureForObservation(
|
|
4028
|
-
replay.checkpoints, failed, currentTreeDigest, currentWorkspaceRevisionDigest(ctx.cwd)
|
|
4029
|
-
) as FailureClassification | undefined;
|
|
4030
|
-
} catch {
|
|
4031
|
-
// A missing/corrupt journal cannot grant recovery mutation; the fallback
|
|
4032
|
-
// classifier remains fail-closed for unknown evidence.
|
|
4033
|
-
}
|
|
4034
|
-
const gateClassification = selectCompletionRecoveryClassification(
|
|
4035
|
-
recordedClassification,
|
|
4036
|
-
gate?.missing,
|
|
4037
|
-
summary,
|
|
4038
|
-
failed?.exitCode ?? 1
|
|
4039
|
-
);
|
|
4040
|
-
const independentRecovery = independentVerificationRecovery(ctx.cwd, task, currentTreeDigest ?? workingTreeEvidenceDigest(workingTreeSnapshot(ctx.cwd)));
|
|
4041
|
-
const classification = gateClassification.category === "scope-protected-path"
|
|
4042
|
-
? gateClassification : independentRecovery?.classification ?? gateClassification;
|
|
4043
|
-
const trajectory = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
|
|
4044
|
-
// Task scope is a retrieval/review hint, not filesystem authority. Recovery
|
|
4045
|
-
// may update a dependency or config file when repository evidence requires
|
|
4046
|
-
// it; protected-path and approval policy still run on the exact call.
|
|
4047
|
-
const dependencyMutationAuthorized = true;
|
|
4048
|
-
const selected = selectRecoveryDecision({
|
|
4049
|
-
featureEnabled: autoRecoveryEnabled && taskAuthorityDecision(task, "CAP-12", "model-turn").allowed,
|
|
4050
|
-
task: {
|
|
4051
|
-
taskId: task.taskId,
|
|
4052
|
-
taskRunId: task.taskRunId,
|
|
4053
|
-
attempt: task.attempt,
|
|
4054
|
-
maxAttempts: task.maxAttempts,
|
|
4055
|
-
changeMode: task.changeMode
|
|
4056
|
-
},
|
|
4057
|
-
classification,
|
|
4058
|
-
currentPhase: trajectory.enforcementSafe ? trajectory.phase ?? "verify" : "handoff",
|
|
4059
|
-
history: runtimeState.recoveryHistory(task.taskId),
|
|
4060
|
-
proposedHypothesisRef: independentRecovery?.hypothesisRef ?? (classification.reasonCodes[0] ? `reason:${classification.reasonCodes[0]}` : null),
|
|
4061
|
-
exactVerifierAvailable: (gate?.missingVerifyCommands.length ?? task.verifyCommands.length) > 0,
|
|
4062
|
-
currentTreeMatchesEvidence: currentTreeDigest && latestExactVerifier?.workingTreeDigest
|
|
4063
|
-
? currentTreeDigest === latestExactVerifier.workingTreeDigest && latestExactVerifier.preWorkspaceRevisionDigest === currentWorkspaceRevisionDigest(ctx.cwd)
|
|
4064
|
-
&& latestExactVerifier.workspaceRevisionDigest === latestExactVerifier.preWorkspaceRevisionDigest
|
|
4065
|
-
: true,
|
|
4066
|
-
dependencyMutationAuthorized,
|
|
4067
|
-
independentDisposition: independentRecovery?.independentDisposition
|
|
4068
|
-
});
|
|
4069
|
-
return applyProofCapabilityHandoff(selected, gate?.missing, Boolean(independentRecovery));
|
|
4070
|
-
}
|
|
4071
|
-
|
|
4072
|
-
function trajectoryRecoveryOptions(ctx: ExtensionContext, task: TaskContract, options: any): any {
|
|
4073
|
-
return {
|
|
4074
|
-
...options,
|
|
4075
|
-
recoveryMutationAllowed: typeof options.recoveryMutationAllowed === "boolean"
|
|
4076
|
-
? options.recoveryMutationAllowed
|
|
4077
|
-
: autoRecoveryEnabled && taskAuthorityDecision(task, "CAP-12", "model-turn").allowed
|
|
4078
|
-
? recoveryDecisionForTask(ctx, task).sourceMutationAllowed
|
|
4079
|
-
: undefined
|
|
4080
|
-
};
|
|
4081
|
-
}
|
|
4082
|
-
|
|
4083
3647
|
const activityInspector = registerActivityInspector(pi, {
|
|
4084
|
-
activeTask:
|
|
3648
|
+
activeTask: noTask,
|
|
4085
3649
|
readEvents: (cwd) => readContextTelemetry(cwd, { limit: 5_000 }) as any[],
|
|
4086
3650
|
protectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfile(ctx.cwd)).readProtectedPaths,
|
|
4087
3651
|
selectAction: selectRuntimeAction,
|
|
@@ -4098,10 +3662,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4098
3662
|
loadProfile: loadProfileFromContext,
|
|
4099
3663
|
projectProfileExists: (cwd) => fs.existsSync(projectProfilePath(cwd)),
|
|
4100
3664
|
activateToolGroups,
|
|
4101
|
-
taskReference:
|
|
4102
|
-
activeTask:
|
|
4103
|
-
resolveTask:
|
|
4104
|
-
resolveTaskAny:
|
|
3665
|
+
taskReference: noTask,
|
|
3666
|
+
activeTask: noTask,
|
|
3667
|
+
resolveTask: noTask,
|
|
3668
|
+
resolveTaskAny: noTask,
|
|
4105
3669
|
bindTask: bindSessionTask,
|
|
4106
3670
|
writeTask,
|
|
4107
3671
|
appendTrace,
|
|
@@ -4114,26 +3678,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4114
3678
|
inspectResume: (cwd, task, sessionId) => inspectTaskResumeState(cwd, task, sessionId, undefined, {
|
|
4115
3679
|
protectedPaths: effectiveProtectedPaths(policy, loadProfile(cwd)).readProtectedPaths
|
|
4116
3680
|
}),
|
|
4117
|
-
syncTrajectory:
|
|
4118
|
-
const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
|
|
4119
|
-
const resume = workingTreeSnapshotHasUnavailableEvidence(snapshot)
|
|
4120
|
-
? { openRepair: false }
|
|
4121
|
-
: { openRepair: (taskAuthorityDecision(task, "CAP-13", "block").allowed
|
|
4122
|
-
|| taskAuthorityDecision(task, "CAP-12", "mutate").allowed) && semanticRepairRuntime.resume({
|
|
4123
|
-
cwd: ctx.cwd,
|
|
4124
|
-
task,
|
|
4125
|
-
sessionId: ctx.sessionManager.getSessionId(),
|
|
4126
|
-
currentDigest: workingTreeEvidenceDigest(snapshot)
|
|
4127
|
-
}) };
|
|
4128
|
-
return phaseToolRuntime.apply(ctx, trajectoryRuntime.sync(ctx.cwd, ctx.sessionManager.getSessionId(), task, trajectoryRecoveryOptions(ctx, task, {
|
|
4129
|
-
sourceHook: "session-start",
|
|
4130
|
-
recoveryRequested: resume.openRepair || undefined,
|
|
4131
|
-
recoveryMutationAllowed: resume.openRepair || undefined
|
|
4132
|
-
})));
|
|
4133
|
-
},
|
|
3681
|
+
syncTrajectory: noTask,
|
|
4134
3682
|
telemetry,
|
|
4135
3683
|
afterStart: async (ctx) => {
|
|
4136
|
-
await independentAcceptance.activate(ctx);
|
|
4137
3684
|
const serviceTier = serviceTierRuntime.restore(ctx);
|
|
4138
3685
|
if (serviceTier.enabled && serviceTier.reasonCode === "provider-not-supported") {
|
|
4139
3686
|
ctx.ui.notify("Piagent Fast mode is enabled, but the active provider is not OpenAI Codex; no service tier was changed.", "warning");
|
|
@@ -4147,15 +3694,14 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4147
3694
|
state: runtimeState,
|
|
4148
3695
|
maxManifestFiles: contextBudgetConfig(policy).maxManifestFiles,
|
|
4149
3696
|
telemetry,
|
|
4150
|
-
activeTask:
|
|
3697
|
+
activeTask: noTask,
|
|
4151
3698
|
writeTask,
|
|
4152
3699
|
bindTask: bindSessionTask,
|
|
4153
3700
|
appendTrace,
|
|
4154
|
-
flushObservedTaskContext,
|
|
3701
|
+
flushObservedTaskContext: noTask,
|
|
4155
3702
|
onTurnEnd: activityInspector.refresh,
|
|
4156
3703
|
onAgentSettled: activityInspector.refresh,
|
|
4157
3704
|
beforeShutdown: async (ctx) => {
|
|
4158
|
-
await independentAcceptance.clear(ctx);
|
|
4159
3705
|
serviceTierRuntime.forget(ctx);
|
|
4160
3706
|
sourceMutationGuardBindings.unbind(ctx);
|
|
4161
3707
|
editFreshnessGuard.clear(ctx);
|
|
@@ -4163,13 +3709,12 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4163
3709
|
sessionCapabilityDigests.delete(`${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`);
|
|
4164
3710
|
}
|
|
4165
3711
|
});
|
|
4166
|
-
registerTaskUsageHooks(pi, { activeTask:
|
|
4167
|
-
pi.on("turn_end", (event, ctx) => independentAcceptance.observeTurnEnd(ctx, event.message));
|
|
3712
|
+
registerTaskUsageHooks(pi, { activeTask: noTask, telemetry });
|
|
4168
3713
|
|
|
4169
3714
|
registerInputHook(pi, {
|
|
4170
3715
|
state: runtimeState,
|
|
4171
3716
|
boilerplateCollapseChars: BOILERPLATE_COLLAPSE_CHARS,
|
|
4172
|
-
activeTask:
|
|
3717
|
+
activeTask: noTask, authorityPolicy: noTask,
|
|
4173
3718
|
readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
|
|
4174
3719
|
imageAccess: (ctx) => {
|
|
4175
3720
|
const projectTrusted = ctx.isProjectTrusted();
|
|
@@ -4195,12 +3740,12 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4195
3740
|
state: runtimeState,
|
|
4196
3741
|
autoContextEnabled,
|
|
4197
3742
|
contextDeltaShadowMode: contextBudgetConfig(policy).contextDeltaShadow,
|
|
4198
|
-
activeTask:
|
|
3743
|
+
activeTask: noTask,
|
|
4199
3744
|
readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
|
|
4200
3745
|
contextExcludePatterns: (ctx) => contextIndexExcludePatterns(policy, loadProfileFromContext(ctx)),
|
|
4201
3746
|
promptPackKey,
|
|
4202
3747
|
retrievalKey,
|
|
4203
|
-
startAutomaticTask: (
|
|
3748
|
+
startAutomaticTask: async () => undefined,
|
|
4204
3749
|
runtimeSnapshot: runtimeSnapshotEnabled ? (ctx) => runtimeSnapshotCapture.capture(ctx, {
|
|
4205
3750
|
effectiveThinkingLevel: String(pi.getThinkingLevel()),
|
|
4206
3751
|
versions: runtimeVersions
|
|
@@ -4210,53 +3755,22 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4210
3755
|
solverShadow, { request, ctx, profile: loadProfileFromContext(ctx), activeTask, runtimeSnapshot, effort: String(pi.getThinkingLevel()), protectedTarget }
|
|
4211
3756
|
),
|
|
4212
3757
|
modelRoute: ({ ctx, features, runtimeSnapshot }) => evaluateModelRoute(ctx, features, runtimeSnapshot),
|
|
4213
|
-
syncTrajectory:
|
|
3758
|
+
syncTrajectory: noTask,
|
|
4214
3759
|
telemetry
|
|
4215
3760
|
});
|
|
4216
3761
|
|
|
4217
|
-
registerCompletionHook(pi, {
|
|
4218
|
-
state: runtimeState,
|
|
4219
|
-
prepareIndependentAcceptance: (ctx, task, response) => independentAcceptance.prepare(ctx, task, response),
|
|
4220
|
-
deferIndependentCompletion: (ctx, task, response, finalizer) => independentAcceptance.deferCompletion(ctx, task, response, finalizer),
|
|
4221
|
-
projectIndependentLifecycle: (ctx, task, candidate) => independentAcceptance.projectLifecycle(ctx, task, candidate),
|
|
4222
|
-
nativeRefusalFallbackAllowed: () => !independentVerificationConfigPath,
|
|
4223
|
-
maxManifestFiles: contextBudgetConfig(policy).maxManifestFiles,
|
|
4224
|
-
activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
|
|
4225
|
-
flushObservedTaskContext,
|
|
4226
|
-
completionProjection: completionTaskProjection,
|
|
4227
|
-
acceptanceEvidenceFiles: taskAcceptanceEvidenceFiles,
|
|
4228
|
-
evaluateGate: (cwd, task, currentDigests, currentDigest) => {
|
|
4229
|
-
const gate = evaluateTaskGate(cwd, task, policy, { currentDigests, currentWorkingTreeDigest: currentDigest });
|
|
4230
|
-
const repairBlock = (taskAuthorityDecision(task, "CAP-13", "block").allowed
|
|
4231
|
-
|| taskAuthorityDecision(task, "CAP-12", "mutate").allowed)
|
|
4232
|
-
? semanticRepairRuntime.completionBlock(cwd, task.taskRunId)
|
|
4233
|
-
: undefined;
|
|
4234
|
-
return repairBlock ? { ...gate, decision: "fail", missing: [...new Set([...gate.missing, repairBlock])] } : gate;
|
|
4235
|
-
},
|
|
4236
|
-
writeTask,
|
|
4237
|
-
activateBaseTools: (ctx) => activateToolGroups(ctx, []),
|
|
4238
|
-
appendTrace,
|
|
4239
|
-
appendSessionTrace,
|
|
4240
|
-
telemetry,
|
|
4241
|
-
semanticReviewAllowed: (task) => taskAuthorityDecision(task, "CAP-13", "model-turn").allowed,
|
|
4242
|
-
finalGateMode: (ctx) => resolveRuntimePolicy(loadProfileFromContext(ctx)).finalGate,
|
|
4243
|
-
verifierInstructions: verifierCommandInstructions,
|
|
4244
|
-
recoveryDecision: (ctx, task, gate, currentDigest) => recoveryDecisionForTask(ctx, task, gate, currentDigest),
|
|
4245
|
-
syncTrajectory: (ctx, task, options) => phaseToolRuntime.apply(ctx, trajectoryRuntime.sync(ctx.cwd, ctx.sessionManager.getSessionId(), task, trajectoryRecoveryOptions(ctx, task, options)))
|
|
4246
|
-
});
|
|
4247
|
-
|
|
4248
3762
|
registerToolResultHook(pi, {
|
|
4249
3763
|
state: runtimeState,
|
|
4250
|
-
activeTask:
|
|
3764
|
+
activeTask: noTask,
|
|
4251
3765
|
maxManifestFiles: contextBudgetConfig(policy).maxManifestFiles,
|
|
4252
|
-
flushObservedTaskContext,
|
|
3766
|
+
flushObservedTaskContext: noTask,
|
|
4253
3767
|
readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
|
|
4254
3768
|
recordObservedBash: (observed) => bashResults.record(observed),
|
|
4255
3769
|
observedBashLedgerPath,
|
|
4256
3770
|
redactText,
|
|
4257
3771
|
observedTaskContext: observedTaskContextFromToolResult,
|
|
4258
|
-
recordObservedTaskChanges,
|
|
4259
|
-
recordObservedTaskVerification:
|
|
3772
|
+
recordObservedTaskChanges: noTask,
|
|
3773
|
+
recordObservedTaskVerification: noTask,
|
|
4260
3774
|
extractLikelyPath: extractLikelyPathFromInput,
|
|
4261
3775
|
mutationTargets: taskMutationTargets,
|
|
4262
3776
|
isShellTool: (toolName) => SHELL_TOOL_NAMES.has(toolName),
|
|
@@ -4264,46 +3778,18 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4264
3778
|
activity: recordActivity,
|
|
4265
3779
|
now: nowIso,
|
|
4266
3780
|
recordSubagentResult: (ctx, event, result) => {
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
now: nowIso,
|
|
4270
|
-
persist: (candidate) => writeTask(ctx.cwd, candidate),
|
|
4271
|
-
trace: (payload) => appendTrace(ctx.cwd, payload),
|
|
4272
|
-
sessionTrace: (payload) => appendSessionTrace(pi, payload)
|
|
4273
|
-
});
|
|
4274
|
-
telemetry(ctx, {
|
|
4275
|
-
event: "helper_result_classified",
|
|
4276
|
-
recordedAt: nowIso(),
|
|
4277
|
-
taskRunId: written?.taskRunId,
|
|
4278
|
-
toolCallId: event.toolCallId,
|
|
4279
|
-
spawned: result.spawned,
|
|
4280
|
-
failed: result.failed,
|
|
4281
|
-
disposition: result.disposition,
|
|
4282
|
-
reasonCode: result.reasonCode,
|
|
4283
|
-
calls: result.calls,
|
|
4284
|
-
tokens: result.tokens
|
|
4285
|
-
});
|
|
3781
|
+
telemetry(ctx, { event: "helper_result_classified", recordedAt: nowIso(), toolCallId: event.toolCallId, spawned: result.spawned, failed: result.failed,
|
|
3782
|
+
disposition: result.disposition, reasonCode: result.reasonCode, calls: result.calls, tokens: result.tokens });
|
|
4286
3783
|
},
|
|
4287
3784
|
observeEditFreshness: (ctx, event, metadata) => {
|
|
4288
|
-
if (!metadata.successful
|
|
3785
|
+
if (!metadata.successful) return;
|
|
4289
3786
|
const protectedPaths = effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths;
|
|
4290
3787
|
const source = event.toolName === "read" ? "read" : ["edit", "write", "apply_patch"].includes(event.toolName) ? "mutation" : undefined;
|
|
4291
3788
|
const candidates = source === "read" && metadata.targetPath ? [metadata.targetPath] : source === "mutation" ? metadata.mutationTargets : [];
|
|
4292
3789
|
const safeTargets = candidates.filter((file) => !matchesProtectedPath(file, protectedPaths));
|
|
4293
|
-
const observed = source ? editFreshnessGuard.observe(ctx,
|
|
4294
|
-
if (observed.length) telemetry(ctx, { event: "edit_freshness_snapshot_observed",
|
|
3790
|
+
const observed = source ? editFreshnessGuard.observe(ctx, FREEFORM_EDIT_SCOPE, safeTargets, source, nowIso()) : [];
|
|
3791
|
+
if (observed.length) telemetry(ctx, { event: "edit_freshness_snapshot_observed", source, paths: observed.map(redactText) });
|
|
4295
3792
|
},
|
|
4296
|
-
completeSemanticRepair: (ctx, event, metadata) => {
|
|
4297
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4298
|
-
return task && (taskAuthorityDecision(task, "CAP-13", "mutate").allowed
|
|
4299
|
-
|| taskAuthorityDecision(task, "CAP-12", "mutate").allowed)
|
|
4300
|
-
? semanticRepairRuntime.complete(ctx, task, event, metadata)
|
|
4301
|
-
: undefined;
|
|
4302
|
-
},
|
|
4303
|
-
syncTrajectory: (ctx, contextObserved) => {
|
|
4304
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4305
|
-
return task ? phaseToolRuntime.apply(ctx, trajectoryRuntime.sync(ctx.cwd, ctx.sessionManager.getSessionId(), task, trajectoryRecoveryOptions(ctx, task, { sourceHook: "tool-result", contextObserved }))) : undefined;
|
|
4306
|
-
}
|
|
4307
3793
|
});
|
|
4308
3794
|
|
|
4309
3795
|
registerToolCallHook(pi, {
|
|
@@ -4313,257 +3799,40 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4313
3799
|
activity: recordActivity,
|
|
4314
3800
|
beforeAuthorize: (event, ctx) => {
|
|
4315
3801
|
const toolInput = isPlainRecord(event.input) ? event.input : {};
|
|
4316
|
-
const
|
|
4317
|
-
|
|
4318
|
-
toolName: event.toolName,
|
|
4319
|
-
toolInput,
|
|
4320
|
-
cwd: ctx.cwd,
|
|
4321
|
-
helpersMode: task ? taskHelperUsageMode(task) : helpersMode(),
|
|
4322
|
-
taskPending: task?.trace.outcome === "pending",
|
|
4323
|
-
dispatchAuthority: Boolean(task && taskAuthorityDecision(task, "CAP-14", "dispatch").allowed),
|
|
4324
|
-
durableSubagentState: task?.orchestration?.subagents
|
|
4325
|
-
});
|
|
3802
|
+
const helperPreflight = evaluateDirectSubagentDispatch({ toolName: event.toolName, toolInput, cwd: ctx.cwd, helpersMode: helpersMode(),
|
|
3803
|
+
taskPending: false, dispatchAuthority: false, durableSubagentState: undefined });
|
|
4326
3804
|
if (helperPreflight.applicable && helperPreflight.dispatch && !helperPreflight.allowed) {
|
|
4327
|
-
telemetry(ctx, {
|
|
4328
|
-
|
|
4329
|
-
recordedAt: nowIso(),
|
|
4330
|
-
taskRunId: task?.taskRunId,
|
|
4331
|
-
toolCallId: event.toolCallId,
|
|
4332
|
-
reasonCode: helperPreflight.reasonCode,
|
|
4333
|
-
role: helperPreflight.role,
|
|
4334
|
-
requestCount: helperPreflight.requestCount
|
|
4335
|
-
});
|
|
3805
|
+
telemetry(ctx, { event: "helper_dispatch_blocked", recordedAt: nowIso(), toolCallId: event.toolCallId, reasonCode: helperPreflight.reasonCode,
|
|
3806
|
+
role: helperPreflight.role, requestCount: helperPreflight.requestCount });
|
|
4336
3807
|
return { block: true, reason: `Blocked ${event.toolName}: ${helperPreflight.reason} Do not retry this helper; continue in the parent.` };
|
|
4337
3808
|
}
|
|
4338
|
-
const verifierReuse = reuseCurrentTreeExactVerifier({ cwd: ctx.cwd, task, toolName: event.toolName, toolInput, sessionEntries: ctx.sessionManager.getEntries() as unknown[],
|
|
4339
|
-
getHostProjectVerificationDigest: (snapshot, command) => task ? runtimeState.projectVerification.currentDigest(ctx, task, snapshot, command) : null });
|
|
4340
|
-
if (verifierReuse.reused) {
|
|
4341
|
-
telemetry(ctx, { event: "verifier_evidence_reused", recordedAt: nowIso(), taskRunId: task?.taskRunId, toolCallId: event.toolCallId, reasonCode: verifierReuse.reasonCode, commandDigest: verifierReuse.commandDigest, workingTreeDigest: verifierReuse.workingTreeDigest }); return;
|
|
4342
|
-
}
|
|
4343
3809
|
const targetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, toolInput);
|
|
4344
3810
|
if (targetInspection.reason) return { block: true, reason: `Blocked ${event.toolName}: ${targetInspection.reason}.` };
|
|
4345
|
-
if (
|
|
4346
|
-
const freshness = editFreshnessGuard.evaluate(ctx,
|
|
3811
|
+
if (event.toolName === "edit" || targetInspection.exactLocalPatchExecutor) {
|
|
3812
|
+
const freshness = editFreshnessGuard.evaluate(ctx, FREEFORM_EDIT_SCOPE, targetInspection.targets);
|
|
4347
3813
|
if (freshness.decision === "stale") {
|
|
4348
|
-
telemetry(ctx, { event: "edit_freshness_stale",
|
|
3814
|
+
telemetry(ctx, { event: "edit_freshness_stale", toolCallId: event.toolCallId, enforce: freshness.enforce, paths: freshness.stalePaths.map(redactText) });
|
|
4349
3815
|
if (freshness.enforce) return { block: true, reason: `Blocked ${event.toolName}: the previously observed source snapshot is stale for ${freshness.stalePaths.map(redactText).join(", ")}. Reread the affected file once; no patch hunk was started.` };
|
|
4350
3816
|
}
|
|
4351
3817
|
}
|
|
4352
|
-
if (!["edit", "write"].includes(event.toolName) && !targetInspection.exactLocalPatchExecutor) return;
|
|
4353
|
-
if (!task || task.trace.outcome !== "pending" || task.changeMode !== "source-change") return;
|
|
4354
|
-
const semanticRepairEnabled = taskAuthorityDecision(task, "CAP-13", "mutate").allowed;
|
|
4355
|
-
const semanticRepairExactlyOff = !taskAuthorityDecision(task, "CAP-13", "observe").allowed;
|
|
4356
|
-
const boundedRecoveryEnabled = autoRecoveryEnabled
|
|
4357
|
-
&& taskAuthorityDecision(task, "CAP-09", "block").allowed
|
|
4358
|
-
&& taskAuthorityDecision(task, "CAP-12", "mutate").allowed
|
|
4359
|
-
&& semanticRepairExactlyOff;
|
|
4360
|
-
if (!semanticRepairEnabled && !boundedRecoveryEnabled) return;
|
|
4361
|
-
const phase = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
|
|
4362
|
-
if (!phase.enforcementSafe || !["verify", "review"].includes(String(phase.phase))) return;
|
|
4363
|
-
|
|
4364
|
-
const currentSnapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
|
|
4365
|
-
if (workingTreeSnapshotHasUnavailableEvidence(currentSnapshot)) return;
|
|
4366
|
-
const currentDigest = workingTreeEvidenceDigest(currentSnapshot);
|
|
4367
|
-
const expectedReviewPaths = taskDeltaFilesFromSnapshot(task, currentSnapshot);
|
|
4368
|
-
const targets = targetInspection.targets;
|
|
4369
|
-
const boundedTargetMutation = targets.length > 0;
|
|
4370
|
-
const verifierCurrent = allVerifyCommandsPassCurrentTree(task, currentDigest, currentWorkspaceRevisionDigest(ctx.cwd));
|
|
4371
|
-
|
|
4372
|
-
if (phase.phase === "verify" && boundedTargetMutation && semanticRepairEnabled && semanticRepairRuntime.prepare({
|
|
4373
|
-
ctx, task, event, currentDigest, currentDeltaPaths: expectedReviewPaths, targetPaths: targets,
|
|
4374
|
-
verifierCurrent
|
|
4375
|
-
})) return;
|
|
4376
|
-
|
|
4377
|
-
// A public verifier can pass before every explicit user criterion is
|
|
4378
|
-
// covered. CAP-12 therefore reserves one exact structured call when the
|
|
4379
|
-
// model catches its own omission in the same task/run/session. The call
|
|
4380
|
-
// still traverses every normal authorization check; verify -> repair is
|
|
4381
|
-
// committed only by the tool-result hook after a successful tree change.
|
|
4382
|
-
if (phase.phase === "verify" && boundedRecoveryEnabled && verifierCurrent && boundedTargetMutation) {
|
|
4383
|
-
const authoredFileDigests = runtimeState.successfulModelMutationDigests(
|
|
4384
|
-
{ taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId },
|
|
4385
|
-
currentSnapshot
|
|
4386
|
-
);
|
|
4387
|
-
if (semanticRepairRuntime.prepareBoundedRecovery({
|
|
4388
|
-
ctx,
|
|
4389
|
-
task,
|
|
4390
|
-
event,
|
|
4391
|
-
currentDigest,
|
|
4392
|
-
currentDeltaPaths: expectedReviewPaths,
|
|
4393
|
-
observedChangedPaths: task.observedChangedFiles,
|
|
4394
|
-
authoredFileDigests,
|
|
4395
|
-
targetPaths: targets,
|
|
4396
|
-
verifierCurrent
|
|
4397
|
-
})) return;
|
|
4398
|
-
}
|
|
4399
|
-
|
|
4400
|
-
if (!semanticRepairEnabled) return;
|
|
4401
|
-
|
|
4402
|
-
let checkpoint = runtimeState.performanceReviewCheckpoint(task.taskRunId);
|
|
4403
|
-
const checkpointReady = checkpoint?.workingTreeDigest === currentDigest
|
|
4404
|
-
&& checkpoint.reviewSatisfied
|
|
4405
|
-
&& !checkpoint.invalidated
|
|
4406
|
-
&& exactReviewPathCoverage(expectedReviewPaths, checkpoint.expectedPaths)
|
|
4407
|
-
&& exactReviewPathCoverage(expectedReviewPaths, checkpoint.reviewedPaths);
|
|
4408
|
-
const credit = runtimeState.performanceReviewCredit(task.taskRunId, currentDigest);
|
|
4409
|
-
const creditReady = Boolean(credit && exactReviewPathCoverage(expectedReviewPaths, credit.reviewedPaths));
|
|
4410
|
-
if (!checkpointReady && !creditReady) return;
|
|
4411
|
-
|
|
4412
|
-
if (!checkpointReady && creditReady && credit) {
|
|
4413
|
-
runtimeState.rememberPerformanceReviewCheckpoint(task.taskRunId, currentDigest, 0, expectedReviewPaths, credit.reviewedPaths);
|
|
4414
|
-
checkpoint = runtimeState.performanceReviewCheckpoint(task.taskRunId);
|
|
4415
|
-
}
|
|
4416
|
-
|
|
4417
|
-
const reviewDecision = performanceReviewToolDecision({
|
|
4418
|
-
toolName: event.toolName,
|
|
4419
|
-
input: event.input,
|
|
4420
|
-
checkpoint,
|
|
4421
|
-
task,
|
|
4422
|
-
currentWorkingTreeDigest: currentDigest,
|
|
4423
|
-
targetPaths: targets
|
|
4424
|
-
});
|
|
4425
|
-
if (reviewDecision) {
|
|
4426
|
-
runtimeState.denyPerformanceReviewTool(task.taskRunId);
|
|
4427
|
-
return reviewDecision;
|
|
4428
|
-
}
|
|
4429
|
-
|
|
4430
|
-
if (targets.length === 0) return;
|
|
4431
|
-
const result = trajectoryRuntime.sync(ctx.cwd, ctx.sessionManager.getSessionId(), task, {
|
|
4432
|
-
sourceHook: "tool-call",
|
|
4433
|
-
recoveryRequested: true,
|
|
4434
|
-
recoveryMutationAllowed: true,
|
|
4435
|
-
observedAt: nowIso()
|
|
4436
|
-
});
|
|
4437
|
-
phaseToolRuntime.apply(ctx, result);
|
|
4438
|
-
observeTrajectorySync(ctx, result, telemetry);
|
|
4439
|
-
if (!result.enforcementSafe || result.state?.currentPhase !== "repair") {
|
|
4440
|
-
return { block: true, reason: `Task ${task.taskId} could not enter an audited repair phase before review-driven mutation.` };
|
|
4441
|
-
}
|
|
4442
3818
|
return;
|
|
4443
3819
|
},
|
|
4444
|
-
reviewBudgetDecision: (event, ctx) => {
|
|
4445
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4446
|
-
const checkpoint = task ? runtimeState.performanceReviewCheckpoint(task.taskRunId) : undefined;
|
|
4447
|
-
if (!task || !checkpoint || !taskAuthorityDecision(task, "CAP-13", "mutate").allowed) return;
|
|
4448
|
-
const currentDigest = workingTreeEvidenceDigest(workingTreeSnapshot(ctx.cwd) as Record<string, string>);
|
|
4449
|
-
const phase = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
|
|
4450
|
-
const toolInput = isPlainRecord(event.input) ? event.input : {};
|
|
4451
|
-
const targetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, toolInput);
|
|
4452
|
-
if (targetInspection.reason) return { block: true, reason: `Blocked ${event.toolName}: ${targetInspection.reason}.` };
|
|
4453
|
-
const targets = targetInspection.targets;
|
|
4454
|
-
if ((["edit", "write"].includes(event.toolName) || targetInspection.patchCarrier) && targets.length === 0) {
|
|
4455
|
-
runtimeState.denyPerformanceReviewTool(task.taskRunId);
|
|
4456
|
-
return { block: true, reason: `Task ${task.taskId} semantic repair requires an exact statically bounded target.` };
|
|
4457
|
-
}
|
|
4458
|
-
const reviewDecision = performanceReviewToolDecision({
|
|
4459
|
-
toolName: event.toolName,
|
|
4460
|
-
input: event.input,
|
|
4461
|
-
checkpoint,
|
|
4462
|
-
task,
|
|
4463
|
-
currentWorkingTreeDigest: currentDigest,
|
|
4464
|
-
currentPhase: phase.phase,
|
|
4465
|
-
targetPaths: targets
|
|
4466
|
-
});
|
|
4467
|
-
if (reviewDecision) runtimeState.denyPerformanceReviewTool(task.taskRunId);
|
|
4468
|
-
return reviewDecision;
|
|
4469
|
-
},
|
|
4470
|
-
beforeStart: (_event, ctx) => {
|
|
4471
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4472
|
-
const control = task?.trace.outcome === "pending" ? inspectTaskControlState(ctx.cwd, task) : null;
|
|
4473
|
-
if (control?.dispatchBlocked) return { block: true, reason: `Task lifecycle control blocks tool start while state is ${control.state}.` };
|
|
4474
|
-
},
|
|
4475
|
-
afterDecision: (_event, ctx, metadata) => {
|
|
4476
|
-
if (metadata.allowed) return;
|
|
4477
|
-
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4478
|
-
if (task) {
|
|
4479
|
-
semanticRepairRuntime.reject({ cwd: ctx.cwd, taskRunId: task.taskRunId, toolCallId: metadata.toolCallId, recordedAt: nowIso() });
|
|
4480
|
-
}
|
|
4481
|
-
},
|
|
4482
3820
|
afterAuthorized: (event, ctx, metadata) => {
|
|
4483
|
-
const
|
|
4484
|
-
|
|
4485
|
-
toolName: event.toolName,
|
|
4486
|
-
toolInput: event.input,
|
|
4487
|
-
cwd: ctx.cwd,
|
|
4488
|
-
helpersMode: task ? taskHelperUsageMode(task) : helpersMode(),
|
|
4489
|
-
taskPending: task?.trace.outcome === "pending",
|
|
4490
|
-
dispatchAuthority: Boolean(task && taskAuthorityDecision(task, "CAP-14", "dispatch").allowed),
|
|
4491
|
-
durableSubagentState: task?.orchestration?.subagents
|
|
4492
|
-
});
|
|
3821
|
+
const helperPreflight = evaluateDirectSubagentDispatch({ toolName: event.toolName, toolInput: event.input, cwd: ctx.cwd, helpersMode: helpersMode(),
|
|
3822
|
+
taskPending: false, dispatchAuthority: false, durableSubagentState: undefined });
|
|
4493
3823
|
if (helperPreflight.applicable && helperPreflight.dispatch && helperPreflight.allowed) {
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
persist: (candidate) => writeTask(ctx.cwd, candidate),
|
|
4497
|
-
trace: (payload) => appendTrace(ctx.cwd, payload)
|
|
4498
|
-
});
|
|
4499
|
-
telemetry(ctx, {
|
|
4500
|
-
event: "helper_dispatch_reserved",
|
|
4501
|
-
recordedAt: nowIso(),
|
|
4502
|
-
taskRunId: reserved?.taskRunId,
|
|
4503
|
-
toolCallId: metadata.toolCallId,
|
|
4504
|
-
role: helperPreflight.role,
|
|
4505
|
-
requestCount: helperPreflight.requestCount
|
|
4506
|
-
});
|
|
4507
|
-
}
|
|
4508
|
-
if (task?.trace.outcome === "pending") {
|
|
4509
|
-
const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
|
|
4510
|
-
const currentDigest = workingTreeEvidenceDigest(snapshot);
|
|
4511
|
-
const toolInput = isPlainRecord(event.input) ? event.input : {};
|
|
4512
|
-
const targets = taskMutationTargets(ctx.cwd, event.toolName, toolInput);
|
|
4513
|
-
const semanticPending = semanticRepairRuntime.pending(ctx.cwd, task.taskRunId, metadata.toolCallId);
|
|
4514
|
-
const reservationTargets = semanticPending?.kind === "mutation" ? semanticPending.targetPaths : targets;
|
|
4515
|
-
if ((["edit", "write", "apply_patch"].includes(event.toolName) || semanticPending?.kind === "mutation") && reservationTargets.length > 0) {
|
|
4516
|
-
runtimeState.reserveAuthorizedModelMutation(
|
|
4517
|
-
{ taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId },
|
|
4518
|
-
metadata.toolCallId,
|
|
4519
|
-
snapshot,
|
|
4520
|
-
reservationTargets,
|
|
4521
|
-
["edit", "write", "apply_patch"].includes(event.toolName)
|
|
4522
|
-
? expectedModelMutationProof(ctx.cwd, event.toolName, toolInput, reservationTargets)
|
|
4523
|
-
: { expectedContentDigests: {}, preContentDigests: {}, fullContentPaths: [], replacePaths: [] }
|
|
4524
|
-
);
|
|
4525
|
-
}
|
|
4526
|
-
const checkpoint = runtimeState.performanceReviewCheckpoint(task.taskRunId);
|
|
4527
|
-
const kind = performanceReviewToolKind({ toolName: event.toolName, input: event.input, checkpoint, task });
|
|
4528
|
-
if (checkpoint && kind) {
|
|
4529
|
-
runtimeState.reservePerformanceReviewTool(task.taskRunId, {
|
|
4530
|
-
toolCallId: metadata.toolCallId,
|
|
4531
|
-
kind,
|
|
4532
|
-
toolName: event.toolName,
|
|
4533
|
-
workingTreeDigest: currentDigest,
|
|
4534
|
-
workingTreeSnapshot: snapshot,
|
|
4535
|
-
targetPaths: targets
|
|
4536
|
-
});
|
|
4537
|
-
}
|
|
3824
|
+
telemetry(ctx, { event: "helper_dispatch_reserved", recordedAt: nowIso(), toolCallId: metadata.toolCallId, role: helperPreflight.role,
|
|
3825
|
+
requestCount: helperPreflight.requestCount });
|
|
4538
3826
|
}
|
|
4539
|
-
return
|
|
4540
|
-
ctx.cwd,
|
|
4541
|
-
ctx.sessionManager.getSessionId(),
|
|
4542
|
-
task,
|
|
4543
|
-
event,
|
|
4544
|
-
nowIso()
|
|
4545
|
-
));
|
|
3827
|
+
return undefined;
|
|
4546
3828
|
},
|
|
4547
3829
|
authorize: async (event, ctx) => {
|
|
4548
|
-
const preTask = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4549
3830
|
const preInput = isPlainRecord(event.input) ? event.input : {};
|
|
4550
3831
|
const authorizationTargetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, preInput);
|
|
4551
3832
|
if (authorizationTargetInspection.reason) {
|
|
4552
3833
|
return { block: true, reason: `Blocked ${event.toolName}: ${authorizationTargetInspection.reason}.` };
|
|
4553
3834
|
}
|
|
4554
3835
|
const authorizationTargets = authorizationTargetInspection.targets;
|
|
4555
|
-
const preSnapshot = preTask ? workingTreeSnapshot(ctx.cwd) as Record<string, string> : undefined;
|
|
4556
|
-
const reservedFirstCall = Boolean(preTask && preSnapshot && !workingTreeSnapshotHasUnavailableEvidence(preSnapshot) && semanticRepairRuntime.reservedCallMatches({
|
|
4557
|
-
cwd: ctx.cwd,
|
|
4558
|
-
taskRunId: preTask.taskRunId,
|
|
4559
|
-
sessionId: ctx.sessionManager.getSessionId(),
|
|
4560
|
-
toolCallId: String(event.toolCallId),
|
|
4561
|
-
toolName: event.toolName,
|
|
4562
|
-
currentDigest: workingTreeEvidenceDigest(preSnapshot),
|
|
4563
|
-
targetPaths: authorizationTargets
|
|
4564
|
-
}));
|
|
4565
|
-
const phaseDecision = reservedFirstCall || (preTask?.workingTreeDigestMigration?.status === "verification-refresh-required" && SHELL_TOOL_NAMES.has(event.toolName)) ? undefined : phaseToolRuntime.toolDecision(ctx, event.toolName);
|
|
4566
|
-
if (phaseDecision) return phaseDecision;
|
|
4567
3836
|
const projectTrusted = ctx.isProjectTrusted();
|
|
4568
3837
|
const eventInput = isPlainRecord(event.input) ? event.input : {};
|
|
4569
3838
|
const backendDecision = executionBackendToolDecision(isTaskMutationTool(event.toolName, eventInput));
|
|
@@ -4581,6 +3850,8 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4581
3850
|
if (permissionDecision.block) {
|
|
4582
3851
|
return { block: true, reason: permissionDecision.reason };
|
|
4583
3852
|
}
|
|
3853
|
+
const experimentDecision = experimentLoopDecision(ctx.cwd, event.toolName);
|
|
3854
|
+
if (experimentDecision) return experimentDecision;
|
|
4584
3855
|
const toolDecision = evaluateToolPolicy(event.toolName, profile, policy);
|
|
4585
3856
|
if (toolDecision.decision === "block" && permissionProfile.mode !== "trusted-full-access") {
|
|
4586
3857
|
return { block: true, reason: `Tool registry blocked ${event.toolName}: ${toolDecision.reason}` };
|
|
@@ -4608,23 +3879,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4608
3879
|
const toolInput = event.input && typeof event.input === "object"
|
|
4609
3880
|
? event.input as Record<string, unknown>
|
|
4610
3881
|
: {};
|
|
4611
|
-
const sessionTask = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4612
|
-
const resumeBlock = sessionTask ? runtimeState.taskResumeBlock(sessionTask.taskRunId) : undefined;
|
|
4613
|
-
if (resumeBlock && isTaskMutationTool(event.toolName, toolInput)) {
|
|
4614
|
-
return { block: true, reason: `Task resume is blocked: ${resumeBlock}` };
|
|
4615
|
-
}
|
|
4616
|
-
const profileMode = profile.mode ?? "unprofiled";
|
|
4617
|
-
const governedTaskProfile = !profileMode.startsWith("unprofiled") && !profileMode.includes("unreadable");
|
|
4618
|
-
const taskContractRequired = governedTaskProfile
|
|
4619
|
-
&& runtime.finalGate === "enforce"
|
|
4620
|
-
&& finalGateConfig(policy, DEFAULT_POLICY).requireTaskContract;
|
|
4621
|
-
if (sessionTask?.trace.outcome === "pending" && (sessionTask.changeMode === "read-only" || sessionTask.mutationPolicy === "forbidden")
|
|
4622
|
-
&& !SHELL_TOOL_NAMES.has(event.toolName) && isTaskMutationTool(event.toolName, toolInput)) {
|
|
4623
|
-
return {
|
|
4624
|
-
block: true,
|
|
4625
|
-
reason: `Task ${sessionTask.taskId} ${sessionTask.changeMode === "read-only" ? "is read-only" : "forbids source mutation"}; ${event.toolName} cannot mutate project or external state in this task. Start a mutation-required source task for implementation.`
|
|
4626
|
-
};
|
|
4627
|
-
}
|
|
4628
3882
|
const preparedInput = prepareToolInputForPolicy(event.toolName, toolInput, policy);
|
|
4629
3883
|
if (preparedInput.reason) {
|
|
4630
3884
|
return { block: true, reason: `Blocked ${event.toolName}: ${preparedInput.reason}.` };
|
|
@@ -4638,10 +3892,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4638
3892
|
}
|
|
4639
3893
|
let shellProjectMutation = false;
|
|
4640
3894
|
let shellMutationTargets: string[] = [];
|
|
4641
|
-
let shellMutationTargetBounded = false;
|
|
4642
|
-
let configuredVerifierShell = false;
|
|
4643
|
-
let authorizedShellCommand = "";
|
|
4644
|
-
let authorizedShellSegments: Array<{ words: string[] }> = [];
|
|
4645
3895
|
let pendingHumanApproval: { prompt: string; title: string; action: Omit<ApprovalActionDraft, "treePrecondition"> } | null = null;
|
|
4646
3896
|
if (SHELL_TOOL_NAMES.has(event.toolName)) {
|
|
4647
3897
|
const shellInput = extractShellCommandInput(toolInput);
|
|
@@ -4650,8 +3900,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4650
3900
|
}
|
|
4651
3901
|
const command = normalizeShellCommandForPolicy(shellInput.command);
|
|
4652
3902
|
const execDecision = evaluateExecPolicy(command, profile, policy);
|
|
4653
|
-
authorizedShellCommand = command;
|
|
4654
|
-
authorizedShellSegments = execDecision.segments;
|
|
4655
3903
|
if (shellTouchesGrantedSourceCheckout(ctx.cwd, command, runtimeState.sourceCheckoutReadRoots(ctx))) {
|
|
4656
3904
|
return {
|
|
4657
3905
|
block: true,
|
|
@@ -4659,9 +3907,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4659
3907
|
};
|
|
4660
3908
|
}
|
|
4661
3909
|
shellProjectMutation = isProjectMutatingShellCommand(command, execDecision.segments, ctx.cwd);
|
|
4662
|
-
configuredVerifierShell = Boolean(sessionTask && commandMatchesVerifyPlan(command, sessionTask.verifyCommands));
|
|
4663
3910
|
const shellWriteCandidates = extractShellWritePathCandidates(command);
|
|
4664
|
-
shellMutationTargetBounded = shellWriteCandidates.length > 0;
|
|
4665
3911
|
shellMutationTargets = shellWriteCandidates
|
|
4666
3912
|
.map((candidate) => normalizeRelative(ctx.cwd, candidate))
|
|
4667
3913
|
.filter((candidate): candidate is string => Boolean(
|
|
@@ -4671,15 +3917,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4671
3917
|
&& !candidate.startsWith("../")
|
|
4672
3918
|
&& !candidate.startsWith(".pi/piagent-state/")
|
|
4673
3919
|
));
|
|
4674
|
-
const mutationForbidden = sessionTask?.trace.outcome === "pending"
|
|
4675
|
-
&& (sessionTask.changeMode === "read-only" || sessionTask.mutationPolicy === "forbidden");
|
|
4676
|
-
const exactForbiddenTaskVerifier = mutationForbidden && sessionTask.changeMode === "source-change" && configuredVerifierShell;
|
|
4677
|
-
if (mutationForbidden && !exactForbiddenTaskVerifier && !isReadOnlyTaskShellCommand(command, execDecision.segments, ctx.cwd)) {
|
|
4678
|
-
return {
|
|
4679
|
-
block: true,
|
|
4680
|
-
reason: `Task ${sessionTask.taskId} ${sessionTask.changeMode === "read-only" ? "is read-only; this shell command is not in the read-only inspection allowlist" : "forbids source mutation; this shell command is neither bounded inspection nor an exact configured verifier"}.`
|
|
4681
|
-
};
|
|
4682
|
-
}
|
|
4683
3920
|
if (execDecision.mode !== "off" && execDecision.decision === "forbid") {
|
|
4684
3921
|
return { block: true, reason: execDecision.reasons.join("; ") };
|
|
4685
3922
|
}
|
|
@@ -4716,7 +3953,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4716
3953
|
pendingHumanApproval = { prompt: `Command requires confirmation.\n\n${confirmationReasons.join("\n")}\n\nAllow?`, title: "Piagent exec policy confirmation",
|
|
4717
3954
|
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 },
|
|
4718
3955
|
commandPreview: command, parameterPreview: preparedInput.confirmationSummary ?? "Shell command", targetPaths: shellMutationTargets, targetSummaries: confirmationReasons, provider: externalReason ? "shell-external" : null, urlOrigin: null,
|
|
4719
|
-
requestedScope: "one-shell-command", reason: confirmationReasons.join("; "), riskClass: shellProjectMutation ? "high" : "medium", allowConsequence: "Run this exact command once after the guard
|
|
3956
|
+
requestedScope: "one-shell-command", reason: confirmationReasons.join("; "), riskClass: shellProjectMutation ? "high" : "medium", allowConsequence: "Run this exact command once, after the guard checks the working tree again.", denyConsequence: "The command does not run; the agent is told you declined." } };
|
|
4720
3957
|
}
|
|
4721
3958
|
}
|
|
4722
3959
|
|
|
@@ -4750,7 +3987,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4750
3987
|
forceScopeAware: authorizationTargetInspection.patchCarrier || Boolean(preparedInput.proxyToolName),
|
|
4751
3988
|
forceWrite: authorizationTargetInspection.patchCarrier || preparedInput.proxyAction?.decision === "confirm",
|
|
4752
3989
|
allowAmbiguousFilesystemContentFields: !usesKnownExternalProvider && !isPiagentTool(event.toolName),
|
|
4753
|
-
sourceCheckoutReadRoots: runtimeState.sourceCheckoutReadRoots(ctx)
|
|
3990
|
+
sourceCheckoutReadRoots: [...runtimeState.sourceCheckoutReadRoots(ctx), ...SKILL_READ_ROOTS]
|
|
4754
3991
|
}
|
|
4755
3992
|
);
|
|
4756
3993
|
if (pathDecision.block) {
|
|
@@ -4772,83 +4009,20 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4772
4009
|
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",
|
|
4773
4010
|
action: { kind: "external-provider-action", preconditionClass: "runtime-only", toolName: event.toolName, rawAction: { toolName: event.toolName, input: preparedInput.input, provider: externalAction.provider, action: externalAction.action },
|
|
4774
4011
|
commandPreview: null, parameterPreview: preparedInput.confirmationSummary ?? String(externalAction.action), targetPaths: [], targetSummaries: [], provider: String(externalAction.provider),
|
|
4775
|
-
urlOrigin: null, requestedScope: "one-external-action", reason: `External provider action ${String(externalAction.action)} requires confirmation`, riskClass: "high", allowConsequence: "
|
|
4012
|
+
urlOrigin: null, requestedScope: "one-external-action", reason: `External provider action ${String(externalAction.action)} requires confirmation`, riskClass: "high", allowConsequence: "Run this exact action once, after the guard checks its authority again.", denyConsequence: "The action does not run; nothing outside changes." } };
|
|
4776
4013
|
}
|
|
4777
4014
|
}
|
|
4778
4015
|
|
|
4779
|
-
|
|
4780
|
-
? shellMutationTargets
|
|
4781
|
-
: authorizationTargets;
|
|
4782
|
-
const directProjectMutation = !SHELL_TOOL_NAMES.has(event.toolName) && (
|
|
4783
|
-
WRITE_TOOL_NAMES.has(event.toolName)
|
|
4784
|
-
|| mutationTargets.length > 0
|
|
4785
|
-
|| preparedInput.proxyShellCarrier === true
|
|
4786
|
-
);
|
|
4787
|
-
const semanticOpaqueCarrier = normalizeActionToken(event.toolName) === "mcp"
|
|
4788
|
-
|| Boolean(mcpServerFromToolCall(event.toolName, toolInput, repositoryMcpGate(ctx.cwd).serverNames));
|
|
4789
|
-
const semanticSnapshot = sessionTask ? workingTreeSnapshot(ctx.cwd) as Record<string, string> : undefined;
|
|
4790
|
-
const semanticAuthorization = sessionTask && semanticSnapshot && (shellProjectMutation || directProjectMutation || SHELL_TOOL_NAMES.has(event.toolName) || semanticOpaqueCarrier)
|
|
4791
|
-
? semanticRepairRuntime.authorize({
|
|
4792
|
-
cwd: ctx.cwd,
|
|
4793
|
-
task: sessionTask,
|
|
4794
|
-
sessionId: ctx.sessionManager.getSessionId(),
|
|
4795
|
-
toolCallId: String(event.toolCallId),
|
|
4796
|
-
toolName: event.toolName,
|
|
4797
|
-
currentDigest: workingTreeSnapshotHasUnavailableEvidence(semanticSnapshot) ? "unavailable" : workingTreeEvidenceDigest(semanticSnapshot),
|
|
4798
|
-
targetPaths: mutationTargets,
|
|
4799
|
-
projectMutation: shellProjectMutation || directProjectMutation,
|
|
4800
|
-
exactVerifier: configuredVerifierShell,
|
|
4801
|
-
shellLike: SHELL_TOOL_NAMES.has(event.toolName),
|
|
4802
|
-
opaqueCarrier: semanticOpaqueCarrier,
|
|
4803
|
-
targetExtractionComplete: SHELL_TOOL_NAMES.has(event.toolName)
|
|
4804
|
-
? !shellHasOpaqueWritePrimitive(authorizedShellCommand)
|
|
4805
|
-
: authorizationTargetInspection.patchCarrier
|
|
4806
|
-
? authorizationTargetInspection.exactLocalPatchExecutor
|
|
4807
|
-
: !semanticOpaqueCarrier && preparedInput.proxyShellCarrier !== true,
|
|
4808
|
-
recordedAt: nowIso()
|
|
4809
|
-
})
|
|
4810
|
-
: { handled: false, allowed: false, bypassPhase: false };
|
|
4811
|
-
if (semanticAuthorization.handled && !semanticAuthorization.allowed) {
|
|
4812
|
-
return { block: true, reason: semanticAuthorization.reason ?? "Semantic repair authorization failed closed." };
|
|
4813
|
-
}
|
|
4814
|
-
const phaseMutationDecision = semanticAuthorization.bypassPhase ? undefined : phaseToolRuntime.mutationDecision(ctx, {
|
|
4815
|
-
projectMutation: shellProjectMutation || directProjectMutation, exactSourceVerifier: configuredVerifierShell,
|
|
4816
|
-
verificationCarrier: SHELL_TOOL_NAMES.has(event.toolName)
|
|
4817
|
-
});
|
|
4818
|
-
if (phaseMutationDecision) return phaseMutationDecision;
|
|
4819
|
-
if (pendingHumanApproval && !sessionTask) {
|
|
4016
|
+
if (pendingHumanApproval) {
|
|
4820
4017
|
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), unavailableFallback: approvalUnavailableFallback(ctx) });
|
|
4821
4018
|
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}` };
|
|
4019
|
+
// A WebUI allow is provisional until the guard consumes it here. Without
|
|
4020
|
+
// this the decision request never got its receipt and the approval stayed
|
|
4021
|
+
// pending, filling the broker until it stopped accepting WebUI decisions.
|
|
4022
|
+
// With task contracts retired every approval takes this path.
|
|
4023
|
+
if (!approval.consume()) return { block: true, reason: "Approval became stale before tool start; the action was blocked." };
|
|
4822
4024
|
pendingHumanApproval = null;
|
|
4823
4025
|
}
|
|
4824
|
-
if (sessionTask && sessionTask.trace.outcome !== "pending" && (shellProjectMutation || directProjectMutation)) {
|
|
4825
|
-
return {
|
|
4826
|
-
block: true,
|
|
4827
|
-
reason: `Task ${sessionTask.taskId} is ${sessionTask.trace.outcome}; start a new attempt or a fresh task before further project mutations.`
|
|
4828
|
-
};
|
|
4829
|
-
}
|
|
4830
|
-
if (!sessionTask && taskContractRequired && (shellProjectMutation || directProjectMutation)) {
|
|
4831
|
-
return {
|
|
4832
|
-
block: true,
|
|
4833
|
-
// Naming the tool matters: this is the first wall a new operator hits,
|
|
4834
|
-
// and "start the task first" does not say with what.
|
|
4835
|
-
reason: "A session-bound Task Implementation Contract is required before project files can be mutated."
|
|
4836
|
-
+ " Call `piagent_task_start` once with explicit project-relative scope, then retry."
|
|
4837
|
-
};
|
|
4838
|
-
}
|
|
4839
|
-
if (
|
|
4840
|
-
sessionTask?.trace.outcome === "pending"
|
|
4841
|
-
&& sessionTask.changeMode === "source-change"
|
|
4842
|
-
&& shellProjectMutation
|
|
4843
|
-
&& opaqueShellMutationNeedsBoundedTarget(authorizedShellCommand, authorizedShellSegments)
|
|
4844
|
-
&& !shellMutationTargetBounded
|
|
4845
|
-
&& !configuredVerifierShell
|
|
4846
|
-
) {
|
|
4847
|
-
return {
|
|
4848
|
-
block: true,
|
|
4849
|
-
reason: `Task ${sessionTask.taskId} cannot run an opaque shell mutation whose write target is not statically bounded. Use edit/write/apply_patch, an explicit project-local redirection, or the exact configured verifier.`
|
|
4850
|
-
};
|
|
4851
|
-
}
|
|
4852
4026
|
if (runtime.contextBudget !== "off" && (["write", "edit"].includes(event.toolName) || authorizationTargetInspection.patchCarrier)) {
|
|
4853
4027
|
const budgetTargets = authorizationTargetInspection.patchCarrier
|
|
4854
4028
|
? authorizationTargets
|
|
@@ -4862,25 +4036,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4862
4036
|
}
|
|
4863
4037
|
}
|
|
4864
4038
|
|
|
4865
|
-
if (pendingHumanApproval) {
|
|
4866
|
-
const currentTreeDigest = semanticSnapshot && !workingTreeSnapshotHasUnavailableEvidence(semanticSnapshot) ? workingTreeEvidenceDigest(semanticSnapshot) : null;
|
|
4867
|
-
const treePrecondition = pendingHumanApproval.action.preconditionClass === "runtime-only" || !currentTreeDigest ? null
|
|
4868
|
-
: { workspaceRevision: `workspace-rev.${crypto.createHash("sha256").update(currentTreeDigest).digest("hex")}`, indexRevision: null, preimageDigest: currentTreeDigest };
|
|
4869
|
-
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,
|
|
4870
|
-
action: { ...pendingHumanApproval.action, treePrecondition }, terminalConfirm: () => ctx.ui.confirm(pendingHumanApproval!.prompt, pendingHumanApproval!.title),
|
|
4871
|
-
unavailableFallback: approvalUnavailableFallback(ctx),
|
|
4872
|
-
recheck: () => !treePrecondition || (() => { const current = workingTreeSnapshot(ctx.cwd);
|
|
4873
|
-
return !workingTreeSnapshotHasUnavailableEvidence(current) && workingTreeEvidenceDigest(current) === treePrecondition.preimageDigest; })() });
|
|
4874
|
-
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}` };
|
|
4875
|
-
if (!approval.consume()) return { block: true, reason: "Approval became stale before tool start; the action was blocked." };
|
|
4876
|
-
}
|
|
4877
|
-
if (
|
|
4878
|
-
sessionTask?.trace.outcome === "pending"
|
|
4879
|
-
&& sessionTask.changeMode === "source-change"
|
|
4880
|
-
&& (shellProjectMutation || configuredVerifierShell)
|
|
4881
|
-
) {
|
|
4882
|
-
runtimeState.rememberShellMutationSnapshot(ctx, event.toolName, event.input, event.toolCallId);
|
|
4883
|
-
}
|
|
4884
4039
|
}
|
|
4885
4040
|
});
|
|
4886
4041
|
// Register only after authorization is live. The same-name override is intentional:
|
|
@@ -4901,8 +4056,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4901
4056
|
createTaskRunId, crypto, currentSessionName, defaultRolePolicy, defaultWorkPlan, digestJson,
|
|
4902
4057
|
dynamicToolsEnabled, effectiveProtectedPaths, emitRuntimeMessage, ensureContextIndexV2, estimateContextTokens,
|
|
4903
4058
|
evaluateExecPolicy, evaluateModelRoute, evaluateRetrievalRoute, evaluateRuntimeSolver, evaluateTaskGate, evaluateToolPolicy, execPolicyConfig,
|
|
4904
|
-
prepareIndependentAcceptance: (ctx: ExtensionContext, task: TaskContract, response?: { origin: "assistant"; bytes: string }) =>
|
|
4905
|
-
independentAcceptance.prepare(ctx, task, response),
|
|
4906
4059
|
extensionDir, externalActionPolicyConfig, extractDocument, finalGateConfig, findMatchingObservedBashResult,
|
|
4907
4060
|
formatContextPreflight, formatCount, formatLiveTaskStatus, formatPercent, formatTechOptionsText, formatTechSelectionSummary,
|
|
4908
4061
|
formatToolResultCaptureStatus, formatUsageSnapshot, fs, hasGitEvidenceRoot, hasOperatorSessionName, helpersMode,
|
|
@@ -4919,7 +4072,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4919
4072
|
runtimeLifecycleMode, runtimeSnapshotCapture, runtimeSnapshotEnabled, runtimeState, runtimeVersions,
|
|
4920
4073
|
safeTaskId, searchContextIndex, searchContextIndexV2, searchMemoryFiles, selectRuntimeAction,
|
|
4921
4074
|
selectValueFromUi, selectVerificationPlan, semanticCompactionInstructions, sendWorkflowFollowUp, setPermissionOverrideForContext,
|
|
4922
|
-
semanticRepairCompletionBlock: (cwd: string, taskRunId: string) => semanticRepairRuntime.completionBlock(cwd, taskRunId),
|
|
4923
4075
|
freshRequestParts, shellArg, shortTaskLabel, solverShadow, summarizeAttempt, taskAcceptanceEvidenceFiles, taskChangedFileEvidence,
|
|
4924
4076
|
techContextDirPath, techContextFilePath, techContextRelativePath, techOptionById, techStackPath,
|
|
4925
4077
|
telemetry, toolRegistryConfig, trajectoryRuntime, uniqueStrings, usageExactCommands,
|
|
@@ -4931,9 +4083,6 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4931
4083
|
registerPolicyTools(pi, registrationDeps);
|
|
4932
4084
|
registerKnowledgeTools(pi, registrationDeps);
|
|
4933
4085
|
registerOnboardingTools(pi, registrationDeps);
|
|
4934
|
-
maybeStartAutomaticTask = registerTaskStartTool(pi, registrationDeps);
|
|
4935
|
-
registerTaskEvidenceTools(pi, registrationDeps);
|
|
4936
|
-
registerTaskCompletionTools(pi, registrationDeps);
|
|
4937
4086
|
|
|
4938
4087
|
function emitRuntimeMessage(
|
|
4939
4088
|
ctx: ExtensionContext,
|
|
@@ -4987,14 +4136,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4987
4136
|
|
|
4988
4137
|
registerPermissionCommands(pi, registrationDeps);
|
|
4989
4138
|
registerFastModeCommand(pi, serviceTierRuntime);
|
|
4990
|
-
|
|
4139
|
+
registerProfileCommands(pi, registrationDeps);
|
|
4991
4140
|
registerMemoryMcpCommands(pi, registrationDeps);
|
|
4992
4141
|
const contextCommandApi = registerContextCommands(pi, registrationDeps);
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
});
|
|
4999
|
-
workflowCommandApi = registerWorkflowCommands(pi, { ...registrationDeps, ...profileCommandApi, ...contextCommandApi });
|
|
4142
|
+
// This module also carries tools that are not task controls (document read,
|
|
4143
|
+
// external source checkout); the wrapper above drops the retired task tool it
|
|
4144
|
+
// registers. The completion module holds only task-bound tools (memory
|
|
4145
|
+
// citations need a task id), so it is no longer registered at all.
|
|
4146
|
+
registerTaskEvidenceTools(pi, registrationDeps);
|
|
4147
|
+
registerSessionCommands(pi, { ...registrationDeps, ...contextCommandApi });
|
|
5000
4148
|
}
|