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