@piagent/platform 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/AGENTS.md +7 -0
  2. package/CHANGELOG.md +132 -2
  3. package/README.md +45 -70
  4. package/README.vi.md +34 -61
  5. package/architecture/layers.json +16 -3
  6. package/catalog/capabilities.json +14 -7
  7. package/catalog/claude-model-additions.json +42 -0
  8. package/docs/auto-delegation-policy.md +2 -0
  9. package/docs/command-reference-vietnamese.md +68 -90
  10. package/docs/context-window-policy.md +5 -5
  11. package/docs/distribution-standard.md +13 -16
  12. package/docs/en/architecture.md +9 -1
  13. package/docs/herdr-workflow.md +2 -3
  14. package/docs/languages.json +4 -4
  15. package/docs/managed-local-acceptance.md +403 -0
  16. package/docs/mcp-and-tools.md +5 -7
  17. package/docs/memory-policy.md +3 -3
  18. package/docs/model-options.md +2 -2
  19. package/docs/oauth-providers.md +1 -1
  20. package/docs/operator-manual-vietnamese.md +82 -127
  21. package/docs/package-architecture-notes.md +4 -4
  22. package/docs/piagent-core-improvement-plan.md +3 -5
  23. package/docs/piagent-core-plan-checkpoint-2026-09-06.json +2 -13
  24. package/docs/piagent-runtime-benchmark-execution-plan.md +10 -8
  25. package/docs/project-adapters.md +3 -3
  26. package/docs/project-onboarding.md +15 -16
  27. package/docs/publishing-for-teams.md +3 -1
  28. package/docs/quality-benchmark.md +2 -0
  29. package/docs/quickstart-vietnamese.md +23 -24
  30. package/docs/release-install-policy.md +3 -2
  31. package/docs/research/harness-next-evidence-review.md +7 -7
  32. package/docs/runtime-harness-standard.md +2 -0
  33. package/docs/runtime-policy-design.md +2 -0
  34. package/docs/runtime-quality-baseline.md +2 -0
  35. package/docs/subagent-orchestration-capabilities.md +1 -1
  36. package/docs/subagents-and-multiagent.md +10 -8
  37. package/docs/task-implementation-contract.md +2 -0
  38. package/docs/task-lifecycle-vietnamese.md +2 -0
  39. package/docs/team-onboarding.md +28 -59
  40. package/docs/usage-observability.md +2 -2
  41. package/docs/vi/architecture.md +9 -1
  42. package/docs/workflow-recipes.md +6 -10
  43. package/docs-site/architecture.html +4 -3
  44. package/docs-site/assets/docs.css +80 -0
  45. package/docs-site/benchmark.html +7 -1
  46. package/docs-site/commands.html +37 -23
  47. package/docs-site/content/architecture.html +2 -2
  48. package/docs-site/content/benchmark.html +5 -0
  49. package/docs-site/content/commands.html +35 -22
  50. package/docs-site/content/ecosystem.html +144 -0
  51. package/docs-site/content/en/architecture.html +1 -1
  52. package/docs-site/content/en/benchmark.html +5 -0
  53. package/docs-site/content/en/commands.html +30 -13
  54. package/docs-site/content/en/ecosystem.html +144 -0
  55. package/docs-site/content/en/index.html +24 -9
  56. package/docs-site/content/en/quickstart.html +10 -12
  57. package/docs-site/content/en/security.html +1 -1
  58. package/docs-site/content/en/team.html +2 -2
  59. package/docs-site/content/en/whats-new.html +92 -1
  60. package/docs-site/content/en/workflows.html +29 -31
  61. package/docs-site/content/index.html +24 -9
  62. package/docs-site/content/quickstart.html +10 -12
  63. package/docs-site/content/security.html +2 -2
  64. package/docs-site/content/team.html +2 -2
  65. package/docs-site/content/whats-new.html +92 -1
  66. package/docs-site/content/workflows.html +36 -38
  67. package/docs-site/context-engine.html +2 -1
  68. package/docs-site/ecosystem.html +283 -0
  69. package/docs-site/en/architecture.html +3 -2
  70. package/docs-site/en/benchmark.html +7 -1
  71. package/docs-site/en/commands.html +32 -14
  72. package/docs-site/en/context-engine.html +2 -1
  73. package/docs-site/en/ecosystem.html +283 -0
  74. package/docs-site/en/index.html +30 -14
  75. package/docs-site/en/mcp-approval.html +2 -1
  76. package/docs-site/en/mcp-auth.html +2 -1
  77. package/docs-site/en/mcp-servers.html +2 -1
  78. package/docs-site/en/mcp.html +2 -1
  79. package/docs-site/en/permissions.html +2 -1
  80. package/docs-site/en/profiles.html +2 -1
  81. package/docs-site/en/quickstart.html +17 -18
  82. package/docs-site/en/runtime.html +2 -1
  83. package/docs-site/en/scope.html +2 -1
  84. package/docs-site/en/security.html +6 -5
  85. package/docs-site/en/team.html +4 -3
  86. package/docs-site/en/whats-new.html +100 -8
  87. package/docs-site/en/workflows.html +34 -35
  88. package/docs-site/index.html +30 -14
  89. package/docs-site/mcp-approval.html +2 -1
  90. package/docs-site/mcp-auth.html +2 -1
  91. package/docs-site/mcp-servers.html +2 -1
  92. package/docs-site/mcp.html +2 -1
  93. package/docs-site/permissions.html +2 -1
  94. package/docs-site/profiles.html +2 -1
  95. package/docs-site/quickstart.html +17 -18
  96. package/docs-site/runtime.html +2 -1
  97. package/docs-site/scope.html +2 -1
  98. package/docs-site/security.html +7 -6
  99. package/docs-site/team.html +4 -3
  100. package/docs-site/whats-new.html +100 -8
  101. package/docs-site/workflows.html +41 -42
  102. package/evals/fs5-causal-arm.v1.json +2 -2
  103. package/evals/private-holdout-v1/CURRENT_BOUNDARY.md +17 -0
  104. package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
  105. package/package.json +8 -7
  106. package/packages/piagent-core/README.md +15 -23
  107. package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +2 -0
  108. package/packages/piagent-core/extensions/experiment-loop-policy.ts +75 -0
  109. package/packages/piagent-core/extensions/local-state-path.js +15 -0
  110. package/packages/piagent-core/extensions/piagent-guard.ts +108 -960
  111. package/packages/piagent-core/extensions/policy-core.js +2 -2
  112. package/packages/piagent-core/extensions/source-checkout-paths.ts +54 -0
  113. package/packages/piagent-core/managed/broker-client.mjs +49 -0
  114. package/packages/piagent-core/managed/capabilities.mjs +28 -0
  115. package/packages/piagent-core/managed/client-agent.mjs +6 -0
  116. package/packages/piagent-core/managed/compaction.mjs +63 -0
  117. package/packages/piagent-core/managed/doctor.mjs +107 -0
  118. package/packages/piagent-core/managed/helper-roles.mjs +57 -0
  119. package/packages/piagent-core/managed/native-catalog.mjs +42 -0
  120. package/packages/piagent-core/managed/patch-snapshot.mjs +48 -0
  121. package/packages/piagent-core/managed/repository-operation.mjs +84 -0
  122. package/packages/piagent-core/managed/request-stream.mjs +130 -0
  123. package/packages/piagent-core/managed/resource-loader.mjs +42 -0
  124. package/packages/piagent-core/managed/review-snapshot-worker.mjs +27 -0
  125. package/packages/piagent-core/managed/sandbox-diagnostic.mjs +12 -0
  126. package/packages/piagent-core/managed/search-fallback.mjs +93 -0
  127. package/packages/piagent-core/managed/session.mjs +493 -0
  128. package/packages/piagent-core/managed/store.d.mts +3 -0
  129. package/packages/piagent-core/managed/store.mjs +64 -0
  130. package/packages/piagent-core/managed/tool-boundary.mjs +275 -0
  131. package/packages/piagent-core/managed/tool-worker.mjs +36 -0
  132. package/packages/piagent-core/managed/toolchain.mjs +120 -0
  133. package/packages/piagent-core/managed/web-fetch.mjs +105 -0
  134. package/packages/piagent-core/managed/web-search.mjs +131 -0
  135. package/packages/piagent-core/managed/workflow.mjs +326 -0
  136. package/packages/piagent-core/package.json +2 -5
  137. package/packages/piagent-core/policies/base-policy.json +80 -26
  138. package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +3 -3
  139. package/packages/piagent-core/runtime/hooks/input-hook.ts +35 -196
  140. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +2 -0
  141. package/packages/piagent-core/runtime/inspection/approval-broker.ts +9 -1
  142. package/packages/piagent-core/runtime/managed-failure.d.mts +10 -0
  143. package/packages/piagent-core/runtime/managed-failure.mjs +166 -0
  144. package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +8 -29
  145. package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +1 -1
  146. package/packages/piagent-core/runtime/policy/block-remedy.ts +5 -3
  147. package/packages/piagent-core/runtime/registration/context-commands.ts +1 -1
  148. package/packages/piagent-core/runtime/registration/knowledge-tools.ts +7 -7
  149. package/packages/piagent-core/runtime/registration/policy-tools.ts +2 -2
  150. package/packages/piagent-core/runtime/registration/session-commands.ts +11 -26
  151. package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +1 -1
  152. package/packages/piagent-core/runtime/session/system-prompt.ts +6 -7
  153. package/packages/piagent-core/runtime/workflows/webui-runtime-command.ts +6 -6
  154. package/packages/piagent-core/runtime/workflows/webui-workflow.ts +6 -11
  155. package/packages/piagent-core/security/sensitive-text.js +9 -0
  156. package/packages/piagent-core/skills/piagent-ops/SKILL.md +8 -10
  157. package/packages/piagent-core/skills/test-audit/SKILL.md +101 -0
  158. package/packages/piagent-core/subagents/piagent-planner.md +1 -1
  159. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-DMU7Xe_O.js → MarkdownMessage-BRPIMCRh.js} +1 -1
  160. package/packages/piagent-webui/dist/client/assets/index-DNJ1vH5d.js +228 -0
  161. package/packages/piagent-webui/dist/client/assets/{index-DVahUVWy.css → index-DgSmcnP8.css} +1 -1
  162. package/packages/piagent-webui/dist/client/index.html +2 -2
  163. package/packages/piagent-webui/extension/piagent-webui.ts +11 -11
  164. package/packages/piagent-webui/extension/session-stream-adapter.ts +3 -1
  165. package/packages/piagent-webui/extension/terminal-session-adapter.ts +6 -1
  166. package/packages/piagent-webui/gateway/company-relay.ts +308 -0
  167. package/packages/piagent-webui/gateway/control-socket.ts +39 -12
  168. package/packages/piagent-webui/gateway/gateway-service.ts +109 -28
  169. package/packages/piagent-webui/gateway/gateway-session-stream.ts +58 -2
  170. package/packages/piagent-webui/gateway/managed-gateway.mjs +41 -0
  171. package/packages/piagent-webui/gateway/managed-launch.ts +145 -0
  172. package/packages/piagent-webui/gateway/managed-projection.ts +20 -0
  173. package/packages/piagent-webui/gateway/pi-host.ts +22 -4
  174. package/packages/piagent-webui/gateway/runtime-session-controls.ts +5 -2
  175. package/packages/piagent-webui/gateway/session-approval-authority.ts +28 -0
  176. package/packages/piagent-webui/gateway/session-catalog.ts +11 -3
  177. package/packages/piagent-webui/gateway/session-command-controller.ts +8 -2
  178. package/packages/piagent-webui/gateway/session-file-facts.ts +25 -0
  179. package/packages/piagent-webui/gateway/session-inspection-registry.ts +40 -9
  180. package/packages/piagent-webui/gateway/session-list-cache.ts +68 -0
  181. package/packages/piagent-webui/gateway/session-runtime-ownership.ts +8 -1
  182. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +20 -27
  183. package/packages/piagent-webui/ownership/managed-terminal-lease.ts +27 -0
  184. package/packages/piagent-webui/ownership/profile-state.ts +2 -0
  185. package/packages/piagent-webui/ownership/session-lease-store.ts +22 -1
  186. package/packages/piagent-webui/server/approval-projection.ts +9 -0
  187. package/packages/piagent-webui/server/core-inspection-provider.ts +4 -9
  188. package/packages/piagent-webui/server/loopback-server.ts +37 -1
  189. package/packages/piagent-webui/server/message-stop-reason.ts +11 -0
  190. package/packages/piagent-webui/server/tool-call-summary.ts +185 -0
  191. package/packages/piagent-webui/server/transcript-projection.ts +86 -3
  192. package/packs/engineering-base/pack.json +4 -0
  193. package/schemas/piagent-webui/runtime-command-v1.schema.json +1 -2
  194. package/schemas/piagent-webui/session-catalog-v1.schema.json +8 -0
  195. package/schemas/piagent-webui/transcript-v1.schema.json +94 -1
  196. package/scripts/build-docs-site.mjs +23 -12
  197. package/scripts/check-public-wording.mjs +5 -6
  198. package/scripts/check-runtime-advisories.mjs +23 -6
  199. package/scripts/claude-model-additions.mjs +33 -0
  200. package/scripts/configure-model-scope.sh +30 -6
  201. package/scripts/configure-subagents.sh +1 -1
  202. package/scripts/explain-command.mjs +5 -6
  203. package/scripts/init-project.sh +0 -2
  204. package/scripts/install-global.sh +3 -2
  205. package/scripts/model-scope-prune.mjs +75 -0
  206. package/scripts/phase-tools-evaluation.mjs +9 -4
  207. package/scripts/piagent-cli.mjs +51 -2
  208. package/scripts/piagent-dashboard.mjs +27 -4
  209. package/scripts/piagent-installations.mjs +26 -0
  210. package/scripts/piagent-studio.mjs +155 -0
  211. package/scripts/runtime-advisory-exceptions.json +29 -0
  212. package/scripts/setup.sh +10 -6
  213. package/scripts/team-doctor.sh +20 -1
  214. package/scripts/typescript-loader.mjs +6 -0
  215. package/scripts/verify-local.sh +2 -2
  216. package/templates/global/settings.json +3 -1
  217. package/templates/project/.pi/project-context.md +3 -3
  218. package/templates/project/AGENTS.md +4 -5
  219. package/docs/piagent-core-reasoning-research-2026-09-06.md +0 -225
  220. package/docs/piagent-core-research-sources-2026-09-06.json +0 -416
  221. package/packages/piagent-webui/dist/client/assets/index-Cui9AAV_.js +0 -176
