@tea-agent/loop-agent 0.42.0-next.8 → 0.42.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 +56 -50
- package/dist/application/dag/run-dag.js +41 -0
- package/dist/application/task-lifecycle/advance.js +9 -4
- package/dist/build-stamp.json +3 -3
- package/dist/cli/program.js +1 -1
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/task-source-prepare.js +3 -1
- package/dist/executors/dag-pi-executor.js +1809 -213
- package/dist/executors/pi-extension-resolver.js +14 -2
- package/dist/executors/shell-executor.js +178 -73
- package/dist/shared/dag-failure-category.js +6 -0
- package/dist/task/contract/apply.js +36 -2
- package/dist/task/source-prepare/parse-intent.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +4 -2
- package/dist/worker/console/chat/pi-runtime.js +45 -2
- package/dist/worker/console/chat/resource-loader.js +4 -1
- package/dist/worker/console/chat/routes.js +28 -12
- package/dist/worker/console/chat/sdd-data-alignment.js +13 -2
- package/dist/worker/console/chat/session-store.js +5 -1
- package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
- package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
- package/dist/worker/console/chat/subagents/index.js +5 -0
- package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
- package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
- package/dist/worker/console/chat/subagents/types.js +13 -0
- package/dist/worker/console/chat/tool-preview.js +10 -0
- package/dist/worker/console/chat/tools.js +11 -1
- package/dist/worker/console/chat/turn-process.js +1 -0
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-B0Q0nClf.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-DCPjC19G.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-CP5n9jmG.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-CmaWi_Wg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Ct5tcMfZ.js} +1 -1
- package/dist/worker/console/static/assets/channel-DAS07MdS.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-C0osm1Zf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-CDWviED7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-mtSk1-j7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-D5J9PHC7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-Begg4WAa.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-sF1AEy7T.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-BqbsWXZb.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BKJrPcJ6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-CcwNMRho.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-BEJi3iIs.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cwk-5jiW.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cwk-5jiW.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-DRLgTvVu.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-BoAVG3cs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-Bzol06hR.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-UmliJM77.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BWLcJkFo.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CgApOm-O.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-Bh3RgTLs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-Dsh-D5nE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-Dkbbpcpb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-3EBaA3t3.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-Bq2Dc5ok.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-De7y55ha.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-fLiDbQRh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-Du617mp1.js} +1 -1
- package/dist/worker/console/static/assets/index-C0O48S_P.js +449 -0
- package/dist/worker/console/static/assets/index-CzKf4U8P.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-BwmlF-XP.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-zsGtS59u.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-DTTPTe3f.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BkXg9aP-.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-7U2ue5IE.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-BUuGHmWO.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-7q6fX0Sv.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-CkQPJN55.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-CCal_tfX.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-B7bpxyAZ.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-CJmww7D-.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-D6ldWJEv.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-VGh33I9e.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CCkDrIjU.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-NC8f5Otb.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-_92ezZdF.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyJg5gzW.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-BdUCGtbP.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B1cw_0Uy.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-CdD1H8ia.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-Dkdk3oFo.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-CKu2uPPY.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-ClqAnuG8.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/chat-view-types.js +1 -1
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +23 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +32 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +30 -14
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +133 -5
- package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/constants.js +22 -22
- package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
- package/dist/worker/observe/static/dag-history-labels.js +1 -0
- package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
- package/dist/worker/observe/static/dag-inspector-humanize.js +339 -0
- package/dist/worker/observe/static/dag-node-purpose.js +10 -10
- package/dist/worker/observe/static/index.html +4 -4
- package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
- package/dist/worker/observe/static/relations.js +5 -5
- package/dist/worker/observe/static/styles.css +171 -0
- package/dist/worker/observe/static/views/dag-graph.js +1 -1
- package/dist/worker/observe/static/views/dag-inspector.js +329 -174
- package/dist/worker/observe/static/views/dag.js +16 -12
- package/dist/worker/observe/static/views/session-timeline.js +5 -3
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +18 -0
- package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +30 -1
- package/dist/workflows/dag/dag-retry-schema.js +3 -0
- package/dist/workflows/dag/frontend-contract-facts.js +130 -0
- package/dist/workflows/dag/frontend-design-policy.js +101 -16
- package/dist/workflows/dag/frontend-implementation-contract.js +410 -41
- package/dist/workflows/dag/frontend-plan-render.js +13 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +25 -1
- package/dist/workflows/dag/frontend-recovery-plan.js +6 -2
- package/dist/workflows/dag/frontend-recovery-run.js +169 -15
- package/dist/workflows/dag/frontend-risk.js +2 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +17 -1
- package/dist/workflows/dag/frontend-shape.js +16 -6
- package/dist/workflows/dag/frontend-test-execution-evidence.js +48 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +3 -0
- package/dist/workflows/dag/frontend-verification-trace.js +38 -4
- package/dist/workflows/dag/frontend-writer-admission.js +46 -12
- package/dist/workflows/dag/init-hybrid.js +59 -29
- package/dist/workflows/dag/node-execution.js +277 -85
- package/dist/workflows/dag/recovery-lease.js +106 -16
- package/dist/workflows/dag/rerun-feedback.js +60 -0
- package/dist/workflows/dag/rerun-plan.js +90 -4
- package/dist/workflows/dag/rerun-run.js +28 -6
- package/dist/workflows/dag/rerun-task.js +224 -15
- package/dist/workflows/dag/retry-policy.js +27 -10
- package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
- package/dist/workflows/dag/runner.js +238 -29
- package/dist/workflows/dag/scheduler.js +21 -6
- package/dist/workflows/dag/structured-output-repair.js +4 -1
- package/dist/workflows/dag/types.js +4 -3
- package/dist/workflows/dag/validate.js +10 -8
- package/dist/workflows/dag/workspace-checkpoint.js +66 -0
- package/docs/operations/README.md +1 -0
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +6 -4
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/harness.json +1 -1
- package/package.json +4 -5
- package/skills/frontend-plan/SKILL.md +14 -1
- package/skills/frontend-plan/references/decision-contract.md +101 -5
- package/skills/frontend-plan/references/design-decisions.md +32 -0
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
- package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
|
@@ -62,9 +62,21 @@ function readSettingsPackages(settingsPath) {
|
|
|
62
62
|
return undefined;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Strip an `npm:` prefix and a trailing `@<version>`/`@<dist-tag>` suffix
|
|
67
|
+
* from a settings package spec, e.g. `npm:pi-lens@3.8.72` → `pi-lens`,
|
|
68
|
+
* `npm:@scope/pkg@1.0.0` → `@scope/pkg`. The leading `@` of a scoped
|
|
69
|
+
* package name is preserved (the version separator is the LAST `@` at a
|
|
70
|
+
* positive index). Mirrors the console-side spec parsing semantics so a
|
|
71
|
+
* version-pinned install (e.g. via `pi install npm:pi-lens@3.8.72`) resolves
|
|
72
|
+
* identically in both surfaces.
|
|
73
|
+
*/
|
|
66
74
|
export function packageNameFromSpec(spec) {
|
|
67
|
-
|
|
75
|
+
const base = spec.startsWith("npm:") ? spec.slice(4) : spec;
|
|
76
|
+
const at = base.lastIndexOf("@");
|
|
77
|
+
if (at > 0)
|
|
78
|
+
return base.slice(0, at);
|
|
79
|
+
return base;
|
|
68
80
|
}
|
|
69
81
|
function loadPackageInventories(repoRoot) {
|
|
70
82
|
const agentDir = getPiAgentDir();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { withFrontendTestReporter, parseFrontendTestExecutionReport } from "../workflows/dag/frontend-test-execution-evidence.js";
|
|
3
4
|
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
5
6
|
import path from "node:path";
|
|
@@ -15,7 +16,7 @@ import { buildRequirementCoverageGateShellCommand, expandShellPreset, buildVerdi
|
|
|
15
16
|
import { materializeBackendTestAnalysisContract } from "../workflows/dag/backend-test-analysis-contract.js";
|
|
16
17
|
import { extractBackendTestContractEnvelope } from "../workflows/dag/backend-test-contract-envelope.js";
|
|
17
18
|
import { materializeFrontendImplementationContract } from "../workflows/dag/frontend-implementation-contract.js";
|
|
18
|
-
import { assertFrontendSourceBindingFresh, buildContractFromCommittedFacts, deterministicSha256, frontendImplementationContractSchema, serializeCompactDeterministicJson, sha256Hex, writeFrontendImplementationContractArtifact, } from "../workflows/dag/frontend-implementation-contract.js";
|
|
19
|
+
import { assertFrontendSourceBindingFresh, buildContractFromCommittedFacts, deterministicSha256, frontendImplementationContractSchema, serializeCompactDeterministicJson, sha256Hex, writeFrontendImplementationContractArtifact, buildFrontendVerifyCommandDirectory, } from "../workflows/dag/frontend-implementation-contract.js";
|
|
19
20
|
import { checkFrontendSourceFidelityLedger } from "../workflows/dag/frontend-source-fidelity-ledger.js";
|
|
20
21
|
import { materializeFrontendTestResult, validateFrontendCaseEvidence, } from "../workflows/dag/frontend-test-result-contract.js";
|
|
21
22
|
import { copyFrontendTestStandardScenarios } from "../workflows/dag/frontend-test-standard-scenarios.js";
|
|
@@ -1907,11 +1908,12 @@ async function executePipelineCommands(input, meta, overrideCommands) {
|
|
|
1907
1908
|
return results;
|
|
1908
1909
|
}
|
|
1909
1910
|
/** A+B (AC-008): derive unresolved verification entrypoints from committed plan
|
|
1910
|
-
* facts. A verification target is unresolved when its
|
|
1911
|
-
* match any frozen command
|
|
1912
|
-
* Returns `{ ref, digest }` entries; a missing/unreadable
|
|
1913
|
-
* closed to [] so the router restarts the contract instead
|
|
1914
|
-
|
|
1911
|
+
* facts. A verification target is unresolved when its commandId does not
|
|
1912
|
+
* match any frozen command directory entry derived from the verify shell
|
|
1913
|
+
* evidence bundle. Returns `{ ref, digest }` entries; a missing/unreadable
|
|
1914
|
+
* plan ledger fails closed to [] so the router restarts the contract instead
|
|
1915
|
+
* of inventing refs. */
|
|
1916
|
+
async function deriveUnresolvedVerificationEntrypointRefs(runDir, frozenCommandIds) {
|
|
1915
1917
|
try {
|
|
1916
1918
|
const { restorePlanPatchFromCommittedFacts } = await import("../workflows/dag/frontend-shadow-dual-write.js");
|
|
1917
1919
|
const records = await readTypedEventStoreFromJsonl(path.join(runDir, "frontend-plan-pi", "plan-typed-facts.jsonl"));
|
|
@@ -1920,15 +1922,13 @@ async function deriveUnresolvedVerificationEntrypointRefs(runDir, frozenCommandL
|
|
|
1920
1922
|
?.verificationTargets;
|
|
1921
1923
|
if (!Array.isArray(targets))
|
|
1922
1924
|
return [];
|
|
1923
|
-
const frozen = new Set(
|
|
1924
|
-
.map((label) => label.trim().toLowerCase())
|
|
1925
|
-
.filter(Boolean));
|
|
1925
|
+
const frozen = new Set(frozenCommandIds.filter(Boolean));
|
|
1926
1926
|
const refs = [];
|
|
1927
1927
|
for (const item of targets) {
|
|
1928
1928
|
const target = item;
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1929
|
+
// v2 plan facts carry commandId as the stable directory key.
|
|
1930
|
+
const commandId = typeof target.commandId === "string" ? target.commandId : "";
|
|
1931
|
+
if (commandId && frozen.has(commandId)) {
|
|
1932
1932
|
continue;
|
|
1933
1933
|
}
|
|
1934
1934
|
const file = typeof target.file === "string" ? target.file : "";
|
|
@@ -1951,6 +1951,23 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
1951
1951
|
const bundle = shell.frontendVerificationBundle;
|
|
1952
1952
|
const cwd = resolveShellCwd(input.cwd, shell.cwd);
|
|
1953
1953
|
const results = [];
|
|
1954
|
+
const commandResultsByKey = new Map();
|
|
1955
|
+
const commandKey = (command) => `${cwd}\u0000${command.trim()}`;
|
|
1956
|
+
const reuseCommandResult = (result, command) => {
|
|
1957
|
+
const reusedAt = new Date().toISOString();
|
|
1958
|
+
return {
|
|
1959
|
+
...result,
|
|
1960
|
+
command,
|
|
1961
|
+
durationMs: 0,
|
|
1962
|
+
wallDurationMs: 0,
|
|
1963
|
+
executionDurationMs: result.executionDurationMs ?? result.durationMs,
|
|
1964
|
+
startedAt: reusedAt,
|
|
1965
|
+
finishedAt: reusedAt,
|
|
1966
|
+
stdoutArtifactPath: undefined,
|
|
1967
|
+
stderrArtifactPath: undefined,
|
|
1968
|
+
reused: true,
|
|
1969
|
+
};
|
|
1970
|
+
};
|
|
1954
1971
|
let beforeStatus;
|
|
1955
1972
|
try {
|
|
1956
1973
|
beforeStatus = await readGitStatusPorcelain(input.cwd);
|
|
@@ -1959,22 +1976,32 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
1959
1976
|
beforeStatus = undefined;
|
|
1960
1977
|
}
|
|
1961
1978
|
const lintResults = [];
|
|
1979
|
+
const successfulLabels = new Map();
|
|
1980
|
+
const successfulCommandTexts = new Map();
|
|
1981
|
+
const executedTests = new Map();
|
|
1982
|
+
const commandExecutionEvidence = new Map();
|
|
1962
1983
|
for (const command of bundle.lintCommands ?? []) {
|
|
1963
|
-
const
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1984
|
+
const key = commandKey(command);
|
|
1985
|
+
const cached = commandResultsByKey.get(key);
|
|
1986
|
+
let result = cached ? reuseCommandResult(cached, command) : undefined;
|
|
1987
|
+
if (!result) {
|
|
1988
|
+
const commandNumber = results.length + 1;
|
|
1989
|
+
result = await executeShellCommand({
|
|
1990
|
+
command,
|
|
1991
|
+
cwd,
|
|
1992
|
+
timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
|
|
1993
|
+
envAllowlist: shell.envAllowlist,
|
|
1994
|
+
dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
|
|
1995
|
+
reportActivity: input.reportActivity,
|
|
1996
|
+
outputArtifacts: {
|
|
1997
|
+
stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
|
|
1998
|
+
stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
|
|
1999
|
+
},
|
|
2000
|
+
});
|
|
2001
|
+
commandResultsByKey.set(key, result);
|
|
2002
|
+
}
|
|
1977
2003
|
results.push(result);
|
|
2004
|
+
lintResults.push(result);
|
|
1978
2005
|
}
|
|
1979
2006
|
let lintAssessment;
|
|
1980
2007
|
if ((bundle.lintCommands?.length ?? 0) > 0 &&
|
|
@@ -2008,30 +2035,64 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2008
2035
|
labels: bundle.behaviorEvidence.commandLabels,
|
|
2009
2036
|
},
|
|
2010
2037
|
];
|
|
2011
|
-
const
|
|
2038
|
+
const failedGroups = new Set();
|
|
2039
|
+
const groupFailures = [];
|
|
2012
2040
|
for (const group of lintBlocked ? [] : groups) {
|
|
2013
2041
|
for (const [index, command] of group.commands.entries()) {
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2042
|
+
if (failedGroups.has(group.name))
|
|
2043
|
+
break;
|
|
2044
|
+
const key = commandKey(command);
|
|
2045
|
+
const cached = commandResultsByKey.get(key);
|
|
2046
|
+
let result = cached ? reuseCommandResult(cached, command) : undefined;
|
|
2047
|
+
if (!result) {
|
|
2048
|
+
const commandNumber = results.length + 1;
|
|
2049
|
+
const reportPath = path.join(meta.runDir, input.task.id, "commands", `${commandNumber}-${randomUUID()}.vitest.json`);
|
|
2050
|
+
const observedCommand = await withFrontendTestReporter({ command, cwd, reportPath });
|
|
2051
|
+
if (observedCommand)
|
|
2052
|
+
await mkdir(path.dirname(reportPath), { recursive: true });
|
|
2053
|
+
result = await executeShellCommand({
|
|
2054
|
+
command: observedCommand ?? command,
|
|
2055
|
+
cwd,
|
|
2056
|
+
timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
|
|
2057
|
+
envAllowlist: shell.envAllowlist,
|
|
2058
|
+
dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
|
|
2059
|
+
reportActivity: input.reportActivity,
|
|
2060
|
+
outputArtifacts: {
|
|
2061
|
+
stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
|
|
2062
|
+
stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
|
|
2063
|
+
},
|
|
2064
|
+
});
|
|
2065
|
+
let reportError;
|
|
2066
|
+
if (observedCommand && result.ok) {
|
|
2067
|
+
try {
|
|
2068
|
+
commandExecutionEvidence.set(key, parseFrontendTestExecutionReport(JSON.parse(await readFile(reportPath, "utf8")), cwd));
|
|
2069
|
+
}
|
|
2070
|
+
catch (error) {
|
|
2071
|
+
reportError = `frontend test execution report unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
if (reportError) {
|
|
2075
|
+
result = { ...result, ok: false, failureCategory: "invalid-output", stderr: reportError };
|
|
2076
|
+
}
|
|
2077
|
+
commandResultsByKey.set(key, result);
|
|
2078
|
+
}
|
|
2027
2079
|
results.push(result);
|
|
2028
2080
|
if (result.ok && group.labels[index]) {
|
|
2081
|
+
const observations = commandExecutionEvidence.get(key);
|
|
2082
|
+
if (observations !== undefined)
|
|
2083
|
+
executedTests.set(group.labels[index], observations);
|
|
2029
2084
|
const labels = successfulLabels.get(group.name) ?? [];
|
|
2030
2085
|
labels.push(group.labels[index]);
|
|
2031
2086
|
successfulLabels.set(group.name, labels);
|
|
2087
|
+
const texts = successfulCommandTexts.get(group.name) ?? [];
|
|
2088
|
+
texts.push(command);
|
|
2089
|
+
successfulCommandTexts.set(group.name, texts);
|
|
2032
2090
|
}
|
|
2033
|
-
if (!result.ok)
|
|
2091
|
+
if (!result.ok) {
|
|
2092
|
+
groupFailures.push(result);
|
|
2093
|
+
failedGroups.add(group.name);
|
|
2034
2094
|
break;
|
|
2095
|
+
}
|
|
2035
2096
|
}
|
|
2036
2097
|
}
|
|
2037
2098
|
if (beforeStatus !== undefined) {
|
|
@@ -2061,12 +2122,15 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2061
2122
|
exitCode: result.exitCode,
|
|
2062
2123
|
failureCategory: result.failureCategory,
|
|
2063
2124
|
command: result.command,
|
|
2125
|
+
reused: result.reused === true,
|
|
2064
2126
|
}));
|
|
2127
|
+
// Lint baseline-debt is tolerated, so a lint-only failure must not become the
|
|
2128
|
+
// verification failure. A shared lint/static or lint/behavior command is
|
|
2129
|
+
// different: once reused by a non-lint lane, its failure belongs to that lane
|
|
2130
|
+
// and must remain the primary failure evidence.
|
|
2065
2131
|
const firstFailure = lintBlocked
|
|
2066
2132
|
? lintResults.find((result) => !result.ok)
|
|
2067
|
-
:
|
|
2068
|
-
.filter((result) => !lintResults.includes(result))
|
|
2069
|
-
.find((result) => !result.ok);
|
|
2133
|
+
: groupFailures[0];
|
|
2070
2134
|
const lintSyntheticFailure = lintBlocked && !firstFailure
|
|
2071
2135
|
? {
|
|
2072
2136
|
failureCategory: "invalid-output",
|
|
@@ -2083,7 +2147,7 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2083
2147
|
mock: {
|
|
2084
2148
|
nodeId: input.task.id,
|
|
2085
2149
|
commandLabels: successfulLabels.get("mock") ?? [],
|
|
2086
|
-
commandTexts:
|
|
2150
|
+
commandTexts: successfulCommandTexts.get("mock") ?? [],
|
|
2087
2151
|
},
|
|
2088
2152
|
static: {
|
|
2089
2153
|
nodeId: input.task.id,
|
|
@@ -2092,6 +2156,7 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2092
2156
|
behavior: {
|
|
2093
2157
|
nodeId: input.task.id,
|
|
2094
2158
|
commandLabels: successfulLabels.get("behavior") ?? [],
|
|
2159
|
+
executedTests,
|
|
2095
2160
|
},
|
|
2096
2161
|
},
|
|
2097
2162
|
});
|
|
@@ -2124,7 +2189,9 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2124
2189
|
nodeId: failedNodeId,
|
|
2125
2190
|
rawFailureCategory: failureCategory,
|
|
2126
2191
|
});
|
|
2127
|
-
const failureOwner = classifiedOwner === "unknown"
|
|
2192
|
+
const failureOwner = classifiedOwner === "unknown" &&
|
|
2193
|
+
["typecheck", "build", "lint", "component-test", "unit-test", "trace"].includes(failureClass)
|
|
2194
|
+
? "implementation" : classifiedOwner;
|
|
2128
2195
|
const restartPhase = classifyRestartPhase(failureOwner);
|
|
2129
2196
|
// A+B (AC-008): verification-config routing. Produce structured
|
|
2130
2197
|
// unresolved entrypoint refs from committed plan facts and the frozen
|
|
@@ -2135,12 +2202,18 @@ async function executeFrontendVerificationBundle(input, meta) {
|
|
|
2135
2202
|
// never re-classifies the same signal twice.
|
|
2136
2203
|
let verificationConfigRoute;
|
|
2137
2204
|
if (failureOwner === "verification-config") {
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2205
|
+
const verifyDirectory = buildFrontendVerifyCommandDirectory({
|
|
2206
|
+
staticLabels: bundle.staticEvidence.commandLabels ?? [],
|
|
2207
|
+
behaviorLabels: bundle.behaviorEvidence.commandLabels ?? [],
|
|
2208
|
+
mockLabels: bundle.mockEvidence?.commandLabels ?? [],
|
|
2209
|
+
lintLabels: bundle.lintEvidence?.commandLabels ?? [],
|
|
2210
|
+
staticCommandTexts: bundle.staticEvidence.commandTexts ?? [],
|
|
2211
|
+
behaviorCommandTexts: bundle.behaviorEvidence.commandTexts ?? [],
|
|
2212
|
+
mockCommandTexts: bundle.mockEvidence?.commandTexts ?? [],
|
|
2213
|
+
lintCommandTexts: bundle.lintEvidence?.commandTexts ?? [],
|
|
2214
|
+
});
|
|
2215
|
+
const frozenCommandLabels = verifyDirectory.map((entry) => entry.label);
|
|
2216
|
+
const unresolvedEntrypointRefs = await deriveUnresolvedVerificationEntrypointRefs(meta.runDir, verifyDirectory.map((entry) => entry.commandId));
|
|
2144
2217
|
verificationConfigRoute = routeVerificationConfig({
|
|
2145
2218
|
unresolvedEntrypointRefs: unresolvedEntrypointRefs.map((ref) => ref.ref),
|
|
2146
2219
|
});
|
|
@@ -2299,19 +2372,38 @@ async function collectReviewRestartPhase(runDir, reviewNodeId) {
|
|
|
2299
2372
|
}
|
|
2300
2373
|
/** A+B (AC-009): deterministic §4.10 restart phase for a committed
|
|
2301
2374
|
* `request_design_changes` fact. Mirrors the review collector. */
|
|
2302
|
-
/**
|
|
2303
|
-
|
|
2304
|
-
async function collectDesignReviewFindingCount(runDir, designNodeId) {
|
|
2375
|
+
/** Read the committed design request as a bounded structured capsule. */
|
|
2376
|
+
async function collectDesignReviewFindings(runDir, designNodeId) {
|
|
2305
2377
|
try {
|
|
2306
2378
|
const records = await readTypedEventStoreFromJsonl(path.join(runDir, designNodeId, "design-typed-facts.jsonl"));
|
|
2307
2379
|
const request = records.find((record) => record.phase === "committed" &&
|
|
2308
2380
|
record.fact.kind ===
|
|
2309
2381
|
"request_design_changes");
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2382
|
+
const fact = request?.fact;
|
|
2383
|
+
const findings = Array.isArray(fact?.findings)
|
|
2384
|
+
? fact.findings.flatMap((finding) => {
|
|
2385
|
+
if (!finding || typeof finding !== "object")
|
|
2386
|
+
return [];
|
|
2387
|
+
const value = finding;
|
|
2388
|
+
if (!(["Critical", "Important", "Minor", "Info"].includes(value.severity) &&
|
|
2389
|
+
typeof value.issue === "string" && value.issue.trim().length > 0))
|
|
2390
|
+
return [];
|
|
2391
|
+
return [{
|
|
2392
|
+
severity: value.severity,
|
|
2393
|
+
...(typeof value.file === "string" && value.file.trim() ? { file: value.file } : {}),
|
|
2394
|
+
...(typeof value.line === "number" && Number.isInteger(value.line) && value.line > 0 ? { line: value.line } : {}),
|
|
2395
|
+
issue: value.issue,
|
|
2396
|
+
...(typeof value.requiredChange === "string" && value.requiredChange.trim() ? { requiredChange: value.requiredChange } : {}),
|
|
2397
|
+
}];
|
|
2398
|
+
}).slice(0, 16)
|
|
2399
|
+
: [];
|
|
2400
|
+
const evidenceRefs = Array.isArray(fact?.evidenceRefs)
|
|
2401
|
+
? fact.evidenceRefs.filter((ref) => typeof ref === "string" && ref.trim().length > 0).slice(0, 16)
|
|
2402
|
+
: [];
|
|
2403
|
+
return { findings, evidenceRefs };
|
|
2312
2404
|
}
|
|
2313
2405
|
catch {
|
|
2314
|
-
return
|
|
2406
|
+
return { findings: [], evidenceRefs: [] };
|
|
2315
2407
|
}
|
|
2316
2408
|
}
|
|
2317
2409
|
async function collectDesignRestartPhase(runDir, designNodeId) {
|
|
@@ -2512,7 +2604,7 @@ async function executeFrontendDesignPolicy(input, meta) {
|
|
|
2512
2604
|
};
|
|
2513
2605
|
}
|
|
2514
2606
|
const uncoveredVTs = contract.verificationTargets
|
|
2515
|
-
.filter((vt) => vt.
|
|
2607
|
+
.filter((vt) => vt.mode !== "static")
|
|
2516
2608
|
.filter((vt) => ![...writeSet].some((pattern) => pathMatchesPattern(vt.file, pattern) || vt.file === pattern));
|
|
2517
2609
|
if (uncoveredVTs.length > 0) {
|
|
2518
2610
|
return {
|
|
@@ -2579,6 +2671,10 @@ async function executeFrontendDesignPolicy(input, meta) {
|
|
|
2579
2671
|
[],
|
|
2580
2672
|
allowedDependencies: config.allowedDependencies,
|
|
2581
2673
|
writeSetPatterns: config.implementationWriteSet ?? [],
|
|
2674
|
+
// Fresh-existence evidence at gate time (Scout pathEvidence.fresh
|
|
2675
|
+
// semantics): a reuse-existing evidencePath must name a file that
|
|
2676
|
+
// exists NOW; greenfield paths are decision=new only.
|
|
2677
|
+
hasFreshPathEvidence: (candidate) => existsSync(path.resolve(input.cwd, candidate)),
|
|
2582
2678
|
});
|
|
2583
2679
|
// 8. Write canonical contract + policy result.
|
|
2584
2680
|
const contractArtifact = await writeFrontendImplementationContractArtifact({
|
|
@@ -2625,17 +2721,15 @@ async function executeFrontendWriterAdmission(input, meta) {
|
|
|
2625
2721
|
const shell = input.task.shell;
|
|
2626
2722
|
const config = shell.frontendWriterAdmission;
|
|
2627
2723
|
try {
|
|
2628
|
-
// 1. Design verdict handling
|
|
2724
|
+
// 1. Design verdict handling. The typed terminal
|
|
2629
2725
|
// verdict is read from the committed typed design terminal fact, not
|
|
2630
2726
|
// from the legacy first-line VERDICT text protocol (AC-003/AC-004).
|
|
2631
|
-
// A request_design_changes verdict
|
|
2632
|
-
//
|
|
2633
|
-
//
|
|
2634
|
-
//
|
|
2635
|
-
// write guard) and the final code review. A missing typed terminal
|
|
2636
|
-
// verdict still fails closed. In small topology the design review node
|
|
2637
|
-
// is absent and the check is vacuous.
|
|
2727
|
+
// A request_design_changes verdict blocks writer admission and is copied
|
|
2728
|
+
// into the receipt as a bounded advisory capsule for recovery. A missing
|
|
2729
|
+
// typed terminal verdict still fails closed. In small topology the design
|
|
2730
|
+
// review node is absent and the check is vacuous.
|
|
2638
2731
|
let verdictPass = true;
|
|
2732
|
+
let designRequested = false;
|
|
2639
2733
|
let designAdvisory;
|
|
2640
2734
|
if (config.designReviewFromNodeId) {
|
|
2641
2735
|
const designKinds = await collectDesignTerminalKinds(meta.runDir, config.designReviewFromNodeId);
|
|
@@ -2643,11 +2737,16 @@ async function executeFrontendWriterAdmission(input, meta) {
|
|
|
2643
2737
|
verdictPass = false;
|
|
2644
2738
|
}
|
|
2645
2739
|
else if (designKinds[0] !== "approve_design") {
|
|
2740
|
+
designRequested = true;
|
|
2741
|
+
const designFindings = await collectDesignReviewFindings(meta.runDir, config.designReviewFromNodeId);
|
|
2646
2742
|
designAdvisory = {
|
|
2647
|
-
verdict:
|
|
2743
|
+
verdict: "request_design_changes",
|
|
2648
2744
|
restartPhase: await collectDesignRestartPhase(meta.runDir, config.designReviewFromNodeId),
|
|
2649
|
-
findingCount:
|
|
2745
|
+
findingCount: designFindings.findings.length,
|
|
2746
|
+
findings: designFindings.findings,
|
|
2747
|
+
evidenceRefs: designFindings.evidenceRefs,
|
|
2650
2748
|
};
|
|
2749
|
+
verdictPass = false;
|
|
2651
2750
|
}
|
|
2652
2751
|
}
|
|
2653
2752
|
// 2. Read the canonical contract + design-policy result.
|
|
@@ -2715,7 +2814,9 @@ async function executeFrontendWriterAdmission(input, meta) {
|
|
|
2715
2814
|
allowedMockStrategies: config.allowedMockStrategies,
|
|
2716
2815
|
workspaceFiles,
|
|
2717
2816
|
});
|
|
2718
|
-
// 6. Design verdict enforcement:
|
|
2817
|
+
// 6. Design verdict enforcement: request_design_changes is a blocked
|
|
2818
|
+
// admission. This keeps the writer fail-closed until the recovery plan
|
|
2819
|
+
// has incorporated the reviewer's concrete findings.
|
|
2719
2820
|
const failureSource = policyRaw.classification === "blocked"
|
|
2720
2821
|
? "frontend-design-policy-shell"
|
|
2721
2822
|
: "frontend-writer-admission-shell";
|
|
@@ -2724,8 +2825,12 @@ async function executeFrontendWriterAdmission(input, meta) {
|
|
|
2724
2825
|
: [
|
|
2725
2826
|
...admission.findings,
|
|
2726
2827
|
{
|
|
2727
|
-
code:
|
|
2728
|
-
|
|
2828
|
+
code: designRequested
|
|
2829
|
+
? "design-review-requested"
|
|
2830
|
+
: "design-approval-missing",
|
|
2831
|
+
message: designRequested
|
|
2832
|
+
? "design review requested changes; writer admission is blocked until recovery incorporates the committed findings"
|
|
2833
|
+
: "design review typed terminal verdict missing or ambiguous (fail closed)",
|
|
2729
2834
|
},
|
|
2730
2835
|
];
|
|
2731
2836
|
const classification = findings.length === 0 && admission.classification === "accepted"
|
|
@@ -63,6 +63,12 @@ const RAW_TO_NORMALIZED = {
|
|
|
63
63
|
// compaction / fresh context before retrying — an executor-side
|
|
64
64
|
// environment condition, not a plan-quality defect.
|
|
65
65
|
"context-overflow": "executor",
|
|
66
|
+
// stopReason=length output-capacity truncation: same executor-side
|
|
67
|
+
// capacity class as context-overflow. Legacy thinking-exhausted labels
|
|
68
|
+
// (pre-output-limit artifacts) map to the same class.
|
|
69
|
+
"output-limit": "executor",
|
|
70
|
+
"writer-thinking-exhausted": "executor",
|
|
71
|
+
"planner-thinking-exhausted": "executor",
|
|
66
72
|
// Deterministic capability/policy refusal: missing verified launcher JS
|
|
67
73
|
// entry, capability allowlist rejection, or missing bounded write tool
|
|
68
74
|
// profile. These are environment/config defects the operator repairs
|
|
@@ -147,7 +147,7 @@ export async function applyTaskContract(input) {
|
|
|
147
147
|
ref,
|
|
148
148
|
changedFiles: [],
|
|
149
149
|
idempotentReplay: true,
|
|
150
|
-
txId: existing.txId,
|
|
150
|
+
...(existing.txId ? { txId: existing.txId } : { noOp: true }),
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
const lock = await acquireTaskContractLock({
|
|
@@ -175,7 +175,9 @@ export async function applyTaskContract(input) {
|
|
|
175
175
|
ref,
|
|
176
176
|
changedFiles: [],
|
|
177
177
|
idempotentReplay: true,
|
|
178
|
-
|
|
178
|
+
...(existingUnderLock.txId
|
|
179
|
+
? { txId: existingUnderLock.txId }
|
|
180
|
+
: { noOp: true }),
|
|
179
181
|
};
|
|
180
182
|
}
|
|
181
183
|
const state = await observeTaskContractState(input.repoRoot, input.taskId);
|
|
@@ -251,6 +253,38 @@ export async function applyTaskContract(input) {
|
|
|
251
253
|
sourceHashes,
|
|
252
254
|
taskConfigSha256,
|
|
253
255
|
});
|
|
256
|
+
// Content-level no-op: when the freshly projected draft hashes
|
|
257
|
+
// identically to both the recorded contract and the on-disk observation,
|
|
258
|
+
// the journaled commit would only churn revision/updatedAt metadata and
|
|
259
|
+
// force downstream consumers (writeSet gate digest binds
|
|
260
|
+
// contractRevision+contractCanonicalHash) to re-issue approvals for
|
|
261
|
+
// zero content change. Return the current ref untouched instead. A
|
|
262
|
+
// drifted disk (observed !== ref) never reaches this short-circuit: the
|
|
263
|
+
// apply is then a repair projection and must run.
|
|
264
|
+
if (state.ref &&
|
|
265
|
+
canonicalHash === state.ref.canonicalHash &&
|
|
266
|
+
state.observedCanonicalHash === state.ref.canonicalHash) {
|
|
267
|
+
await assertLockHeld(lock);
|
|
268
|
+
await appendRequestLedgerEntry({
|
|
269
|
+
repoRoot: input.repoRoot,
|
|
270
|
+
taskId: input.taskId,
|
|
271
|
+
entry: {
|
|
272
|
+
schemaVersion: 1,
|
|
273
|
+
requestId: input.requestId,
|
|
274
|
+
requestPayloadSha256: input.requestPayloadSha256,
|
|
275
|
+
revision: state.ref.revision,
|
|
276
|
+
canonicalHash: state.ref.canonicalHash,
|
|
277
|
+
recordedAt: new Date().toISOString(),
|
|
278
|
+
resultSummary: { ok: true, operation: "apply" },
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
ref: state.ref,
|
|
283
|
+
changedFiles: [],
|
|
284
|
+
idempotentReplay: false,
|
|
285
|
+
noOp: true,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
254
288
|
const nextRevision = currentRevision + 1;
|
|
255
289
|
const nextRef = {
|
|
256
290
|
schemaVersion: TASK_CONTRACT_REF_SCHEMA_VERSION,
|
|
@@ -13,6 +13,13 @@ const HEADING_ALIASES = {
|
|
|
13
13
|
"需求范围",
|
|
14
14
|
"已确认范围",
|
|
15
15
|
"已确认需求",
|
|
16
|
+
// Common zh-CN frontend-PRD variants seen in real PRDs; without these a
|
|
17
|
+
// raw-PRD fallback parse (semantic intake unavailable) yields an empty
|
|
18
|
+
// scope and flips the import to incomplete (EMPTY scope deadlock).
|
|
19
|
+
"目标表面",
|
|
20
|
+
"交付范围",
|
|
21
|
+
"实现范围",
|
|
22
|
+
"交付内容",
|
|
16
23
|
]),
|
|
17
24
|
nonGoals: new Set(["非目标", "out of scope", "non-goals", "non goals"]),
|
|
18
25
|
acceptance: new Set([
|
|
@@ -22,14 +22,16 @@ import path from "node:path";
|
|
|
22
22
|
import { ensureSecureDir, writeSecureJsonSync, } from "../../../infrastructure/console/app-data.js";
|
|
23
23
|
import { isTerminalOperationState, } from "../../../infrastructure/console/operation-store.js";
|
|
24
24
|
import { scrubSecrets } from "./explore-tools.js";
|
|
25
|
-
|
|
25
|
+
/** Bounded preview stored on the event ring / SSE (not the live tool payload). */
|
|
26
|
+
export const OPERATION_PREVIEW_MAX_CHARS = 4000;
|
|
27
|
+
const OPERATION_PREVIEW_TRUNCATION_SUFFIX = "…[truncated]";
|
|
26
28
|
function boundedRedactedText(value, maxChars = OPERATION_PREVIEW_MAX_CHARS) {
|
|
27
29
|
if (typeof value !== "string" || !value.trim())
|
|
28
30
|
return undefined;
|
|
29
31
|
const { scrubbed } = scrubSecrets(value);
|
|
30
32
|
return scrubbed.length <= maxChars
|
|
31
33
|
? scrubbed
|
|
32
|
-
: `${scrubbed.slice(0, maxChars)}
|
|
34
|
+
: `${scrubbed.slice(0, maxChars)}${OPERATION_PREVIEW_TRUNCATION_SUFFIX}`;
|
|
33
35
|
}
|
|
34
36
|
function safeIdentity(value) {
|
|
35
37
|
return boundedRedactedText(value, 240);
|
|
@@ -32,7 +32,8 @@ import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMess
|
|
|
32
32
|
import { parseProviderError, } from "./provider-error.js";
|
|
33
33
|
import { projectCompactSnapshot, } from "./chat-event-store.js";
|
|
34
34
|
import { extractUsageSample } from "./usage.js";
|
|
35
|
-
import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
35
|
+
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
36
|
+
import { buildAgentCustomTool, ExploreOrchestrator, } from "./subagents/index.js";
|
|
36
37
|
import { createOperatorChatResourceLoader, } from "./resource-loader.js";
|
|
37
38
|
import { applySkillAutoInvocationPreferences, isAutoInvocationEnabled, loadResourcePreferences, skillResourceId, } from "./resource-preferences-store.js";
|
|
38
39
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
@@ -115,6 +116,7 @@ export function OPERATOR_CHAT_ACTIVE_TOOL_NAMES() {
|
|
|
115
116
|
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
116
117
|
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
117
118
|
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
119
|
+
...OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS,
|
|
118
120
|
];
|
|
119
121
|
}
|
|
120
122
|
/**
|
|
@@ -1024,14 +1026,33 @@ export class ConsolePiRuntime {
|
|
|
1024
1026
|
compactionControllers = new Map();
|
|
1025
1027
|
siftStates = new Map();
|
|
1026
1028
|
/** Session-less tool definition caches (no AgentSession state). */
|
|
1027
|
-
/** Session-less tool definition caches (no AgentSession state). */
|
|
1028
1029
|
operatorCustomToolsPromise;
|
|
1029
1030
|
exploreCustomToolsPromise;
|
|
1031
|
+
exploreOrchestrator;
|
|
1030
1032
|
bindings;
|
|
1031
1033
|
constructor(options) {
|
|
1032
1034
|
this.options = options;
|
|
1033
1035
|
this.loader = createOperatorChatResourceLoader();
|
|
1034
1036
|
this.bindings = options.bindings ?? createDefaultPiSdkBindings();
|
|
1037
|
+
this.exploreOrchestrator = new ExploreOrchestrator({
|
|
1038
|
+
cwd: this.options.cwd,
|
|
1039
|
+
bindings: this.bindings,
|
|
1040
|
+
loadSafeExploreCustomTools: () => this.loadExploreCustomTools(),
|
|
1041
|
+
// Fallback when the parent session's model is unknown at Agent-call
|
|
1042
|
+
// time (e.g. turn started before materialization finished): resolve
|
|
1043
|
+
// the default Chat model from the child services' modelRuntime.
|
|
1044
|
+
resolveModel: async (modelRuntime) => {
|
|
1045
|
+
const available = this.bindings.listAvailableModels({ modelRuntime });
|
|
1046
|
+
const descriptor = await resolveDefaultChatModel(this.options.cwd, available);
|
|
1047
|
+
if (!descriptor)
|
|
1048
|
+
return undefined;
|
|
1049
|
+
return this.bindings.resolveModel({
|
|
1050
|
+
modelRuntime,
|
|
1051
|
+
provider: descriptor.provider,
|
|
1052
|
+
modelId: descriptor.modelId,
|
|
1053
|
+
});
|
|
1054
|
+
},
|
|
1055
|
+
});
|
|
1035
1056
|
}
|
|
1036
1057
|
/** Absolute path for repo-scoped resource preferences (may be undefined in unit tests). */
|
|
1037
1058
|
get resourcePreferencesPath() {
|
|
@@ -1526,12 +1547,31 @@ export class ConsolePiRuntime {
|
|
|
1526
1547
|
const goalTools = this.options.goalRegistry
|
|
1527
1548
|
? await buildGoalTools({ sessionId, registry: this.options.goalRegistry })
|
|
1528
1549
|
: [];
|
|
1550
|
+
const agentTool = await buildAgentCustomTool({
|
|
1551
|
+
parentSessionId: sessionId,
|
|
1552
|
+
orchestrator: this.exploreOrchestrator,
|
|
1553
|
+
resolveModel: () => {
|
|
1554
|
+
const model = this.sessionModels.get(sessionId);
|
|
1555
|
+
const modelRuntime = this.modelRuntimes.get(sessionId);
|
|
1556
|
+
if (!model || !modelRuntime)
|
|
1557
|
+
return undefined;
|
|
1558
|
+
return this.bindings.resolveModel({
|
|
1559
|
+
modelRuntime,
|
|
1560
|
+
provider: model.provider,
|
|
1561
|
+
modelId: model.modelId,
|
|
1562
|
+
});
|
|
1563
|
+
},
|
|
1564
|
+
}).catch((error) => {
|
|
1565
|
+
process.stderr.write(`[console] chat Agent tool build failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1566
|
+
return undefined;
|
|
1567
|
+
});
|
|
1529
1568
|
return [
|
|
1530
1569
|
...operatorCustomTools,
|
|
1531
1570
|
...exploreCustomTools,
|
|
1532
1571
|
...(questionTool ? [questionTool] : []),
|
|
1533
1572
|
...(todoTool ? [todoTool] : []),
|
|
1534
1573
|
...goalTools,
|
|
1574
|
+
...(agentTool ? [agentTool] : []),
|
|
1535
1575
|
];
|
|
1536
1576
|
}
|
|
1537
1577
|
loadOperatorCustomTools() {
|
|
@@ -2595,6 +2635,7 @@ export class ConsolePiRuntime {
|
|
|
2595
2635
|
catch {
|
|
2596
2636
|
// ignore
|
|
2597
2637
|
}
|
|
2638
|
+
void this.exploreOrchestrator.abortByParent(sessionId);
|
|
2598
2639
|
};
|
|
2599
2640
|
try {
|
|
2600
2641
|
if (options?.turnId && this.options.questionRegistry) {
|
|
@@ -2970,10 +3011,12 @@ export class ConsolePiRuntime {
|
|
|
2970
3011
|
this.thinkingSelections.delete(sessionId);
|
|
2971
3012
|
this.reloadInFlight.delete(sessionId);
|
|
2972
3013
|
this.pendingSteering.delete(sessionId);
|
|
3014
|
+
void this.exploreOrchestrator.abortByParent(sessionId);
|
|
2973
3015
|
}
|
|
2974
3016
|
disposeAll() {
|
|
2975
3017
|
for (const id of [...this.sessions.keys()])
|
|
2976
3018
|
this.dispose(id);
|
|
3019
|
+
void this.exploreOrchestrator.abortAll();
|
|
2977
3020
|
}
|
|
2978
3021
|
}
|
|
2979
3022
|
function mapAssistantMessageEvent(type, sessionId, message) {
|