@piagent/platform 1.6.0 → 1.6.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 (213) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +17 -14
  3. package/README.vi.md +26 -1
  4. package/benchmarks/production-v1/spend-control.v1.json +27 -3
  5. package/benchmarks/production-v1/suite.json +5 -1
  6. package/catalog/capabilities.json +16 -16
  7. package/docs/authority-manifest.md +1 -1
  8. package/docs/auto-delegation-policy.md +38 -73
  9. package/docs/command-reference-vietnamese.md +23 -31
  10. package/docs/context-engine.md +5 -3
  11. package/docs/context-window-policy.md +37 -1
  12. package/docs/distribution-standard.md +7 -7
  13. package/docs/en/architecture.md +26 -13
  14. package/docs/languages.json +4 -4
  15. package/docs/operator-manual-vietnamese.md +21 -41
  16. package/docs/publishing-for-teams.md +5 -0
  17. package/docs/quality-benchmark.md +179 -23
  18. package/docs/release-install-policy.md +6 -6
  19. package/docs/runtime-harness-standard.md +3 -3
  20. package/docs/security-threat-model.md +1 -1
  21. package/docs/subagent-orchestration-capabilities.md +19 -51
  22. package/docs/subagents-and-multiagent.md +21 -47
  23. package/docs/task-implementation-contract.md +3 -3
  24. package/docs/task-lifecycle-vietnamese.md +1 -1
  25. package/docs/team-onboarding.md +2 -2
  26. package/docs/vi/architecture.md +26 -13
  27. package/docs-site/architecture.html +1 -1
  28. package/docs-site/benchmark.html +89 -95
  29. package/docs-site/commands.html +1 -1
  30. package/docs-site/content/benchmark.html +88 -94
  31. package/docs-site/content/en/benchmark.html +134 -90
  32. package/docs-site/content/en/index.html +19 -19
  33. package/docs-site/content/en/runtime.html +4 -4
  34. package/docs-site/content/en/whats-new.html +12 -1
  35. package/docs-site/content/en/workflows.html +2 -2
  36. package/docs-site/content/index.html +19 -19
  37. package/docs-site/content/runtime.html +4 -4
  38. package/docs-site/content/whats-new.html +12 -1
  39. package/docs-site/content/workflows.html +2 -2
  40. package/docs-site/context-engine.html +1 -1
  41. package/docs-site/en/architecture.html +1 -1
  42. package/docs-site/en/benchmark.html +135 -91
  43. package/docs-site/en/commands.html +1 -1
  44. package/docs-site/en/context-engine.html +1 -1
  45. package/docs-site/en/index.html +20 -20
  46. package/docs-site/en/mcp-approval.html +1 -1
  47. package/docs-site/en/mcp-auth.html +1 -1
  48. package/docs-site/en/mcp-servers.html +1 -1
  49. package/docs-site/en/mcp.html +1 -1
  50. package/docs-site/en/permissions.html +1 -1
  51. package/docs-site/en/profiles.html +1 -1
  52. package/docs-site/en/quickstart.html +1 -1
  53. package/docs-site/en/runtime.html +5 -5
  54. package/docs-site/en/scope.html +1 -1
  55. package/docs-site/en/security.html +1 -1
  56. package/docs-site/en/team.html +1 -1
  57. package/docs-site/en/whats-new.html +13 -2
  58. package/docs-site/en/workflows.html +3 -3
  59. package/docs-site/index.html +20 -20
  60. package/docs-site/mcp-approval.html +1 -1
  61. package/docs-site/mcp-auth.html +1 -1
  62. package/docs-site/mcp-servers.html +1 -1
  63. package/docs-site/mcp.html +1 -1
  64. package/docs-site/permissions.html +1 -1
  65. package/docs-site/profiles.html +1 -1
  66. package/docs-site/quickstart.html +1 -1
  67. package/docs-site/runtime.html +5 -5
  68. package/docs-site/scope.html +1 -1
  69. package/docs-site/security.html +1 -1
  70. package/docs-site/team.html +1 -1
  71. package/docs-site/whats-new.html +13 -2
  72. package/docs-site/workflows.html +3 -3
  73. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  74. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  75. package/evals/fixtures/codex-relative-efficiency-v1.invalid.json +4 -0
  76. package/evals/fixtures/codex-relative-efficiency-v1.valid.json +94 -0
  77. package/evals/fixtures/helper-request.invalid.json +1 -1
  78. package/evals/fixtures/helper-request.valid.json +1 -1
  79. package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +7 -0
  80. package/evals/fixtures/task-authority-snapshot.valid.json +4 -4
  81. package/evals/fs4-readiness-matrix.v1.json +2 -2
  82. package/evals/fs5-causal-arm.v1.json +2 -2
  83. package/evals/fs5-pilot-protocol.v1.json +3 -3
  84. package/evals/fs5-pilot-protocol.v2.json +8 -8
  85. package/evals/fs5-pilot-protocol.v3.json +8 -8
  86. package/evals/fs5-pilot-protocol.v4.json +6 -6
  87. package/evals/fs5-pilot-protocol.v5.json +7 -6
  88. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  89. package/evals/runtime-conformance-v1/README.md +43 -0
  90. package/evals/runtime-conformance-v1/evaluate-paid-canary.mjs +198 -0
  91. package/evals/runtime-conformance-v1/lane.json +57 -0
  92. package/evals/runtime-conformance-v1/paid-canary.json +31 -0
  93. package/evals/runtime-conformance-v1/runner.mjs +456 -0
  94. package/package.json +1 -1
  95. package/packages/piagent-core/README.md +13 -4
  96. package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
  97. package/packages/piagent-core/benchmark/benchmark-core.js +4 -3
  98. package/packages/piagent-core/benchmark/benchmark-forensics.js +4 -2
  99. package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
  100. package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
  101. package/packages/piagent-core/benchmark/benchmark-record-validation.js +74 -5
  102. package/packages/piagent-core/benchmark/benchmark-report.js +15 -1
  103. package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
  104. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +201 -4
  105. package/packages/piagent-core/benchmark/benchmark-suite.js +22 -1
  106. package/packages/piagent-core/benchmark/benchmark-summary-support.js +4 -0
  107. package/packages/piagent-core/benchmark/benchmark-usage.js +15 -4
  108. package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
  109. package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
  110. package/packages/piagent-core/extensions/acceptance-receipt.js +89 -48
  111. package/packages/piagent-core/extensions/context-engine.js +1 -11
  112. package/packages/piagent-core/extensions/context-index-policy.js +4 -0
  113. package/packages/piagent-core/extensions/context-query-paths.js +57 -0
  114. package/packages/piagent-core/extensions/criterion-context-pack.js +16 -3
  115. package/packages/piagent-core/extensions/criterion-graph.js +4 -1
  116. package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
  117. package/packages/piagent-core/extensions/guard-types.ts +4 -0
  118. package/packages/piagent-core/extensions/piagent-guard.ts +194 -226
  119. package/packages/piagent-core/extensions/task-lifecycle.js +32 -0
  120. package/packages/piagent-core/extensions/task-state.js +69 -8
  121. package/packages/piagent-core/extensions/verification-intelligence.js +49 -12
  122. package/packages/piagent-core/extensions/workspace-evidence-roots.js +52 -16
  123. package/packages/piagent-core/package.json +1 -1
  124. package/packages/piagent-core/policies/base-policy.json +42 -2
  125. package/packages/piagent-core/policy/authority-manifest.v1.json +3 -3
  126. package/packages/piagent-core/prompts/be-to-fe.md +2 -8
  127. package/packages/piagent-core/prompts/plan.md +1 -1
  128. package/packages/piagent-core/prompts/platform-improve.md +2 -6
  129. package/packages/piagent-core/prompts/review.md +2 -7
  130. package/packages/piagent-core/prompts/scout.md +1 -1
  131. package/packages/piagent-core/prompts/task.md +4 -3
  132. package/packages/piagent-core/runtime/hooks/completion-hook.ts +7 -6
  133. package/packages/piagent-core/runtime/hooks/input-hook.ts +6 -0
  134. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +100 -1
  135. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +58 -72
  136. package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
  137. package/packages/piagent-core/runtime/inspection/bounded-emission-guard.ts +286 -0
  138. package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +37 -7
  139. package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
  140. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +20 -3
  141. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +47 -11
  142. package/packages/piagent-core/runtime/orchestration/helper-lifecycle.ts +16 -12
  143. package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +71 -0
  144. package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +28 -20
  145. package/packages/piagent-core/runtime/orchestration/role-policy.ts +27 -9
  146. package/packages/piagent-core/runtime/orchestration/subagent-tool-policy.ts +329 -0
  147. package/packages/piagent-core/runtime/policy/block-remedy.ts +0 -4
  148. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +3 -0
  149. package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
  150. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +20 -10
  151. package/packages/piagent-core/runtime/quality/performance-assurance.ts +3 -3
  152. package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
  153. package/packages/piagent-core/runtime/recovery/handoff-projection.ts +4 -1
  154. package/packages/piagent-core/runtime/recovery/pristine-task-policy-refresh.ts +91 -0
  155. package/packages/piagent-core/runtime/recovery/recovery-policy.ts +7 -4
  156. package/packages/piagent-core/runtime/recovery/resume-state.ts +112 -14
  157. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +7 -13
  158. package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
  159. package/packages/piagent-core/runtime/registration/context-commands.ts +16 -2
  160. package/packages/piagent-core/runtime/registration/knowledge-tools.ts +9 -5
  161. package/packages/piagent-core/runtime/registration/operator-request-intake.ts +49 -0
  162. package/packages/piagent-core/runtime/registration/policy-tools.ts +4 -4
  163. package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
  164. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +12 -9
  165. package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
  166. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +19 -13
  167. package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
  168. package/packages/piagent-core/runtime/registration/task-start-tool.ts +57 -57
  169. package/packages/piagent-core/runtime/runtime-limits.ts +16 -2
  170. package/packages/piagent-core/runtime/session/adaptive-context-analysis.ts +461 -0
  171. package/packages/piagent-core/runtime/session/adaptive-context-governor.ts +464 -0
  172. package/packages/piagent-core/runtime/session/adaptive-context-ledger.ts +265 -0
  173. package/packages/piagent-core/runtime/session/message-signals.ts +7 -3
  174. package/packages/piagent-core/runtime/session/operator-request-carry.ts +44 -0
  175. package/packages/piagent-core/runtime/session/runtime-state.ts +11 -7
  176. package/packages/piagent-core/runtime/session/source-checkout-read-grants.ts +35 -0
  177. package/packages/piagent-core/runtime/session/system-prompt.ts +76 -14
  178. package/packages/piagent-core/runtime/session/task-contract-presentation.ts +39 -0
  179. package/packages/piagent-core/runtime/tools/phase-tools.ts +8 -8
  180. package/packages/piagent-core/runtime/tools/tool-groups.ts +12 -2
  181. package/packages/piagent-core/runtime/verification/exact-verifier-reuse.ts +101 -0
  182. package/packages/piagent-core/runtime/workflows/runtime-intake-compaction.ts +164 -0
  183. package/packages/piagent-core/runtime/workflows/task-intake.ts +167 -39
  184. package/packages/piagent-core/subagents/piagent-oracle.md +5 -3
  185. package/packages/piagent-core/subagents/piagent-planner.md +6 -4
  186. package/packages/piagent-core/subagents/piagent-reviewer.md +5 -3
  187. package/packages/piagent-core/subagents/piagent-scout.md +4 -2
  188. package/packages/piagent-core/subagents/piagent-worker.md +6 -4
  189. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-CxeZqTte.js → MarkdownMessage-B4XehTCU.js} +1 -1
  190. package/packages/piagent-webui/dist/client/assets/{index-DynIMe8I.js → index-CgOEyRPG.js} +4 -4
  191. package/packages/piagent-webui/dist/client/index.html +1 -1
  192. package/packages/piagent-webui/extension/piagent-webui.ts +3 -1
  193. package/packages/piagent-webui/extension/same-session-bridge.ts +1 -1
  194. package/packages/piagent-webui/extension/session-stream-adapter.ts +33 -10
  195. package/packages/piagent-webui/gateway/gateway-session-stream.ts +37 -21
  196. package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +237 -0
  197. package/packages/piagent-webui/gateway/session-operation-retry-control.ts +38 -0
  198. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +9 -2
  199. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +8 -7
  200. package/packages/piagent-webui/ownership/session-lease-store.ts +5 -2
  201. package/packages/piagent-webui/server/core-inspection-provider.ts +2 -1
  202. package/packages/piagent-webui/server/transcript-projection.ts +91 -5
  203. package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
  204. package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
  205. package/schemas/piagent-webui/subagent-tree-v1.schema.json +11 -1
  206. package/schemas/project-profile.schema.json +1 -1
  207. package/schemas/task-contract.schema.json +15 -0
  208. package/scripts/benchmark-runner-core.mjs +14 -6
  209. package/scripts/benchmark-runner-finalization.mjs +4 -0
  210. package/scripts/configure-subagents.sh +42 -34
  211. package/scripts/helper-evaluation.mjs +1 -1
  212. package/scripts/team-doctor.sh +1 -1
  213. package/scripts/verify-local.sh +5 -0