@@ -1,4 +1,4 @@
1
- import { observedFocusedTestSummary, retainFocusedVerification, unresolvedFocusedVerification } from "./acceptance-focused-verification.js";
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, extractShellGlobCandidates, extractShellPathCandidates, findProtectedPathInCommand, unresolvedPathExpansions,
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, shellGlobMatchesPath, shellGlobSegmentMatches, shellGlobTargetsProtectedPath,
18
+ resolveRepositoryPathCandidate, shellGlobTargetsProtectedPath,
19
19
  unresolvedExpansionReason
20
20
  } from "./shell-reach.ts";
21
- import { extractShellWritePathCandidates, shellHasFileWriteRedirection } from "./shell-write-targets.js";
22
- import { isProjectMutatingShellCommand, isReadOnlyTaskShellCommand } from "./readonly-inline-inspection.ts";
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
- externalExecutableIndex, extractShellCommandInput, findShellExternalConfirmationReason, normalizeActionToken, normalizeShellCommandForPolicy
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, activeSessionTask, bindSessionTask, createTaskRunId, hasGitEvidenceRoot, listTaskContracts,
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 { classifyRecordedVerificationFailure, classifyVerificationFailure, latestObservedVerification, meaningfulVerificationCommands, recordedFailureForObservation, selectCompletionRecoveryClassification, selectVerificationPlan } from "./verification-intelligence.js";
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 type { FailureClassification } from "./failure-types.ts";
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, invalidateAcceptanceReceiptAfterMutation, refreshAcceptanceReceipt
83
+ buildAcceptanceReceipt, refreshAcceptanceReceipt
84
84
  } from "./acceptance-receipt.js";
