@piagent/platform 1.5.5 → 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.
- package/CHANGELOG.md +166 -0
- package/README.md +25 -21
- package/README.vi.md +34 -5
- package/adapters/web-frontend/profile.json +2 -4
- package/architecture/layers.json +18 -3
- package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
- package/benchmarks/deep-logic-v1/grade.mjs +272 -10
- package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
- package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
- package/benchmarks/deep-logic-v1/project/package.json +1 -1
- package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
- package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
- package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
- package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
- package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
- package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
- package/benchmarks/deep-logic-v1/suite.json +51 -3
- package/benchmarks/deep-logic-v1/variant.mjs +22 -1
- package/benchmarks/production-v1/spend-control.v1.json +92 -0
- package/benchmarks/production-v1/suite.json +46 -3
- package/catalog/capabilities.json +18 -18
- package/docs/authority-manifest.md +1 -1
- package/docs/auto-delegation-policy.md +38 -73
- package/docs/command-reference-vietnamese.md +27 -35
- package/docs/context-engine.md +12 -4
- package/docs/context-window-policy.md +37 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +27 -13
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +27 -45
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +10 -0
- package/docs/quality-benchmark.md +427 -62
- package/docs/release-install-policy.md +10 -9
- package/docs/runtime-harness-standard.md +3 -3
- package/docs/runtime-policy-design.md +4 -4
- package/docs/runtime-quality-baseline.md +13 -6
- package/docs/security-threat-model.md +2 -2
- package/docs/subagent-orchestration-capabilities.md +19 -51
- package/docs/subagents-and-multiagent.md +21 -47
- package/docs/task-implementation-contract.md +3 -3
- package/docs/task-lifecycle-vietnamese.md +3 -3
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +27 -13
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +89 -95
- package/docs-site/commands.html +1 -1
- package/docs-site/content/benchmark.html +88 -94
- package/docs-site/content/en/benchmark.html +134 -90
- package/docs-site/content/en/index.html +19 -19
- package/docs-site/content/en/runtime.html +4 -4
- package/docs-site/content/en/whats-new.html +12 -1
- package/docs-site/content/en/workflows.html +2 -2
- package/docs-site/content/index.html +19 -19
- package/docs-site/content/runtime.html +4 -4
- package/docs-site/content/whats-new.html +12 -1
- package/docs-site/content/workflows.html +2 -2
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +135 -91
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +20 -20
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +5 -5
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +13 -2
- package/docs-site/en/workflows.html +3 -3
- package/docs-site/index.html +20 -20
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +5 -5
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +13 -2
- package/docs-site/workflows.html +3 -3
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
- package/evals/fixtures/codex-relative-efficiency-v1.invalid.json +4 -0
- package/evals/fixtures/codex-relative-efficiency-v1.valid.json +94 -0
- package/evals/fixtures/helper-request.invalid.json +1 -1
- package/evals/fixtures/helper-request.valid.json +1 -1
- package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
- package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
- package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +7 -0
- package/evals/fixtures/task-authority-snapshot.valid.json +4 -4
- package/evals/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +3 -3
- package/evals/fs5-pilot-protocol.v1.json +3 -3
- package/evals/fs5-pilot-protocol.v2.json +8 -8
- package/evals/fs5-pilot-protocol.v3.json +8 -8
- package/evals/fs5-pilot-protocol.v4.json +7 -6
- package/evals/fs5-pilot-protocol.v5.json +9 -7
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/evals/runtime-conformance-v1/README.md +43 -0
- package/evals/runtime-conformance-v1/evaluate-paid-canary.mjs +198 -0
- package/evals/runtime-conformance-v1/lane.json +57 -0
- package/evals/runtime-conformance-v1/paid-canary.json +31 -0
- package/evals/runtime-conformance-v1/runner.mjs +456 -0
- package/package.json +3 -1
- package/packages/piagent-core/README.md +13 -4
- package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
- package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
- package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
- package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
- package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +736 -664
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +441 -4
- package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
- package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
- package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
- package/packages/piagent-core/benchmark/benchmark-preflight.js +5 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +252 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +130 -12
- package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
- package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
- package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +979 -0
- package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
- package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
- package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
- package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
- package/packages/piagent-core/benchmark/benchmark-suite.js +149 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +380 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +322 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
- package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
- package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
- package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
- package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
- package/packages/piagent-core/extensions/acceptance-receipt.js +123 -59
- package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
- package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
- package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
- package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
- package/packages/piagent-core/extensions/context-engine.js +149 -156
- package/packages/piagent-core/extensions/context-evidence.js +48 -0
- package/packages/piagent-core/extensions/context-import-links.js +296 -0
- package/packages/piagent-core/extensions/context-index-policy.js +4 -0
- package/packages/piagent-core/extensions/context-query-paths.js +57 -0
- package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +435 -7
- package/packages/piagent-core/extensions/criterion-graph.js +82 -9
- package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
- package/packages/piagent-core/extensions/guard-types.ts +6 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +420 -260
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +39 -2
- package/packages/piagent-core/extensions/task-state.js +109 -142
- package/packages/piagent-core/extensions/verification-intelligence.js +52 -13
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +193 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +44 -2
- package/packages/piagent-core/policy/authority-manifest.v1.json +3 -3
- package/packages/piagent-core/prompts/be-to-fe.md +2 -8
- package/packages/piagent-core/prompts/plan.md +1 -1
- package/packages/piagent-core/prompts/platform-improve.md +2 -6
- package/packages/piagent-core/prompts/review.md +2 -7
- package/packages/piagent-core/prompts/scout.md +1 -1
- package/packages/piagent-core/prompts/task.md +4 -3
- package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
- package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
- package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
- package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
- package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
- package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
- package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +12 -12
- package/packages/piagent-core/runtime/hooks/input-hook.ts +17 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +107 -3
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +134 -166
- package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
- package/packages/piagent-core/runtime/inspection/bounded-emission-guard.ts +286 -0
- package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +37 -7
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +70 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +73 -9
- package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
- package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
- package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
- package/packages/piagent-core/runtime/orchestration/helper-lifecycle.ts +16 -12
- package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +71 -0
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +28 -20
- package/packages/piagent-core/runtime/orchestration/role-policy.ts +27 -9
- package/packages/piagent-core/runtime/orchestration/subagent-tool-policy.ts +329 -0
- package/packages/piagent-core/runtime/policy/block-remedy.ts +0 -4
- package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +7 -2
- package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +22 -11
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +9 -5
- package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
- package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
- package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
- package/packages/piagent-core/runtime/recovery/handoff-projection.ts +4 -1
- package/packages/piagent-core/runtime/recovery/pristine-task-policy-refresh.ts +91 -0
- package/packages/piagent-core/runtime/recovery/recovery-policy.ts +7 -4
- package/packages/piagent-core/runtime/recovery/resume-state.ts +112 -14
- package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +60 -13
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
- package/packages/piagent-core/runtime/registration/context-commands.ts +82 -7
- package/packages/piagent-core/runtime/registration/knowledge-tools.ts +9 -5
- package/packages/piagent-core/runtime/registration/operator-request-intake.ts +49 -0
- package/packages/piagent-core/runtime/registration/policy-tools.ts +50 -15
- package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +61 -25
- package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +169 -0
- package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +108 -103
- package/packages/piagent-core/runtime/runtime-limits.ts +16 -2
- package/packages/piagent-core/runtime/session/adaptive-context-analysis.ts +461 -0
- package/packages/piagent-core/runtime/session/adaptive-context-governor.ts +464 -0
- package/packages/piagent-core/runtime/session/adaptive-context-ledger.ts +265 -0
- package/packages/piagent-core/runtime/session/message-signals.ts +7 -3
- package/packages/piagent-core/runtime/session/operator-request-carry.ts +44 -0
- package/packages/piagent-core/runtime/session/runtime-state.ts +161 -5
- package/packages/piagent-core/runtime/session/source-checkout-read-grants.ts +35 -0
- package/packages/piagent-core/runtime/session/system-prompt.ts +76 -14
- package/packages/piagent-core/runtime/session/task-contract-presentation.ts +39 -0
- package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
- package/packages/piagent-core/runtime/tools/phase-tools.ts +8 -8
- package/packages/piagent-core/runtime/tools/tool-groups.ts +12 -2
- package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
- package/packages/piagent-core/runtime/verification/exact-verifier-reuse.ts +101 -0
- package/packages/piagent-core/runtime/workflows/runtime-intake-compaction.ts +164 -0
- package/packages/piagent-core/runtime/workflows/task-intake.ts +207 -38
- package/packages/piagent-core/security/sensitive-data.js +13 -218
- package/packages/piagent-core/security/sensitive-project-file.js +42 -0
- package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
- package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
- package/packages/piagent-core/security/sensitive-text.js +234 -0
- package/packages/piagent-core/subagents/piagent-oracle.md +5 -3
- package/packages/piagent-core/subagents/piagent-planner.md +6 -4
- package/packages/piagent-core/subagents/piagent-reviewer.md +5 -3
- package/packages/piagent-core/subagents/piagent-scout.md +4 -2
- package/packages/piagent-core/subagents/piagent-worker.md +7 -5
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-B4XehTCU.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-CgOEyRPG.js +176 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/extension/piagent-webui.ts +3 -1
- package/packages/piagent-webui/extension/same-session-bridge.ts +1 -1
- package/packages/piagent-webui/extension/session-stream-adapter.ts +33 -10
- package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +122 -20
- package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
- package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
- package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
- package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +237 -0
- package/packages/piagent-webui/gateway/session-operation-retry-control.ts +38 -0
- package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +165 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +141 -125
- package/packages/piagent-webui/ownership/session-lease-store.ts +5 -2
- package/packages/piagent-webui/server/core-inspection-provider.ts +2 -1
- package/packages/piagent-webui/server/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +113 -9
- package/packages/piagent-webui/shared/text-visibility.ts +10 -0
- package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
- package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
- package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
- package/schemas/piagent-webui/subagent-tree-v1.schema.json +11 -1
- package/schemas/project-profile.schema.json +1 -1
- package/schemas/task-contract.schema.json +27 -1
- package/scripts/benchmark-runner-core.mjs +367 -363
- package/scripts/benchmark-runner-finalization.mjs +313 -0
- package/scripts/benchmark-runner-host-readiness.mjs +94 -0
- package/scripts/benchmark-runner-support.mjs +200 -0
- package/scripts/benchmark-session.mjs +95 -41
- package/scripts/configure-model-scope.sh +4 -3
- package/scripts/configure-subagents.sh +42 -34
- package/scripts/helper-evaluation.mjs +2 -2
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/setup.sh +2 -2
- package/scripts/team-doctor.sh +1 -1
- package/scripts/verify-local.sh +10 -1
- package/templates/global/settings.json +2 -1
- package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
2
|
|
|
5
|
-
import { toolResultFingerprint } from "../../extensions/context-engine.js";
|
|
6
|
-
import type { TaskContract } from "../../extensions/guard-types.ts";
|
|
3
|
+
import { estimateContextTokens, toolResultFingerprint } from "../../extensions/context-engine.js";
|
|
7
4
|
import { matchesProtectedPath } from "../../extensions/policy-core.js";
|
|
8
5
|
import { changedSnapshotFiles, taskDeltaFilesFromSnapshot } from "../../extensions/task-contract-view.js";
|
|
9
6
|
import { classifyVerificationFailure } from "../../extensions/verification-intelligence.js";
|
|
10
|
-
import { redactForStorage
|
|
7
|
+
import { redactForStorage } from "../../extensions/redaction-core.js";
|
|
11
8
|
import { appendObservedBashResult, hashEvidenceCommand, observedBashResultFromToolResultEvent } from "../../extensions/runtime-evidence.js";
|
|
12
9
|
import { workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence } from "../../extensions/task-state.js";
|
|
13
10
|
import { workingTreeObservation } from "../../extensions/working-tree-digest.js";
|
|
11
|
+
import { recordObservedContextEvidence } from "../context/context-evidence-qualification.ts";
|
|
12
|
+
import { confirmContextDeliveryFromToolResult, type ContextDeliveryConfirmationDependencies } from "../context/context-delivery.ts";
|
|
14
13
|
import { recordMutationResult } from "../inspection/mutation-provenance-recorder.ts";
|
|
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";
|
|
19
|
+
import { buildEditRecoveryContext, type EditRecoveryContext } from "../recovery/edit-recovery-context.ts";
|
|
20
|
+
import { EditRecoveryDeliveryState } from "../recovery/edit-recovery-delivery.ts";
|
|
18
21
|
import { attachToolResultCompactionDetails, compactToolResultDetails, compactToolResultTextContent, type ToolResultCaptureSummary } from "../session/tool-result-compaction.ts";
|
|
19
|
-
import {
|
|
22
|
+
import type { ObservedTaskContext } from "../session/runtime-state.ts";
|
|
20
23
|
import { observeTrajectorySync } from "../trajectory/trajectory-observability.ts";
|
|
21
24
|
import type { TrajectorySyncResult } from "../trajectory/trajectory-runtime.ts";
|
|
25
|
+
import { filterGrepProtectedContent, filterProtectedPathListContent } from "./tool-result-content-guards.ts";
|
|
22
26
|
import { patchLineStats } from "./tool-result-metadata.ts";
|
|
27
|
+
import { appendToolResultText, boundedToolResultText, countChangedStringLeaves, isPlainRecord, numericExitCode, redactToolResultTextContent, successfulToolResult } from "./tool-result-value-helpers.ts";
|
|
23
28
|
|
|
29
|
+
export { filterGrepProtectedContent, filterProtectedPathListContent };
|
|
24
30
|
type ToolResultEvent = { toolCallId?: string; toolName: string; input?: unknown; content?: unknown; details?: unknown; isError?: boolean; usage?: unknown };
|
|
25
31
|
type ObservedBashResult = NonNullable<ReturnType<typeof observedBashResultFromToolResultEvent>>;
|
|
26
32
|
type ObservedVerificationResult = ObservedBashResult & { outputText?: string };
|
|
27
33
|
type WorkingTreeObservation = ReturnType<typeof workingTreeObservation>;
|
|
28
34
|
|
|
29
|
-
type ToolResultHookDependencies = {
|
|
30
|
-
state: RuntimeSessionState;
|
|
31
|
-
activeTask: (ctx: ExtensionContext) => TaskContract | undefined;
|
|
32
|
-
maxManifestFiles: number;
|
|
35
|
+
type ToolResultHookDependencies = ContextDeliveryConfirmationDependencies & {
|
|
33
36
|
readProtectedPaths: (ctx: ExtensionContext) => string[];
|
|
34
37
|
recordObservedBash: (observed: ObservedBashResult) => void;
|
|
35
38
|
observedBashLedgerPath: (cwd: string) => string;
|
|
@@ -61,6 +64,11 @@ type ToolResultHookDependencies = {
|
|
|
61
64
|
telemetry: (ctx: ExtensionContext, payload: Record<string, unknown>) => void;
|
|
62
65
|
activity?: (ctx: ExtensionContext, payload: Record<string, unknown>) => void;
|
|
63
66
|
now: () => string;
|
|
67
|
+
observeEditFreshness?: (
|
|
68
|
+
ctx: ExtensionContext,
|
|
69
|
+
event: ToolResultEvent,
|
|
70
|
+
metadata: { taskRunId?: string; successful: boolean; targetPath?: string; mutationTargets: string[] }
|
|
71
|
+
) => void;
|
|
64
72
|
completeSemanticRepair?: (
|
|
65
73
|
ctx: ExtensionContext,
|
|
66
74
|
event: ToolResultEvent,
|
|
@@ -69,164 +77,24 @@ type ToolResultHookDependencies = {
|
|
|
69
77
|
success: boolean;
|
|
70
78
|
exitCode: number;
|
|
71
79
|
currentWorkingTreeDigest: string;
|
|
72
|
-
changedPaths: string[];
|
|
80
|
+
changedPaths: string[]; authoredChangedPaths: string[];
|
|
73
81
|
retryableFailure: boolean; correctiveFailure: boolean;
|
|
74
82
|
}
|
|
75
83
|
) => TrajectorySyncResult | undefined;
|
|
76
84
|
syncTrajectory?: (ctx: ExtensionContext, contextObserved: boolean) => TrajectorySyncResult | undefined;
|
|
85
|
+
recordSubagentResult?: (
|
|
86
|
+
ctx: ExtensionContext,
|
|
87
|
+
event: ToolResultEvent,
|
|
88
|
+
result: DirectSubagentResult
|
|
89
|
+
) => void;
|
|
77
90
|
};
|
|
78
91
|
|
|
79
|
-
function normalizeRelative(cwd: string, candidate: unknown): string | undefined {
|
|
80
|
-
if (typeof candidate !== "string" || candidate.trim().length === 0) return undefined;
|
|
81
|
-
let raw = candidate.trim();
|
|
82
|
-
try {
|
|
83
|
-
raw = decodeURIComponent(raw);
|
|
84
|
-
} catch {
|
|
85
|
-
return undefined;
|
|
86
|
-
}
|
|
87
|
-
if (/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(raw)) {
|
|
88
|
-
if (!raw.toLowerCase().startsWith("file://")) return undefined;
|
|
89
|
-
try {
|
|
90
|
-
raw = fileURLToPath(raw);
|
|
91
|
-
} catch {
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const absolute = path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
|
|
96
|
-
return path.relative(cwd, absolute).split(path.sep).join("/");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
100
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
101
|
-
const prototype = Object.getPrototypeOf(value);
|
|
102
|
-
return prototype === Object.prototype || prototype === null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function numericExitCode(value: unknown): number | undefined {
|
|
106
|
-
if (typeof value === "number" && Number.isInteger(value)) return value;
|
|
107
|
-
if (typeof value === "string" && /^-?\d+$/.test(value.trim())) return Number.parseInt(value, 10);
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function successfulToolResult(event: ToolResultEvent): boolean {
|
|
112
|
-
if (event.isError === true) return false;
|
|
113
|
-
const details = isPlainRecord(event.details) ? event.details : {};
|
|
114
|
-
const exitCode = numericExitCode(details.exitCode ?? details.status);
|
|
115
|
-
return exitCode === undefined || exitCode === 0;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function boundedToolResultText(content: unknown): string {
|
|
119
|
-
const text = typeof content === "string"
|
|
120
|
-
? content
|
|
121
|
-
: Array.isArray(content)
|
|
122
|
-
? content
|
|
123
|
-
.filter((block) => block && typeof block === "object" && (block as { type?: unknown }).type === "text")
|
|
124
|
-
.map((block) => String((block as { text?: unknown }).text ?? ""))
|
|
125
|
-
.join("\n")
|
|
126
|
-
: "";
|
|
127
|
-
return text.slice(-20_000);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function countChangedStringLeaves(before: unknown, after: unknown): number {
|
|
131
|
-
if (typeof before === "string" && typeof after === "string") return before === after ? 0 : 1;
|
|
132
|
-
if (Array.isArray(before) && Array.isArray(after)) {
|
|
133
|
-
return before.reduce((total, item, index) => total + countChangedStringLeaves(item, after[index]), 0);
|
|
134
|
-
}
|
|
135
|
-
if (!before || !after || typeof before !== "object" || typeof after !== "object") return 0;
|
|
136
|
-
return Object.entries(before as Record<string, unknown>).reduce(
|
|
137
|
-
(total, [key, value]) => total + countChangedStringLeaves(value, (after as Record<string, unknown>)[key]),
|
|
138
|
-
0
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function redactToolResultTextContent(content: unknown): { content: unknown; redacted: number } {
|
|
143
|
-
if (!Array.isArray(content)) return { content, redacted: 0 };
|
|
144
|
-
let redacted = 0;
|
|
145
|
-
const safeContent = content.map((block) => {
|
|
146
|
-
if (!block || typeof block !== "object") return block;
|
|
147
|
-
const typed = block as { type?: unknown; text?: unknown };
|
|
148
|
-
if (typed.type !== "text" || typeof typed.text !== "string") return block;
|
|
149
|
-
const safeText = redactSensitiveText(typed.text);
|
|
150
|
-
if (!safeText.redacted) return block;
|
|
151
|
-
redacted += 1;
|
|
152
|
-
return { ...block, text: safeText.text };
|
|
153
|
-
});
|
|
154
|
-
return { content: safeContent, redacted };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function filterTextBlocks(
|
|
158
|
-
content: unknown,
|
|
159
|
-
blocked: (line: string) => boolean,
|
|
160
|
-
emptyText: string,
|
|
161
|
-
noticeText: (count: number) => string
|
|
162
|
-
): { changed: boolean; content?: unknown; redactedLines: number } {
|
|
163
|
-
if (!Array.isArray(content)) return { changed: false, redactedLines: 0 };
|
|
164
|
-
let changed = false;
|
|
165
|
-
let redactedLines = 0;
|
|
166
|
-
const filtered = content.map((block) => {
|
|
167
|
-
if (!block || typeof block !== "object") return block;
|
|
168
|
-
const text = (block as { type?: unknown; text?: unknown }).text;
|
|
169
|
-
if ((block as { type?: unknown }).type !== "text" || typeof text !== "string") return block;
|
|
170
|
-
const kept: string[] = [];
|
|
171
|
-
let blockRedactedLines = 0;
|
|
172
|
-
for (const line of text.split(/\r?\n/)) {
|
|
173
|
-
if (blocked(line)) {
|
|
174
|
-
changed = true;
|
|
175
|
-
redactedLines += 1;
|
|
176
|
-
blockRedactedLines += 1;
|
|
177
|
-
} else {
|
|
178
|
-
kept.push(line);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (blockRedactedLines === 0) return block;
|
|
182
|
-
const notice = noticeText(blockRedactedLines);
|
|
183
|
-
const nextText = kept.join("\n").trim().length > 0
|
|
184
|
-
? `${kept.join("\n")}\n${notice}`
|
|
185
|
-
: `${emptyText}\n${notice}`;
|
|
186
|
-
return { ...block, text: nextText };
|
|
187
|
-
});
|
|
188
|
-
return { changed, content: filtered, redactedLines };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function filterGrepProtectedContent(content: unknown, protectedPatterns: string[]) {
|
|
192
|
-
return filterTextBlocks(
|
|
193
|
-
content,
|
|
194
|
-
(line) => {
|
|
195
|
-
const linePath = line.match(/^(.+?)(?::\d+:|-\d+-)/)?.[1];
|
|
196
|
-
return Boolean(linePath && matchesProtectedPath(linePath, protectedPatterns));
|
|
197
|
-
},
|
|
198
|
-
"No matches found in non-protected paths.",
|
|
199
|
-
(count) => `[Piagent Pi guard redacted ${count} protected grep line${count === 1 ? "" : "s"}.]`
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function filterProtectedPathListContent(
|
|
204
|
-
cwd: string,
|
|
205
|
-
content: unknown,
|
|
206
|
-
protectedPatterns: string[],
|
|
207
|
-
basePath: string,
|
|
208
|
-
toolName: string
|
|
209
|
-
) {
|
|
210
|
-
return filterTextBlocks(
|
|
211
|
-
content,
|
|
212
|
-
(line) => {
|
|
213
|
-
const trimmed = line.trim();
|
|
214
|
-
if (!trimmed || trimmed.startsWith("[") || trimmed.startsWith("No ") || trimmed === "(empty directory)") return false;
|
|
215
|
-
const entry = trimmed.replace(/[\\/]+$/, "");
|
|
216
|
-
const candidates = new Set<string>();
|
|
217
|
-
const direct = normalizeRelative(cwd, entry);
|
|
218
|
-
const underBase = normalizeRelative(cwd, path.posix.join(basePath || ".", entry));
|
|
219
|
-
if (direct !== undefined) candidates.add(direct);
|
|
220
|
-
if (underBase !== undefined) candidates.add(underBase);
|
|
221
|
-
return [...candidates].some((candidate) => matchesProtectedPath(candidate, protectedPatterns));
|
|
222
|
-
},
|
|
223
|
-
"No entries found in non-protected paths.",
|
|
224
|
-
(count) => `[Piagent Pi guard redacted ${count} protected ${toolName} line${count === 1 ? "" : "s"}.]`
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
92
|
export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResultHookDependencies): void {
|
|
93
|
+
const editRecoveryDelivery = new EditRecoveryDeliveryState();
|
|
94
|
+
pi.on("session_compact", async (_event, ctx) => editRecoveryDelivery.advanceEpoch(ctx));
|
|
95
|
+
pi.on("session_shutdown", async (_event, ctx) => editRecoveryDelivery.clearSession(ctx));
|
|
229
96
|
pi.on("tool_result", async (event, ctx) => {
|
|
97
|
+
confirmContextDeliveryFromToolResult(pi, ctx, event, dependencies);
|
|
230
98
|
const taskIdentity = dependencies.state.taskIdentity(ctx);
|
|
231
99
|
const resultToolCallId = event.toolCallId ?? toolResultFingerprint(event.toolName, event.input, []).key;
|
|
232
100
|
const readProtectedPaths = dependencies.readProtectedPaths(ctx);
|
|
@@ -245,12 +113,21 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
245
113
|
}
|
|
246
114
|
|
|
247
115
|
const observedContextEntry = dependencies.observedTaskContext(ctx.cwd, event, readProtectedPaths);
|
|
248
|
-
|
|
249
|
-
const pendingContext = dependencies.state.
|
|
116
|
+
recordObservedContextEvidence(ctx, observedContextEntry, taskIdentity, dependencies);
|
|
117
|
+
const pendingContext = dependencies.state.qualifiedTaskContext(ctx);
|
|
250
118
|
const shellSnapshotBefore = dependencies.isShellTool(event.toolName)
|
|
251
119
|
? dependencies.state.consumeShellMutationSnapshot(ctx, event.toolName, event.input)
|
|
252
120
|
: undefined;
|
|
253
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;
|
|
254
131
|
const eventTree = currentTask
|
|
255
132
|
? workingTreeObservation(workingTreeSnapshot(ctx.cwd) as Record<string, string>)
|
|
256
133
|
: undefined;
|
|
@@ -303,10 +180,12 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
303
180
|
}
|
|
304
181
|
}
|
|
305
182
|
const outputText = boundedToolResultText(event.content);
|
|
183
|
+
const toolFailureReasonCode = classifyToolFailure(event.toolName, effectiveToolError, event.content, event.input);
|
|
184
|
+
const failureReasonCode = subagentResult?.reasonCode ?? toolFailureReasonCode;
|
|
306
185
|
const performanceReviewOutputText = boundedPerformanceReviewResultText(event.content);
|
|
307
186
|
const observedExitCode = observed?.exitCode
|
|
308
187
|
?? numericExitCode(isPlainRecord(event.details) ? event.details.exitCode ?? event.details.status : undefined)
|
|
309
|
-
?? (
|
|
188
|
+
?? (effectiveToolError ? 1 : 0);
|
|
310
189
|
const failure = observedExitCode === 0
|
|
311
190
|
? undefined
|
|
312
191
|
: classifyVerificationFailure(outputText, observedExitCode);
|
|
@@ -326,6 +205,7 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
326
205
|
exitCode: observedExitCode,
|
|
327
206
|
currentWorkingTreeDigest: currentTreeDigest,
|
|
328
207
|
changedPaths: [...new Set([...directMutationResult.changedPaths, ...shellChangedPaths])].sort(),
|
|
208
|
+
authoredChangedPaths: Object.keys(directMutationResult.recordedDigests).sort(),
|
|
329
209
|
retryableFailure: failure?.retryable === true, correctiveFailure: failure?.sourceMutationPermission === "eligible-in-scope" && failure?.confidence === "high"
|
|
330
210
|
})
|
|
331
211
|
: undefined;
|
|
@@ -389,6 +269,37 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
389
269
|
let resultContent: unknown = event.content;
|
|
390
270
|
let resultDetails: unknown = event.details;
|
|
391
271
|
let resultChanged = false;
|
|
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
|
+
}
|
|
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
|
+
});
|
|
392
303
|
if (event.toolName === "grep") {
|
|
393
304
|
const filtered = filterGrepProtectedContent(resultContent, readProtectedPaths);
|
|
394
305
|
if (filtered.changed) {
|
|
@@ -412,6 +323,44 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
412
323
|
}
|
|
413
324
|
}
|
|
414
325
|
|
|
326
|
+
const recovery = buildEditRecoveryContext({
|
|
327
|
+
cwd: ctx.cwd,
|
|
328
|
+
targetPath: resultTarget,
|
|
329
|
+
reasonCode: toolFailureReasonCode,
|
|
330
|
+
protectedPaths: readProtectedPaths
|
|
331
|
+
});
|
|
332
|
+
if (recovery) {
|
|
333
|
+
if (editRecoveryDelivery.reserve(ctx, taskIdentity?.taskRunId ?? "", recovery.key)) {
|
|
334
|
+
editRecovery = recovery;
|
|
335
|
+
const injectedChars = recovery.text.length;
|
|
336
|
+
const injectedEstimatedTokens = estimateContextTokens(recovery.text);
|
|
337
|
+
const recoveryDetails = {
|
|
338
|
+
schemaVersion: 1,
|
|
339
|
+
targetPath: dependencies.redactText(recovery.targetPath),
|
|
340
|
+
contentHash: recovery.contentHash,
|
|
341
|
+
originalChars: recovery.originalChars,
|
|
342
|
+
injectedChars,
|
|
343
|
+
injectedEstimatedTokens,
|
|
344
|
+
sensitiveContentRedacted: recovery.redacted
|
|
345
|
+
};
|
|
346
|
+
resultDetails = isPlainRecord(resultDetails)
|
|
347
|
+
? { ...resultDetails, piagentEditRecovery: recoveryDetails }
|
|
348
|
+
: { ...(resultDetails === undefined ? {} : { value: resultDetails }), piagentEditRecovery: recoveryDetails };
|
|
349
|
+
resultChanged = true;
|
|
350
|
+
dependencies.telemetry(ctx, {
|
|
351
|
+
event: "edit_recovery_context",
|
|
352
|
+
toolCallId: resultToolCallId,
|
|
353
|
+
taskRunId: taskIdentity?.taskRunId,
|
|
354
|
+
targetPath: dependencies.redactText(recovery.targetPath),
|
|
355
|
+
contentHash: recovery.contentHash,
|
|
356
|
+
originalChars: recovery.originalChars,
|
|
357
|
+
injectedChars,
|
|
358
|
+
injectedEstimatedTokens,
|
|
359
|
+
sensitiveContentRedacted: recovery.redacted
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
415
364
|
const safeContent = redactToolResultTextContent(resultContent);
|
|
416
365
|
const safeDetails = redactForStorage(resultDetails);
|
|
417
366
|
const sensitiveValuesRedacted = safeContent.redacted + countChangedStringLeaves(resultDetails, safeDetails);
|
|
@@ -428,7 +377,7 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
428
377
|
outputHash: fingerprint.outputHash,
|
|
429
378
|
recordedAt: dependencies.now()
|
|
430
379
|
});
|
|
431
|
-
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) {
|
|
432
381
|
resultContent = [{
|
|
433
382
|
type: "text",
|
|
434
383
|
text: `[Piagent delta: unchanged ${event.toolName} result; ${fingerprint.outputChars} chars / ${fingerprint.outputLines} lines match the previous identical call.]`
|
|
@@ -463,11 +412,22 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
463
412
|
if (compactionCaptures.length > 0) {
|
|
464
413
|
resultDetails = attachToolResultCompactionDetails(resultDetails, compactionCaptures);
|
|
465
414
|
}
|
|
415
|
+
// Compact the original error independently, then append the already bounded
|
|
416
|
+
// and sanitized recovery snapshot. Otherwise a small but line-dense file can
|
|
417
|
+
// be compacted into a preview and cease to be a complete one-shot recovery.
|
|
418
|
+
if (editRecovery) {
|
|
419
|
+
resultContent = appendToolResultText(resultContent, editRecovery.text);
|
|
420
|
+
resultChanged = true;
|
|
421
|
+
}
|
|
466
422
|
|
|
467
423
|
const resultFingerprintId = event.toolCallId ?? fingerprint.key;
|
|
468
424
|
const record = dependencies.activity ?? dependencies.telemetry;
|
|
469
|
-
const
|
|
425
|
+
const changedPaths = [...new Set([...directMutationResult.changedPaths, ...shellChangedPaths])]
|
|
426
|
+
.filter((filePath) => !matchesProtectedPath(filePath, readProtectedPaths))
|
|
427
|
+
.map((filePath) => dependencies.redactText(filePath))
|
|
428
|
+
.sort();
|
|
470
429
|
const lineStats = patchLineStats(event.details);
|
|
430
|
+
const editRecoveryFailure = ["edit-anchor-not-unique", "edit-anchor-stale"].includes(String(toolFailureReasonCode));
|
|
471
431
|
record(ctx, {
|
|
472
432
|
activityId: `result:${resultFingerprintId}`,
|
|
473
433
|
event: "tool_result",
|
|
@@ -475,6 +435,7 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
475
435
|
toolCallId: resultFingerprintId,
|
|
476
436
|
toolName: event.toolName,
|
|
477
437
|
targetPath: resultTarget ? dependencies.redactText(resultTarget) : undefined,
|
|
438
|
+
changedPaths: changedPaths.length > 0 ? changedPaths : undefined,
|
|
478
439
|
inputHash: fingerprint.inputHash,
|
|
479
440
|
outputHash: fingerprint.outputHash,
|
|
480
441
|
outputChars: fingerprint.outputChars,
|
|
@@ -483,7 +444,14 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
483
444
|
compacted: compactionCaptures.length > 0,
|
|
484
445
|
compactedCaptures: compactionCaptures.length,
|
|
485
446
|
sensitiveValuesRedacted,
|
|
486
|
-
isError:
|
|
447
|
+
isError: effectiveToolError,
|
|
448
|
+
reasonCode: failureReasonCode,
|
|
449
|
+
// An explicit false is evidence too: it proves that every classified
|
|
450
|
+
// edit-anchor failure passed through the recovery policy even when the
|
|
451
|
+
// target was too large, private, protected, or otherwise ineligible.
|
|
452
|
+
editRecoveryContext: editRecoveryFailure ? Boolean(editRecovery) : undefined,
|
|
453
|
+
editRecoveryInjectedChars: editRecovery?.text.length,
|
|
454
|
+
editRecoveryEstimatedTokens: editRecovery ? estimateContextTokens(editRecovery.text) : undefined,
|
|
487
455
|
exitCode: observed?.exitCode ?? (dependencies.isShellTool(event.toolName) ? event.isError ? 1 : 0 : undefined),
|
|
488
456
|
exitCodeExact: observed?.exitCode !== undefined,
|
|
489
457
|
...lineStats,
|
|
@@ -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
|
+
}
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
import { redactSensitiveText } from "../../extensions/redaction-core.js";
|
|
4
4
|
import type { ActivityInspectorEvent } from "../product/activity-inspector.ts";
|
|
5
|
+
import { handledToolFailure } from "./tool-failure-classification.ts";
|
|
5
6
|
import type { RuntimeEventDraft, RuntimeEventRevision } from "./runtime-event-store.ts";
|
|
6
7
|
|
|
7
8
|
export type RuntimeEventIdentity = {
|
|
@@ -52,7 +53,8 @@ export function adaptActivityTelemetryEvent(input: ActivityEventAdapterInput): R
|
|
|
52
53
|
const operationRef = hasOperation ? opaque("operation", input.identity.agentOperationId) : null;
|
|
53
54
|
const toolCallRef = hasOperation ? opaque("tool", rawToolCall) : null;
|
|
54
55
|
const activityType = hasOperation ? "tool" : ["bash", "shell", "exec"].includes(toolName) ? "command" : "other";
|
|
55
|
-
const
|
|
56
|
+
const resultFailure = eventName === "tool_result" && (event.isError === true || typeof event.exitCode === "number" && event.exitCode !== 0);
|
|
57
|
+
const failed = resultFailure && !handledToolFailure(event.reasonCode);
|
|
56
58
|
const blocked = eventName === "tool_decision";
|
|
57
59
|
// `tool_call` is emitted before policy authorization. Calling it "started"
|
|
58
60
|
// made the Dashboard claim a command was running while it was still waiting
|
|
@@ -64,7 +66,7 @@ export function adaptActivityTelemetryEvent(input: ActivityEventAdapterInput): R
|
|
|
64
66
|
const preview = safeText(previewValue, 4000);
|
|
65
67
|
const storedRedactions = typeof event.sensitiveValuesRedacted === "number" && Number.isInteger(event.sensitiveValuesRedacted)
|
|
66
68
|
? Math.max(0, Math.min(10_000, event.sensitiveValuesRedacted)) : 0;
|
|
67
|
-
const reasonCode = blocked ? "policy-blocked" :
|
|
69
|
+
const reasonCode = blocked ? "policy-blocked" : resultFailure ? safeText(event.reasonCode ?? "tool-result-failed", 80).text || "tool-result-failed" : null;
|
|
68
70
|
return {
|
|
69
71
|
...base(input.identity, input.revision, observedAt), agentOperationId: operationRef, toolCallId: toolCallRef,
|
|
70
72
|
kind, evidence: "derived",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { ActivityInspectorEvent } from "../product/activity-inspector.ts";
|
|
2
|
+
|
|
3
|
+
const RECOVERY_WINDOW_MS = 15 * 60 * 1_000;
|
|
4
|
+
const SHELL_TOOLS = new Set(["bash", "shell", "exec", "command"]);
|
|
5
|
+
const MUTATION_TOOLS = /(?:^|[._-])(?:apply[_-]?patch|edit|write|create[_-]?file|replace)(?:$|[._-])/i;
|
|
6
|
+
|
|
7
|
+
export type ActivityRecovery = {
|
|
8
|
+
recoveredAt: string;
|
|
9
|
+
recoveryToolCallId: string;
|
|
10
|
+
recoveryToolName: string;
|
|
11
|
+
exitCode: number | null;
|
|
12
|
+
exitCodeExact: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function resultFailed(event: ActivityInspectorEvent | undefined): boolean {
|
|
16
|
+
return Boolean(event && (event.isError === true || typeof event.exitCode === "number" && event.exitCode !== 0));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function time(value: unknown): number {
|
|
20
|
+
const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
21
|
+
return Number.isFinite(parsed) ? parsed : Number.NaN;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalized(value: unknown): string {
|
|
25
|
+
return typeof value === "string" ? value.replaceAll("\\", "/").replace(/\s+/g, " ").trim() : "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function family(toolName: unknown): "read" | "mutation" | "shell" | "other" {
|
|
29
|
+
const value = String(toolName ?? "").toLowerCase();
|
|
30
|
+
if (SHELL_TOOLS.has(value)) return "shell";
|
|
31
|
+
if (MUTATION_TOOLS.test(value)) return "mutation";
|
|
32
|
+
if (/(?:^|[._-])(?:read|document[_-]?read)(?:$|[._-])/.test(value)) return "read";
|
|
33
|
+
return "other";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function likelyCorrectedReadTarget(failed: ActivityInspectorEvent, candidate: ActivityInspectorEvent): boolean {
|
|
37
|
+
if (family(failed.toolName) !== "read" || family(candidate.toolName) !== "read") return false;
|
|
38
|
+
const failedPath = normalized(failed.targetPath).toLowerCase();
|
|
39
|
+
const candidatePath = normalized(candidate.targetPath).toLowerCase();
|
|
40
|
+
const failedSlash = failedPath.lastIndexOf("/"), candidateSlash = candidatePath.lastIndexOf("/");
|
|
41
|
+
if (!failedPath || !candidatePath || failedPath.slice(0, failedSlash) !== candidatePath.slice(0, candidateSlash)) return false;
|
|
42
|
+
const failedName = failedPath.slice(failedSlash + 1), candidateName = candidatePath.slice(candidateSlash + 1);
|
|
43
|
+
const failedExtension = failedName.includes(".") ? failedName.slice(failedName.lastIndexOf(".")) : "";
|
|
44
|
+
const candidateExtension = candidateName.includes(".") ? candidateName.slice(candidateName.lastIndexOf(".")) : "";
|
|
45
|
+
if (failedExtension !== candidateExtension) return false;
|
|
46
|
+
const tokens = (value: string) => new Set(value.slice(0, value.length - failedExtension.length).split(/[^a-z0-9]+/).filter(Boolean));
|
|
47
|
+
const failedTokens = tokens(failedName), candidateTokens = tokens(candidateName);
|
|
48
|
+
const overlap = [...failedTokens].filter((token) => candidateTokens.has(token)).length;
|
|
49
|
+
return overlap >= 2 && overlap === Math.min(failedTokens.size, candidateTokens.size);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sameLogicalTarget(failed: ActivityInspectorEvent, candidate: ActivityInspectorEvent): boolean {
|
|
53
|
+
const failedPath = normalized(failed.targetPath), candidatePath = normalized(candidate.targetPath);
|
|
54
|
+
if (failedPath && candidatePath && failedPath === candidatePath) {
|
|
55
|
+
const failedFamily = family(failed.toolName), candidateFamily = family(candidate.toolName);
|
|
56
|
+
return failedFamily === candidateFamily || failedFamily === "read" && candidateFamily === "mutation";
|
|
57
|
+
}
|
|
58
|
+
const failedCommand = normalized(failed.command), candidateCommand = normalized(candidate.command);
|
|
59
|
+
return Boolean(failedCommand && candidateCommand && failedCommand === candidateCommand
|
|
60
|
+
&& family(failed.toolName) === "shell" && family(candidate.toolName) === "shell");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function recoveredToolCalls(events: readonly ActivityInspectorEvent[]): ReadonlyMap<string, ActivityRecovery> {
|
|
64
|
+
const results = new Map<string, { event: ActivityInspectorEvent; order: number }>();
|
|
65
|
+
for (let order = 0; order < events.length; order += 1) {
|
|
66
|
+
const event = events[order];
|
|
67
|
+
if (event?.event === "tool_result" && event.toolCallId) results.set(event.toolCallId, { event, order });
|
|
68
|
+
}
|
|
69
|
+
const segments = new Map<number, number>();
|
|
70
|
+
let segment = 0;
|
|
71
|
+
for (let order = 0; order < events.length; order += 1) {
|
|
72
|
+
if (events[order]?.event === "user_input") segment += 1;
|
|
73
|
+
segments.set(order, segment);
|
|
74
|
+
}
|
|
75
|
+
const calls = events.map((event, order) => ({ event, order }))
|
|
76
|
+
.filter((entry) => entry.event?.event === "tool_call" && Boolean(entry.event.toolCallId));
|
|
77
|
+
const recovered = new Map<string, ActivityRecovery>();
|
|
78
|
+
for (let failedCallIndex = 0; failedCallIndex < calls.length; failedCallIndex += 1) {
|
|
79
|
+
const failedCall = calls[failedCallIndex];
|
|
80
|
+
const failedResult = results.get(failedCall.event.toolCallId!);
|
|
81
|
+
if (!failedResult || !resultFailed(failedResult.event)) continue;
|
|
82
|
+
const failedAt = time(failedResult.event.recordedAt ?? failedCall.event.recordedAt);
|
|
83
|
+
const match = calls.find((candidate, candidateIndex) => {
|
|
84
|
+
if (candidate.order <= failedResult.order || segments.get(candidate.order) !== segments.get(failedCall.order)) return false;
|
|
85
|
+
const candidateResult = results.get(candidate.event.toolCallId!);
|
|
86
|
+
if (!candidateResult || resultFailed(candidateResult.event)) return false;
|
|
87
|
+
const recoveredAt = time(candidateResult.event.recordedAt ?? candidate.event.recordedAt);
|
|
88
|
+
return Number.isFinite(failedAt) && Number.isFinite(recoveredAt) && recoveredAt >= failedAt
|
|
89
|
+
&& recoveredAt - failedAt <= RECOVERY_WINDOW_MS
|
|
90
|
+
&& (sameLogicalTarget(failedCall.event, candidate.event)
|
|
91
|
+
|| candidateIndex === failedCallIndex + 1 && likelyCorrectedReadTarget(failedCall.event, candidate.event));
|
|
92
|
+
});
|
|
93
|
+
if (!match) continue;
|
|
94
|
+
const recoveryResult = results.get(match.event.toolCallId!)!.event;
|
|
95
|
+
recovered.set(failedCall.event.toolCallId!, {
|
|
96
|
+
recoveredAt: String(recoveryResult.recordedAt ?? match.event.recordedAt),
|
|
97
|
+
recoveryToolCallId: match.event.toolCallId!,
|
|
98
|
+
recoveryToolName: String(match.event.toolName ?? "unknown"),
|
|
99
|
+
exitCode: typeof recoveryResult.exitCode === "number" ? recoveryResult.exitCode : null,
|
|
100
|
+
exitCodeExact: recoveryResult.exitCodeExact === true
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return recovered;
|
|
104
|
+
}
|