@@ -67,7 +67,7 @@ import {
67
67
  } from "./context-index-policy.js";
68
68
  import {
69
69
  DEFAULT_MAX_TASK_ATTEMPTS, activeSessionTask, bindSessionTask, createTaskRunId, hasGitEvidenceRoot, listTaskContracts,
70
- pathWithinChangeEvidenceRoot, priorTaskAttempts, repositoryFileManifest, repositoryFileManifestDetails, resolveTaskContract, safeTaskId, summarizeAttempt, taskContractValidationErrors, taskDigestMigrationArchiveStatus,
70
+ priorTaskAttempts, repositoryFileManifest, repositoryFileManifestDetails, resolveTaskContract, safeTaskId, summarizeAttempt, taskContractValidationErrors, taskDigestMigrationArchiveStatus,
71
71
  workPlanDependencyError, workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence, writeTaskContract
72
72
  } from "./task-state.js";
73
73
  import { classifyRecordedVerificationFailure, classifyVerificationFailure, latestObservedVerification, meaningfulVerificationCommands, selectCompletionRecoveryClassification, selectVerificationPlan } from "./verification-intelligence.js";
@@ -110,6 +110,7 @@ import {
110
110
  } from "../runtime/session/tool-result-compaction.ts";
111
111
  import { RuntimeSessionState } from "../runtime/session/runtime-state.ts";
