@piagent/platform 1.5.4 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/README.md +9 -8
- package/README.vi.md +9 -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 +68 -0
- package/benchmarks/production-v1/suite.json +42 -3
- package/catalog/capabilities.json +6 -6
- package/docs/command-reference-vietnamese.md +6 -6
- package/docs/context-engine.md +7 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +1 -0
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +13 -11
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +9 -0
- package/docs/quality-benchmark.md +271 -62
- package/docs/release-install-policy.md +10 -9
- 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/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +1 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.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/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v4.json +6 -5
- package/evals/fs5-pilot-protocol.v5.json +7 -6
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/package.json +3 -1
- 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.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -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 +3 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +116 -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-stage-diagnostic.js +782 -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 +128 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -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 +34 -11
- 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 +148 -145
- 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-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
- package/packages/piagent-core/extensions/criterion-graph.js +78 -8
- package/packages/piagent-core/extensions/guard-types.ts +3 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +250 -43
- package/packages/piagent-core/extensions/policy-core.js +1 -1
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
- package/packages/piagent-core/extensions/task-state.js +40 -134
- package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +3 -0
- 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 +5 -6
- package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +9 -3
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +29 -5
- 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/tool-failure-classification.ts +53 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
- 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/role-policy.ts +1 -1
- package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
- 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/semantic-repair-handshake.ts +55 -2
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
- package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
- package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
- package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
- package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
- package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
- 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-worker.md +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- 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 +93 -7
- 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-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -120
- package/packages/piagent-webui/server/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +22 -4
- package/packages/piagent-webui/shared/text-visibility.ts +10 -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/session-live-state-v1.schema.json +76 -0
- package/schemas/task-contract.schema.json +12 -1
- package/scripts/benchmark-runner-core.mjs +355 -359
- package/scripts/benchmark-runner-finalization.mjs +309 -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/helper-evaluation.mjs +1 -1
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/piagent-dashboard.mjs +13 -1
- package/scripts/setup.sh +2 -2
- package/scripts/verify-local.sh +5 -1
- package/templates/global/settings.json +2 -1
- package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
|
@@ -2,6 +2,7 @@ import type { TaskContract } from "../../extensions/guard-types.ts";
|
|
|
2
2
|
import { acceptanceBoundaryProofGuidance } from "../../extensions/acceptance-boundary-guidance.js";
|
|
3
3
|
import { normalizePathCandidate } from "../../extensions/policy-core.js";
|
|
4
4
|
import { acceptanceLanguageAdapterStatus } from "../../extensions/acceptance-language-adapters.js";
|
|
5
|
+
import { hasGraphOrderContract } from "./performance-assurance-signals.ts";
|
|
5
6
|
import {
|
|
6
7
|
MAX_PERFORMANCE_MUTATIONS_PER_REVISION,
|
|
7
8
|
MAX_PERFORMANCE_REPAIR_PATHS,
|
|
@@ -55,8 +56,7 @@ const SIGNALS: Array<{ code: string; pattern: RegExp }> = [
|
|
|
55
56
|
{ code: "boundary-contract", pattern: /\b(?:backpressure|boundary|budget|capacity|ceil(?:ing)?|clamp|inclusive|minimum|maximum|maxchars|overflow|round(?:ing)?|safe[-\s]+integer|basis points?|positive integer|non-negative|falsey|falsy|nullish)\b/i },
|
|
56
57
|
{ code: "return-shape-contract", pattern: /\b(?:return shape|returned? (?:element|object|value|representation)s?|preserve (?:the )?return)\b/i },
|
|
57
58
|
{ code: "public-api-contract", pattern: /\b(?:exported api|public api|do not change (?:the )?(?:api|signature)|preserve (?:the )?(?:api|input|output|return)|without changing unrelated behavior)\b/i },
|
|
58
|
-
{ code: "
|
|
59
|
-
{ code: "identity-isolation-contract", pattern: /\b(?:tenant|authorization|permission|cache[- ]?key|collision|isolation|same tuple|cross[- ]tenant)\b/i },
|
|
59
|
+
{ code: "identity-isolation-contract", pattern: /\b(?:auth(?:orization)?|unauthoriz(?:ed|ation)|permission|cache[- ]?key|collision|same tuple|cross[- ]tenant|same[- ]tenant|tenant boundary|tenant[- ]scoped (?:cache|storage))\b/i },
|
|
60
60
|
{ code: "concurrency-contract", pattern: /\b(?:concurren|race condition|stale response|bounded retry|idempot(?:ent|ency)?|lock)\b/i },
|
|
61
61
|
{ code: "data-migration-contract", pattern: /\b(?:migration|schema|money|invoice|ledger|transaction|data loss)\b/i },
|
|
62
62
|
{ code: "non-mutation-contract", pattern: /\b(?:do not mutate|must not mutate|without mutating|input unchanged|preserve input)\b/i }
|
|
@@ -97,6 +97,10 @@ export function analyzePerformanceAssurance(input: AssuranceInput): PerformanceA
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
const reasonCodes = SIGNALS.filter((signal) => signal.pattern.test(request)).map((signal) => signal.code);
|
|
100
|
+
if (hasGraphOrderContract(request)) reasonCodes.push("graph-order-contract");
|
|
101
|
+
if (/\b(?:tenants?|tenantid)\b/i.test(request) && /\bisolat(?:e|ed|es|ing|ion)\b/i.test(request)) {
|
|
102
|
+
reasonCodes.push("identity-isolation-contract");
|
|
103
|
+
}
|
|
100
104
|
if (request.length >= 320) reasonCodes.push("long-contract");
|
|
101
105
|
if (namedEntrypoints(request) > 1) reasonCodes.push("multiple-entrypoints");
|
|
102
106
|
const exactnessSignals = reasonCodes.filter((code) => [
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { shouldIndexPath } from "../../extensions/context-engine.js";
|
|
6
|
+
import { matchesProtectedPath } from "../../extensions/policy-core.js";
|
|
7
|
+
import { redactSensitiveProjectFileText, redactSensitiveText } from "../../security/sensitive-data.js";
|
|
8
|
+
import type { ToolFailureReasonCode } from "../inspection/tool-failure-classification.ts";
|
|
9
|
+
|
|
10
|
+
const MAX_RECOVERY_FILE_BYTES = 8_192;
|
|
11
|
+
const PRIVATE_PATH = /(?:^|\/)(?:\.git|\.pi|node_modules)(?:\/|$)|(?:^|\/)\.env(?:\.|$)|(?:^|\/)(?:auth|credentials?|secrets?|tokens?)\.json$/i;
|
|
12
|
+
|
|
13
|
+
export type EditRecoveryContext = {
|
|
14
|
+
key: string;
|
|
15
|
+
targetPath: string;
|
|
16
|
+
text: string;
|
|
17
|
+
contentHash: string;
|
|
18
|
+
originalChars: number;
|
|
19
|
+
redacted: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function projectRelativeTarget(cwd: string, supplied: string): { root: string; absolute: string; relative: string } | undefined {
|
|
23
|
+
try {
|
|
24
|
+
const root = fs.realpathSync.native(cwd);
|
|
25
|
+
const suppliedAbsolute = path.resolve(root, supplied);
|
|
26
|
+
const suppliedRelative = path.relative(root, suppliedAbsolute).split(path.sep).join("/");
|
|
27
|
+
if (!suppliedRelative || suppliedRelative === "." || suppliedRelative === ".."
|
|
28
|
+
|| suppliedRelative.startsWith("../") || path.isAbsolute(suppliedRelative)) return undefined;
|
|
29
|
+
let cursor = root;
|
|
30
|
+
for (const segment of suppliedRelative.split("/")) {
|
|
31
|
+
cursor = path.join(cursor, segment);
|
|
32
|
+
if (fs.lstatSync(cursor).isSymbolicLink()) return undefined;
|
|
33
|
+
}
|
|
34
|
+
const absolute = fs.realpathSync.native(suppliedAbsolute);
|
|
35
|
+
const relative = path.relative(root, absolute).split(path.sep).join("/");
|
|
36
|
+
if (!relative || relative === "." || relative === ".." || relative.startsWith("../") || path.isAbsolute(relative)) return undefined;
|
|
37
|
+
return { root, absolute, relative };
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function stableSmallTextFile(root: string, absolute: string): string | undefined {
|
|
44
|
+
let descriptor: number | undefined;
|
|
45
|
+
try {
|
|
46
|
+
const initial = fs.lstatSync(absolute);
|
|
47
|
+
if (!initial.isFile() || initial.isSymbolicLink() || initial.size > MAX_RECOVERY_FILE_BYTES) return undefined;
|
|
48
|
+
const real = fs.realpathSync.native(absolute);
|
|
49
|
+
if (real !== root && !real.startsWith(`${root}${path.sep}`)) return undefined;
|
|
50
|
+
descriptor = fs.openSync(real, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
51
|
+
const observed = fs.fstatSync(descriptor);
|
|
52
|
+
if (!observed.isFile() || observed.dev !== initial.dev || observed.ino !== initial.ino
|
|
53
|
+
|| observed.size !== initial.size || observed.mtimeMs !== initial.mtimeMs || observed.ctimeMs !== initial.ctimeMs
|
|
54
|
+
|| observed.size > MAX_RECOVERY_FILE_BYTES) return undefined;
|
|
55
|
+
const buffer = Buffer.alloc(observed.size + 1);
|
|
56
|
+
let bytesRead = 0;
|
|
57
|
+
while (bytesRead < buffer.length) {
|
|
58
|
+
const count = fs.readSync(descriptor, buffer, bytesRead, buffer.length - bytesRead, null);
|
|
59
|
+
if (count === 0) break;
|
|
60
|
+
bytesRead += count;
|
|
61
|
+
}
|
|
62
|
+
const afterRead = fs.fstatSync(descriptor);
|
|
63
|
+
if (bytesRead !== observed.size || afterRead.dev !== observed.dev || afterRead.ino !== observed.ino
|
|
64
|
+
|| afterRead.size !== observed.size || afterRead.mtimeMs !== observed.mtimeMs || afterRead.ctimeMs !== observed.ctimeMs) return undefined;
|
|
65
|
+
const bytes = buffer.subarray(0, bytesRead);
|
|
66
|
+
const sample = bytes.subarray(0, Math.min(bytes.length, 8_192));
|
|
67
|
+
if (sample.includes(0)) return undefined;
|
|
68
|
+
const controls = [...sample].filter((byte) => byte < 7 || (byte > 13 && byte < 32)).length;
|
|
69
|
+
if (sample.length > 0 && controls / sample.length >= 0.02) return undefined;
|
|
70
|
+
const text = bytes.toString("utf8");
|
|
71
|
+
return Buffer.from(text, "utf8").equals(bytes) ? text : undefined;
|
|
72
|
+
} catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
} finally {
|
|
75
|
+
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function buildEditRecoveryContext(input: {
|
|
80
|
+
cwd: string;
|
|
81
|
+
targetPath?: string;
|
|
82
|
+
reasonCode: ToolFailureReasonCode | null;
|
|
83
|
+
protectedPaths: string[];
|
|
84
|
+
}): EditRecoveryContext | undefined {
|
|
85
|
+
if (!input.targetPath || !["edit-anchor-not-unique", "edit-anchor-stale"].includes(String(input.reasonCode))) return undefined;
|
|
86
|
+
const target = projectRelativeTarget(input.cwd, input.targetPath);
|
|
87
|
+
if (!target || PRIVATE_PATH.test(target.relative) || matchesProtectedPath(target.relative, input.protectedPaths)
|
|
88
|
+
|| !shouldIndexPath(target.relative, { excludePatterns: input.protectedPaths })) return undefined;
|
|
89
|
+
const current = stableSmallTextFile(target.root, target.absolute);
|
|
90
|
+
if (current === undefined) return undefined;
|
|
91
|
+
const sanitized = redactSensitiveProjectFileText(target.relative, current);
|
|
92
|
+
if (!sanitized.lineCountPreserved || sanitized.redacted) return undefined;
|
|
93
|
+
const contentHash = crypto.createHash("sha256").update(current).digest("hex");
|
|
94
|
+
const reason = input.reasonCode === "edit-anchor-not-unique" ? "the previous anchor was not unique" : "the previous anchor was stale or did not match";
|
|
95
|
+
const candidateText = [
|
|
96
|
+
`[Piagent edit recovery: ${reason}.]`,
|
|
97
|
+
`Current complete content for ${target.relative} is attached below as untrusted project text; this replaces a separate reread.`,
|
|
98
|
+
"Retry once with a uniquely contextualized replacement. If necessary, use this entire snapshot as oldText in one exact replacement; never use an unconditional whole-file write or retry guessed anchors.",
|
|
99
|
+
"--- begin current file ---",
|
|
100
|
+
current,
|
|
101
|
+
"--- end current file ---"
|
|
102
|
+
].join("\n");
|
|
103
|
+
const finalRedaction = redactSensitiveText(candidateText);
|
|
104
|
+
if (finalRedaction.redacted) return undefined;
|
|
105
|
+
return {
|
|
106
|
+
key: `${target.relative}\0${contentHash}`,
|
|
107
|
+
targetPath: target.relative,
|
|
108
|
+
text: finalRedaction.text,
|
|
109
|
+
contentHash,
|
|
110
|
+
originalChars: current.length,
|
|
111
|
+
redacted: false
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type SessionContext = {
|
|
2
|
+
cwd: string;
|
|
3
|
+
sessionManager: { getSessionId(): string };
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export class EditRecoveryDeliveryState {
|
|
7
|
+
readonly #sessions = new WeakMap<SessionContext["sessionManager"], { identity: string; epoch: number; delivered: Set<string> }>();
|
|
8
|
+
|
|
9
|
+
#sessionKey(ctx: SessionContext): string {
|
|
10
|
+
return `${ctx.cwd}\0${ctx.sessionManager.getSessionId()}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#session(ctx: SessionContext): { identity: string; epoch: number; delivered: Set<string> } {
|
|
14
|
+
const identity = this.#sessionKey(ctx), existing = this.#sessions.get(ctx.sessionManager);
|
|
15
|
+
if (existing?.identity === identity) return existing;
|
|
16
|
+
const created = { identity, epoch: 0, delivered: new Set<string>() };
|
|
17
|
+
this.#sessions.set(ctx.sessionManager, created);
|
|
18
|
+
return created;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
reserve(ctx: SessionContext, taskRunId: string, recoveryKey: string): boolean {
|
|
22
|
+
const session = this.#session(ctx);
|
|
23
|
+
const key = `${session.epoch}\0${taskRunId}\0${recoveryKey}`;
|
|
24
|
+
if (session.delivered.has(key)) return false;
|
|
25
|
+
// Refuse further automatic recovery in this context epoch once the bounded
|
|
26
|
+
// allowance is exhausted. Evicting an old key would permit duplicate
|
|
27
|
+
// injection and make recovery tokens unbounded under repeated failures.
|
|
28
|
+
if (session.delivered.size >= 16) return false;
|
|
29
|
+
session.delivered.add(key);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
advanceEpoch(ctx: SessionContext): void {
|
|
34
|
+
const session = this.#session(ctx);
|
|
35
|
+
session.epoch += 1;
|
|
36
|
+
session.delivered.clear();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
clearSession(ctx: SessionContext): void {
|
|
40
|
+
this.#sessions.delete(ctx.sessionManager);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -17,6 +17,7 @@ export const MAX_SEMANTIC_REPAIR_PATHS = 12;
|
|
|
17
17
|
export const MAX_SEMANTIC_REPAIR_REVISIONS = 2;
|
|
18
18
|
export const MAX_SEMANTIC_REPAIR_MUTATIONS = 8;
|
|
19
19
|
export const MAX_SEMANTIC_REPAIR_UNSUCCESSFUL_CALLS = 3;
|
|
20
|
+
export const BOUNDED_RECOVERY_CONFLICT_CODE = "post-verifier-model-omission";
|
|
20
21
|
|
|
21
22
|
type RepairStatus = "reserved" | "authorized" | "active" | "retry-ready" | "verifier-pending" | "passed" | "cancelled" | "locked";
|
|
22
23
|
type RepairPendingCall = {
|
|
@@ -173,6 +174,46 @@ export function decideSemanticRepairHandshake(input: {
|
|
|
173
174
|
return { authorized: includesConflictingSource && targets.length === eligibleTargets.length, conflictCodes, eligibleTargets, eligiblePaths, pathConflictCodes };
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Grant a single fail-closed recovery entry when the model notices an omission
|
|
179
|
+
* after a current exact verifier has already moved the task into verify.
|
|
180
|
+
*
|
|
181
|
+
* This is intentionally narrower than semantic repair: every target must be
|
|
182
|
+
* an actual current task delta, must have been observed in this task, and must
|
|
183
|
+
* still carry successful structured-mutation authorship for the exact task,
|
|
184
|
+
* run, and session. The caller separately enforces task scope and CAP-09/12/13
|
|
185
|
+
* authority before persisting the reservation.
|
|
186
|
+
*/
|
|
187
|
+
export function decideBoundedRecoveryHandshake(input: {
|
|
188
|
+
task: TaskContract;
|
|
189
|
+
mutationTargets: string[];
|
|
190
|
+
currentDeltaPaths: string[];
|
|
191
|
+
observedChangedPaths: string[];
|
|
192
|
+
authoredFileDigests: Record<string, string>;
|
|
193
|
+
verifierCurrent: boolean;
|
|
194
|
+
}): SemanticRepairHandshakeDecision {
|
|
195
|
+
const targets = uniquePaths(input.mutationTargets);
|
|
196
|
+
const empty = { authorized: false, conflictCodes: [], eligibleTargets: [], eligiblePaths: [], pathConflictCodes: {} };
|
|
197
|
+
if (!input.verifierCurrent || targets.length === 0 || targets.length > MAX_SEMANTIC_REPAIR_PATHS) return empty;
|
|
198
|
+
|
|
199
|
+
const delta = new Set(uniquePaths(input.currentDeltaPaths));
|
|
200
|
+
const observed = new Set(uniquePaths(input.observedChangedPaths));
|
|
201
|
+
const authored = new Set(uniquePaths(Object.entries(input.authoredFileDigests)
|
|
202
|
+
.filter(([, digest]) => typeof digest === "string" && digest.length > 0)
|
|
203
|
+
.map(([file]) => file)));
|
|
204
|
+
const eligibleTargets = targets.filter((file) => delta.has(file) && observed.has(file) && authored.has(file));
|
|
205
|
+
if (eligibleTargets.length !== targets.length) return empty;
|
|
206
|
+
|
|
207
|
+
const pathConflictCodes = Object.fromEntries(targets.map((file) => [file, [BOUNDED_RECOVERY_CONFLICT_CODE]]));
|
|
208
|
+
return {
|
|
209
|
+
authorized: true,
|
|
210
|
+
conflictCodes: [BOUNDED_RECOVERY_CONFLICT_CODE],
|
|
211
|
+
eligibleTargets: targets,
|
|
212
|
+
eligiblePaths: targets,
|
|
213
|
+
pathConflictCodes
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
176
217
|
export function semanticRepairStatePath(cwd: string, taskRunId: string): string {
|
|
177
218
|
return path.join(cwd, ".pi", "piagent-state", "semantic-repair", `${safeTaskId(taskRunId)}.json`);
|
|
178
219
|
}
|
|
@@ -364,6 +405,7 @@ export function authorizeSemanticRepairCall(input: {
|
|
|
364
405
|
return { handled: true, allowed: true, bypassPhase: false, state: writeState(input.cwd, state) };
|
|
365
406
|
}
|
|
366
407
|
if (state.status !== "active") return { handled: true, allowed: false, bypassPhase: false, reason: `semantic repair is ${state.status}` };
|
|
408
|
+
const boundedRecovery = state.conflictCodes.includes(BOUNDED_RECOVERY_CONFLICT_CODE);
|
|
367
409
|
if (input.exactVerifier) {
|
|
368
410
|
if (state.successfulMutationsInRevision < 1) return denyActiveCall(input.cwd, state, "semantic repair verifier requires a successful mutation in this revision", input.recordedAt);
|
|
369
411
|
state.status = "verifier-pending";
|
|
@@ -372,6 +414,7 @@ export function authorizeSemanticRepairCall(input: {
|
|
|
372
414
|
return { handled: true, allowed: true, bypassPhase: false, state: writeState(input.cwd, state) };
|
|
373
415
|
}
|
|
374
416
|
if (input.projectMutation) {
|
|
417
|
+
if (boundedRecovery) return denyActiveCall(input.cwd, state, "bounded recovery permits exactly one successful mutation before the exact verifier", input.recordedAt);
|
|
375
418
|
const targets = uniquePaths(input.targetPaths);
|
|
376
419
|
if (input.targetExtractionComplete === false) return denyActiveCall(input.cwd, state, "semantic repair mutation targets are not statically complete", input.recordedAt);
|
|
377
420
|
if (targets.length === 0 || targets.some((file) => !state.eligiblePaths.includes(file))) return denyActiveCall(input.cwd, state, "semantic repair mutation is outside the exact persisted grant", input.recordedAt);
|
|
@@ -402,6 +445,7 @@ export function pendingSemanticRepairCall(cwd: string, taskRunId: string, toolCa
|
|
|
402
445
|
export function completeSemanticRepairCall(input: {
|
|
403
446
|
cwd: string; taskRunId: string; toolCallId: string; success: boolean; exitCode?: number;
|
|
404
447
|
currentDigest: string; changedPaths: string[]; retryableFailure?: boolean; correctiveFailure?: boolean; recordedAt?: string;
|
|
448
|
+
authoredChangedPaths?: string[];
|
|
405
449
|
}): { result: "unmatched" | "opened" | "recorded" | "retry" | "correction" | "passed" | "cancelled" | "locked"; state?: SemanticRepairState } {
|
|
406
450
|
const view = readSemanticRepairState(input.cwd, input.taskRunId), state = view.state;
|
|
407
451
|
if (!view.enforcementSafe || !state?.pending || state.pending.toolCallId !== input.toolCallId) return { result: "unmatched", state };
|
|
@@ -410,6 +454,7 @@ export function completeSemanticRepairCall(input: {
|
|
|
410
454
|
return { result: "locked", state: writeState(input.cwd, state) };
|
|
411
455
|
}
|
|
412
456
|
const pending = state.pending, changed = uniquePaths(input.changedPaths), treeChanged = input.currentDigest !== pending.preDigest;
|
|
457
|
+
const boundedRecovery = state.conflictCodes.includes(BOUNDED_RECOVERY_CONFLICT_CODE);
|
|
413
458
|
const now = input.recordedAt ?? new Date().toISOString();
|
|
414
459
|
if (!pending.authorized) { state.status = "locked"; state.pending = null; state.updatedAt = now; return { result: "locked", state: writeState(input.cwd, state) }; }
|
|
415
460
|
if (pending.kind === "verifier") {
|
|
@@ -418,12 +463,20 @@ export function completeSemanticRepairCall(input: {
|
|
|
418
463
|
state.failedCalls += 1;
|
|
419
464
|
if (treeChanged || unsuccessful(state) >= MAX_SEMANTIC_REPAIR_UNSUCCESSFUL_CALLS) state.status = "locked";
|
|
420
465
|
else if (input.retryableFailure && !state.transientRetryUsed) { state.transientRetryUsed = true; state.status = "retry-ready"; }
|
|
421
|
-
else if (input.correctiveFailure && state.revision < MAX_SEMANTIC_REPAIR_REVISIONS) { state.revision += 1; state.successfulMutationsInRevision = 0; state.status = "active"; }
|
|
466
|
+
else if (!boundedRecovery && input.correctiveFailure && state.revision < MAX_SEMANTIC_REPAIR_REVISIONS) { state.revision += 1; state.successfulMutationsInRevision = 0; state.status = "active"; }
|
|
422
467
|
else state.status = "locked";
|
|
423
468
|
const result = state.status === "retry-ready" ? "retry" : state.status === "active" ? "correction" : "locked";
|
|
424
469
|
return { result, state: writeState(input.cwd, state) };
|
|
425
470
|
}
|
|
426
|
-
const
|
|
471
|
+
const authoredChanged = uniquePaths(input.authoredChangedPaths ?? []);
|
|
472
|
+
const boundedTargetMismatch = boundedRecovery
|
|
473
|
+
&& (changed.length !== pending.targetPaths.length || changed.some((file, index) => pending.targetPaths[index] !== file));
|
|
474
|
+
const boundedAuthorshipMissing = boundedRecovery
|
|
475
|
+
&& (authoredChanged.length !== changed.length || changed.some((file, index) => authoredChanged[index] !== file));
|
|
476
|
+
const unexpected = changed.length === 0
|
|
477
|
+
|| changed.some((file) => !pending.targetPaths.includes(file) || !state.eligiblePaths.includes(file))
|
|
478
|
+
|| boundedTargetMismatch
|
|
479
|
+
|| boundedAuthorshipMissing;
|
|
427
480
|
if (treeChanged && (!input.success || unexpected)) { state.status = "locked"; state.pending = null; state.updatedAt = now; return { result: "locked", state: writeState(input.cwd, state) }; }
|
|
428
481
|
if (input.success && treeChanged) {
|
|
429
482
|
if (pending.opensRepair) writeSemanticRepairOrigin(input.cwd, state, input.currentDigest, now);
|
|
@@ -3,8 +3,10 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import type { TaskContract } from "../../extensions/guard-types.ts";
|
|
4
4
|
import type { TrajectorySyncResult } from "../trajectory/trajectory-runtime.ts";
|
|
5
5
|
import {
|
|
6
|
+
BOUNDED_RECOVERY_CONFLICT_CODE,
|
|
6
7
|
authorizeSemanticRepairCall,
|
|
7
8
|
completeSemanticRepairCall,
|
|
9
|
+
decideBoundedRecoveryHandshake,
|
|
8
10
|
decideSemanticRepairHandshake,
|
|
9
11
|
pendingSemanticRepairCall,
|
|
10
12
|
readSemanticRepairState,
|
|
@@ -31,6 +33,7 @@ export type SemanticRepairCompletionMetadata = {
|
|
|
31
33
|
exitCode: number;
|
|
32
34
|
currentWorkingTreeDigest: string;
|
|
33
35
|
changedPaths: string[];
|
|
36
|
+
authoredChangedPaths?: string[];
|
|
34
37
|
retryableFailure: boolean; correctiveFailure: boolean;
|
|
35
38
|
};
|
|
36
39
|
|
|
@@ -100,6 +103,52 @@ export class SemanticRepairRuntime {
|
|
|
100
103
|
return true;
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
prepareBoundedRecovery(input: {
|
|
107
|
+
ctx: ExtensionContext; task: TaskContract; event: ToolEvent; currentDigest: string;
|
|
108
|
+
currentDeltaPaths: string[]; observedChangedPaths: string[];
|
|
109
|
+
authoredFileDigests: Record<string, string>; targetPaths: string[]; verifierCurrent: boolean;
|
|
110
|
+
}): boolean {
|
|
111
|
+
// This path is a one-shot CAP-12 bridge, not CAP-13's multi-revision
|
|
112
|
+
// semantic-repair protocol. Any durable state, including a cancelled or
|
|
113
|
+
// interrupted reservation, consumes the task-run grant and fails closed.
|
|
114
|
+
if (readSemanticRepairState(input.ctx.cwd, input.task.taskRunId).status !== "missing"
|
|
115
|
+
|| semanticRepairStateRequired(input.ctx.cwd, input.task.taskRunId)) return false;
|
|
116
|
+
const decision = decideBoundedRecoveryHandshake({
|
|
117
|
+
task: input.task,
|
|
118
|
+
mutationTargets: input.targetPaths,
|
|
119
|
+
currentDeltaPaths: input.currentDeltaPaths,
|
|
120
|
+
observedChangedPaths: input.observedChangedPaths,
|
|
121
|
+
authoredFileDigests: input.authoredFileDigests,
|
|
122
|
+
verifierCurrent: input.verifierCurrent
|
|
123
|
+
});
|
|
124
|
+
if (!decision.authorized) return false;
|
|
125
|
+
const toolCallId = String(input.event.toolCallId);
|
|
126
|
+
const reserved = reserveSemanticRepairCall({
|
|
127
|
+
cwd: input.ctx.cwd,
|
|
128
|
+
task: input.task,
|
|
129
|
+
sessionId: input.ctx.sessionManager.getSessionId(),
|
|
130
|
+
toolCallId,
|
|
131
|
+
toolName: input.event.toolName,
|
|
132
|
+
currentDigest: input.currentDigest,
|
|
133
|
+
decision,
|
|
134
|
+
targetPaths: input.targetPaths,
|
|
135
|
+
recordedAt: this.dependencies.now()
|
|
136
|
+
});
|
|
137
|
+
if (!reserved.reserved) return false;
|
|
138
|
+
this.reservationTokens.set(
|
|
139
|
+
this.reservationKey(input.ctx.cwd, input.task.taskRunId, toolCallId),
|
|
140
|
+
reserved.reservationToken!
|
|
141
|
+
);
|
|
142
|
+
this.dependencies.trace(input.ctx, input.task, {
|
|
143
|
+
event: "bounded_recovery_repair_reserved",
|
|
144
|
+
workingTreeDigest: input.currentDigest,
|
|
145
|
+
reasonCode: BOUNDED_RECOVERY_CONFLICT_CODE,
|
|
146
|
+
eligiblePaths: decision.eligiblePaths,
|
|
147
|
+
targetCount: input.targetPaths.length
|
|
148
|
+
});
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
103
152
|
reservedCallMatches(input: {
|
|
104
153
|
cwd: string; taskRunId: string; sessionId: string; toolCallId: string;
|
|
105
154
|
toolName: string; currentDigest: string; targetPaths: string[];
|
|
@@ -131,7 +180,8 @@ export class SemanticRepairRuntime {
|
|
|
131
180
|
if (view.state.successfulMutations > 0 && !semanticRepairOriginMatches(cwd, view.state)) return "semantic repair durable origin is missing or invalid";
|
|
132
181
|
if (view.state.status === "passed") return;
|
|
133
182
|
if (view.state.successfulMutations === 0 && view.state.pending === null && !semanticRepairStateRequired(cwd, taskRunId)) return;
|
|
134
|
-
|
|
183
|
+
const label = view.state.conflictCodes.includes(BOUNDED_RECOVERY_CONFLICT_CODE) ? "bounded recovery" : "semantic repair";
|
|
184
|
+
return `${label} exact final verifier is not satisfied (${view.state.status})`;
|
|
135
185
|
}
|
|
136
186
|
|
|
137
187
|
complete(
|
|
@@ -148,13 +198,16 @@ export class SemanticRepairRuntime {
|
|
|
148
198
|
exitCode: metadata.exitCode,
|
|
149
199
|
currentDigest: metadata.currentWorkingTreeDigest,
|
|
150
200
|
changedPaths: metadata.changedPaths,
|
|
201
|
+
authoredChangedPaths: metadata.authoredChangedPaths,
|
|
151
202
|
retryableFailure: metadata.retryableFailure,
|
|
152
203
|
correctiveFailure: metadata.correctiveFailure,
|
|
153
204
|
recordedAt: this.dependencies.now()
|
|
154
205
|
});
|
|
155
206
|
if (completion.result === "unmatched") return;
|
|
156
207
|
this.dependencies.trace(ctx, task, {
|
|
157
|
-
event: completion.
|
|
208
|
+
event: completion.state?.conflictCodes.includes(BOUNDED_RECOVERY_CONFLICT_CODE)
|
|
209
|
+
? `bounded_recovery_repair_${completion.result}`
|
|
210
|
+
: completion.result === "opened" ? "semantic_contradiction_repair_opened" : `semantic_repair_${completion.result}`,
|
|
158
211
|
toolName: event.toolName,
|
|
159
212
|
revision: completion.state?.revision,
|
|
160
213
|
successfulMutations: completion.state?.successfulMutations,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { stageContextDelivery } from "../context/context-delivery.ts";
|
|
2
3
|
|
|
3
4
|
type ExtensionContext = any;
|
|
4
5
|
|
|
@@ -10,7 +11,7 @@ export function registerContextCommands(pi: ExtensionAPI, deps: Record<string, a
|
|
|
10
11
|
contextIndexV2Status, crypto, emitRuntimeMessage, ensureContextIndexV2, estimateContextTokens,
|
|
11
12
|
formatContextPreflight,
|
|
12
13
|
formatCount, formatPercent, fs, helpersMode, loadProfileFromContext, memoryHandbookPath,
|
|
13
|
-
policy, projectFilePath, resolveMemorySettings, resolveOrchestrationPolicy, searchContextIndex,
|
|
14
|
+
policy, projectFilePath, resolveMemorySettings, resolveOrchestrationPolicy, retrievalKey, runtimeState, searchContextIndex,
|
|
14
15
|
searchContextIndexV2, selectRuntimeAction, semanticCompactionInstructions, telemetry
|
|
15
16
|
} = deps;
|
|
16
17
|
function emitContextIndexSearch(ctx: ExtensionContext, query: string): void {
|
|
@@ -123,6 +124,7 @@ export function registerContextCommands(pi: ExtensionAPI, deps: Record<string, a
|
|
|
123
124
|
});
|
|
124
125
|
telemetry(ctx, {
|
|
125
126
|
event: "context_pack",
|
|
127
|
+
turnId: runtimeState.currentTurn(ctx)?.turnId,
|
|
126
128
|
queryHash: pack.queryHash,
|
|
127
129
|
confidence: pack.confidence,
|
|
128
130
|
candidates: pack.candidates,
|
|
@@ -131,12 +133,65 @@ export function registerContextCommands(pi: ExtensionAPI, deps: Record<string, a
|
|
|
131
133
|
selectedPaths: pack.selected.map((item) => item.path),
|
|
132
134
|
source: "command"
|
|
133
135
|
});
|
|
136
|
+
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId());
|
|
137
|
+
const deliveryId = task?.trace?.outcome === "pending" && pack.selected.length > 0 ? crypto.randomUUID() : undefined;
|
|
138
|
+
if (deliveryId) {
|
|
139
|
+
stageContextDelivery(ctx, {
|
|
140
|
+
deliveryId,
|
|
141
|
+
taskRunId: task.taskRunId,
|
|
142
|
+
turnId: runtimeState.currentTurn(ctx)?.turnId,
|
|
143
|
+
entries: pack.selected.map((item) => ({
|
|
144
|
+
path: item.path,
|
|
145
|
+
reason: "Runtime confirmed delivery of a /context pack message."
|
|
146
|
+
})),
|
|
147
|
+
pack: {
|
|
148
|
+
retrievalKey: retrievalKey(ctx, query),
|
|
149
|
+
queryHash: pack.queryHash,
|
|
150
|
+
confidence: pack.confidence,
|
|
151
|
+
estimatedTokens: pack.estimatedTokens,
|
|
152
|
+
paths: pack.selected.map((item) => item.path)
|
|
153
|
+
},
|
|
154
|
+
injection: {
|
|
155
|
+
source: "context-command",
|
|
156
|
+
queryHash: pack.queryHash,
|
|
157
|
+
confidence: pack.confidence,
|
|
158
|
+
estimatedTokens: pack.estimatedTokens,
|
|
159
|
+
selectedItems: pack.selected.map((item) => ({
|
|
160
|
+
path: item.path,
|
|
161
|
+
estimatedTokens: item.estimatedTokens,
|
|
162
|
+
sources: item.sources,
|
|
163
|
+
links: item.links,
|
|
164
|
+
fileContentHash: item.fileContentHash,
|
|
165
|
+
...(item.sanitizedContentDigest ? { sanitizedContentDigest: item.sanitizedContentDigest } : {}),
|
|
166
|
+
payloadHash: item.payloadHash,
|
|
167
|
+
representation: item.representation,
|
|
168
|
+
ranges: item.ranges,
|
|
169
|
+
generation: item.generation,
|
|
170
|
+
sensitiveContentRedacted: item.sensitiveContentRedacted
|
|
171
|
+
}))
|
|
172
|
+
}
|
|
173
|
+
}, { state: runtimeState, telemetry });
|
|
174
|
+
}
|
|
134
175
|
emitRuntimeMessage(ctx, "piagent-context-pack-v2", pack.text, {
|
|
135
176
|
queryHash: pack.queryHash,
|
|
136
177
|
confidence: pack.confidence,
|
|
137
178
|
estimatedTokens: pack.estimatedTokens,
|
|
138
179
|
paths: pack.selected.map((item) => item.path),
|
|
139
|
-
|
|
180
|
+
selectedItems: pack.selected.map((item) => ({
|
|
181
|
+
path: item.path,
|
|
182
|
+
estimatedTokens: item.estimatedTokens,
|
|
183
|
+
sources: item.sources,
|
|
184
|
+
links: item.links,
|
|
185
|
+
fileContentHash: item.fileContentHash,
|
|
186
|
+
...(item.sanitizedContentDigest ? { sanitizedContentDigest: item.sanitizedContentDigest } : {}),
|
|
187
|
+
payloadHash: item.payloadHash,
|
|
188
|
+
representation: item.representation,
|
|
189
|
+
ranges: item.ranges,
|
|
190
|
+
generation: item.generation,
|
|
191
|
+
sensitiveContentRedacted: item.sensitiveContentRedacted
|
|
192
|
+
})),
|
|
193
|
+
finderRecommended: pack.finderRecommended,
|
|
194
|
+
contextDelivery: deliveryId ? { schemaVersion: 1, deliveryId } : undefined
|
|
140
195
|
});
|
|
141
196
|
}
|
|
142
197
|
|
|
@@ -156,13 +211,19 @@ export function registerContextCommands(pi: ExtensionAPI, deps: Record<string, a
|
|
|
156
211
|
const report = buildContextEfficiencyReport(ctx.cwd);
|
|
157
212
|
const task = activeSessionTask(ctx);
|
|
158
213
|
const taskEfficiency = task ? buildTaskEfficiencyMetrics(ctx.cwd, task) : null;
|
|
214
|
+
const wasteScore = report.metrics.contextWasteScore === null
|
|
215
|
+
? `unavailable (diagnostic estimate ${report.metrics.contextWasteScoreEstimate}/100; evidence ${formatPercent(report.metrics.contextWasteScoreEvidenceCoverage)}, ${report.coverage.wasteScore.status})`
|
|
216
|
+
: `${report.metrics.contextWasteScore}/100 (lower is better; evidence ${formatPercent(report.metrics.contextWasteScoreEvidenceCoverage)}, ${report.coverage.wasteScore.status})`;
|
|
159
217
|
emitRuntimeMessage(ctx, "piagent-context-efficiency", [
|
|
160
|
-
`contextWasteScore: ${
|
|
218
|
+
`contextWasteScore: ${wasteScore}`,
|
|
161
219
|
`activeTools: ${report.metrics.averageActiveTools}`,
|
|
162
|
-
`
|
|
163
|
-
`
|
|
220
|
+
`toolSchemaPrefixShare: ${formatPercent(report.metrics.toolSchemaPrefixShare)}`,
|
|
221
|
+
`toolSchemaToSystemRatio: ${report.metrics.toolSchemaToSystemRatio}`,
|
|
222
|
+
`duplicateReads: ${report.metrics.duplicateReads}/${report.metrics.comparableReadCalls} comparable (${report.metrics.readCalls} observed; coverage ${formatPercent(report.metrics.readEvidenceCoverage)})`,
|
|
223
|
+
`fallbackRereads: ${report.metrics.contextFallbackRereads}/${report.metrics.contextSelections}`,
|
|
164
224
|
`duplicateOutput: ${formatPercent(report.metrics.duplicateOutputRate)}`,
|
|
165
225
|
`lowConfidencePacks: ${report.metrics.lowConfidencePacks}/${report.sample.contextPacks}`,
|
|
226
|
+
`editRecovery: ${report.metrics.editRecoveryContextCount}/${report.metrics.editRecoveryFailures} failures returned context; ${report.metrics.editRecoverySuppressedFailures} suppressed; ${report.metrics.editRecoveryEstimatedTokens} estimated tokens; classification coverage ${formatPercent(report.metrics.editRecoveryFailureEvidenceCoverage)}`,
|
|
166
227
|
`taskEfficiency: ${taskEfficiency ? `${taskEfficiency.solver.route}; verify=${taskEfficiency.verification.attempts}; outcome=${taskEfficiency.outcome.task}` : "no active task"}`,
|
|
167
228
|
...report.recommendations.map((recommendation) => `- ${recommendation}`)
|
|
168
229
|
].join("\n"), { ...report, taskEfficiency });
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { stageContextDelivery } from "../context/context-delivery.ts";
|
|
3
|
+
import { formatContextEfficiencyReport } from "../context/context-efficiency-format.ts";
|
|
2
4
|
|
|
3
5
|
type PiagentToolGroup = any;
|
|
4
6
|
type TechStackManifest = any;
|
|
@@ -6,7 +8,7 @@ type TechStackManifest = any;
|
|
|
6
8
|
|
|
7
9
|
export function registerPolicyTools(pi: ExtensionAPI, deps: Record<string, any>): void {
|
|
8
10
|
const {
|
|
9
|
-
PIAGENT_TOOL_NAMES, READ_ONLY_TOOL_NAMES, StringEnum, Type, activateToolGroups,
|
|
11
|
+
PIAGENT_TOOL_NAMES, READ_ONLY_TOOL_NAMES, StringEnum, Type, activateToolGroups, activeSessionTask,
|
|
10
12
|
buildContextEfficiencyReport, buildContextIndexStatus, buildContextIndexV2, buildContextPack, buildContextPreflight,
|
|
11
13
|
buildTestImpact, buildUsageSnapshot, candidateFileBudget, contextBudgetConfig, contextIndexExcludePatterns,
|
|
12
14
|
contextIndexV2Status, crypto, dynamicToolsEnabled, effectiveProtectedPaths, ensureContextIndexV2,
|
|
@@ -142,7 +144,48 @@ export function registerPolicyTools(pi: ExtensionAPI, deps: Record<string, any>)
|
|
|
142
144
|
limit: 18,
|
|
143
145
|
excludePatterns
|
|
144
146
|
});
|
|
145
|
-
|
|
147
|
+
telemetry(ctx, { event: "context_pack", turnId: runtimeState.currentTurn(ctx)?.turnId, source: "context-tool", queryHash: pack.queryHash, confidence: pack.confidence, candidates: pack.candidates, selected: pack.selected.length, estimatedTokens: pack.estimatedTokens, selectedPaths: pack.selected.map((item) => item.path) });
|
|
148
|
+
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId());
|
|
149
|
+
const deliveryId = task?.trace?.outcome === "pending" && pack.selected.length > 0 ? crypto.randomUUID() : undefined;
|
|
150
|
+
if (deliveryId) {
|
|
151
|
+
const packRetrievalKey = retrievalKey(ctx, params.query);
|
|
152
|
+
stageContextDelivery(ctx, {
|
|
153
|
+
deliveryId,
|
|
154
|
+
taskRunId: task.taskRunId,
|
|
155
|
+
turnId: runtimeState.currentTurn(ctx)?.turnId,
|
|
156
|
+
entries: pack.selected.map((item) => ({
|
|
157
|
+
path: item.path,
|
|
158
|
+
reason: "Runtime confirmed delivery of an explicit Context Engine tool pack."
|
|
159
|
+
})),
|
|
160
|
+
pack: {
|
|
161
|
+
retrievalKey: packRetrievalKey,
|
|
162
|
+
queryHash: pack.queryHash,
|
|
163
|
+
confidence: pack.confidence,
|
|
164
|
+
estimatedTokens: pack.estimatedTokens,
|
|
165
|
+
paths: pack.selected.map((item) => item.path)
|
|
166
|
+
},
|
|
167
|
+
injection: {
|
|
168
|
+
source: "context-tool",
|
|
169
|
+
queryHash: pack.queryHash,
|
|
170
|
+
confidence: pack.confidence,
|
|
171
|
+
estimatedTokens: pack.estimatedTokens,
|
|
172
|
+
selectedItems: pack.selected.map((item) => ({
|
|
173
|
+
path: item.path,
|
|
174
|
+
estimatedTokens: item.estimatedTokens,
|
|
175
|
+
sources: item.sources,
|
|
176
|
+
links: item.links,
|
|
177
|
+
fileContentHash: item.fileContentHash,
|
|
178
|
+
...(item.sanitizedContentDigest ? { sanitizedContentDigest: item.sanitizedContentDigest } : {}),
|
|
179
|
+
payloadHash: item.payloadHash,
|
|
180
|
+
representation: item.representation,
|
|
181
|
+
ranges: item.ranges,
|
|
182
|
+
generation: item.generation,
|
|
183
|
+
sensitiveContentRedacted: item.sensitiveContentRedacted
|
|
184
|
+
}))
|
|
185
|
+
}
|
|
186
|
+
}, { state: runtimeState, telemetry });
|
|
187
|
+
}
|
|
188
|
+
result = { ...pack, text: undefined, status, contextDelivery: deliveryId ? { schemaVersion: 1, deliveryId } : undefined };
|
|
146
189
|
text = pack.text;
|
|
147
190
|
} else if (params.action === "impact") {
|
|
148
191
|
await ensureContextIndexV2(ctx.cwd, { excludePatterns, rebuildMissing: false });
|
|
@@ -156,15 +199,7 @@ export function registerPolicyTools(pi: ExtensionAPI, deps: Record<string, any>)
|
|
|
156
199
|
} else {
|
|
157
200
|
result = buildContextEfficiencyReport(ctx.cwd);
|
|
158
201
|
const report = result as ReturnType<typeof buildContextEfficiencyReport>;
|
|
159
|
-
text =
|
|
160
|
-
`contextWasteScore: ${report.metrics.contextWasteScore}/100 (lower is better)`,
|
|
161
|
-
`activeTools: ${report.metrics.averageActiveTools}`,
|
|
162
|
-
`toolSchemaShare: ${formatPercent(report.metrics.toolSchemaShare)}`,
|
|
163
|
-
`duplicateReads: ${report.metrics.duplicateReads}/${report.metrics.readCalls}`,
|
|
164
|
-
`duplicateOutput: ${formatPercent(report.metrics.duplicateOutputRate)}`,
|
|
165
|
-
`lowConfidencePacks: ${report.metrics.lowConfidencePacks}/${report.sample.contextPacks}`,
|
|
166
|
-
...report.recommendations.map((recommendation) => `- ${recommendation}`)
|
|
167
|
-
].join("\n");
|
|
202
|
+
text = formatContextEfficiencyReport(report, formatPercent);
|
|
168
203
|
}
|
|
169
204
|
telemetry(ctx, {
|
|
170
205
|
event: "context_engine_action",
|