@tea-agent/loop-agent 0.38.3 → 0.38.4
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/AGENTS.md +4 -1
- package/CHANGELOG.md +153 -96
- package/README.md +9 -5
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/executors/dag-pi-executor.js +18 -9
- package/dist/executors/pi-playwright-cli-tool.js +51 -12
- package/dist/executors/shell-executor.js +209 -13
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +32 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/operator-actions.js +116 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +36 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-pm3c2Rsl.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
- package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
- package/dist/worker/console/static/assets/{index-Ya5FE7cD.css → index-C8rOIAJr.css} +1 -1
- package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-DEbyrEfW.js} +71 -71
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-CAyXjQjN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-CcWKn6tS.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/observe/node-input.js +2 -0
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +11 -0
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +561 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +60 -4
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
- package/dist/workflows/dag/failure-category.js +8 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +5 -1
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +151 -11
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +125 -24
- package/dist/workflows/dag/node-execution.js +20 -2
- package/dist/workflows/dag/rerun-plan.js +112 -0
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +91 -0
- package/dist/workflows/dag/types.js +9 -1
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +2 -2
- package/package.json +5 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/playwright-cli/SKILL.md +22 -0
- package/skills/playwright-cli-case-generator/SKILL.md +20 -1
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { collectNextInfo, formatNextInfo, } from "../../scripts/next-info.mjs";
|
|
2
|
+
export async function runInspectNext(repoRoot, args = []) {
|
|
3
|
+
const json = args.includes("--json");
|
|
4
|
+
const result = collectNextInfo({ cwd: repoRoot });
|
|
5
|
+
process.stdout.write(formatNextInfo(result, { json }));
|
|
6
|
+
if (!result.ok)
|
|
7
|
+
process.exitCode = result.exitCode || 1;
|
|
8
|
+
}
|
|
@@ -9,7 +9,7 @@ import { cleanupPlaywrightCliDefaultSession, createPlaywrightCliTool, PI_COMMAND
|
|
|
9
9
|
import { dagCommandPolicyAllows, resolveDagCommandPolicy, } from "../workflows/dag/types.js";
|
|
10
10
|
import { redactPromptForLog, truncateOutput, } from "../shared/output-truncation.js";
|
|
11
11
|
import { GitStatusUnavailableError, pathsChangedDuringRun, readGitStatusPorcelain, recoverRootNulArtifact, snapshotGitStatusPathFingerprints, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
|
|
12
|
-
import { isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "../workflows/dag/retry-policy.js";
|
|
12
|
+
import { isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "../workflows/dag/retry-policy.js";
|
|
13
13
|
import { assessBackendTestMdPlanCompleteness, assessBackendTestMdWriterCompleteness, assessBackendTestPytestPlanCompleteness, assessBackendTestPytestWriterCompleteness, assessBackendTestShardChildCompleteness, backendTestWriterProgressRoleForTask, classifyBackendTestWriterCompletenessFailure, isBackendTestCompletenessRetryCandidate, isBackendTestMdPlanTask, isBackendTestPytestPlanTask, isBackendTestShardChildTask, writeBackendTestWriterProgressArtifacts, } from "../workflows/dag/backend-test-writer-completeness.js";
|
|
14
14
|
import { resolveBackendTestLayout } from "../workflows/dag/backend-test-layout.js";
|
|
15
15
|
import { redactSecrets, truncateUtf8Preview } from "../shared/preview.js";
|
|
@@ -942,22 +942,31 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
942
942
|
if (meta.writeGuardAttribution === "best-effort") {
|
|
943
943
|
stderrParts.push("write guard note: concurrent rank writers use best-effort per-node attribution; keep same-rank writeSet entries disjoint");
|
|
944
944
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const writerCleanTimeout = !mapped.ok &&
|
|
949
|
-
mapped.failureCategory === "timeout" &&
|
|
950
|
-
writeGuardOk &&
|
|
945
|
+
const rawFailureCategory = mapped.failureCategory;
|
|
946
|
+
const writeToolCallCount = readWriterThinkingExhaustionEvidence(result).writeToolCallCount;
|
|
947
|
+
const zeroSideEffects = writeGuardOk &&
|
|
951
948
|
!completenessFailure &&
|
|
952
949
|
!writerThinkingExhausted &&
|
|
953
|
-
|
|
950
|
+
!writerOutcomeViolation &&
|
|
954
951
|
changeManifestChangedFiles !== undefined &&
|
|
955
952
|
changeManifestChangedFiles.length === 0 &&
|
|
956
|
-
|
|
953
|
+
writeToolCallCount === 0;
|
|
954
|
+
const targetCleanTransport = !mapped.ok &&
|
|
955
|
+
isTargetTemplateTransientRetryNode(input.task) &&
|
|
956
|
+
rawFailureCategory !== undefined &&
|
|
957
|
+
["timeout", "network", "rate-limit", "unavailable"].includes(rawFailureCategory) &&
|
|
958
|
+
zeroSideEffects;
|
|
959
|
+
const frontendCleanTimeout = !mapped.ok &&
|
|
960
|
+
rawFailureCategory === "timeout" &&
|
|
961
|
+
isWriterTransportRetryCandidate(input.task) &&
|
|
962
|
+
!isTargetTemplateTransientRetryNode(input.task) &&
|
|
963
|
+
zeroSideEffects;
|
|
964
|
+
const writerCleanTimeout = targetCleanTransport || frontendCleanTimeout;
|
|
957
965
|
return {
|
|
958
966
|
...mapped,
|
|
959
967
|
ok: false,
|
|
960
968
|
stderr: stderrParts.filter(Boolean).join("\n\n"),
|
|
969
|
+
...(rawFailureCategory ? { rawFailureCategory } : {}),
|
|
961
970
|
failureCategory: mapped.ok
|
|
962
971
|
? writeGuardOk
|
|
963
972
|
? completenessFailure
|
|
@@ -4,7 +4,7 @@ import { appendFile, lstat, mkdir, readFile, realpath, stat, } from "node:fs/pro
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { processTreeSpawnOptions, terminateProcessTree, } from "./process-tree.js";
|
|
6
6
|
import { resolvePlaywrightCliLauncher } from "./playwright-cli-launcher.js";
|
|
7
|
-
import { PLAYWRIGHT_CLI_ALLOWED_COMMANDS, isPlaywrightCliCommand, } from "../shared/playwright-cli-command-policy.js";
|
|
7
|
+
import { PLAYWRIGHT_CLI_ALLOWED_COMMANDS, isPlaywrightCliCommand, parsePlaywrightCliFindMatchCount, } from "../shared/playwright-cli-command-policy.js";
|
|
8
8
|
export { PLAYWRIGHT_CLI_ALLOWED_COMMANDS, isPlaywrightCliCommand, };
|
|
9
9
|
/** Logical capability name only; execution always uses the verified JS launcher. */
|
|
10
10
|
export const PLAYWRIGHT_CLI_EXECUTABLE = "playwright-cli";
|
|
@@ -111,6 +111,13 @@ export async function readPlaywrightCliReceipts(runDir, nodeId) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
export function summarizePlaywrightCliReceipts(receipts) {
|
|
114
|
+
// Sequence numbers are assigned at dispatch time, which is the controller's
|
|
115
|
+
// causal order. Receipt lines are appended when each command COMPLETES, so
|
|
116
|
+
// concurrent commands (e.g. parallel find calls issued in one turn) persist
|
|
117
|
+
// out of sequence order. Judge chain contiguity on the dispatch order by
|
|
118
|
+
// sorting on `sequence` first; a genuinely missing receipt (gap) or a
|
|
119
|
+
// duplicated sequence still fails the contiguity check below.
|
|
120
|
+
const orderedReceipts = [...receipts].sort((a, b) => a.sequence - b.sequence);
|
|
114
121
|
let hasSuccessfulOpen = false;
|
|
115
122
|
let hasInteractionOrAssertion = false;
|
|
116
123
|
let hasMeaningfulAssertion = false;
|
|
@@ -122,7 +129,7 @@ export function summarizePlaywrightCliReceipts(receipts) {
|
|
|
122
129
|
let hasContiguousControllerSequence = true;
|
|
123
130
|
let previousSequence = 0;
|
|
124
131
|
const caseIds = new Set();
|
|
125
|
-
for (const receipt of
|
|
132
|
+
for (const receipt of orderedReceipts) {
|
|
126
133
|
caseIds.add(receipt.caseId);
|
|
127
134
|
const succeeded = receipt.exitCode === 0 && !receipt.timedOut;
|
|
128
135
|
const ordered = receipt.sequence === previousSequence + 1;
|
|
@@ -137,8 +144,10 @@ export function summarizePlaywrightCliReceipts(receipts) {
|
|
|
137
144
|
if (PLAYWRIGHT_CLI_INTERACTION_COMMANDS.has(receipt.command) && succeeded) {
|
|
138
145
|
hasInteractionOrAssertion = true;
|
|
139
146
|
}
|
|
140
|
-
// A successful find
|
|
141
|
-
|
|
147
|
+
// A successful find (or find-unique, its semantic wrapper) is the only
|
|
148
|
+
// model-visible semantic assertion in v1.
|
|
149
|
+
if ((receipt.command === "find" || receipt.command === "find-unique") &&
|
|
150
|
+
succeeded) {
|
|
142
151
|
hasMeaningfulAssertion = true;
|
|
143
152
|
if (ordered && sawOpen)
|
|
144
153
|
sawMeaningfulAssertionAfterOpen = true;
|
|
@@ -523,6 +532,17 @@ export function preparePlaywrightCliArgv(input, ctx) {
|
|
|
523
532
|
else if (command === "pdf" || command === "snapshot") {
|
|
524
533
|
args = normalizePdfOrSnapshotArgs(command, rawArgs, ctx);
|
|
525
534
|
}
|
|
535
|
+
else if (command === "find-unique") {
|
|
536
|
+
// find-unique is a controller-side semantic wrapper: the underlying CLI
|
|
537
|
+
// only knows `find`; uniqueness (matchCount === 1) is judged by the
|
|
538
|
+
// executor after parsing the find output. Require exactly one text
|
|
539
|
+
// literal — a unique-control assertion is meaningless with multiple.
|
|
540
|
+
const literals = rawArgs.filter((arg) => !arg.startsWith("-"));
|
|
541
|
+
if (rawArgs.length !== 1 || literals.length !== 1) {
|
|
542
|
+
throw new PlaywrightCliPolicyError("find-unique-args", "find-unique requires exactly one text literal and no flags");
|
|
543
|
+
}
|
|
544
|
+
args = literals;
|
|
545
|
+
}
|
|
526
546
|
else {
|
|
527
547
|
args = rewriteArgsForPaths(command, rawArgs, ctx);
|
|
528
548
|
}
|
|
@@ -818,7 +838,10 @@ export async function executePlaywrightCliCommand(input, ctx, signal) {
|
|
|
818
838
|
};
|
|
819
839
|
}
|
|
820
840
|
let executable = PLAYWRIGHT_CLI_EXECUTABLE;
|
|
821
|
-
|
|
841
|
+
// find-unique executes as the underlying CLI `find` command; uniqueness is
|
|
842
|
+
// a controller-side judgment on the parsed match count.
|
|
843
|
+
const cliCommand = command === "find-unique" ? "find" : command;
|
|
844
|
+
let argv = [cliCommand, ...args];
|
|
822
845
|
try {
|
|
823
846
|
await validatePlaywrightCliOutputPath(command, args, ctx);
|
|
824
847
|
await validatePlaywrightCliInputFiles(command, args, ctx);
|
|
@@ -827,7 +850,7 @@ export async function executePlaywrightCliCommand(input, ctx, signal) {
|
|
|
827
850
|
if (!ctx.runCommand) {
|
|
828
851
|
const launcher = resolvePlaywrightCliLauncher(ctx.repoRoot);
|
|
829
852
|
executable = launcher.executable;
|
|
830
|
-
argv = [...launcher.argvPrefix,
|
|
853
|
+
argv = [...launcher.argvPrefix, cliCommand, ...args];
|
|
831
854
|
}
|
|
832
855
|
}
|
|
833
856
|
catch (error) {
|
|
@@ -887,6 +910,19 @@ export async function executePlaywrightCliCommand(input, ctx, signal) {
|
|
|
887
910
|
}
|
|
888
911
|
const finishedAt = new Date();
|
|
889
912
|
const combined = `${runResult.stdout}\n${runResult.stderr}`;
|
|
913
|
+
// find-family commands report their match count in stdout with exit code
|
|
914
|
+
// 0 even on zero matches — parse the count so receipts carry the real
|
|
915
|
+
// assertion signal for finalize-side audits.
|
|
916
|
+
const matchCount = command === "find" || command === "find-unique"
|
|
917
|
+
? parsePlaywrightCliFindMatchCount(runResult.stdout || "")
|
|
918
|
+
: undefined;
|
|
919
|
+
// find-unique fails closed unless exactly one match was found (including
|
|
920
|
+
// zero and unparseable outputs).
|
|
921
|
+
const findUniqueFailed = command === "find-unique" && matchCount !== 1 && !runResult.timedOut;
|
|
922
|
+
const effectiveExitCode = findUniqueFailed && runResult.exitCode === 0 ? 1 : runResult.exitCode;
|
|
923
|
+
const effectiveStderr = findUniqueFailed
|
|
924
|
+
? `${runResult.stderr}${runResult.stderr ? "\n" : ""}find-unique: expected exactly 1 match, got ${matchCount === undefined ? "unparseable output" : `${matchCount}`}`
|
|
925
|
+
: runResult.stderr;
|
|
890
926
|
const receipt = {
|
|
891
927
|
schemaVersion: 1,
|
|
892
928
|
caseId: ctx.caseId,
|
|
@@ -895,25 +931,28 @@ export async function executePlaywrightCliCommand(input, ctx, signal) {
|
|
|
895
931
|
argsRedacted: redactPlaywrightCliArgs(command, args),
|
|
896
932
|
startedAt: startedAt.toISOString(),
|
|
897
933
|
finishedAt: finishedAt.toISOString(),
|
|
898
|
-
exitCode:
|
|
934
|
+
exitCode: effectiveExitCode,
|
|
899
935
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
900
936
|
outputSha256: createHash("sha256").update(combined).digest("hex"),
|
|
901
937
|
toolVersion: ctx.toolVersion ?? "unknown",
|
|
902
938
|
timedOut: runResult.timedOut || undefined,
|
|
939
|
+
...(matchCount !== undefined ? { matchCount } : {}),
|
|
903
940
|
errorClass: runResult.timedOut
|
|
904
941
|
? "timeout"
|
|
905
942
|
: runResult.signal === "abort"
|
|
906
943
|
? "abort"
|
|
907
|
-
:
|
|
944
|
+
: effectiveExitCode === 0
|
|
908
945
|
? undefined
|
|
909
|
-
:
|
|
946
|
+
: findUniqueFailed
|
|
947
|
+
? "find-unique-count-mismatch"
|
|
948
|
+
: "nonzero-exit",
|
|
910
949
|
};
|
|
911
950
|
await appendReceipt(ctx, receipt);
|
|
912
951
|
return {
|
|
913
|
-
ok:
|
|
914
|
-
exitCode:
|
|
952
|
+
ok: effectiveExitCode === 0 && !runResult.timedOut,
|
|
953
|
+
exitCode: effectiveExitCode,
|
|
915
954
|
stdout: runResult.stdout,
|
|
916
|
-
stderr:
|
|
955
|
+
stderr: effectiveStderr,
|
|
917
956
|
timedOut: runResult.timedOut,
|
|
918
957
|
receipt,
|
|
919
958
|
errorClass: receipt.errorClass,
|
|
@@ -123,6 +123,39 @@ function shouldReuseSuccessfulShellCommands(shell) {
|
|
|
123
123
|
const DEFAULT_SHELL_TIMEOUT_MS = 300_000;
|
|
124
124
|
const SUMMARY_STDOUT_MAX = 4_000;
|
|
125
125
|
const SUMMARY_STDERR_MAX = 2_000;
|
|
126
|
+
const TEST_TIMEOUT_CLASSIFY_MAX_CHARS = 8_000;
|
|
127
|
+
const TEST_TIMEOUT_MARKERS = [
|
|
128
|
+
/Test timed out in \d+ms/i,
|
|
129
|
+
/Timeout of \d+ms exceeded/i,
|
|
130
|
+
];
|
|
131
|
+
const DETERMINISTIC_NON_TIMEOUT_FAILURE_MARKERS = [
|
|
132
|
+
/AssertionError/i,
|
|
133
|
+
/expected:?\s/i,
|
|
134
|
+
/received:?\s/i,
|
|
135
|
+
/Snapshot /i,
|
|
136
|
+
/toMatchSnapshot/i,
|
|
137
|
+
/SyntaxError/i,
|
|
138
|
+
/TS\d{4}/,
|
|
139
|
+
/error TS/i,
|
|
140
|
+
/eslint/i,
|
|
141
|
+
/compile error/i,
|
|
142
|
+
/Failed to compile/i,
|
|
143
|
+
];
|
|
144
|
+
function isIdentifiedTestCommand(command) {
|
|
145
|
+
return /(?:^|[\s"'`])(?:npx\s+)?vitest(?:\s|$)|(?:^|[\s"'`])npm(?:\.cmd)?\s+test(?:\s|$)|(?:^|[\s"'`])pnpm(?:\.cmd)?\s+test(?:\s|$)|(?:^|[\s"'`])yarn(?:\.cmd)?\s+test(?:\s|$)/i.test(command);
|
|
146
|
+
}
|
|
147
|
+
function classifyShellCommandFailure(input) {
|
|
148
|
+
if (input.processCategory !== "nonzero-exit")
|
|
149
|
+
return input.processCategory;
|
|
150
|
+
if (!isIdentifiedTestCommand(input.command))
|
|
151
|
+
return "nonzero-exit";
|
|
152
|
+
const sample = `${input.stdout}\n${input.stderr}`.slice(0, TEST_TIMEOUT_CLASSIFY_MAX_CHARS);
|
|
153
|
+
const hasTimeoutMarker = TEST_TIMEOUT_MARKERS.some((marker) => marker.test(sample));
|
|
154
|
+
if (!hasTimeoutMarker)
|
|
155
|
+
return "nonzero-exit";
|
|
156
|
+
const mixedDeterministicFailure = DETERMINISTIC_NON_TIMEOUT_FAILURE_MARKERS.some((marker) => marker.test(sample));
|
|
157
|
+
return mixedDeterministicFailure ? "nonzero-exit" : "test-timeout";
|
|
158
|
+
}
|
|
126
159
|
const NODE_STDOUT_MAX = 12_000;
|
|
127
160
|
const NODE_STDERR_MAX = 8_000;
|
|
128
161
|
const MAX_BUFFERED_OUTPUT_BYTES = 64 * 1024;
|
|
@@ -329,16 +362,21 @@ export async function executeShellCommand(input) {
|
|
|
329
362
|
});
|
|
330
363
|
});
|
|
331
364
|
child.on("close", (code) => {
|
|
332
|
-
let
|
|
365
|
+
let processCategory = "nonzero-exit";
|
|
333
366
|
if (timedOut) {
|
|
334
|
-
|
|
367
|
+
processCategory = "timeout";
|
|
335
368
|
}
|
|
336
369
|
else if (code === 0) {
|
|
337
|
-
|
|
370
|
+
processCategory = "success";
|
|
338
371
|
}
|
|
339
372
|
finish({
|
|
340
373
|
exitCode: code,
|
|
341
|
-
failureCategory
|
|
374
|
+
failureCategory: classifyShellCommandFailure({
|
|
375
|
+
command: input.command,
|
|
376
|
+
processCategory,
|
|
377
|
+
stdout: formatBoundedOutput(stdout),
|
|
378
|
+
stderr: formatBoundedOutput(stderr),
|
|
379
|
+
}),
|
|
342
380
|
ok: code === 0 && !timedOut,
|
|
343
381
|
});
|
|
344
382
|
});
|
|
@@ -2348,7 +2386,76 @@ async function executeFrontendLintBaseline(input, meta) {
|
|
|
2348
2386
|
};
|
|
2349
2387
|
}
|
|
2350
2388
|
}
|
|
2351
|
-
|
|
2389
|
+
async function writeShellPresetProcessReceipt(input) {
|
|
2390
|
+
await writeDagNodeJsonArtifact(input.runDir, input.nodeId, "process-receipt.json", {
|
|
2391
|
+
schemaVersion: 1,
|
|
2392
|
+
kind: "shell-preset",
|
|
2393
|
+
preset: input.preset,
|
|
2394
|
+
ok: input.ok,
|
|
2395
|
+
durationMs: input.durationMs,
|
|
2396
|
+
steps: input.steps,
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
async function ensureShellPresetProcessReceipt(input) {
|
|
2400
|
+
const receiptPath = path.join(input.runDir, input.nodeId, "process-receipt.json");
|
|
2401
|
+
if (existsSync(receiptPath))
|
|
2402
|
+
return;
|
|
2403
|
+
if (existsSync(path.join(input.runDir, input.nodeId, "shell-command-receipt.json"))) {
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
const detail = input.result.ok ? input.result.stdout : input.result.stderr;
|
|
2407
|
+
await writeShellPresetProcessReceipt({
|
|
2408
|
+
runDir: input.runDir,
|
|
2409
|
+
nodeId: input.nodeId,
|
|
2410
|
+
preset: input.preset,
|
|
2411
|
+
ok: input.result.ok,
|
|
2412
|
+
durationMs: input.result.durationMs,
|
|
2413
|
+
steps: [
|
|
2414
|
+
{
|
|
2415
|
+
id: "run",
|
|
2416
|
+
label: input.preset,
|
|
2417
|
+
ok: input.result.ok,
|
|
2418
|
+
detail: detail?.slice(0, 8 * 1024),
|
|
2419
|
+
durationMs: input.result.durationMs,
|
|
2420
|
+
},
|
|
2421
|
+
],
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
function namedShellHandler(shell) {
|
|
2425
|
+
if (!shell)
|
|
2426
|
+
return null;
|
|
2427
|
+
const record = shell;
|
|
2428
|
+
for (const key of [
|
|
2429
|
+
"finalWriteSetApprovalGate",
|
|
2430
|
+
"frontendTestStandardScenarios",
|
|
2431
|
+
"frontendTestEnvironmentProbe",
|
|
2432
|
+
"frontendBrowserToolPreflight",
|
|
2433
|
+
"frontendPrewriteGate",
|
|
2434
|
+
"frontendLintBaseline",
|
|
2435
|
+
"frontendVerificationBundle",
|
|
2436
|
+
"frontendReviewContext",
|
|
2437
|
+
"frontendTestCaseChecklist",
|
|
2438
|
+
"frontendTestCaseManifest",
|
|
2439
|
+
"frontendTestEvidenceValidation",
|
|
2440
|
+
"frontendTestResultFinalize",
|
|
2441
|
+
"frontendTestL5Report",
|
|
2442
|
+
"frontendTestHtmlReport",
|
|
2443
|
+
"frontendTestReports",
|
|
2444
|
+
"backendTestPipeline",
|
|
2445
|
+
"jsonArtifactGate",
|
|
2446
|
+
"projectGovernanceGate",
|
|
2447
|
+
"requirementCoverageGate",
|
|
2448
|
+
"verdictGate",
|
|
2449
|
+
"verifyEvidence",
|
|
2450
|
+
"repairArtifactGate",
|
|
2451
|
+
"jacocoCoverage",
|
|
2452
|
+
]) {
|
|
2453
|
+
if (record[key] != null)
|
|
2454
|
+
return key;
|
|
2455
|
+
}
|
|
2456
|
+
return null;
|
|
2457
|
+
}
|
|
2458
|
+
async function executeDagShellNodeInner(input, meta) {
|
|
2352
2459
|
const shell = input.task.shell;
|
|
2353
2460
|
if (shell?.finalWriteSetApprovalGate) {
|
|
2354
2461
|
const started = Date.now();
|
|
@@ -2395,28 +2502,81 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2395
2502
|
}
|
|
2396
2503
|
if (shell?.frontendBrowserToolPreflight) {
|
|
2397
2504
|
const started = Date.now();
|
|
2505
|
+
const steps = [];
|
|
2506
|
+
const finish = async (result) => {
|
|
2507
|
+
await writeShellPresetProcessReceipt({
|
|
2508
|
+
runDir: meta.runDir,
|
|
2509
|
+
nodeId: input.task.id,
|
|
2510
|
+
preset: "frontendBrowserToolPreflight",
|
|
2511
|
+
ok: result.ok,
|
|
2512
|
+
durationMs: result.durationMs,
|
|
2513
|
+
steps: steps.length > 0
|
|
2514
|
+
? steps
|
|
2515
|
+
: [
|
|
2516
|
+
{
|
|
2517
|
+
id: "run",
|
|
2518
|
+
label: "浏览器工具能力探测",
|
|
2519
|
+
ok: result.ok,
|
|
2520
|
+
detail: result.ok ? result.stdout : result.stderr,
|
|
2521
|
+
durationMs: result.durationMs,
|
|
2522
|
+
},
|
|
2523
|
+
],
|
|
2524
|
+
});
|
|
2525
|
+
return result;
|
|
2526
|
+
};
|
|
2398
2527
|
try {
|
|
2399
2528
|
if (resolvePiBackend() === "cli-only") {
|
|
2529
|
+
steps.push({
|
|
2530
|
+
id: "pi-backend",
|
|
2531
|
+
label: "Pi SDK 后端可用",
|
|
2532
|
+
ok: false,
|
|
2533
|
+
detail: "CODE_AGENT_PI_BACKEND=cli-only",
|
|
2534
|
+
});
|
|
2400
2535
|
throw new Error("browser-command-capability-unavailable: CODE_AGENT_PI_BACKEND=cli-only rejects SDK-only playwright_cli");
|
|
2401
2536
|
}
|
|
2537
|
+
steps.push({
|
|
2538
|
+
id: "pi-backend",
|
|
2539
|
+
label: "Pi SDK 后端可用",
|
|
2540
|
+
ok: true,
|
|
2541
|
+
});
|
|
2402
2542
|
const sdkCapability = await checkPiSdkCustomToolCapability(input.cwd);
|
|
2403
2543
|
if (!sdkCapability.ok) {
|
|
2544
|
+
steps.push({
|
|
2545
|
+
id: "sdk-custom-tool",
|
|
2546
|
+
label: "Pi SDK custom-tool 能力",
|
|
2547
|
+
ok: false,
|
|
2548
|
+
detail: sdkCapability.detail,
|
|
2549
|
+
});
|
|
2404
2550
|
throw new Error(`browser-command-capability-unavailable: ${sdkCapability.detail}`);
|
|
2405
2551
|
}
|
|
2552
|
+
steps.push({
|
|
2553
|
+
id: "sdk-custom-tool",
|
|
2554
|
+
label: "Pi SDK custom-tool 能力",
|
|
2555
|
+
ok: true,
|
|
2556
|
+
});
|
|
2406
2557
|
const toolProbe = await createPlaywrightCliTool({
|
|
2407
2558
|
repoRoot: input.cwd,
|
|
2408
2559
|
runDir: meta.runDir,
|
|
2409
2560
|
nodeId: input.task.id,
|
|
2410
2561
|
caseId: "frontend-browser-preflight",
|
|
2411
2562
|
evidenceDir: "testcase/frontend/evidence/frontend-browser-preflight",
|
|
2412
|
-
// Capability-only registration probe. The tested URL is selected later by
|
|
2413
|
-
// retrieve-frontend-test-context-pi and validated by the environment shell.
|
|
2414
2563
|
baseUrl: "http://localhost:5173/",
|
|
2415
2564
|
inputRoot: "testcase/frontend/fixtures",
|
|
2416
2565
|
});
|
|
2417
2566
|
if (toolProbe.name !== "playwright_cli") {
|
|
2567
|
+
steps.push({
|
|
2568
|
+
id: "structured-tool",
|
|
2569
|
+
label: "注册 playwright_cli",
|
|
2570
|
+
ok: false,
|
|
2571
|
+
detail: toolProbe.name,
|
|
2572
|
+
});
|
|
2418
2573
|
throw new Error("browser-command-capability-unavailable: structured playwright_cli tool registration failed");
|
|
2419
2574
|
}
|
|
2575
|
+
steps.push({
|
|
2576
|
+
id: "structured-tool",
|
|
2577
|
+
label: "注册 playwright_cli",
|
|
2578
|
+
ok: true,
|
|
2579
|
+
});
|
|
2420
2580
|
const launcher = resolvePlaywrightCliLauncher(input.cwd);
|
|
2421
2581
|
const result = await defaultPlaywrightCliCommandRunner({
|
|
2422
2582
|
executable: launcher.executable,
|
|
@@ -2429,8 +2589,19 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2429
2589
|
if (result.exitCode !== 0 ||
|
|
2430
2590
|
result.timedOut ||
|
|
2431
2591
|
!["open", "close", "find", "snapshot", "click"].every((command) => new RegExp(`\\b${command}\\b`, "i").test(help))) {
|
|
2592
|
+
steps.push({
|
|
2593
|
+
id: "playwright-cli-help",
|
|
2594
|
+
label: "playwright-cli --help 合同",
|
|
2595
|
+
ok: false,
|
|
2596
|
+
detail: help.slice(0, 8 * 1024),
|
|
2597
|
+
});
|
|
2432
2598
|
throw new Error("playwright-cli-contract-incompatible");
|
|
2433
2599
|
}
|
|
2600
|
+
steps.push({
|
|
2601
|
+
id: "playwright-cli-help",
|
|
2602
|
+
label: "playwright-cli --help 合同",
|
|
2603
|
+
ok: true,
|
|
2604
|
+
});
|
|
2434
2605
|
const capability = {
|
|
2435
2606
|
schemaVersion: 1,
|
|
2436
2607
|
status: "ready",
|
|
@@ -2441,22 +2612,28 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2441
2612
|
targetUrlAuthority: "retrieve-context-and-environment-probe",
|
|
2442
2613
|
};
|
|
2443
2614
|
await writeDagNodeJsonArtifact(meta.runDir, input.task.id, "frontend-browser-capability.json", capability);
|
|
2444
|
-
|
|
2615
|
+
steps.push({
|
|
2616
|
+
id: "capability-artifact",
|
|
2617
|
+
label: "写出 frontend-browser-capability.json",
|
|
2618
|
+
ok: true,
|
|
2619
|
+
detail: JSON.stringify(capability, null, 2),
|
|
2620
|
+
});
|
|
2621
|
+
return await finish({
|
|
2445
2622
|
ok: true,
|
|
2446
2623
|
stdout: JSON.stringify(capability),
|
|
2447
2624
|
stderr: "",
|
|
2448
2625
|
failureCategory: "success",
|
|
2449
2626
|
durationMs: Date.now() - started,
|
|
2450
|
-
};
|
|
2627
|
+
});
|
|
2451
2628
|
}
|
|
2452
2629
|
catch (error) {
|
|
2453
|
-
return {
|
|
2630
|
+
return await finish({
|
|
2454
2631
|
ok: false,
|
|
2455
2632
|
stdout: "",
|
|
2456
2633
|
stderr: `playwright-cli-unavailable: ${error instanceof Error ? error.message : String(error)}`,
|
|
2457
2634
|
failureCategory: "tool-policy",
|
|
2458
2635
|
durationMs: Date.now() - started,
|
|
2459
|
-
};
|
|
2636
|
+
});
|
|
2460
2637
|
}
|
|
2461
2638
|
}
|
|
2462
2639
|
if (shell?.frontendPrewriteGate) {
|
|
@@ -2878,6 +3055,7 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2878
3055
|
const cwd = resolveShellCwd(input.cwd, shell.cwd);
|
|
2879
3056
|
const timeoutMs = shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
2880
3057
|
const failFast = shell.failFast !== false;
|
|
3058
|
+
const attemptNumber = input.attempt ?? 1;
|
|
2881
3059
|
const results = [];
|
|
2882
3060
|
const executedCommandKeys = new Set();
|
|
2883
3061
|
let beforeStatus;
|
|
@@ -2973,8 +3151,8 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2973
3151
|
dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
|
|
2974
3152
|
reportActivity: input.reportActivity,
|
|
2975
3153
|
outputArtifacts: {
|
|
2976
|
-
stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
|
|
2977
|
-
stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
|
|
3154
|
+
stdoutPath: path.join(meta.runDir, input.task.id, `attempt-${attemptNumber}`, "commands", `${commandNumber}.stdout.txt`),
|
|
3155
|
+
stderrPath: path.join(meta.runDir, input.task.id, `attempt-${attemptNumber}`, "commands", `${commandNumber}.stderr.txt`),
|
|
2978
3156
|
},
|
|
2979
3157
|
});
|
|
2980
3158
|
results.push(result);
|
|
@@ -3033,6 +3211,7 @@ export async function executeDagShellNode(input, meta) {
|
|
|
3033
3211
|
schemaVersion: 1,
|
|
3034
3212
|
runId: meta.runId,
|
|
3035
3213
|
nodeId: input.task.id,
|
|
3214
|
+
attempt: attemptNumber,
|
|
3036
3215
|
workspaceFingerprint: workspaceFingerprint ?? null,
|
|
3037
3216
|
failFast,
|
|
3038
3217
|
commands: results.map((result) => ({
|
|
@@ -3051,6 +3230,7 @@ export async function executeDagShellNode(input, meta) {
|
|
|
3051
3230
|
subchecks: parseCheckRepoSubcheckDurations(result),
|
|
3052
3231
|
})),
|
|
3053
3232
|
};
|
|
3233
|
+
await writeDagNodeJsonArtifact(meta.runDir, input.task.id, path.posix.join(`attempt-${attemptNumber}`, "shell-command-receipt.json"), receipt);
|
|
3054
3234
|
await writeDagNodeJsonArtifact(meta.runDir, input.task.id, "shell-command-receipt.json", receipt);
|
|
3055
3235
|
const summaryMarkdown = buildShellResultSummaryMarkdown({
|
|
3056
3236
|
nodeId: input.task.id,
|
|
@@ -3102,8 +3282,24 @@ export async function executeDagShellNode(input, meta) {
|
|
|
3102
3282
|
stdout: `${recordPrefix}${reuseNote}${aggregate.stdout}`,
|
|
3103
3283
|
stderr: stderrParts.filter(Boolean).join("\n\n"),
|
|
3104
3284
|
failureCategory,
|
|
3285
|
+
...(firstFailure?.failureCategory
|
|
3286
|
+
? { rawFailureCategory: firstFailure.failureCategory }
|
|
3287
|
+
: {}),
|
|
3105
3288
|
durationMs: Date.now() - started,
|
|
3106
3289
|
// extra field consumed by node-execution if supported; also embedded in stdout marker
|
|
3107
3290
|
...{ commandResults },
|
|
3108
3291
|
};
|
|
3109
3292
|
}
|
|
3293
|
+
export async function executeDagShellNode(input, meta) {
|
|
3294
|
+
const handler = namedShellHandler(input.task.shell);
|
|
3295
|
+
const result = await executeDagShellNodeInner(input, meta);
|
|
3296
|
+
if (handler) {
|
|
3297
|
+
await ensureShellPresetProcessReceipt({
|
|
3298
|
+
runDir: meta.runDir,
|
|
3299
|
+
nodeId: input.task.id,
|
|
3300
|
+
preset: handler,
|
|
3301
|
+
result,
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
return result;
|
|
3305
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal switch for the experimental "rerun failed node with an edited
|
|
3
|
+
* prompt" capability. Default is OFF; nothing may depend on it being enabled.
|
|
4
|
+
* The switch is deliberately env-based (no harness.json surface) while the
|
|
5
|
+
* feature is internal.
|
|
6
|
+
*/
|
|
7
|
+
export const DAG_RERUN_PROMPT_OVERRIDE_ENV = "HARNESS_DAG_RERUN_PROMPT_OVERRIDE";
|
|
8
|
+
/** Upper bound for an override input body (characters). */
|
|
9
|
+
export const MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS = 65_536;
|
|
10
|
+
export function isDagRerunPromptOverrideEnabled(env = process.env) {
|
|
11
|
+
const value = env[DAG_RERUN_PROMPT_OVERRIDE_ENV];
|
|
12
|
+
return value === "1" || value === "true";
|
|
13
|
+
}
|
|
14
|
+
export const DAG_RERUN_PROMPT_ADDENDUM_TAG = "operator_addendum";
|
|
15
|
+
/**
|
|
16
|
+
* Compose the final subtask_prompt for a rerun override.
|
|
17
|
+
* - "replace": the operator text becomes the whole prompt (advanced mode).
|
|
18
|
+
* - "append" (recommended): the frozen base prompt is kept verbatim and the
|
|
19
|
+
* operator addendum is appended in a clearly marked block, mirroring the
|
|
20
|
+
* rerunFeedback / retry_instruction augmentation semantics — small diffs,
|
|
21
|
+
* no risk of dropping template scaffolding.
|
|
22
|
+
*/
|
|
23
|
+
export function composeDagPromptOverridePrompt(input) {
|
|
24
|
+
if (input.mode === "replace") {
|
|
25
|
+
return input.text;
|
|
26
|
+
}
|
|
27
|
+
return `${input.basePrompt}\n\n<${DAG_RERUN_PROMPT_ADDENDUM_TAG}>\n${input.text.trim()}\n</${DAG_RERUN_PROMPT_ADDENDUM_TAG}>\n`;
|
|
28
|
+
}
|
|
@@ -1155,6 +1155,19 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1155
1155
|
modelCallable: "always",
|
|
1156
1156
|
humanConfirmation: "none",
|
|
1157
1157
|
},
|
|
1158
|
+
{
|
|
1159
|
+
action: "dagPromptOverrideConfig",
|
|
1160
|
+
cli: "console dag-prompt-override-config",
|
|
1161
|
+
kind: "read",
|
|
1162
|
+
inputSchemaVersion: 1,
|
|
1163
|
+
resultSchemaVersion: 1,
|
|
1164
|
+
envelopeSchemaVersion: 1,
|
|
1165
|
+
requiredErrorCodes: [],
|
|
1166
|
+
description: "Read-only probe of the experimental prompt-override rerun switch (env-gated, default off). The browser Recovery UI hides the failed-node prompt editor unless this returns enabled=true. Never mutates.",
|
|
1167
|
+
inputParams: [],
|
|
1168
|
+
modelCallable: "always",
|
|
1169
|
+
humanConfirmation: "none",
|
|
1170
|
+
},
|
|
1158
1171
|
{
|
|
1159
1172
|
action: "dagRerunPlan",
|
|
1160
1173
|
cli: "loop-agent dag rerun --run-id <id> --from-node <node> --plan --json",
|
|
@@ -1177,6 +1190,12 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1177
1190
|
required: true,
|
|
1178
1191
|
description: "node id to rerun from (prefer failed/attention node from dagReport primaryFailure.nodeId)",
|
|
1179
1192
|
},
|
|
1193
|
+
{
|
|
1194
|
+
name: "promptOverrides",
|
|
1195
|
+
type: "array",
|
|
1196
|
+
required: false,
|
|
1197
|
+
description: "experimental (switch off by default): [{nodeId, prompt, mode?}] — mode append (recommended) keeps the frozen base prompt and appends the text as a marked addendum block, replace swaps the whole subtask_prompt; only FAILED nodes inside the reset closure are admitted and the planHash binds the composed change",
|
|
1198
|
+
},
|
|
1180
1199
|
],
|
|
1181
1200
|
modelCallable: "always",
|
|
1182
1201
|
humanConfirmation: "none",
|
|
@@ -1219,6 +1238,12 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1219
1238
|
required: true,
|
|
1220
1239
|
description: "why continue from this node (e.g. provider flake; resume from review-pi)",
|
|
1221
1240
|
},
|
|
1241
|
+
{
|
|
1242
|
+
name: "promptOverrides",
|
|
1243
|
+
type: "array",
|
|
1244
|
+
required: false,
|
|
1245
|
+
description: "experimental (switch off by default): must match the dagRerunPlan promptOverrides exactly (same mode/text); the planHash already binds them, any drift is rejected",
|
|
1246
|
+
},
|
|
1222
1247
|
],
|
|
1223
1248
|
// always: fresh eligible dagRerun plans are model-callable (2026-08-11).
|
|
1224
1249
|
// The CLI keeps enforcing parent lifecycle, fromNode, planHash,
|
|
@@ -1822,6 +1847,13 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
1822
1847
|
source: "loop-agent",
|
|
1823
1848
|
exclusionReason: "Not exposed to Operator Chat in M5.",
|
|
1824
1849
|
},
|
|
1850
|
+
{
|
|
1851
|
+
command: "loop-agent init model-catalog",
|
|
1852
|
+
coverage: "excluded",
|
|
1853
|
+
action: null,
|
|
1854
|
+
source: "loop-agent",
|
|
1855
|
+
exclusionReason: "Not exposed to Operator Chat in M5.",
|
|
1856
|
+
},
|
|
1825
1857
|
{
|
|
1826
1858
|
command: "loop-agent init doctor",
|
|
1827
1859
|
coverage: "excluded",
|
|
@@ -9,6 +9,7 @@ export const PLAYWRIGHT_CLI_ALLOWED_COMMANDS = [
|
|
|
9
9
|
"goto",
|
|
10
10
|
"snapshot",
|
|
11
11
|
"find",
|
|
12
|
+
"find-unique",
|
|
12
13
|
"click",
|
|
13
14
|
"dblclick",
|
|
14
15
|
"fill",
|
|
@@ -35,6 +36,20 @@ export const PLAYWRIGHT_CLI_ALLOWED_COMMANDS = [
|
|
|
35
36
|
"requests",
|
|
36
37
|
"request",
|
|
37
38
|
];
|
|
39
|
+
/**
|
|
40
|
+
* Parse the match count from a playwright-cli find output. The CLI prints
|
|
41
|
+
* `Found N match(es) for "...":` on success and `No matches found for "...".`
|
|
42
|
+
* on zero — both with exit code 0, so the count is the only assertion signal.
|
|
43
|
+
* Returns undefined when the output shape is unrecognized (e.g. error pages).
|
|
44
|
+
*/
|
|
45
|
+
export function parsePlaywrightCliFindMatchCount(output) {
|
|
46
|
+
const found = /Found\s+(\d+)\s+match(?:es)?\s+for\b/i.exec(output);
|
|
47
|
+
if (found)
|
|
48
|
+
return Number(found[1]);
|
|
49
|
+
if (/No matches found for\b/i.test(output))
|
|
50
|
+
return 0;
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
38
53
|
const allowedCommandSet = new Set(PLAYWRIGHT_CLI_ALLOWED_COMMANDS);
|
|
39
54
|
export function isPlaywrightCliCommand(value) {
|
|
40
55
|
return allowedCommandSet.has(value);
|