@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
@@ -11,9 +11,9 @@ $ARGUMENTS
11
11
 
12
12
  Core flow:
13
13
 
14
- 1. Use the runtime-created contract for a bounded source task; do not call task-management tools during automatic intake. If runtime pauses for broad, high-risk, or ambiguous scope, call `piagent_task_start` exactly once with project-relative path/glob scope and reuse an active contract.
14
+ 1. Use the runtime-created contract for a bounded source task; do not call task-management tools during automatic intake. If runtime pauses for a broad, high-risk, or ambiguous task, call `piagent_task_start` exactly once with project-relative focus paths/globs and reuse an active contract.
15
15
  2. Read the likely target and nearest relevant test. The runtime injects bounded navigation context when useful.
16
- 3. Make the smallest in-scope change with ordinary tools and one writer.
16
+ 3. Make the smallest complete evidence-backed change with ordinary tools and one writer. Initial task scope guides retrieval/review but does not restrict necessary FE, BE, test, config, or child-repository edits.
17
17
  4. Run every exact command returned in `task.verifyCommands` after the latest mutation.
18
18
  5. Automatic tasks need no lifecycle calls. Manual high-risk and explicitly requested custom plans keep their returned checkpoints.
19
19
  6. Let runtime hooks record context, changed files, verification, trace, and final-gate state. Load recovery tools only if the runtime reports missing evidence.
@@ -21,13 +21,14 @@ Core flow:
21
21
  Risk-adaptive enrichment:
22
22
 
23
23
  - `tiny`: stay targeted; no diagnostic Piagent tools or subagents.
24
- - `normal`: stop searching once current-file evidence is sufficient. Use a read-only subagent only for a genuinely independent lane.
24
+ - `normal`: stop searching once current-file evidence is sufficient. Keep the parent direct. One fresh read-only helper is eligible only when runtime evidence proves at least two independent lanes and at least 30% projected total token savings after handoff/merge.
25
25
  - `high-risk`: inspect current source, relevant vendor documentation, and explicit security/data/release review lenses; keep required human confirmations.
26
26
 
27
27
  Always:
28
28
 
29
29
  - Treat memory, context-index, and generated tech snapshots as advisory; verify against current files.
30
30
  - If navigation confidence is low, run at most one bounded read-only finder pass; do not start an open-ended scout loop.
31
+ - Never delegate implementation, fork/inherit the parent transcript, run more than one helper, or retry a deterministic helper/runtime/scope failure.
31
32
  - Do not touch protected paths or use undeclared capabilities.
32
33
  - If verification cannot run, report `blocked` or `partial`, never done. If runtime schedules a continuation, keep working in the same task and do not repeat management tools.
33
34
  - Do not paste full test/build/tool logs. Summarize the signal and quote only relevant failures.
@@ -8,7 +8,7 @@ import {
8
8
  } from "../../extensions/acceptance-receipt.js";
9
9
  import { hasDurableContextEvidence } from "../../extensions/context-evidence.js";
10
10
  import type { TaskContract } from "../../extensions/guard-types.js";
11
- import { runtimeLifecycleMode, workingTreeEvidenceDigest } from "../../extensions/task-lifecycle.js";
11
+ import { applyAssistedReadOnlyFinalHandoff, runtimeLifecycleMode, workingTreeEvidenceDigest } from "../../extensions/task-lifecycle.js";
12
12
  import { recordCompletionAudit } from "../../extensions/task-runtime-audit.js";
13
13
  import { workingTreeSnapshot } from "../../extensions/task-state.js";
14
14
  import { taskDeltaFilesFromSnapshot } from "../../extensions/task-contract-view.js";
@@ -203,6 +203,7 @@ export function registerCompletionHook(pi: ExtensionAPI, dependencies: Completio
203
203
  );
204
204
  let completionGate: CompletionGate | undefined;