112
112
  import type { ObservedTaskContext } from "../runtime/session/runtime-state.ts";
113
+ import { reuseCurrentTreeExactVerifier } from "../runtime/verification/exact-verifier-reuse.ts";
113
114
  import {
114
115
  PIAGENT_TOOL_GROUPS,
115
116
  PIAGENT_TOOL_NAMES,
@@ -161,11 +162,14 @@ import { inspectTaskResumeState } from "../runtime/recovery/resume-state.ts";
161
162
  import { SemanticRepairRuntime } from "../runtime/recovery/semantic-repair-runtime.ts";
162
163
  import { defaultRolePolicy } from "../runtime/orchestration/role-policy.ts";
163
164
  import { helpersMode } from "../runtime/orchestration/helper-lifecycle.ts";
165
+ import { evaluateDirectSubagentDispatch, recordDirectSubagentResult, reserveDirectSubagentDispatch, taskHelperUsageMode } from "../runtime/orchestration/subagent-tool-policy.ts";
164
166
  import { buildLiveTaskStatus, formatLiveTaskStatus } from "../runtime/product/operator-projections.ts";
165
167
  import { buildTaskEfficiencyMetrics } from "../runtime/product/efficiency-metrics.ts";
166
168
  import { performanceReviewToolDecision, performanceReviewToolKind } from "../runtime/quality/performance-assurance.ts";
167
169
  import { expectedModelMutationProof } from "../runtime/quality/model-mutation-proof.ts";
170
+ import { EditFreshnessGuard, editFreshnessModeFromEnvironment } from "../runtime/quality/edit-freshness-guard.ts";
168
171
  import { captureVerifierFileSnapshot } from "../runtime/inspection/verifier-snapshot-store.ts";
172
+ import { fallbackBasePolicy } from "./fallback-base-policy.ts";
169
173
  import { prefixCompletions, registerPiagentTool, registerRuntimeCommand, registerRuntimeTool } from "../runtime/registration/extension-registration.ts";
170
174
  import { FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP, ONBOARDING_COMMAND_ACTIONS, WORKFLOW_COMMAND_EXCLUSIONS } from "../runtime/registration/operator-catalogs.ts";
171
175
  import { registerPiagentStatusCommand } from "../runtime/registration/runtime-model-status.ts";
@@ -341,11 +345,11 @@ const DEFAULT_RUNTIME_POLICY: Required<RuntimePolicySettings> = {
341
345
 
342
346
  const DEFAULT_ORCHESTRATION_POLICY: ResolvedOrchestrationPolicy = {
343
347
  defaultMode: "solo-first",
344
- maxConcurrentSubagents: 2,
348
+ maxConcurrentSubagents: 1,
345
349
  defaultReviewLenses: ["correctness", "tests", "scope"],
346
350
  roleModelGuidance: {
347
351
  planner: "Use the strongest available model for decomposition, architecture, risk, and acceptance criteria.",
348
- worker: "Use the fastest reliable model for a bounded, already-planned single write set.",
352
+ worker: "Disabled by default. The parent model owns all implementation and mutation.",
349
353
  reviewer: "Use a model or thinking setting decorrelated from the worker when review quality matters.",
350
354
  watchdog: "Use a strong model only for final risk review, security, release, or high-impact changes."
351
355
  },
@@ -357,9 +361,9 @@ const DEFAULT_ORCHESTRATION_POLICY: ResolvedOrchestrationPolicy = {
357
361
  readBeforeTask: true
358
362
  },
359
363
  rules: [
360
- "Default to one parent agent; do not start a swarm for ordinary implementation.",
361
- "Use subagents only for bounded read-only scout, planning, review, or a single approved worker.",
362
- "Parallel read-only review is allowed when the diff is non-trivial; parallel writers require explicit user approval and isolation.",
364
+ "Default to the parent model working directly; a helper is an exceptional token optimization, not a reasoning phase.",
365
+ "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.",
366
+ "Never delegate implementation, inherit parent history, retry a deterministic helper failure, or run parallel helpers/writers.",
363
367
  "Treat Field Guide memory as advisory; verify every durable fact against current repository files.",
364
368
  "Keep review lenses explicit so cheap review work catches drift before release."
365
369
  ]
@@ -386,176 +390,7 @@ const SHELL_TOOL_NAMES = new Set(["bash", "shell", "exec"]);
386
390
  const MAX_MCP_PROXY_ARGS_CHARS = 131_072;
387
391
  const SESSION_PERMISSION_OVERRIDES = new Map<string, PermissionProfileMode>();
388
392
 
389
- // The policy used when `policies/base-policy.json` cannot be read. It runs in
390
- // exactly the case where something is already wrong, so it must never be the
391
- // looser of the two: this copy had drifted behind the file and silently dropped
392
- // `.pi/piagent-state/**` -- the guard's own state -- along with the `sudo ` and
393
- // `chmod -R 777` blocks. A missing policy file is a reason to be stricter, not a
394
- // quiet downgrade. `tests/policy-core.test.mjs` fails if this drifts again.
395
- const DEFAULT_POLICY: BasePolicy = {
396
- protectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", "**/node_modules/**", "**/dist/**", ".pi/piagent-state/**", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
397
- shellProtectedPaths: [".git/**", "**/auth.json", "**/.env", "**/.env.*", ".pi/piagent-state/**", ".pi/settings.json", ".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", CONTEXT_INDEX_FILE],
398
- blockedCommandPatterns: ["rm -rf /", "rm -rf ~", "rm -rf $HOME", "git reset --hard", "git clean -fd", "sudo ", "chmod -R 777"],
399
- requireConfirmationPatterns: ["deploy", "release", "publish", "migration", "gh pr merge", "git push"],
400
- defaultRequiredContext: ["AGENTS.md", "README.md"],
401
- permissionProfiles: {
402
- defaultMode: "workspace-write",
403
- allowedModes: ["read-only", "workspace-write", "trusted-full-access"]
404
- },
405
- execPolicy: {
406
- defaultMode: "enforce",
407
- bannedPrefixSuggestions: [
408
- ["python"],
409
- ["python3"],
410
- ["node"],
411
- ["node", "-e"],
412
- ["bash"],
413
- ["bash", "-lc"],
414
- ["sh"],
415
- ["sh", "-c"],
416
- ["zsh"],
417
- ["zsh", "-lc"],
418
- ["git"],
419
- ["sudo"],
420
- ["env"]
421
- ],
422
- rules: [
423
- {
424
- id: "prompt-git-add-broad",
425
- action: "prompt",
426
- match: "regex",
427
- value: "(?:^|\\s)git\\s+(?:-C\\s+\\S+\\s+)?add\\s+(?:(?:--all|-A)(?:\\s+(?:\\.|:/))?|--\\s+(?:\\.|:/)|(?:\\.|:/))(?:\\s|$)",
428
- reason: "Broad git staging can include unrelated or sensitive changes; inspect git status/diff and confirm the exact scope first."
429
- }
430
- ]
431
- },
432
- contextBudget: {
433
- defaultMode: "enforce",
434
- contextDeltaShadow: "sample",
435
- maxContextFileChars: 50000,
436
- maxMemoryFileChars: 20000,
437
- maxManifestFiles: 80,
438
- warnFragmentChars: 4000
439
- },
440
- toolRegistry: {
441
- defaultMode: "advisory",
442
- alwaysAllowedTools: [
443
- "piagent_tools",
444
- "piagent_context_engine",
445
- "piagent_context",
446
- "piagent_permission_status",
447
- "piagent_exec_policy_check",
448
- "piagent_context_budget",
449
- "piagent_tool_policy_check",
450
- "piagent_task_gate_check",
451
- "piagent_usage_snapshot",
452
- "piagent_orchestration_policy",
453
- "piagent_memory_status",
454
- "piagent_memory_note",
455
- "piagent_memory_search",
456
- "piagent_memory_citation_record",
457
- "piagent_context_index_status",
458
- "piagent_context_index_record",
459
- "piagent_context_index_search",
460
- "piagent_profile_options",
461
- "piagent_profile_apply",
462
- "piagent_profile_tech_options",
463
- "piagent_profile_tech_apply",
464
- "piagent_profile_tech_context_record",
465
- "piagent_project_onboarding_record",
466
- "piagent_task_start",
467
- "piagent_task_progress",
468
- "piagent_source_checkout",
469
- "piagent_context_record",
470
- "piagent_verify_record",
471
- "piagent_trace_record"
472
- ],
473
- toolCapabilities: {
474
- bash: ["shell"],
475
- shell: ["shell"],
476
- exec: ["shell"],
477
- read: ["filesystem-readonly"],
478
- grep: ["filesystem-readonly"],
479
- find: ["filesystem-readonly"],
480
- ls: ["filesystem-readonly"],
481
- write: ["filesystem-write"],
482
- edit: ["filesystem-write"],
483
- apply_patch: ["filesystem-write"],
484
- browser: ["browser"],
485
- github: ["github"]
486
- }
487
- },
488
- externalActionPolicy: {
489
- defaultMode: "enforce",
490
- providerKeywords: [
491
- "github",
492
- "gitlab",
493
- "bitbucket",
494
- "vercel",
495
- "netlify",
496
- "cloudflare",
497
- "aws",
498
- "gcp",
499
- "azure",
500
- "slack",
501
- "teams",
502
- "jira",
503
- "linear",
504
- "notion",
505
- "figma",
506
- "stripe",
507
- "supabase",
508
- "firebase"
509
- ],
510
- writeVerbs: [
511
- "add",
512
- "approve",
513
- "archive",
514
- "assign",
515
- "close",
516
- "comment",
517
- "create",
518
- "delete",
519
- "deploy",
520
- "dispatch",
521
- "merge",
522
- "open",
523
- "post",
524
- "publish",
525
- "push",
526
- "release",
527
- "remove",
528
- "reopen",
529
- "run",
530
- "send",
531
- "submit",
532
- "trigger",
533
- "update",
534
- "upload",
535
- "write"
536
- ],
537
- safeVerbs: [
538
- "fetch",
539
- "find",
540
- "get",
541
- "inspect",
542
- "list",
543
- "read",
544
- "search",
545
- "show",
546
- "view"
547
- ]
548
- },
549
- finalGate: {
550
- defaultMode: "enforce",
551
- requireTaskContract: true,
552
- requireContextManifest: true,
553
- requireVerifyEvidence: true,
554
- requireTrace: true,
555
- requirePassingVerify: true
556
- },
557
- orchestrationPolicy: DEFAULT_ORCHESTRATION_POLICY
558
- };
393
+ const DEFAULT_POLICY = fallbackBasePolicy(CONTEXT_INDEX_FILE, DEFAULT_ORCHESTRATION_POLICY);
559
394
 
560
395
  // Which platform supplies the adapters is fixed by where this file is installed,
561
396
  // so it is resolved once here rather than threaded through every profile load.
@@ -1016,7 +851,12 @@ function inspectPathInputsFromInput(
1016
851
  const seen = new Set<string>();
1017
852
  const walked = walkStringInputs(input, [], 0, includeFilesystemContentFields);
1018
853
  for (const item of walked.items) {
1019
- const normalized = normalizeRelative(cwd, item.value);
854
+ const relative = normalizeRelative(cwd, item.value);
855
+ // `path.relative(root, root)` is the empty string. Treat that canonical
856
+ // project-root spelling as `.` so a capability such as `**/*` admits
857
+ // `ls .` and an absolute path equal to cwd. The empty candidate matched no
858
+ // scope and made fresh helpers unable to inspect even their own project.
859
+ const normalized = relative === "" ? "." : relative;
1020
860
  if (normalized === undefined) continue;
1021
861
  const key = `${item.field}\0${normalized}`;
1022
862
  if (seen.has(key)) continue;
@@ -1126,6 +966,7 @@ function evaluatePathLikeToolAccess(
1126
966
  forceScopeAware?: boolean;
1127
967
  forceWrite?: boolean;
1128
968
  allowAmbiguousFilesystemContentFields?: boolean;
969
+ sourceCheckoutReadRoots?: string[];
1129
970
  } = {}
1130
971
  ): { block: boolean; reason?: string } {
1131
972
  const scopeAwareTool = options.forceScopeAware === true || ["read", "write", "edit", "grep", "find", "ls"].includes(toolName)
@@ -1167,9 +1008,13 @@ function evaluatePathLikeToolAccess(
1167
1008
  }
1168
1009
 
1169
1010
  for (const item of inspectedPaths) {
1011
+ const fieldAccessMode = filesystemFieldAccessMode(toolName, item.field, writesFilesystem);
1012
+ const sourceCheckoutReadRoot = fieldAccessMode === "read"
1013
+ ? grantedSourceCheckoutRootForPath(cwd, item.path, options.sourceCheckoutReadRoots ?? [])
1014
+ : undefined;
1170
1015
  if (scopeAwareTool && isFilesystemScopeField(item.field)) {
1171
1016
  const boundary = inspectRepositoryPathBoundary(cwd, item.path);
1172
- if (boundary.reason) return { block: true, reason: `Blocked ${toolName}: ${boundary.reason}` };
1017
+ if (boundary.reason && !sourceCheckoutReadRoot) return { block: true, reason: `Blocked ${toolName}: ${boundary.reason}` };
1173
1018
  }
1174
1019
  const resolvedPath = resolveRepositoryPathCandidate(cwd, item.path);
1175
1020
  const readMatched = matchesProtectedPath(item.path, readProtectedPaths)
@@ -1181,7 +1026,6 @@ function evaluatePathLikeToolAccess(
1181
1026
  };
1182
1027
  }
1183
1028
 
1184
- const fieldAccessMode = filesystemFieldAccessMode(toolName, item.field, writesFilesystem);
1185
1029
  if (fieldAccessMode === "write") {
1186
1030
  const readOnlyMatched = matchesProtectedPath(item.path, readOnlyPaths)
1187
1031
  ?? (resolvedPath ? matchesProtectedPath(resolvedPath, readOnlyPaths) : undefined);
@@ -1205,7 +1049,8 @@ function evaluatePathLikeToolAccess(
1205
1049
  reason: `Blocked ${toolName} write outside resolved filesystem scope from ${item.field}: ${item.path}`
1206
1050
  };
1207
1051
  }
1208
- } else if (scopeAwareTool && filesystemRead && isFilesystemScopeField(item.field) && !matchesAnyPath(item.path, filesystemRead)) {
1052
+ } else if (scopeAwareTool && filesystemRead && isFilesystemScopeField(item.field)
1053
+ && !sourceCheckoutReadRoot && !matchesAnyPath(item.path, filesystemRead)) {
1209
1054
  return {
1210
1055
  block: true,
1211
1056
  reason: `Blocked ${toolName} read outside resolved filesystem scope from ${item.field}: ${item.path}`
@@ -1236,6 +1081,21 @@ function evaluatePathLikeToolAccess(
1236
1081
  return { block: false };
1237
1082
  }
1238
1083
 
1084
+ function grantedSourceCheckoutRootForPath(cwd: string, candidate: string, roots: string[]): string | undefined {
1085
+ if (roots.length === 0) return undefined;
1086
+ const absolute = path.resolve(cwd, normalizePathCandidate(candidate));
1087
+ let canonical: string;
1088
+ try {
1089
+ canonical = fs.realpathSync.native(absolute);
1090
+ } catch {
1091
+ return undefined;
1092
+ }
1093
+ return roots.find((root) => {
1094
+ const relative = path.relative(root, canonical);
1095
+ return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
1096
+ });
1097
+ }
1098
+
1239
1099
  function stateRoot(cwd: string): string {
1240
1100
  return path.join(cwd, ".pi", "piagent-state");
1241
1101
  }
@@ -1513,7 +1373,7 @@ function resolveOrchestrationPolicy(profile: ProjectProfile, policy: BasePolicy)
1513
1373
  };
1514
1374
  return {
1515
1375
  defaultMode: normalizeOrchestrationMode(configured.defaultMode, defaultPolicy.defaultMode),
1516
- maxConcurrentSubagents: boundedInteger(configured.maxConcurrentSubagents, defaultPolicy.maxConcurrentSubagents, 0, 6),
1376
+ maxConcurrentSubagents: Math.min(1, boundedInteger(configured.maxConcurrentSubagents, defaultPolicy.maxConcurrentSubagents, 0, 1)),
1517
1377
  defaultReviewLenses: normalizeReviewLenses(configured.defaultReviewLenses, defaultPolicy.defaultReviewLenses),
1518
1378
  roleModelGuidance,
1519
1379
  fieldGuide: {
@@ -1846,13 +1706,18 @@ function isReadOnlyTaskShellCommand(
1846
1706
  if (/[<>]|`|\$\(/.test(command)) {
1847
1707
  return false;
1848
1708
  }
1849
- const safeCommands = new Set(["pwd", "ls", "find", "rg", "grep", "cat", "sed", "head", "tail", "wc", "stat", "file", "test", "[", "which"]);
1709
+ const safeCommands = new Set(["pwd", "ls", "find", "rg", "grep", "cat", "sed", "head", "tail", "wc", "stat", "file", "test", "[", "which", "printf", "sort"]);
1850
1710
  const safeGitSubcommands = new Set(["status", "diff", "log", "show", "ls-files", "rev-parse"]);
1851
1711
  return segments.length > 0 && segments.every((segment) => {
1852
1712
  const words = segment.words.filter(Boolean);
1853
1713
  const executable = path.basename(words[0] ?? "");
1854
1714
  if (executable === "sed" && words.some((word) => /^-.*i/.test(word))) return false;
1855
1715
  if (executable === "find" && words.some((word) => ["-delete", "-exec", "-execdir", "-ok", "-okdir", "-fprint", "-fprint0", "-fprintf", "-fls"].includes(word))) return false;
1716
+ if (executable === "sort" && words.slice(1).some((word) => (
1717
+ word === "--output"
1718
+ || word.startsWith("--output=")
1719
+ || /^-[^-]*o/.test(word)
1720
+ ))) return false;
1856
1721
  if (safeCommands.has(executable)) return true;
1857
1722
  if (executable === "git") return safeGitSubcommands.has(words[1] ?? "");
1858
1723
  if (executable === "command") return words[1] === "-v";
@@ -1860,6 +1725,16 @@ function isReadOnlyTaskShellCommand(
1860
1725
  });
1861
1726
  }
1862
1727
 
1728
+ function shellTouchesGrantedSourceCheckout(cwd: string, command: string, roots: string[]): boolean {
1729
+ if (roots.length === 0) return false;
1730
+ for (const root of roots) {
1731
+ const relative = path.relative(cwd, root).split(path.sep).join("/");
1732
+ if (command.includes(root) || (relative && command.includes(relative))) return true;
1733
+ }
1734
+ return extractShellPathCandidates(command)
1735
+ .some((candidate) => Boolean(grantedSourceCheckoutRootForPath(cwd, normalizeRelative(cwd, candidate) ?? candidate, roots)));
1736
+ }
1737
+
1863
1738
  const PROJECT_MUTATING_EXECUTABLES = new Set([
1864
1739
  "apply_patch", "bash", "chmod", "chown", "cp", "dd", "install", "ln", "make", "mkdir", "mv", "prename", "rename",
1865
1740
  "node", "patch", "perl", "php", "python", "python3", "rm", "rmdir", "rsync", "ruby", "scp", "sh",
@@ -3714,7 +3589,9 @@ function evaluateTaskGate(
3714
3589
  if (task.changedFiles.length === 0) missing.push("changed files");
3715
3590
  if (changedFileEvidence.undeclared.length > 0) missing.push(`declared observed changes (${changedFileEvidence.undeclared.join(", ")})`);
3716
3591
  if (changedFileEvidence.unsupportedClaims.length > 0) missing.push(`supported changed-file claims (${changedFileEvidence.unsupportedClaims.join(", ")})`);
3717
- if (changedFileEvidence.outsideScope.length > 0) missing.push(`changes within task scope (${changedFileEvidence.outsideScope.join(", ")})`);
3592
+ if (changedFileEvidence.outsideScope.length > 0) {
3593
+ warnings.push(`Task focus expanded beyond the initial scope (${changedFileEvidence.outsideScope.join(", ")}).`);
3594
+ }
3718
3595
  }
3719
3596
  if ((task.changeMode === "read-only" || task.mutationPolicy === "forbidden") && changedFileEvidence.expected.length > 0) {
3720
3597
  missing.push(`mutation-forbidden task has observed changes (${changedFileEvidence.expected.join(", ")})`);
@@ -3725,9 +3602,11 @@ function evaluateTaskGate(
3725
3602
  currentWorkingTreeDigest
3726
3603
  });
3727
3604
  const semanticEnforcement = taskAuthorityDecision(task, "CAP-13", "block").allowed;
3728
- if (semanticEnforcement && acceptance.criticalMissing.length > 0) {
3729
- missing.push(`critical acceptance evidence (${acceptance.criticalMissing.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")})`);
3730
- }
3605
+ const abstainedIds = new Set(acceptance.adapterAbstained.map((criterion) => criterion.id));
3606
+ const adapterCritical = acceptance.criticalMissing.filter((criterion) => abstainedIds.has(criterion.id));
3607
+ const linkedCritical = acceptance.criticalMissing.filter((criterion) => !abstainedIds.has(criterion.id));
3608
+ if (semanticEnforcement && linkedCritical.length > 0) missing.push(`critical acceptance evidence (${linkedCritical.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")})`);
3609
+ if (semanticEnforcement && adapterCritical.length > 0) missing.push(`critical acceptance evidence adapter-unresolved (${adapterCritical.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")}); remedy: add a direct-relative focused test or configure a deterministic adapter`);
3731
3610
  const acceptanceConflicts = acceptanceSemanticConflicts(task, {
3732
3611
  cwd,
3733
3612
  changedFiles: changedFileEvidence.expected
@@ -3735,7 +3614,11 @@ function evaluateTaskGate(
3735
3614
  if (semanticEnforcement && acceptanceConflicts.length > 0) {
3736
3615
  missing.push(`acceptance semantic conflicts (${acceptanceConflicts.join(", ")})`);
3737
3616
  }
3738
- const normalMissing = acceptance.missing.filter((criterion) => criterion.priority !== "critical");
3617
+ const criticalIds = new Set(acceptance.criticalMissing.map((criterion) => criterion.id));
3618
+ const normalMissing = acceptance.missing.filter((criterion) => !criticalIds.has(criterion.id));
3619
+ if (semanticEnforcement && acceptance.adapterAbstained.length > 0) {
3620
+ warnings.push(`Acceptance proof adapter abstained; rigorous criteria remain pending and hand off without an automatic model retry: ${acceptance.adapterAbstained.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")}`);
3621
+ }
3739
3622
  if (!semanticEnforcement && (acceptance.criticalMissing.length > 0 || acceptanceConflicts.length > 0)) warnings.push("Acceptance projection is advisory under the pinned task authority and cannot block completion.");
3740
3623
  if (normalMissing.length > 0) {
3741
3624
  warnings.push(`Acceptance criteria pending evidence: ${normalMissing.map((criterion) => `${criterion.id}:${criterion.obligation}`).join(", ")}`);
@@ -3915,6 +3798,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
3915
3798
  const runtimeState = new RuntimeSessionState({
3916
3799
  maxObservedContext: contextBudgetConfig(policy).maxManifestFiles
3917
3800
  });
3801
+ const editFreshnessGuard = new EditFreshnessGuard(editFreshnessModeFromEnvironment(process.env.PIAGENT_EDIT_FRESHNESS));
3918
3802
  const sourceMutationGuardBindings = createSourceMutationGuardBindings(policy, loadProfileFromContext);
3919
3803
  const runtimeSnapshotCapture = new RuntimeSnapshotCapture(), runtimeVersions = readRuntimeVersionMetadata(PLATFORM_ROOT);
3920
3804
  const solverShadow = solverMode === "off" ? undefined : new SolverShadowRuntime(solverMode);
@@ -4137,8 +4021,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
4137
4021
  failed?.exitCode ?? 1
4138
4022
  );
4139
4023
  const trajectory = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
4140
- const dependencyMutationAuthorized = task.scope.some((entry) => /(?:^|\/)(?:package(?:-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|[^/]*(?:config|lock)[^/]*)$/i.test(entry));
4141
- return selectRecoveryDecision({
4024
+ // Task scope is a retrieval/review hint, not filesystem authority. Recovery
4025
+ // may update a dependency or config file when repository evidence requires
4026
+ // it; protected-path and approval policy still run on the exact call.
4027
+ const dependencyMutationAuthorized = true;
4028
+ const selected = selectRecoveryDecision({
4142
4029
  featureEnabled: autoRecoveryEnabled && taskAuthorityDecision(task, "CAP-12", "model-turn").allowed,
4143
4030
  task: {
4144
4031
  taskId: task.taskId,
@@ -4157,6 +4044,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
4157
4044
  : true,
4158
4045
  dependencyMutationAuthorized
4159
4046
  });
4047
+ return gate?.missing.some((item) => /^critical acceptance evidence adapter-unresolved\b/i.test(item))
4048
+ ? { ...selected, action: "handoff", continuation: "none", nextPhase: null, sourceMutationAllowed: false, reasonCodes: ["unknown-diagnostic-exhausted"] }
4049
+ : selected;
4160
4050
  }
4161
4051
 
4162
4052
  function trajectoryRecoveryOptions(ctx: ExtensionContext, task: TaskContract, options: any): any {
@@ -4194,6 +4084,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4194
4084
  resolveTaskAny: (cwd, reference) => resolveTaskContract(cwd, reference, undefined) as TaskContract | undefined,
4195
4085
  bindTask: bindSessionTask,
4196
4086
  writeTask,
4087
+ appendTrace,
4197
4088
  capabilityState: (ctx) => verifyProjectCapabilityState(extensionDir, ctx.cwd, ctx.isProjectTrusted(), { allowRepin: true, forceFull: true, sessionId: ctx.sessionManager.getSessionId() }),
4198
4089
  permissionProfile: (ctx, profile) => resolvePermissionProfile(profile, policy, permissionOverrideFromContext(ctx)),
4199
4090
  legacyProjectWarning: legacyProjectStateWarning,
@@ -4237,6 +4128,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4237
4128
  onAgentSettled: activityInspector.refresh,
4238
4129
  beforeShutdown: (ctx) => {
4239
4130
  sourceMutationGuardBindings.unbind(ctx);
4131
+ editFreshnessGuard.clear(ctx);
4240
4132
  activityInspector.dispose(ctx);
4241
4133
  sessionCapabilityDigests.delete(`${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`);
4242
4134
  }
@@ -4334,6 +4226,36 @@ export default function piagentGuard(pi: ExtensionAPI) {
4334
4226
  telemetry,
4335
4227
  activity: recordActivity,
4336
4228
  now: nowIso,
4229
+ recordSubagentResult: (ctx, event, result) => {
4230
+ const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4231
+ const written = recordDirectSubagentResult(task, event, result, {
4232
+ now: nowIso,
4233
+ persist: (candidate) => writeTask(ctx.cwd, candidate),
4234
+ trace: (payload) => appendTrace(ctx.cwd, payload),
4235
+ sessionTrace: (payload) => appendSessionTrace(pi, payload)
4236
+ });
4237
+ telemetry(ctx, {
4238
+ event: "helper_result_classified",
4239
+ recordedAt: nowIso(),
4240
+ taskRunId: written?.taskRunId,
4241
+ toolCallId: event.toolCallId,
4242
+ spawned: result.spawned,
4243
+ failed: result.failed,
4244
+ disposition: result.disposition,
4245
+ reasonCode: result.reasonCode,
4246
+ calls: result.calls,
4247
+ tokens: result.tokens
4248
+ });
4249
+ },
4250
+ observeEditFreshness: (ctx, event, metadata) => {
4251
+ if (!metadata.successful || !metadata.taskRunId) return;
4252
+ const protectedPaths = effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths;
4253
+ const source = event.toolName === "read" ? "read" : ["edit", "write", "apply_patch"].includes(event.toolName) ? "mutation" : undefined;
4254
+ const candidates = source === "read" && metadata.targetPath ? [metadata.targetPath] : source === "mutation" ? metadata.mutationTargets : [];
4255
+ const safeTargets = candidates.filter((file) => !matchesProtectedPath(file, protectedPaths));
4256
+ const observed = source ? editFreshnessGuard.observe(ctx, metadata.taskRunId, safeTargets, source, nowIso()) : [];
4257
+ if (observed.length) telemetry(ctx, { event: "edit_freshness_snapshot_observed", taskRunId: metadata.taskRunId, source, paths: observed.map(redactText) });
4258
+ },
4337
4259
  completeSemanticRepair: (ctx, event, metadata) => {
4338
4260
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4339
4261
  return task && (taskAuthorityDecision(task, "CAP-13", "mutate").allowed
@@ -4354,10 +4276,42 @@ export default function piagentGuard(pi: ExtensionAPI) {
4354
4276
  activity: recordActivity,
4355
4277
  beforeAuthorize: (event, ctx) => {
4356
4278
  const toolInput = isPlainRecord(event.input) ? event.input : {};
4279
+ const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4280
+ const helperPreflight = evaluateDirectSubagentDispatch({
4281
+ toolName: event.toolName,
4282
+ toolInput,
4283
+ cwd: ctx.cwd,
4284
+ helpersMode: task ? taskHelperUsageMode(task) : helpersMode(),
4285
+ taskPending: task?.trace.outcome === "pending",
4286
+ dispatchAuthority: Boolean(task && taskAuthorityDecision(task, "CAP-14", "dispatch").allowed),
4287
+ durableSubagentState: task?.orchestration?.subagents
4288
+ });
4289
+ if (helperPreflight.applicable && helperPreflight.dispatch && !helperPreflight.allowed) {
4290
+ telemetry(ctx, {
4291
+ event: "helper_dispatch_blocked",
4292
+ recordedAt: nowIso(),
4293
+ taskRunId: task?.taskRunId,
4294
+ toolCallId: event.toolCallId,
4295
+ reasonCode: helperPreflight.reasonCode,
4296
+ role: helperPreflight.role,
4297
+ requestCount: helperPreflight.requestCount
4298
+ });
4299
+ return { block: true, reason: `Blocked ${event.toolName}: ${helperPreflight.reason} Do not retry this helper; continue in the parent.` };
4300
+ }
4301
+ const verifierReuse = reuseCurrentTreeExactVerifier({ cwd: ctx.cwd, task, toolName: event.toolName, toolInput, sessionEntries: ctx.sessionManager.getEntries() as unknown[] });
4302
+ if (verifierReuse.reused) {
4303
+ telemetry(ctx, { event: "verifier_evidence_reused", recordedAt: nowIso(), taskRunId: task?.taskRunId, toolCallId: event.toolCallId, reasonCode: verifierReuse.reasonCode, commandDigest: verifierReuse.commandDigest, workingTreeDigest: verifierReuse.workingTreeDigest }); return;
4304
+ }
4357
4305
  const targetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, toolInput);
4358
4306
  if (targetInspection.reason) return { block: true, reason: `Blocked ${event.toolName}: ${targetInspection.reason}.` };
4307
+ if (task?.trace.outcome === "pending" && task.changeMode === "source-change" && (event.toolName === "edit" || targetInspection.exactLocalPatchExecutor)) {
4308
+ const freshness = editFreshnessGuard.evaluate(ctx, task.taskRunId, targetInspection.targets);
4309
+ if (freshness.decision === "stale") {
4310
+ telemetry(ctx, { event: "edit_freshness_stale", taskRunId: task.taskRunId, toolCallId: event.toolCallId, enforce: freshness.enforce, paths: freshness.stalePaths.map(redactText) });
4311
+ 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.` };
4312
+ }
4313
+ }
4359
4314
  if (!["edit", "write"].includes(event.toolName) && !targetInspection.exactLocalPatchExecutor) return;
4360
- const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4361
4315
  if (!task || task.trace.outcome !== "pending" || task.changeMode !== "source-change") return;
4362
4316
  const semanticRepairEnabled = taskAuthorityDecision(task, "CAP-13", "mutate").allowed;
4363
4317
  const semanticRepairExactlyOff = !taskAuthorityDecision(task, "CAP-13", "observe").allowed;
@@ -4374,11 +4328,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
4374
4328
  const currentDigest = workingTreeEvidenceDigest(currentSnapshot);
4375
4329
  const expectedReviewPaths = taskDeltaFilesFromSnapshot(task, currentSnapshot);
4376
4330
  const targets = targetInspection.targets;
4377
- const boundedInScopeMutation = targets.length > 0
4378
- && targets.every((file) => taskScopeIncludesPath(task.scope, file));
4331
+ const boundedTargetMutation = targets.length > 0;
4379
4332
  const verifierCurrent = allVerifyCommandsPassCurrentTree(task, currentDigest);
4380
4333
 
4381
- if (phase.phase === "verify" && boundedInScopeMutation && semanticRepairEnabled && semanticRepairRuntime.prepare({
4334
+ if (phase.phase === "verify" && boundedTargetMutation && semanticRepairEnabled && semanticRepairRuntime.prepare({
4382
4335
  ctx, task, event, currentDigest, currentDeltaPaths: expectedReviewPaths, targetPaths: targets,
4383
4336
  verifierCurrent
4384
4337
  })) return;
@@ -4388,7 +4341,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4388
4341
  // model catches its own omission in the same task/run/session. The call
4389
4342
  // still traverses every normal authorization check; verify -> repair is
4390
4343
  // committed only by the tool-result hook after a successful tree change.
4391
- if (phase.phase === "verify" && boundedRecoveryEnabled && verifierCurrent && boundedInScopeMutation) {
4344
+ if (phase.phase === "verify" && boundedRecoveryEnabled && verifierCurrent && boundedTargetMutation) {
4392
4345
  const authoredFileDigests = runtimeState.successfulModelMutationDigests(
4393
4346
  { taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId },
4394
4347
  currentSnapshot
@@ -4436,7 +4389,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
4436
4389
  return reviewDecision;
4437
4390
  }
4438
4391
 
4439
- if (targets.length === 0 || targets.some((file) => !taskScopeIncludesPath(task.scope, file))) return;
4392
+ if (targets.length === 0) return;
4440
4393
  const result = trajectoryRuntime.sync(ctx.cwd, ctx.sessionManager.getSessionId(), task, {
4441
4394
  sourceHook: "tool-call",
4442
4395
  recoveryRequested: true,
@@ -4460,11 +4413,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
4460
4413
  const targetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, toolInput);
4461
4414
  if (targetInspection.reason) return { block: true, reason: `Blocked ${event.toolName}: ${targetInspection.reason}.` };
4462
4415
  const targets = targetInspection.targets;
4463
- if ((["edit", "write"].includes(event.toolName) || targetInspection.patchCarrier) && (
4464
- targets.length === 0 || targets.some((file) => !taskScopeIncludesPath(task.scope, file))
4465
- )) {
4416
+ if ((["edit", "write"].includes(event.toolName) || targetInspection.patchCarrier) && targets.length === 0) {
4466
4417
  runtimeState.denyPerformanceReviewTool(task.taskRunId);
4467
- return { block: true, reason: `Task ${task.taskId} semantic repair must stay inside its declared task scope.` };
4418
+ return { block: true, reason: `Task ${task.taskId} semantic repair requires an exact statically bounded target.` };
4468
4419
  }
4469
4420
  const reviewDecision = performanceReviewToolDecision({
4470
4421
  toolName: event.toolName,
@@ -4492,6 +4443,30 @@ export default function piagentGuard(pi: ExtensionAPI) {
4492
4443
  },
4493
4444
  afterAuthorized: (event, ctx, metadata) => {
4494
4445
  const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
4446
+ const helperPreflight = evaluateDirectSubagentDispatch({
4447
+ toolName: event.toolName,
4448
+ toolInput: event.input,
4449
+ cwd: ctx.cwd,
4450
+ helpersMode: task ? taskHelperUsageMode(task) : helpersMode(),
4451
+ taskPending: task?.trace.outcome === "pending",
4452
+ dispatchAuthority: Boolean(task && taskAuthorityDecision(task, "CAP-14", "dispatch").allowed),
4453
+ durableSubagentState: task?.orchestration?.subagents
4454
+ });
4455
+ if (helperPreflight.applicable && helperPreflight.dispatch && helperPreflight.allowed) {
4456
+ const reserved = reserveDirectSubagentDispatch(task, metadata.toolCallId, {
4457
+ now: nowIso,
4458
+ persist: (candidate) => writeTask(ctx.cwd, candidate),
4459
+ trace: (payload) => appendTrace(ctx.cwd, payload)
4460
+ });
4461
+ telemetry(ctx, {
4462
+ event: "helper_dispatch_reserved",
4463
+ recordedAt: nowIso(),
4464
+ taskRunId: reserved?.taskRunId,
4465
+ toolCallId: metadata.toolCallId,
4466
+ role: helperPreflight.role,
4467
+ requestCount: helperPreflight.requestCount
4468
+ });
4469
+ }
4495
4470
  if (task?.trace.outcome === "pending") {
4496
4471
  const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
4497
4472
  const currentDigest = workingTreeEvidenceDigest(snapshot);
@@ -4639,6 +4614,12 @@ export default function piagentGuard(pi: ExtensionAPI) {
4639
4614
  const execDecision = evaluateExecPolicy(command, profile, policy);
4640
4615
  authorizedShellCommand = command;
4641
4616
  authorizedShellSegments = execDecision.segments;
4617
+ if (shellTouchesGrantedSourceCheckout(ctx.cwd, command, runtimeState.sourceCheckoutReadRoots(ctx))) {
4618
+ return {
4619
+ block: true,
4620
+ reason: "Shared source checkouts are shell-inaccessible. Inspect the exact session-granted checkout with read, grep, find, or ls; cache mutation is never authorized."
4621
+ };
4622
+ }
4642
4623
  shellProjectMutation = isProjectMutatingShellCommand(command, execDecision.segments);
4643
4624
  configuredVerifierShell = Boolean(sessionTask && commandMatchesVerifyPlan(command, sessionTask.verifyCommands));
4644
4625
  const shellWriteCandidates = extractShellWritePathCandidates(command);
@@ -4730,7 +4711,8 @@ export default function piagentGuard(pi: ExtensionAPI) {
4730
4711
  {
4731
4712
  forceScopeAware: authorizationTargetInspection.patchCarrier || Boolean(preparedInput.proxyToolName),
4732
4713
  forceWrite: authorizationTargetInspection.patchCarrier || preparedInput.proxyAction?.decision === "confirm",
4733
- allowAmbiguousFilesystemContentFields: !usesKnownExternalProvider && !isPiagentTool(event.toolName)
4714
+ allowAmbiguousFilesystemContentFields: !usesKnownExternalProvider && !isPiagentTool(event.toolName),
4715
+ sourceCheckoutReadRoots: runtimeState.sourceCheckoutReadRoots(ctx)
4734
4716
  }
4735
4717
  );
4736
4718
  if (pathDecision.block) {
@@ -4816,32 +4798,18 @@ export default function piagentGuard(pi: ExtensionAPI) {
4816
4798
  + " Call `piagent_task_start` once with explicit project-relative scope, then retry."
4817
4799
  };
4818
4800
  }
4819
- if (sessionTask?.trace.outcome === "pending" && sessionTask.changeMode === "source-change") {
4820
- if (
4821
- shellProjectMutation
4822
- && opaqueShellMutationNeedsBoundedTarget(authorizedShellCommand, authorizedShellSegments)
4823
- && !shellMutationTargetBounded
4824
- && !configuredVerifierShell
4825
- ) {
4826
- return {
4827
- block: true,
4828
- reason: `Task ${sessionTask.taskId} cannot run an opaque shell mutation whose write target is not statically bounded. Use edit/write/apply_patch, an explicit in-scope redirection, or the exact configured verifier.`
4829
- };
4830
- }
4831
- const outsideScope = mutationTargets.filter((file) => !taskScopeIncludesPath(sessionTask.scope, file));
4832
- if (outsideScope.length > 0) {
4833
- return {
4834
- block: true,
4835
- reason: `Task ${sessionTask.taskId} cannot mutate paths outside its declared scope: ${outsideScope.join(", ")}.`
4836
- };
4837
- }
4838
- const outsideEvidenceRoot = mutationTargets.filter((file) => !pathWithinChangeEvidenceRoot(ctx.cwd, file));
4839
- if (outsideEvidenceRoot.length > 0) {
4840
- return {
4841
- block: true,
4842
- reason: `Task ${sessionTask.taskId} cannot mutate paths outside a change evidence root: ${outsideEvidenceRoot.join(", ")}. In a parent workspace with separate repos, write under a child repo or a scoped workspace directory such as plans/**.`
4843
- };
4844
- }
4801
+ if (
4802
+ sessionTask?.trace.outcome === "pending"
4803
+ && sessionTask.changeMode === "source-change"
4804
+ && shellProjectMutation
4805
+ && opaqueShellMutationNeedsBoundedTarget(authorizedShellCommand, authorizedShellSegments)
4806
+ && !shellMutationTargetBounded
4807
+ && !configuredVerifierShell
4808
+ ) {
4809
+ return {
4810
+ block: true,
4811
+ 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.`
4812
+ };
4845
4813
  }
4846
4814
  if (runtime.contextBudget !== "off" && (["write", "edit"].includes(event.toolName) || authorizationTargetInspection.patchCarrier)) {
4847
4815
  const budgetTargets = authorizationTargetInspection.patchCarrier