@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,7 +2,6 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
|
-
|
|
6
5
|
import {
|
|
7
6
|
contextIndexExcludeDigest,
|
|
8
7
|
contextIndexExcludePolicyVersion,
|
|
@@ -11,9 +10,13 @@ import {
|
|
|
11
10
|
import { matchesAnyPath, matchesProtectedPath } from "./policy-core.js";
|
|
12
11
|
import { appendJsonlBounded, readJsonlTail } from "./state-retention.js";
|
|
13
12
|
import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
|
|
14
|
-
|
|
13
|
+
import { contextMetricPartition } from "./context-efficiency-metrics.js";
|
|
14
|
+
import { writeContextEfficiencyReport } from "./context-efficiency-report.js";
|
|
15
|
+
import { inspectContextTelemetryIntegrity } from "./context-telemetry-integrity.js";
|
|
16
|
+
import { directExplicitImportLinks, extractJavaScriptModuleImports } from "./context-import-links.js";
|
|
17
|
+
import { redactSensitiveProjectFileText } from "../security/sensitive-data.js";
|
|
15
18
|
const INDEX_SCHEMA_VERSION = 2;
|
|
16
|
-
const TELEMETRY_SCHEMA_VERSION =
|
|
19
|
+
const TELEMETRY_SCHEMA_VERSION = 2;
|
|
17
20
|
const RRF_K = 60;
|
|
18
21
|
const DEFAULT_MAX_FILE_BYTES = 512 * 1024;
|
|
19
22
|
const DEFAULT_MAX_FILES = 8_000;
|
|
@@ -75,6 +78,10 @@ function clampInteger(value, fallback, min, max) {
|
|
|
75
78
|
return Math.max(min, Math.min(max, Math.trunc(number)));
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
function ratio(numerator, denominator) {
|
|
82
|
+
return denominator > 0 ? numerator / denominator : 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
function requireExplicitExcludePatterns(options, operation) {
|
|
79
86
|
if (!Array.isArray(options?.excludePatterns)) {
|
|
80
87
|
throw new TypeError(`${operation} requires an explicit excludePatterns array`);
|
|
@@ -258,7 +265,7 @@ function isTextBuffer(buffer) {
|
|
|
258
265
|
return sample.length === 0 || suspicious / sample.length < 0.02;
|
|
259
266
|
}
|
|
260
267
|
|
|
261
|
-
function shouldIndexPath(relativePath, options) {
|
|
268
|
+
export function shouldIndexPath(relativePath, options = {}) {
|
|
262
269
|
const rel = normalizeRelative(relativePath);
|
|
263
270
|
if (!rel || path.isAbsolute(rel) || rel.startsWith("../")) return false;
|
|
264
271
|
const segments = rel.split("/");
|
|
@@ -417,12 +424,12 @@ function extractSymbols(text, language) {
|
|
|
417
424
|
return symbols;
|
|
418
425
|
}
|
|
419
426
|
|
|
420
|
-
function extractImports(text, language) {
|
|
427
|
+
function extractImports(text, language, filePath = "") {
|
|
421
428
|
const lines = text.split(/\r?\n/);
|
|
422
429
|
const imports = [];
|
|
423
430
|
const patterns = [];
|
|
424
431
|
if (["typescript", "javascript", "svelte", "vue"].includes(language)) {
|
|
425
|
-
|
|
432
|
+
return extractJavaScriptModuleImports(text, { embedded: ["svelte", "vue"].includes(language), jsx: /\.[jt]sx$/i.test(filePath) });
|
|
426
433
|
} else if (language === "python") {
|
|
427
434
|
patterns.push(/^\s*from\s+([A-Za-z_][\w.]*)\s+import\b/, /^\s*import\s+([A-Za-z_][\w.]*)/);
|
|
428
435
|
} else if (["java", "kotlin", "scala"].includes(language)) {
|
|
@@ -627,7 +634,7 @@ export async function buildContextIndexV2(cwd, options = {}) {
|
|
|
627
634
|
for (const item of changed) {
|
|
628
635
|
const language = languageForPath(item.relativePath);
|
|
629
636
|
const symbols = extractSymbols(item.text, language);
|
|
630
|
-
const imports = extractImports(item.text, language);
|
|
637
|
+
const imports = extractImports(item.text, language, item.relativePath);
|
|
631
638
|
insertFile.run(item.relativePath, item.contentHash, item.stat.size, item.stat.mtimeMs, language, indexedAt);
|
|
632
639
|
insertFts.run(item.relativePath, item.text, symbols.map((symbol) => symbol.name).join(" "));
|
|
633
640
|
for (const symbol of symbols) {
|
|
@@ -859,10 +866,13 @@ function retrievalFeedback(cwd) {
|
|
|
859
866
|
} catch {
|
|
860
867
|
return {
|
|
861
868
|
rows: [],
|
|
869
|
+
observedSelected: 0,
|
|
862
870
|
selected: 0,
|
|
863
871
|
used: 0,
|
|
864
872
|
unused: 0,
|
|
865
|
-
utilizationRate: 0
|
|
873
|
+
utilizationRate: 0,
|
|
874
|
+
fallbackRereads: 0,
|
|
875
|
+
fallbackRereadRate: 0
|
|
866
876
|
};
|
|
867
877
|
}
|
|
868
878
|
const cached = retrievalFeedbackCache.get(cwd);
|
|
@@ -871,33 +881,61 @@ function retrievalFeedback(cwd) {
|
|
|
871
881
|
const events = readContextTelemetry(cwd, { limit: 12_000, maxBytes: 12 * 1024 * 1024 });
|
|
872
882
|
const selectedCounts = new Map();
|
|
873
883
|
const usedCounts = new Map();
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
884
|
+
const fallbackReadCounts = new Map();
|
|
885
|
+
const latestPackByPartition = new Map();
|
|
886
|
+
const taskByTurn = new Map(events.filter((event) => event.event === "turn_task_bound" && typeof event.sessionId === "string" && typeof event.turnId === "string" && typeof event.taskRunId === "string")
|
|
887
|
+
.map((event) => [`${event.sessionId}\0${event.turnId}`, event.taskRunId]));
|
|
888
|
+
let observedSelected = 0, selected = 0, used = 0, fallbackRereads = 0;
|
|
889
|
+
|
|
890
|
+
const eventPaths = (event) => [...new Set([
|
|
891
|
+
...(Array.isArray(event.selectedPaths) ? event.selectedPaths : []),
|
|
892
|
+
...(Array.isArray(event.selectedItems) ? event.selectedItems.map((item) => item?.path) : [])
|
|
893
|
+
].map(normalizeRelative).filter((filePath) => shouldIndexPath(filePath, {})))];
|
|
894
|
+
const successfulMutationPaths = (event) => {
|
|
895
|
+
if (event.event !== "tool_result" || event.isError === true) return [];
|
|
896
|
+
const explicit = [event.changedPaths, event.mutationPaths, event.targetPaths]
|
|
897
|
+
.find((value) => Array.isArray(value));
|
|
898
|
+
if (explicit) return [...new Set(explicit.map(normalizeRelative).filter(Boolean))];
|
|
899
|
+
// These tools have direct file-mutation semantics. Shell and opaque MCP
|
|
900
|
+
// results are intentionally excluded unless they carry explicit changed
|
|
901
|
+
// paths; a zero exit code alone is not mutation evidence.
|
|
902
|
+
const toolName = String(event.toolName ?? "").toLowerCase();
|
|
903
|
+
if (!["write", "edit", "apply_patch", "patch"].includes(toolName)) return [];
|
|
904
|
+
const targetPath = normalizeRelative(event.targetPath);
|
|
905
|
+
return targetPath ? [targetPath] : [];
|
|
906
|
+
};
|
|
877
907
|
|
|
878
908
|
for (const event of events) {
|
|
879
|
-
const
|
|
880
|
-
if (event.event === "
|
|
881
|
-
const selectedPaths =
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
)
|
|
886
|
-
const pack = { selectedPaths: new Set(selectedPaths), usedPaths: new Set() };
|
|
887
|
-
latestPackBySession.set(sessionId, pack);
|
|
909
|
+
const partition = contextMetricPartition(event, taskByTurn);
|
|
910
|
+
if (event.event === "context_pack_injected") {
|
|
911
|
+
const selectedPaths = eventPaths(event);
|
|
912
|
+
observedSelected += selectedPaths.length;
|
|
913
|
+
if (!partition) continue;
|
|
914
|
+
const pack = { selectedPaths: new Set(selectedPaths), usedPaths: new Set(), fallbackReadPaths: new Set() };
|
|
915
|
+
latestPackByPartition.set(partition, pack);
|
|
888
916
|
for (const filePath of selectedPaths) {
|
|
889
917
|
selectedCounts.set(filePath, (selectedCounts.get(filePath) ?? 0) + 1);
|
|
890
918
|
selected += 1;
|
|
891
919
|
}
|
|
892
920
|
continue;
|
|
893
921
|
}
|
|
894
|
-
if (
|
|
922
|
+
if (!partition) continue;
|
|
923
|
+
const pack = latestPackByPartition.get(partition);
|
|
924
|
+
if (!pack) continue;
|
|
895
925
|
const targetPath = normalizeRelative(event.targetPath);
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
926
|
+
if (event.event === "tool_call" && ["read", "grep", "find", "ls"].includes(event.toolName)
|
|
927
|
+
&& targetPath && pack.selectedPaths.has(targetPath) && !pack.fallbackReadPaths.has(targetPath)) {
|
|
928
|
+
pack.fallbackReadPaths.add(targetPath);
|
|
929
|
+
fallbackReadCounts.set(targetPath, (fallbackReadCounts.get(targetPath) ?? 0) + 1);
|
|
930
|
+
fallbackRereads += 1;
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
for (const mutationPath of successfulMutationPaths(event)) {
|
|
934
|
+
if (!pack.selectedPaths.has(mutationPath) || pack.usedPaths.has(mutationPath)) continue;
|
|
935
|
+
pack.usedPaths.add(mutationPath);
|
|
936
|
+
usedCounts.set(mutationPath, (usedCounts.get(mutationPath) ?? 0) + 1);
|
|
937
|
+
used += 1;
|
|
938
|
+
}
|
|
901
939
|
}
|
|
902
940
|
|
|
903
941
|
const rows = [...usedCounts.entries()]
|
|
@@ -909,16 +947,20 @@ function retrievalFeedback(cwd) {
|
|
|
909
947
|
path: filePath,
|
|
910
948
|
selectedCount,
|
|
911
949
|
usedCount,
|
|
950
|
+
fallbackRereadCount: fallbackReadCounts.get(filePath) ?? 0,
|
|
912
951
|
feedbackScore: utilization * evidence
|
|
913
952
|
};
|
|
914
953
|
})
|
|
915
954
|
.sort((left, right) => right.feedbackScore - left.feedbackScore || right.usedCount - left.usedCount || left.path.localeCompare(right.path));
|
|
916
955
|
const value = {
|
|
917
956
|
rows,
|
|
957
|
+
observedSelected,
|
|
918
958
|
selected,
|
|
919
959
|
used,
|
|
920
960
|
unused: Math.max(0, selected - used),
|
|
921
|
-
utilizationRate: ratio(used, selected)
|
|
961
|
+
utilizationRate: ratio(used, selected),
|
|
962
|
+
fallbackRereads,
|
|
963
|
+
fallbackRereadRate: ratio(fallbackRereads, selected)
|
|
922
964
|
};
|
|
923
965
|
retrievalFeedbackCache.set(cwd, { signature, value });
|
|
924
966
|
return value;
|
|
@@ -941,15 +983,15 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
|
|
|
941
983
|
const allFiles = db.prepare("SELECT path, mtime_ms FROM files").all().filter((file) => pathAllowed(file.path));
|
|
942
984
|
const explicitRows = [];
|
|
943
985
|
for (const file of allFiles) {
|
|
986
|
+
const normalizedFile = file.path.toLowerCase();
|
|
944
987
|
const basename = path.posix.basename(file.path).toLowerCase();
|
|
945
988
|
const matched = explicitPaths.some((candidate) => {
|
|
946
989
|
const normalized = candidate.toLowerCase();
|
|
947
|
-
return
|
|
990
|
+
return normalizedFile === normalized || (!normalized.includes("/") && basename === normalized);
|
|
948
991
|
});
|
|
949
992
|
if (matched) explicitRows.push({ path: file.path, exact: true });
|
|
950
993
|
}
|
|
951
994
|
addRankedList(scores, explicitRows, "explicit", 3);
|
|
952
|
-
|
|
953
995
|
let lexicalRows = [];
|
|
954
996
|
if (terms.length > 0) {
|
|
955
997
|
lexicalRows = db.prepare(`
|
|
@@ -958,7 +1000,6 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
|
|
|
958
1000
|
`).all(ftsQuery(terms), Math.max(limit * 4, 30)).filter((row) => pathAllowed(row.path));
|
|
959
1001
|
addRankedList(scores, lexicalRows, "lexical", 1.5);
|
|
960
1002
|
}
|
|
961
|
-
|
|
962
1003
|
const symbolRows = [];
|
|
963
1004
|
const symbolStatement = db.prepare(`
|
|
964
1005
|
SELECT file_path, name, kind, line, end_line, signature
|
|
@@ -973,13 +1014,11 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
|
|
|
973
1014
|
|
|
974
1015
|
const changedRows = gitChangedFiles(cwd).map((filePath) => ({ path: filePath })).filter((row) => scores.has(row.path));
|
|
975
1016
|
addRankedList(scores, changedRows, "git-change", 0.8);
|
|
976
|
-
|
|
977
1017
|
const testRows = allFiles
|
|
978
1018
|
.filter((file) => /(^|\/)(?:test|tests|__tests__)(\/|$)|(?:\.test|\.spec|_test)\./i.test(file.path))
|
|
979
1019
|
.filter((file) => terms.some((term) => file.path.toLowerCase().includes(term)))
|
|
980
1020
|
.map((file) => ({ path: file.path }));
|
|
981
1021
|
addRankedList(scores, testRows, "test", 1.1);
|
|
982
|
-
|
|
983
1022
|
// Feedback is deliberately positive-only and weak. A file is boosted only
|
|
984
1023
|
// after a prior context pack selected it and the same session actually used
|
|
985
1024
|
// it. New files and unused historical candidates are never penalized.
|
|
@@ -987,12 +1026,13 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
|
|
|
987
1026
|
addRankedList(scores, feedbackRows, "feedback", 0.45);
|
|
988
1027
|
|
|
989
1028
|
const graphSeeds = new Map([...scores.entries()].map(([filePath, value]) => [filePath, value.score]));
|
|
990
|
-
const importRows = db.prepare("SELECT file_path, target_path FROM imports WHERE target_path IS NOT NULL")
|
|
1029
|
+
const importRows = db.prepare("SELECT file_path, specifier, target_path FROM imports WHERE target_path IS NOT NULL")
|
|
991
1030
|
.all()
|
|
992
1031
|
.filter((row) => pathAllowed(row.file_path) && pathAllowed(row.target_path));
|
|
1032
|
+
const directLinks = directExplicitImportLinks(importRows, explicitRows);
|
|
1033
|
+
addRankedList(scores, directLinks.rankingRows, "import", 1.8);
|
|
993
1034
|
const graphRows = personalizedPageRank(importRows, graphSeeds).slice(0, Math.max(limit * 3, 30));
|
|
994
1035
|
addRankedList(scores, graphRows, "graph", 1);
|
|
995
|
-
|
|
996
1036
|
const ranked = [...scores.values()]
|
|
997
1037
|
.filter((candidate) => (
|
|
998
1038
|
shouldIndexPath(candidate.path, { excludePatterns })
|
|
@@ -1012,6 +1052,7 @@ export async function searchContextIndexV2(cwd, query, options = {}) {
|
|
|
1012
1052
|
path: candidate.path,
|
|
1013
1053
|
score: Number(candidate.score.toFixed(6)),
|
|
1014
1054
|
sources: [...candidate.sources],
|
|
1055
|
+
links: directLinks.byPath.get(candidate.path) ?? [],
|
|
1015
1056
|
symbols
|
|
1016
1057
|
};
|
|
1017
1058
|
});
|
|
@@ -1105,31 +1146,79 @@ export async function buildContextPack(cwd, query, options = {}) {
|
|
|
1105
1146
|
}
|
|
1106
1147
|
|
|
1107
1148
|
for (const result of rankedResults) {
|
|
1108
|
-
let text;
|
|
1149
|
+
let text, fileContentHash, sanitizedContentDigest, sensitiveContentRedacted = false;
|
|
1150
|
+
let safeResult = result;
|
|
1109
1151
|
try {
|
|
1110
1152
|
const file = projectFileInfo(cwd, result.path, projectRoot, directoryCache);
|
|
1111
1153
|
if (!file) continue;
|
|
1112
|
-
|
|
1154
|
+
const bytes = fs.readFileSync(file.absolute);
|
|
1155
|
+
const sanitized = redactSensitiveProjectFileText(result.path, bytes.toString("utf8"));
|
|
1156
|
+
if (!sanitized.lineCountPreserved) continue;
|
|
1157
|
+
text = sanitized.text;
|
|
1158
|
+
sensitiveContentRedacted = sanitized.redacted;
|
|
1159
|
+
safeResult = {
|
|
1160
|
+
...result,
|
|
1161
|
+
symbols: result.symbols.map((symbol) => ({
|
|
1162
|
+
...symbol,
|
|
1163
|
+
signature: redactSensitiveProjectFileText(result.path, String(symbol.signature ?? "")).text
|
|
1164
|
+
}))
|
|
1165
|
+
};
|
|
1166
|
+
sanitizedContentDigest = sanitized.redacted
|
|
1167
|
+
? `context-sanitized-file-v1:${sha256(sanitized.text)}`
|
|
1168
|
+
: undefined;
|
|
1169
|
+
fileContentHash = sanitizedContentDigest ?? `context-file-v1:${sha256(bytes)}`;
|
|
1113
1170
|
} catch {
|
|
1114
1171
|
continue;
|
|
1115
1172
|
}
|
|
1116
|
-
const snippets = lineRangesForFile(text,
|
|
1173
|
+
const snippets = lineRangesForFile(text, safeResult, search.terms, includeCode);
|
|
1117
1174
|
const body = snippets.length > 0
|
|
1118
1175
|
? snippets.map((snippet) => `lines ${snippet.start}-${snippet.end}\n${snippet.text}`).join("\n\n")
|
|
1119
|
-
:
|
|
1176
|
+
: safeResult.symbols.slice(0, 8).map((symbol) => `line ${symbol.line}: ${symbol.signature}`).join("\n");
|
|
1120
1177
|
const itemText = [`### ${result.path}`, `why: ${result.sources.join(", ")}`, body].filter(Boolean).join("\n");
|
|
1121
1178
|
const tokens = estimateContextTokens(itemText);
|
|
1122
|
-
const
|
|
1179
|
+
const itemRanges = snippets.length > 0
|
|
1180
|
+
? snippets.map(({ start, end }) => ({ start, end }))
|
|
1181
|
+
: safeResult.symbols.slice(0, 8).map((symbol) => ({ start: symbol.line, end: symbol.endLine ?? symbol.line }));
|
|
1182
|
+
const representation = snippets.length > 0 ? "snippet" : body ? "symbol-map" : "navigation";
|
|
1183
|
+
const item = {
|
|
1184
|
+
...safeResult,
|
|
1185
|
+
text: itemText,
|
|
1186
|
+
estimatedTokens: tokens,
|
|
1187
|
+
truncated: false,
|
|
1188
|
+
fileContentHash,
|
|
1189
|
+
...(sanitizedContentDigest ? { sanitizedContentDigest } : {}),
|
|
1190
|
+
payloadHash: `context-payload-v1:${sha256(itemText)}`,
|
|
1191
|
+
representation,
|
|
1192
|
+
ranges: itemRanges,
|
|
1193
|
+
generation: 1,
|
|
1194
|
+
sensitiveContentRedacted
|
|
1195
|
+
};
|
|
1123
1196
|
if (estimateContextTokens(renderPack([...selected, item])) > budgetTokens) {
|
|
1124
1197
|
if (selected.length === 0) {
|
|
1125
1198
|
const remaining = budgetTokens - estimateContextTokens(renderPack([]));
|
|
1126
1199
|
let chars = Math.max(0, remaining * 4 - 16);
|
|
1127
|
-
let truncated = {
|
|
1200
|
+
let truncated = {
|
|
1201
|
+
...safeResult,
|
|
1202
|
+
text: itemText.slice(0, chars),
|
|
1203
|
+
estimatedTokens: remaining,
|
|
1204
|
+
truncated: true,
|
|
1205
|
+
fileContentHash,
|
|
1206
|
+
...(sanitizedContentDigest ? { sanitizedContentDigest } : {}),
|
|
1207
|
+
payloadHash: "",
|
|
1208
|
+
representation: `${representation}-truncated`,
|
|
1209
|
+
ranges: [],
|
|
1210
|
+
generation: 1,
|
|
1211
|
+
sensitiveContentRedacted
|
|
1212
|
+
};
|
|
1128
1213
|
while (chars > 0 && estimateContextTokens(renderPack([truncated])) > budgetTokens) {
|
|
1129
1214
|
chars = Math.max(0, chars - 16);
|
|
1130
1215
|
truncated = { ...truncated, text: itemText.slice(0, chars) };
|
|
1131
1216
|
}
|
|
1132
|
-
if (chars > 0) selected.push(
|
|
1217
|
+
if (chars > 0) selected.push({
|
|
1218
|
+
...truncated,
|
|
1219
|
+
estimatedTokens: estimateContextTokens(truncated.text),
|
|
1220
|
+
payloadHash: `context-payload-v1:${sha256(truncated.text)}`
|
|
1221
|
+
});
|
|
1133
1222
|
}
|
|
1134
1223
|
continue;
|
|
1135
1224
|
}
|
|
@@ -1203,10 +1292,10 @@ export async function buildTestImpact(cwd, changedFiles = [], options = {}) {
|
|
|
1203
1292
|
export function appendContextTelemetry(cwd, event) {
|
|
1204
1293
|
const paths = contextEnginePaths(cwd);
|
|
1205
1294
|
const record = {
|
|
1206
|
-
schemaVersion: TELEMETRY_SCHEMA_VERSION,
|
|
1207
1295
|
source: "piagent",
|
|
1208
|
-
|
|
1209
|
-
|
|
1296
|
+
...event,
|
|
1297
|
+
schemaVersion: TELEMETRY_SCHEMA_VERSION, telemetrySource: "piagent",
|
|
1298
|
+
recordedAt: typeof event?.recordedAt === "string" ? event.recordedAt : nowIso()
|
|
1210
1299
|
};
|
|
1211
1300
|
appendJsonlBounded(paths.telemetry, record, { maxBytes: MAX_TELEMETRY_FILE_BYTES, mode: 0o600, projectRoot: cwd });
|
|
1212
1301
|
return record;
|
|
@@ -1219,113 +1308,27 @@ export function readContextTelemetry(cwd, options = {}) {
|
|
|
1219
1308
|
return readJsonlTail(target, { limit, maxBytes, projectRoot: cwd });
|
|
1220
1309
|
}
|
|
1221
1310
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1311
|
+
/** Fail-closed telemetry inspection for benchmark evidence. */
|
|
1312
|
+
export function inspectContextTelemetry(cwd, options = {}) {
|
|
1313
|
+
return inspectContextTelemetryIntegrity(cwd, contextEnginePaths(cwd).telemetry, options);
|
|
1224
1314
|
}
|
|
1225
|
-
|
|
1226
1315
|
export function buildContextEfficiencyReport(cwd, options = {}) {
|
|
1227
1316
|
const events = readContextTelemetry(cwd, options);
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
const key = `${event.toolName}:${event.targetHash ?? event.inputHash ?? ""}`;
|
|
1240
|
-
if (seenReads.has(key)) duplicateReads += 1;
|
|
1241
|
-
else seenReads.add(key);
|
|
1242
|
-
}
|
|
1243
|
-
const outputChars = toolResults.reduce((sum, event) => sum + Number(event.outputChars ?? 0), 0);
|
|
1244
|
-
const duplicateOutputChars = toolResults.reduce((sum, event) => sum + (event.repeated ? Number(event.outputChars ?? 0) : 0), 0);
|
|
1245
|
-
const averageActiveTools = prompts.length > 0
|
|
1246
|
-
? prompts.reduce((sum, event) => sum + Number(event.activeTools ?? 0), 0) / prompts.length
|
|
1247
|
-
: 0;
|
|
1248
|
-
const averageSystemPromptTokens = prompts.length > 0
|
|
1249
|
-
? prompts.reduce((sum, event) => sum + Number(event.systemPromptTokens ?? 0), 0) / prompts.length
|
|
1250
|
-
: 0;
|
|
1251
|
-
const averageToolSchemaTokens = prompts.length > 0
|
|
1252
|
-
? prompts.reduce((sum, event) => sum + Number(event.toolSchemaTokens ?? 0), 0) / prompts.length
|
|
1253
|
-
: 0;
|
|
1254
|
-
const lowConfidencePacks = packs.filter((event) => ["none", "low"].includes(event.confidence)).length;
|
|
1255
|
-
const feedback = retrievalFeedback(cwd);
|
|
1256
|
-
const duplicateReadRate = ratio(duplicateReads, readCalls);
|
|
1257
|
-
const duplicateOutputRate = ratio(duplicateOutputChars, outputChars);
|
|
1258
|
-
const schemaShare = ratio(averageToolSchemaTokens, averageSystemPromptTokens);
|
|
1259
|
-
const lowConfidenceRate = ratio(lowConfidencePacks, packs.length);
|
|
1260
|
-
const activeToolPenalty = Math.max(0, Math.min(1, (averageActiveTools - 12) / 24));
|
|
1261
|
-
const wasteScore = Math.round(100 * (
|
|
1262
|
-
duplicateReadRate * 0.3
|
|
1263
|
-
+ duplicateOutputRate * 0.25
|
|
1264
|
-
+ Math.min(1, schemaShare * 3) * 0.2
|
|
1265
|
-
+ lowConfidenceRate * 0.15
|
|
1266
|
-
+ activeToolPenalty * 0.1
|
|
1267
|
-
));
|
|
1268
|
-
const recommendations = [];
|
|
1269
|
-
if (duplicateReadRate > 0.2) recommendations.push("Repeated reads are high; reuse the current working set before searching again.");
|
|
1270
|
-
if (duplicateOutputRate > 0.15) recommendations.push("Repeated tool output is high; prefer delta results and narrower verification.");
|
|
1271
|
-
if (schemaShare > 0.15 || averageActiveTools > 20) recommendations.push("Tool surface is large; activate only the workflow groups needed for the next turn.");
|
|
1272
|
-
if (lowConfidenceRate > 0.4) recommendations.push("Retrieval confidence is low; rebuild the index or run one bounded finder pass.");
|
|
1273
|
-
if (feedback.selected >= 4 && feedback.utilizationRate < 0.45) recommendations.push("Context-pack utilization is low; reduce pack breadth or improve task-specific ranking signals.");
|
|
1274
|
-
if (recommendations.length === 0) recommendations.push("No dominant context waste signal was detected in the sampled events.");
|
|
1275
|
-
const report = {
|
|
1276
|
-
schemaVersion: 1,
|
|
1277
|
-
source: "piagent",
|
|
1278
|
-
generatedAt: nowIso(),
|
|
1279
|
-
sample: {
|
|
1280
|
-
events: events.length,
|
|
1281
|
-
prompts: prompts.length,
|
|
1282
|
-
toolCalls: toolCalls.length,
|
|
1283
|
-
toolResults: toolResults.length,
|
|
1284
|
-
contextPacks: packs.length,
|
|
1285
|
-
compactions: compactions.length
|
|
1286
|
-
},
|
|
1287
|
-
metrics: {
|
|
1288
|
-
averageActiveTools: Number(averageActiveTools.toFixed(2)),
|
|
1289
|
-
averageSystemPromptTokens: Math.round(averageSystemPromptTokens),
|
|
1290
|
-
averageToolSchemaTokens: Math.round(averageToolSchemaTokens),
|
|
1291
|
-
toolSchemaShare: Number(schemaShare.toFixed(4)),
|
|
1292
|
-
readCalls,
|
|
1293
|
-
duplicateReads,
|
|
1294
|
-
duplicateReadRate: Number(duplicateReadRate.toFixed(4)),
|
|
1295
|
-
outputChars,
|
|
1296
|
-
duplicateOutputChars,
|
|
1297
|
-
duplicateOutputRate: Number(duplicateOutputRate.toFixed(4)),
|
|
1298
|
-
lowConfidencePacks,
|
|
1299
|
-
lowConfidenceRate: Number(lowConfidenceRate.toFixed(4)),
|
|
1300
|
-
contextSelections: feedback.selected,
|
|
1301
|
-
contextSelectionsUsed: feedback.used,
|
|
1302
|
-
contextSelectionsUnused: feedback.unused,
|
|
1303
|
-
contextUtilizationRate: Number(feedback.utilizationRate.toFixed(4)),
|
|
1304
|
-
contextWasteScore: wasteScore
|
|
1305
|
-
},
|
|
1306
|
-
methodology: {
|
|
1307
|
-
scoreRange: "0-100; lower is better",
|
|
1308
|
-
weights: {
|
|
1309
|
-
duplicateReads: 0.3,
|
|
1310
|
-
duplicateOutput: 0.25,
|
|
1311
|
-
toolSchemaShare: 0.2,
|
|
1312
|
-
lowConfidenceRetrieval: 0.15,
|
|
1313
|
-
activeToolExcess: 0.1
|
|
1314
|
-
},
|
|
1315
|
-
retrievalFeedback: "Positive-only reranking: a path receives a weak boost only when a prior pack selected it and the same session later used it. Unseen and unused paths are not penalized.",
|
|
1316
|
-
note: "This is an operational signal, not a quality verdict. Compare it with task acceptance and verification results."
|
|
1317
|
-
},
|
|
1318
|
-
recommendations
|
|
1319
|
-
};
|
|
1320
|
-
const paths = contextEnginePaths(cwd);
|
|
1321
|
-
ensurePrivateStateDirectory(cwd, paths.root, "Context report directory");
|
|
1322
|
-
const reportPath = resolveLocalStatePath(cwd, paths.report, { label: "Context efficiency report" });
|
|
1323
|
-
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 });
|
|
1324
|
-
return report;
|
|
1317
|
+
return writeContextEfficiencyReport(cwd, events, {
|
|
1318
|
+
contextEnginePaths: () => contextEnginePaths(cwd),
|
|
1319
|
+
nowIso,
|
|
1320
|
+
retrievalFeedback: () => retrievalFeedback(cwd)
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
function canonicalJson(value) {
|
|
1324
|
+
return JSON.stringify(value, (_key, nested) => {
|
|
1325
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return nested;
|
|
1326
|
+
return Object.fromEntries(Object.keys(nested).sort().map((key) => [key, nested[key]]));
|
|
1327
|
+
});
|
|
1325
1328
|
}
|
|
1326
1329
|
|
|
1327
1330
|
export function toolResultFingerprint(toolName, input, content) {
|
|
1328
|
-
const inputHash = sha256(
|
|
1331
|
+
const inputHash = sha256(canonicalJson(input ?? {}));
|
|
1329
1332
|
const text = Array.isArray(content)
|
|
1330
1333
|
? content.filter((block) => block && typeof block === "object" && block.type === "text").map((block) => block.text ?? "").join("\n")
|
|
1331
1334
|
: String(content ?? "");
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const PLANNED_CONTEXT_REASON = /^criterion-[a-z0-9_-]+(?:\s|$)/i;
|
|
2
|
+
const RUNTIME_OBSERVED_READ_REASONS = new Set([
|
|
3
|
+
"Runtime observed successful source read.",
|
|
4
|
+
"Runtime observed successful document read."
|
|
5
|
+
]);
|
|
6
|
+
const RUNTIME_CONFIRMED_DELIVERY_REASONS = new Set([
|
|
7
|
+
"Runtime confirmed delivery of a bounded Context Engine navigation pack.",
|
|
8
|
+
"Runtime confirmed delivery of criterion-selected context.",
|
|
9
|
+
"Runtime confirmed delivery of a /context pack message.",
|
|
10
|
+
"Runtime confirmed delivery of an explicit Context Engine tool pack."
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function isPlannedContextEntry(entry) {
|
|
14
|
+
return Boolean(
|
|
15
|
+
entry
|
|
16
|
+
&& typeof entry.path === "string"
|
|
17
|
+
&& typeof entry.reason === "string"
|
|
18
|
+
&& PLANNED_CONTEXT_REASON.test(entry.reason.trim())
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isRuntimeOwnedContextEvidenceEntry(entry) {
|
|
23
|
+
if (!entry || typeof entry !== "object") return false;
|
|
24
|
+
const reason = typeof entry.reason === "string" ? entry.reason.trim() : "";
|
|
25
|
+
return RUNTIME_OBSERVED_READ_REASONS.has(reason) || RUNTIME_CONFIRMED_DELIVERY_REASONS.has(reason);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isDurableContextEvidenceEntry(entry) {
|
|
29
|
+
return Boolean(
|
|
30
|
+
entry
|
|
31
|
+
&& typeof entry.path === "string"
|
|
32
|
+
&& entry.path.trim().length > 0
|
|
33
|
+
&& isRuntimeOwnedContextEvidenceEntry(entry)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function durableContextEvidenceEntries(taskOrEntries) {
|
|
38
|
+
const entries = Array.isArray(taskOrEntries)
|
|
39
|
+
? taskOrEntries
|
|
40
|
+
: Array.isArray(taskOrEntries?.contextManifest)
|
|
41
|
+
? taskOrEntries.contextManifest
|
|
42
|
+
: [];
|
|
43
|
+
return entries.filter(isDurableContextEvidenceEntry);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function hasDurableContextEvidence(taskOrEntries) {
|
|
47
|
+
return durableContextEvidenceEntries(taskOrEntries).length > 0;
|
|
48
|
+
}
|