205
205
  if (handoffAttempt) {
206
+ if (applyAssistedReadOnlyFinalHandoff(task, { durableReadEvidence: readOnlyEvidenceObserved, finalHandoffObserved: true }).changed) task = writeTask(ctx.cwd, task);
206
207
  let projected = completionProjection(ctx.cwd, task, currentDigests);
207
208
  const baseProjectedGate = evaluateGate(ctx.cwd, projected, currentDigests, currentDigest);
208
209
  const exactOutput = evaluateExactFinalOutputContract(projected, text, ctx.cwd);
@@ -377,20 +378,20 @@ export function registerCompletionHook(pi: ExtensionAPI, dependencies: Completio
377
378
  ? [
378
379
  "Continue the same bounded task with one acceptance-proof repair pass.",
379
380
  ...criticalRecoveryGuidance,
380
- "Add or correct focused in-scope tests for every missing critical obligation. Assert exact boundary partitions and requested error classes; if a focused test exposes a defect, repair the in-scope source before rerunning verification.",
381
+ "Add or correct focused tests for every missing critical obligation. Assert exact boundary partitions and requested error classes; if a focused test exposes a defect, repair the evidence-backed source before rerunning verification.",
381
382
  ...verifierInstructions(gate.missingVerifyCommands),
382
- "Do not broaden task scope, repeat a failed hypothesis, expand permission, or perform an external action."
383
+ "Do not repeat a failed hypothesis, expand permission, or perform an external action."
383
384
  ]
384
385
  : [
385
- "Continue the same bounded task with one targeted in-scope source repair, then run every exact configured verifier.",
386
+ "Continue the same bounded task with one targeted evidence-backed source repair, then run every exact configured verifier.",
386
387
  ...verifierInstructions(gate.missingVerifyCommands),
387
- "Do not broaden task scope, repeat a failed hypothesis, expand permission, or perform an external action."
388
+ "Do not repeat a failed hypothesis, expand permission, or perform an external action."
388
389
  ]
389
390
  : selectedRecovery.reasonCodes.includes("unknown-diagnostic-pass") || task.changeMode === "read-only" || task.mutationPolicy === "forbidden"
390
391
  ? [
391
392
  ...(gate.missingVerifyCommands.length > 0
392
393
  ? ["Run only the missing exact verifier commands against the current working tree.", ...verifierInstructions(gate.missingVerifyCommands)]
393
- : ["Run one bounded diagnostic pass using targeted reads and report the concrete evidence or unknown."]),
394
+ : [(task.changeMode === "read-only" || task.mutationPolicy === "forbidden") && gate.missing.length > 0 && gate.missing.every((item) => /^completed work plan\b/i.test(item)) ? "Complete only the pending read-only review checkpoint, then re-emit the complete substantive assessment from the immediately preceding response; do not repeat repository reads or replace it with checkpoint evidence." : "Run one bounded diagnostic pass using targeted reads and report the concrete evidence or unknown."]),
394
395
  "Do not mutate project source, expand permission, or perform an external action."
395
396
  ]
396
397
  : [
@@ -9,6 +9,7 @@ import { LONG_INPUT_CHARS } from "../runtime-limits.ts";
9
9
  import type { AuthorityResumeDecision } from "../policy/authority-resume-policy.ts";
10
10
  import { buildHandoffProjection, writeHandoffProjection } from "../recovery/handoff-projection.ts";
11
11
  import { buildContextPreflight, buildUsageSnapshot } from "../session/usage.ts";
12
+ import { registerAdaptiveContextGovernor } from "../session/adaptive-context-governor.ts";
12
13
  import { activeTaskToolGroups, toolGroupsForPrompt } from "../tools/tool-groups.ts";
13
14
  import type { PiagentToolGroup } from "../tools/tool-groups.ts";
14
15
  import type { RuntimeSessionState } from "../session/runtime-state.ts";
@@ -38,6 +39,11 @@ type InputHookDependencies = {
38
39
  };
39
40
 
40
41
  export function registerInputHook(pi: ExtensionAPI, dependencies: InputHookDependencies): void {
42
+ registerAdaptiveContextGovernor(pi, {
43
+ activeTask: dependencies.activeTask,
44
+ telemetry: dependencies.telemetry
45
+ });
46
+
41
47
  pi.on("input", async (event, ctx) => {
42
48
  const text = event.text.trim();
43
49
  if (!text || isFreshOrUtilityInput(text)) return { action: "continue" };
@@ -21,6 +21,8 @@ import { buildContextPreflight, buildUsageSnapshot } from "../session/usage.ts";
21
21
  import { toolResultCaptureRoot } from "../session/tool-result-compaction.ts";
22
22
  import { ensureTaskAuthorityResumePolicy } from "../policy/authority-resume-policy.ts";
23
23
  import { buildHandoffProjection, writeHandoffProjection } from "../recovery/handoff-projection.ts";
24
+ import { refreshPristineRuntimeTaskVerification } from "../recovery/pristine-task-policy-refresh.ts";
25
+ import { refreshLegacyHiddenWorkspaceEvidenceCoverage } from "../recovery/task-evidence-coverage-refresh.ts";
24
26
  import { activeTaskToolGroups } from "../tools/tool-groups.ts";
25
27
  import type { PiagentToolGroup } from "../tools/tool-groups.ts";
26
28
  import { observeTrajectorySync } from "../trajectory/trajectory-observability.ts";
@@ -40,6 +42,7 @@ type SessionStartHookDependencies = {
40
42
  resolveTaskAny: (cwd: string, reference: string) => TaskContract | undefined;
41
43
  bindTask: (cwd: string, sessionId: string, sessionName: string | undefined, task: TaskContract) => unknown;
42
44
  writeTask: (cwd: string, task: TaskContract) => TaskContract;
45
+ appendTrace: (cwd: string, payload: Record<string, unknown>) => void;
43
46
  capabilityState: (ctx: ExtensionContext) => { ok: boolean; reason?: string; repinned?: string };
44
47
  permissionProfile: (ctx: ExtensionContext, profile: ProjectProfile) => { mode: string; warning?: string };
45
48
  legacyProjectWarning: (cwd: string) => string | undefined;
@@ -138,6 +141,103 @@ export function registerSessionStartHook(pi: ExtensionAPI, dependencies: Session
138
141
  resumedTask = dependencies.writeTask(ctx.cwd, resumedTask);
139
142
  dependencies.bindTask(ctx.cwd, sessionId, sessionName, resumedTask);
140
143
  }
144
+ const capabilityState = dependencies.capabilityState(ctx);
145
+ if (resumedTask?.trace.outcome === "pending"
146
+ && authorityPolicy?.disposition === "resume-pinned"
147
+ && capabilityState.ok) {
148
+ // Re-read from the durable session binding immediately before the
149
+ // synchronous pristine-tree check and write. This prevents an earlier
150
+ // session-start projection from overwriting newer same-process task
151
+ // evidence or work-plan state.
152
+ const latestTask = dependencies.activeTask(ctx.cwd, sessionId);
153
+ if (latestTask?.taskRunId === resumedTask.taskRunId) {
154
+ resumedTask = latestTask;
155
+ let refreshTask = latestTask;
156
+ // Coverage reconciliation must precede resume inspection. Otherwise a
157
+ // path newly visible to the evidence collector looks like a task delta
158
+ // and can incorrectly block a valid dirty-task resume.
159
+ const coverage = refreshLegacyHiddenWorkspaceEvidenceCoverage(ctx.cwd, refreshTask);
160
+ if (coverage.refreshed) {
161
+ try {
162
+ resumedTask = dependencies.writeTask(ctx.cwd, coverage.task);
163
+ refreshTask = resumedTask;
164
+ try {
165
+ dependencies.appendTrace(ctx.cwd, {
166
+ event: "task_evidence_coverage_expanded",
167
+ taskId: resumedTask.taskId,
168
+ taskRunId: resumedTask.taskRunId,
169
+ sessionId,
170
+ sessionName,
171
+ reason: coverage.reason,
172
+ addedPathCount: coverage.addedPaths.length,
173
+ addedPathExamples: coverage.addedPaths.slice(0, 20),
174
+ retainedMutationPathCount: coverage.retainedMutationPaths.length,
175
+ retainedMutationPathExamples: coverage.retainedMutationPaths.slice(0, 20),
176
+ ambiguousPathCount: coverage.ambiguousPaths.length,
177
+ ambiguousPathExamples: coverage.ambiguousPaths.slice(0, 20),
178
+ previousBaselineDigest: coverage.previousBaselineDigest,
179
+ nextBaselineDigest: coverage.nextBaselineDigest
180
+ });
181
+ } catch (error) {
182
+ try {
183
+ ctx.ui.notify(`Piagent expanded legacy task evidence coverage, but its auxiliary trace could not be written: ${error instanceof Error ? error.message : String(error)}`, "warning");
184
+ } catch {
185
+ // UI availability is not part of the durable coverage commit.
186
+ }
187
+ }
188
+ } catch (error) {
189
+ try {
190
+ ctx.ui.notify(`Piagent kept the existing task baseline because its evidence-coverage refresh could not be persisted: ${error instanceof Error ? error.message : String(error)}`, "warning");
191
+ } catch {
192
+ // Continue startup with the last durably readable task.
193
+ }
194
+ refreshTask = dependencies.activeTask(ctx.cwd, sessionId) ?? latestTask;
195
+ resumedTask = refreshTask;
196
+ }
197
+ }
198
+ const refreshResume = dependencies.inspectResume(ctx.cwd, refreshTask, sessionId);
199
+ const refreshAllowed = refreshResume.enforcementSafe
200
+ && refreshResume.decision !== "blocked"
201
+ && refreshResume.decision !== "paused";
202
+ if (refreshAllowed) {
203
+ const refresh = refreshPristineRuntimeTaskVerification(ctx.cwd, refreshTask, profile);
204
+ if (refresh.refreshed) {
205
+ try {
206
+ resumedTask = dependencies.writeTask(ctx.cwd, refresh.task);
207
+ try {
208
+ dependencies.appendTrace(ctx.cwd, {
209
+ event: "task_pristine_verifier_refreshed",
210
+ taskId: resumedTask.taskId,
211
+ taskRunId: resumedTask.taskRunId,
212
+ sessionId,
213
+ sessionName,
214
+ reason: refresh.reason,
215
+ verifyGroup: refresh.verifyGroup,
216
+ previousCommands: refresh.previousCommands,
217
+ verifyCommands: refresh.nextCommands
218
+ });
219
+ } catch (error) {
220
+ // The contract write and its task journal entry are already
221
+ // durable. An auxiliary trace/notice failure must not abort
222
+ // session startup or roll the task back to stale commands.
223
+ try {
224
+ ctx.ui.notify(`Piagent refreshed the task verifier, but its auxiliary trace could not be written: ${error instanceof Error ? error.message : String(error)}`, "warning");
225
+ } catch {
226
+ // UI availability is not part of the durable refresh commit.
227
+ }
228
+ }
229
+ } catch (error) {
230
+ try {
231
+ ctx.ui.notify(`Piagent kept the existing task verifier because its pristine refresh could not be persisted: ${error instanceof Error ? error.message : String(error)}`, "warning");
232
+ } catch {
233
+ // Continue startup with the last durably readable task.
234
+ }
235
+ resumedTask = dependencies.activeTask(ctx.cwd, sessionId) ?? refreshTask;
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
141
241
  if (resumedTask?.workingTreeDigestMigration && resumedTask.workingTreeDigestMigration.status !== "refreshed") dependencies.state.clearDigestMigrationState(ctx, resumedTask.taskRunId, resumedTask.taskId);
142
242
  if (resumedTask?.trace.outcome === "pending") dependencies.state.cacheTaskIdentity(ctx, resumedTask);
143
243
  else dependencies.state.clearSession(ctx);
@@ -180,7 +280,6 @@ export function registerSessionStartHook(pi: ExtensionAPI, dependencies: Session
180
280
  : " (run /onboard to select a profile)";
181
281
  const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
182
282
  const preflight = buildContextPreflight(snapshot, "task", 0);
183
- const capabilityState = dependencies.capabilityState(ctx);
184
283
  const permissionProfile = dependencies.permissionProfile(ctx, profile);
185
284
  const executionBackend = resolveExecutionBackend();
186
285
  const contextHint = preflight.recommendation === "fresh-session"
@@ -4,7 +4,7 @@ import { estimateContextTokens, toolResultFingerprint } from "../../extensions/c
4
4
  import { matchesProtectedPath } from "../../extensions/policy-core.js";
5
5
  import { changedSnapshotFiles, taskDeltaFilesFromSnapshot } from "../../extensions/task-contract-view.js";
6
6
  import { classifyVerificationFailure } from "../../extensions/verification-intelligence.js";
7
- import { redactForStorage, redactSensitiveText } from "../../extensions/redaction-core.js";
7
+ import { redactForStorage } from "../../extensions/redaction-core.js";
8
8
  import { appendObservedBashResult, hashEvidenceCommand, observedBashResultFromToolResultEvent } from "../../extensions/runtime-evidence.js";
9
9
  import { workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence } from "../../extensions/task-state.js";
10
10
  import { workingTreeObservation } from "../../extensions/working-tree-digest.js";
@@ -12,6 +12,7 @@ import { recordObservedContextEvidence } from "../context/context-evidence-quali
12
12
  import { confirmContextDeliveryFromToolResult, type ContextDeliveryConfirmationDependencies } from "../context/context-delivery.ts";
13
13
  import { recordMutationResult } from "../inspection/mutation-provenance-recorder.ts";
14
14
  import { classifyToolFailure } from "../inspection/tool-failure-classification.ts";
15
+ import { classifyDirectSubagentResult, type DirectSubagentResult } from "../orchestration/subagent-tool-policy.ts";
15
16
  import { boundedGitDiffReview } from "../quality/performance-assurance.ts";
16
17
  import { currentFileContentDigests } from "../quality/model-mutation-proof.ts";
17
18
  import { boundedPerformanceReviewResultText } from "../quality/performance-review-evidence.ts";
@@ -23,6 +24,7 @@ import { observeTrajectorySync } from "../trajectory/trajectory-observability.ts
23
24
  import type { TrajectorySyncResult } from "../trajectory/trajectory-runtime.ts";
24
25
  import { filterGrepProtectedContent, filterProtectedPathListContent } from "./tool-result-content-guards.ts";
25
26
  import { patchLineStats } from "./tool-result-metadata.ts";
27
+ import { appendToolResultText, boundedToolResultText, countChangedStringLeaves, isPlainRecord, numericExitCode, redactToolResultTextContent, successfulToolResult } from "./tool-result-value-helpers.ts";
26
28
 
27
29
  export { filterGrepProtectedContent, filterProtectedPathListContent };
28
30
  type ToolResultEvent = { toolCallId?: string; toolName: string; input?: unknown; content?: unknown; details?: unknown; isError?: boolean; usage?: unknown };
@@ -62,6 +64,11 @@ type ToolResultHookDependencies = ContextDeliveryConfirmationDependencies & {
62
64
  telemetry: (ctx: ExtensionContext, payload: Record<string, unknown>) => void;
63
65
  activity?: (ctx: ExtensionContext, payload: Record<string, unknown>) => void;
64
66
  now: () => string;
67
+ observeEditFreshness?: (
68
+ ctx: ExtensionContext,
69
+ event: ToolResultEvent,
70
+ metadata: { taskRunId?: string; successful: boolean; targetPath?: string; mutationTargets: string[] }
71
+ ) => void;
65
72
  completeSemanticRepair?: (
66
73
  ctx: ExtensionContext,
67
74
  event: ToolResultEvent,
@@ -75,73 +82,13 @@ type ToolResultHookDependencies = ContextDeliveryConfirmationDependencies & {
75
82
  }
76
83
  ) => TrajectorySyncResult | undefined;
77
84
  syncTrajectory?: (ctx: ExtensionContext, contextObserved: boolean) => TrajectorySyncResult | undefined;
85
+ recordSubagentResult?: (
86
+ ctx: ExtensionContext,
87
+ event: ToolResultEvent,
88
+ result: DirectSubagentResult
89
+ ) => void;
78
90
  };
79
91
 
80
- function isPlainRecord(value: unknown): value is Record<string, unknown> {
81
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
82
- const prototype = Object.getPrototypeOf(value);
83
- return prototype === Object.prototype || prototype === null;
84
- }
85
-
86
- function numericExitCode(value: unknown): number | undefined {
87
- if (typeof value === "number" && Number.isInteger(value)) return value;
88
- if (typeof value === "string" && /^-?\d+$/.test(value.trim())) return Number.parseInt(value, 10);
89
- return undefined;
90
- }
91
-
92
- function successfulToolResult(event: ToolResultEvent): boolean {
93
- if (event.isError === true) return false;
94
- const details = isPlainRecord(event.details) ? event.details : {};
95
- const exitCode = numericExitCode(details.exitCode ?? details.status);
96
- return exitCode === undefined || exitCode === 0;
97
- }
98
-
99
- function boundedToolResultText(content: unknown): string {
100
- const text = typeof content === "string"
101
- ? content
102
- : Array.isArray(content)
103
- ? content
104
- .filter((block) => block && typeof block === "object" && (block as { type?: unknown }).type === "text")
105
- .map((block) => String((block as { text?: unknown }).text ?? ""))
106
- .join("\n")
107
- : "";
108
- return text.slice(-20_000);
109
- }
110
-
111
- function countChangedStringLeaves(before: unknown, after: unknown): number {
112
- if (typeof before === "string" && typeof after === "string") return before === after ? 0 : 1;
113
- if (Array.isArray(before) && Array.isArray(after)) {
114
- return before.reduce((total, item, index) => total + countChangedStringLeaves(item, after[index]), 0);
115
- }
116
- if (!before || !after || typeof before !== "object" || typeof after !== "object") return 0;
117
- return Object.entries(before as Record<string, unknown>).reduce(
118
- (total, [key, value]) => total + countChangedStringLeaves(value, (after as Record<string, unknown>)[key]),
119
- 0
120
- );
121
- }
122
-
123
- function redactToolResultTextContent(content: unknown): { content: unknown; redacted: number } {
124
- if (!Array.isArray(content)) return { content, redacted: 0 };
125
- let redacted = 0;
126
- const safeContent = content.map((block) => {
127
- if (!block || typeof block !== "object") return block;
128
- const typed = block as { type?: unknown; text?: unknown };
129
- if (typed.type !== "text" || typeof typed.text !== "string") return block;
130
- const safeText = redactSensitiveText(typed.text);
131
- if (!safeText.redacted) return block;
132
- redacted += 1;
133
- return { ...block, text: safeText.text };
134
- });
135
- return { content: safeContent, redacted };
136
- }
137
-
138
- function appendToolResultText(content: unknown, text: string): unknown[] {
139
- const block = { type: "text", text };
140
- if (Array.isArray(content)) return [...content, block];
141
- if (typeof content === "string" && content) return [{ type: "text", text: content }, block];
142
- return [block];
143
- }
144
-
145
92
  export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResultHookDependencies): void {
146
93
  const editRecoveryDelivery = new EditRecoveryDeliveryState();
147
94
  pi.on("session_compact", async (_event, ctx) => editRecoveryDelivery.advanceEpoch(ctx));
@@ -172,6 +119,15 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
172
119
  ? dependencies.state.consumeShellMutationSnapshot(ctx, event.toolName, event.input)
173
120
  : undefined;
174
121
  const currentTask = dependencies.activeTask(ctx);
122
+ const subagentResult = classifyDirectSubagentResult({
123
+ toolName: event.toolName,
124
+ toolInput: event.input,
125
+ content: event.content,
126
+ details: event.details,
127
+ isError: event.isError
128
+ });
129
+ if (subagentResult) dependencies.recordSubagentResult?.(ctx, event, subagentResult);
130
+ const effectiveToolError = event.isError === true || subagentResult?.failed === true;
175
131
  const eventTree = currentTask
176
132
  ? workingTreeObservation(workingTreeSnapshot(ctx.cwd) as Record<string, string>)
177
133
  : undefined;
@@ -224,11 +180,12 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
224
180
  }
225
181
  }
226
182
  const outputText = boundedToolResultText(event.content);
227
- const failureReasonCode = classifyToolFailure(event.toolName, event.isError === true, event.content, event.input);
183
+ const toolFailureReasonCode = classifyToolFailure(event.toolName, effectiveToolError, event.content, event.input);
184
+ const failureReasonCode = subagentResult?.reasonCode ?? toolFailureReasonCode;
228
185
  const performanceReviewOutputText = boundedPerformanceReviewResultText(event.content);
229
186
  const observedExitCode = observed?.exitCode
230
187
  ?? numericExitCode(isPlainRecord(event.details) ? event.details.exitCode ?? event.details.status : undefined)
231
- ?? (event.isError === true ? 1 : 0);
188
+ ?? (effectiveToolError ? 1 : 0);
232
189
  const failure = observedExitCode === 0
233
190
  ? undefined
234
191
  : classifyVerificationFailure(outputText, observedExitCode);
@@ -313,7 +270,36 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
313
270
  let resultDetails: unknown = event.details;
314
271
  let resultChanged = false;
315
272
  let editRecovery: EditRecoveryContext | undefined;
273
+ if (subagentResult) {
274
+ const classification = {
275
+ schemaVersion: 1,
276
+ spawned: subagentResult.spawned,
277
+ failed: subagentResult.failed,
278
+ reasonCode: subagentResult.reasonCode,
279
+ disposition: subagentResult.disposition,
280
+ role: subagentResult.role,
281
+ requestRef: subagentResult.requestRef,
282
+ calls: subagentResult.calls,
283
+ tokens: subagentResult.tokens
284
+ };
285
+ resultDetails = isPlainRecord(resultDetails)
286
+ ? { ...resultDetails, piagentSubagentOutcome: classification }
287
+ : { ...(resultDetails === undefined ? {} : { value: resultDetails }), piagentSubagentOutcome: classification };
288
+ if (subagentResult.failed) {
289
+ resultContent = appendToolResultText(
290
+ resultContent,
291
+ `[Piagent helper outcome: failed (${subagentResult.reasonCode ?? "helper-run-failed"}). Treat this result as insufficient evidence; continue in the parent and do not retry the helper.]`
292
+ );
293
+ }
294
+ resultChanged = true;
295
+ }
316
296
  const resultTarget = dependencies.extractLikelyPath(ctx.cwd, isPlainRecord(event.input) ? event.input : {});
297
+ dependencies.observeEditFreshness?.(ctx, event, {
298
+ taskRunId: taskIdentity?.taskRunId,
299
+ successful: successfulToolResult(event),
300
+ targetPath: resultTarget,
301
+ mutationTargets: directMutationTargets
302
+ });
317
303
  if (event.toolName === "grep") {
318
304
  const filtered = filterGrepProtectedContent(resultContent, readProtectedPaths);
319
305
  if (filtered.changed) {
@@ -340,7 +326,7 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
340
326
  const recovery = buildEditRecoveryContext({
341
327
  cwd: ctx.cwd,
342
328
  targetPath: resultTarget,
343
- reasonCode: failureReasonCode,
329
+ reasonCode: toolFailureReasonCode,
344
330
  protectedPaths: readProtectedPaths
345
331
  });
346
332
  if (recovery) {
@@ -391,7 +377,7 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
391
377
  outputHash: fingerprint.outputHash,
392
378
  recordedAt: dependencies.now()
393
379
  });
394
- if (repeated && ["read", "grep", "find", "ls"].includes(event.toolName) && fingerprint.outputChars > 0) {
380
+ if (repeated && ["read", "grep", "find", "ls", "subagent"].includes(event.toolName) && fingerprint.outputChars > 0) {
395
381
  resultContent = [{
396
382
  type: "text",
397
383
  text: `[Piagent delta: unchanged ${event.toolName} result; ${fingerprint.outputChars} chars / ${fingerprint.outputLines} lines match the previous identical call.]`
@@ -441,7 +427,7 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
441
427
  .map((filePath) => dependencies.redactText(filePath))
442
428
  .sort();
443
429
  const lineStats = patchLineStats(event.details);
444
- const editRecoveryFailure = ["edit-anchor-not-unique", "edit-anchor-stale"].includes(String(failureReasonCode));
430
+ const editRecoveryFailure = ["edit-anchor-not-unique", "edit-anchor-stale"].includes(String(toolFailureReasonCode));
445
431
  record(ctx, {
446
432
  activityId: `result:${resultFingerprintId}`,
447
433
  event: "tool_result",
@@ -458,7 +444,7 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
458
444
  compacted: compactionCaptures.length > 0,
459
445
  compactedCaptures: compactionCaptures.length,
460
446
  sensitiveValuesRedacted,
461
- isError: event.isError,
447
+ isError: effectiveToolError,
462
448
  reasonCode: failureReasonCode,
463
449
  // An explicit false is evidence too: it proves that every classified
464
450
  // edit-anchor failure passed through the recovery policy even when the
@@ -0,0 +1,66 @@
1
+ import { redactSensitiveText } from "../../extensions/redaction-core.js";
2
+
3
+ export function isPlainRecord(value: unknown): value is Record<string, unknown> {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5
+ const prototype = Object.getPrototypeOf(value);
6
+ return prototype === Object.prototype || prototype === null;
7
+ }
8
+
9
+ export function numericExitCode(value: unknown): number | undefined {
10
+ if (typeof value === "number" && Number.isInteger(value)) return value;
11
+ if (typeof value === "string" && /^-?\d+$/.test(value.trim())) return Number.parseInt(value, 10);
12
+ return undefined;
13
+ }
14
+
15
+ export function successfulToolResult(event: { isError?: boolean; details?: unknown }): boolean {
16
+ if (event.isError === true) return false;
17
+ const details = isPlainRecord(event.details) ? event.details : {};
18
+ const exitCode = numericExitCode(details.exitCode ?? details.status);
19
+ return exitCode === undefined || exitCode === 0;
20
+ }
21
+
22
+ export function boundedToolResultText(content: unknown): string {
23
+ const text = typeof content === "string"
24
+ ? content
25
+ : Array.isArray(content)
26
+ ? content
27
+ .filter((block) => block && typeof block === "object" && (block as { type?: unknown }).type === "text")
28
+ .map((block) => String((block as { text?: unknown }).text ?? ""))
29
+ .join("\n")
30
+ : "";
31
+ return text.slice(-20_000);
32
+ }
33
+
34
+ export function countChangedStringLeaves(before: unknown, after: unknown): number {
35
+ if (typeof before === "string" && typeof after === "string") return before === after ? 0 : 1;
36
+ if (Array.isArray(before) && Array.isArray(after)) {
37
+ return before.reduce((total, item, index) => total + countChangedStringLeaves(item, after[index]), 0);
38
+ }
39
+ if (!before || !after || typeof before !== "object" || typeof after !== "object") return 0;
40
+ return Object.entries(before as Record<string, unknown>).reduce(
41
+ (total, [key, value]) => total + countChangedStringLeaves(value, (after as Record<string, unknown>)[key]),
42
+ 0
43
+ );
44
+ }
45
+
46
+ export function redactToolResultTextContent(content: unknown): { content: unknown; redacted: number } {
47
+ if (!Array.isArray(content)) return { content, redacted: 0 };
48
+ let redacted = 0;
49
+ const safeContent = content.map((block) => {
50
+ if (!block || typeof block !== "object") return block;
51
+ const typed = block as { type?: unknown; text?: unknown };
52
+ if (typed.type !== "text" || typeof typed.text !== "string") return block;
53
+ const safeText = redactSensitiveText(typed.text);
54
+ if (!safeText.redacted) return block;
55
+ redacted += 1;
56
+ return { ...block, text: safeText.text };
57
+ });
58
+ return { content: safeContent, redacted };
59
+ }
60
+
61
+ export function appendToolResultText(content: unknown, text: string): unknown[] {
62
+ const block = { type: "text", text };
63
+ if (Array.isArray(content)) return [...content, block];
64
+ if (typeof content === "string" && content) return [{ type: "text", text: content }, block];
65
+ return [block];
66
+ }