85
85
  import { acceptanceLanguageAdapterForPath, isAcceptanceTestPath } from "./acceptance-language-adapters.js";
86
- import { allVerifyCommandsPassCurrentTree, changedSnapshotFiles, compactTaskDetails, mergeObservedTaskContext, passingVerifyCommandsForDigest, taskDeltaFilesFromSnapshot } from "./task-contract-view.js";
86
+ import { allVerifyCommandsPassCurrentTree, compactTaskDetails, passingVerifyCommandsForDigest, taskDeltaFilesFromSnapshot } from "./task-contract-view.js";
87
87
  import { applyRuntimeLifecycleObservation, runtimeLifecycleMode, workingTreeEvidenceDigest } from "./task-lifecycle.js";
88
- import { completeTaskDigestRefresh } from "./task-digest-migration.js";
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, recordDirectSubagentResult, reserveDirectSubagentDispatch, taskHelperUsageMode } from "../runtime/orchestration/subagent-tool-policy.ts";
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: 1,
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
- "Default to the parent model working directly; a helper is an exceptional token optimization, not a reasoning phase.",
366
- "Use at most one fresh read-only helper only when runtime evidence proves at least two independent lanes and at least 30% projected net token savings.",
367
- "Never delegate implementation, inherit parent history, retry a deterministic helper failure, or run parallel helpers/writers.",
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
- const SHELL_TOOL_NAMES = new Set(["bash", "shell", "exec"]);
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(1, boundedInteger(configured.maxConcurrentSubagents, defaultPolicy.maxConcurrentSubagents, 0, 1)),
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 autoRecoveryEnabled = environmentFeatureEnabled("PIAGENT_AUTO_RECOVERY");
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(), phaseToolRuntime = new PhaseToolRuntime(pi, dynamicToolsEnabled ? phaseToolModeFromEnvironment(process.env.PIAGENT_PHASE_TOOLS) : "off", telemetry, (ctx) => {
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: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
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: sessionTaskReference,
4102
- activeTask: (cwd, sessionId) => activeSessionTask(cwd, sessionId) as TaskContract | undefined,
4103
- resolveTask: (cwd, reference, sessionId) => resolveTaskContract(cwd, reference, sessionId) as TaskContract | undefined,
4104
- resolveTaskAny: (cwd, reference) => resolveTaskContract(cwd, reference, undefined) as TaskContract | undefined,
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: (ctx, task) => {
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: (cwd, sessionId) => activeSessionTask(cwd, sessionId) as TaskContract | undefined,
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: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined, telemetry });
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: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined, authorityPolicy: (ctx, task) => ensureTaskAuthorityResumePolicy(ctx.cwd, task, { authorityProfile: loadProfileFromContext(ctx).authorityProfile, environment: process.env }),
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: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
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: (prompt, ctx) => maybeStartAutomaticTask(prompt, ctx),
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: (ctx, task, options) => phaseToolRuntime.apply(ctx, trajectoryRuntime.sync(ctx.cwd, ctx.sessionManager.getSessionId(), task, trajectoryRecoveryOptions(ctx, task, options))),
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: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
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: (...args) => recordObservedTaskVerification(...args, policy.finalGate?.acceptanceProofMode === "diagnostic"),
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
- const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4268
- const written = recordDirectSubagentResult(task, event, result, {
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 || !metadata.taskRunId) return;
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, metadata.taskRunId, safeTargets, source, nowIso()) : [];
4294
- if (observed.length) telemetry(ctx, { event: "edit_freshness_snapshot_observed", taskRunId: metadata.taskRunId, source, paths: observed.map(redactText) });
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 task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4317
- const helperPreflight = evaluateDirectSubagentDispatch({
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
- event: "helper_dispatch_blocked",
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 (task?.trace.outcome === "pending" && task.changeMode === "source-change" && (event.toolName === "edit" || targetInspection.exactLocalPatchExecutor)) {
4346
- const freshness = editFreshnessGuard.evaluate(ctx, task.taskRunId, targetInspection.targets);
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", taskRunId: task.taskRunId, toolCallId: event.toolCallId, enforce: freshness.enforce, paths: freshness.stalePaths.map(redactText) });
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 task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4484
- const helperPreflight = evaluateDirectSubagentDispatch({
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
- const reserved = reserveDirectSubagentDispatch(task, metadata.toolCallId, {
4495
- now: nowIso,
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 phaseToolRuntime.apply(ctx, trajectoryRuntime.syncOptionalToolCall(
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 rechecks the task and working tree.", denyConsequence: "Block this command and return the denial to the active Pi operation." } };
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: "Release this exact provider action once after the guard rechecks runtime authority.", denyConsequence: "Block the provider action without changing external state." } };
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
- const mutationTargets = SHELL_TOOL_NAMES.has(event.toolName)
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
- const profileCommandApi = registerProfileCommands(pi, registrationDeps);
4139
+ registerProfileCommands(pi, registrationDeps);
4991
4140
  registerMemoryMcpCommands(pi, registrationDeps);
4992
4141
  const contextCommandApi = registerContextCommands(pi, registrationDeps);
4993
- let workflowCommandApi: { startFreshWorkflow: (...args: any[]) => Promise<void> };
4994
- registerSessionCommands(pi, {
4995
- ...registrationDeps,
4996
- ...contextCommandApi,
4997
- startFreshWorkflow: (...args: any[]) => workflowCommandApi.startFreshWorkflow(...args)
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
  }