@tea-agent/loop-agent 0.37.0 → 0.37.1-beta.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/AGENTS.md +2 -6
- package/CHANGELOG.md +71 -144
- package/README.md +1 -1
- package/bin/loop-agent.js +1 -37
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/application/task-lifecycle/observe.js +0 -15
- package/dist/application/task-lifecycle/plan-transitions.js +0 -15
- package/dist/cli/command-definitions.js +0 -7
- package/dist/cli/program.js +3 -8
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +30 -47
- package/dist/executors/pi-sdk-executor.js +0 -34
- package/dist/executors/shell-executor.js +95 -6
- package/dist/shared/operator/capabilities.js +13 -200
- package/dist/shared/package-metadata.js +0 -42
- package/dist/task/contract/project.js +0 -4
- package/dist/task/source-prepare/completeness.js +0 -53
- package/dist/task/source-prepare/parse-intent.js +12 -82
- package/dist/task/source-prepare/prepare.js +5 -102
- package/dist/task/source-prepare/semantic-intake.js +23 -144
- package/dist/worker/console/chat/artifact-card.js +1 -8
- package/dist/worker/console/chat/chat-event-store.js +0 -152
- package/dist/worker/console/chat/human-gate-card.js +1 -9
- package/dist/worker/console/chat/operation-card.js +2 -71
- package/dist/worker/console/chat/pi-runtime.js +62 -111
- package/dist/worker/console/chat/routes.js +10 -43
- package/dist/worker/console/chat/session-store.js +1 -9
- package/dist/worker/console/chat/shortcuts.js +7 -9
- package/dist/worker/console/chat/turn-process.js +23 -204
- package/dist/worker/console/chat/workspace-landing.js +1 -2
- package/dist/worker/console/operation-runner.js +6 -155
- package/dist/worker/console/operator-actions.js +14 -391
- package/dist/worker/console/operator-user-error.js +0 -4
- package/dist/worker/console/recovery-cta.js +3 -50
- package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
- package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -13
- package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
- package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -114
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -67
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
- package/dist/worker/loop-agent/loop-agent-client.js +3 -17
- package/dist/worker/observability/read-model.js +0 -20
- package/dist/worker/observe/spec-evidence.js +8 -3
- package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
- package/dist/worker/observe/static/operator-chrome.js +3 -10
- package/dist/worker/observe/static/views/dag-inspector.js +71 -6
- package/dist/worker/observe/static/views/dag.js +0 -12
- package/dist/worker/preflight.js +1 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
- package/dist/workflows/dag/frontend-prewrite-gate.js +61 -364
- package/dist/workflows/dag/frontend-repair.js +18 -219
- package/dist/workflows/dag/frontend-verification-trace.js +32 -47
- package/dist/workflows/dag/init-hybrid.js +82 -165
- package/dist/workflows/dag/node-execution.js +0 -89
- package/dist/workflows/dag/recovery-recommendation.js +0 -58
- package/dist/workflows/dag/runner.js +15 -333
- package/dist/workflows/dag/scheduler.js +3 -257
- package/dist/workflows/dag/types.js +9 -130
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +67 -102
- package/docs/templates/backend-test-dag.json +50 -32
- package/docs/templates/init-managed-agents.md +2 -5
- package/harness.json +2 -2
- package/package.json +3 -9
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +0 -4
- package/dist/build-stamp.json +0 -6
- package/dist/commands/dag-request-interrupt.js +0 -20
- package/dist/worker/console/chat/assistant-content.js +0 -121
- package/dist/worker/console/chat/semantic-activity.js +0 -477
- package/dist/worker/console/operation-run-facts.js +0 -190
- package/dist/worker/console/operation-wait.js +0 -355
- package/dist/worker/console/recovery-error-copy.js +0 -198
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +0 -1
- package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +0 -1
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +0 -36
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +0 -132
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +0 -10
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +0 -1
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +0 -1
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +0 -231
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +0 -15
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +0 -88
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +0 -206
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +0 -1
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +0 -1
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +0 -1
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +0 -1
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +0 -166
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +0 -62
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +0 -321
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +0 -4
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +0 -1
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +0 -30
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +0 -3
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +0 -24
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +0 -24
- package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +0 -41
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +0 -1
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +0 -85
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +0 -156
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +0 -292
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +0 -106
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +0 -1
- package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +0 -2
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +0 -1
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +0 -70
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +0 -139
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +0 -89
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +0 -1
- package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +0 -1
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +0 -1
- package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +0 -308
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +0 -96
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +0 -1
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +0 -39
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +0 -7
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +0 -1
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +0 -84
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +0 -40
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +0 -162
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +0 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +0 -120
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +0 -34
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +0 -78
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +0 -7
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- package/dist/worker/observability/interrupt-eligibility.js +0 -264
- package/dist/workflows/dag/contract-output-registry.js +0 -14
- package/dist/workflows/dag/contract-validator-registrations.js +0 -8
- package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
- package/dist/workflows/dag/frontend-recovery-run.js +0 -539
- package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
- package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
- package/dist/workflows/dag/interrupt-request.js +0 -559
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { advanceTaskLifecycle, } from "../application/task-lifecycle/index.js";
|
|
2
2
|
import { buildOperatorResult, operatorFailed, processExitCodeForOutcome, writeOperatorJson, } from "../shared/operator/index.js";
|
|
3
|
-
import { createRunDagProgressObserver, validateRunDagProgressIntervalMs, } from "./run-dag-progress.js";
|
|
4
3
|
const COMMAND = "task advance";
|
|
5
4
|
const USAGE = `usage:
|
|
6
5
|
task advance <task-id> [title]
|
|
@@ -25,8 +24,6 @@ const USAGE = `usage:
|
|
|
25
24
|
[--dag-output <path>]
|
|
26
25
|
[--skip-finalize]
|
|
27
26
|
[--no-strict-models]
|
|
28
|
-
[--quiet]
|
|
29
|
-
[--progress-interval-ms <ms>]
|
|
30
27
|
[--dry-run]
|
|
31
28
|
[--json]`;
|
|
32
29
|
function pushList(target, value) {
|
|
@@ -173,19 +170,6 @@ export function parseTaskAdvanceArgs(args) {
|
|
|
173
170
|
options.strictModels = false;
|
|
174
171
|
continue;
|
|
175
172
|
}
|
|
176
|
-
if (token === "--quiet") {
|
|
177
|
-
options.quiet = true;
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
if (token === "--progress-interval-ms") {
|
|
181
|
-
const raw = next();
|
|
182
|
-
const parsed = Number(raw);
|
|
183
|
-
if (!Number.isFinite(parsed)) {
|
|
184
|
-
throw new Error(`progress-interval-ms must be an integer >= 1000\n${USAGE}`);
|
|
185
|
-
}
|
|
186
|
-
options.progressIntervalMs = validateRunDagProgressIntervalMs(parsed);
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
173
|
if (token === "--help" || token === "-h") {
|
|
190
174
|
throw new Error(USAGE);
|
|
191
175
|
}
|
|
@@ -224,7 +208,7 @@ function mapOutcome(result) {
|
|
|
224
208
|
return "blocked";
|
|
225
209
|
return "succeeded";
|
|
226
210
|
}
|
|
227
|
-
|
|
211
|
+
function toUseCaseInput(repoRoot, taskId, options) {
|
|
228
212
|
const timeouts = new Map((options.verifyTimeout ?? []).map((entry) => {
|
|
229
213
|
const parsed = parseVerifyTimeout(entry);
|
|
230
214
|
return [parsed.label, parsed.timeoutMs];
|
|
@@ -287,8 +271,7 @@ export function toUseCaseInput(repoRoot, taskId, options, observer) {
|
|
|
287
271
|
dagOutputPath: options.dagOutputPath,
|
|
288
272
|
skipFinalize: options.skipFinalize,
|
|
289
273
|
strictModels: options.strictModels,
|
|
290
|
-
|
|
291
|
-
onProgress: options.quiet
|
|
274
|
+
onProgress: options.json
|
|
292
275
|
? undefined
|
|
293
276
|
: (message) => {
|
|
294
277
|
process.stderr.write(`[task advance] ${message}\n`);
|
|
@@ -313,18 +296,8 @@ export async function runTaskAdvance(repoRoot, args) {
|
|
|
313
296
|
process.exitCode = processExitCodeForOutcome(envelope.outcome);
|
|
314
297
|
return;
|
|
315
298
|
}
|
|
316
|
-
let progress;
|
|
317
299
|
try {
|
|
318
|
-
|
|
319
|
-
// the single final OperatorCommandResultV1 JSON). The observer is created
|
|
320
|
-
// ONLY for the approve-gate execution path and disposed on every exit;
|
|
321
|
-
// its timer starts only after onRunStart (double guard, no stray timer).
|
|
322
|
-
if (options.approveGate && !options.dryRun && !options.quiet) {
|
|
323
|
-
progress = createRunDagProgressObserver({
|
|
324
|
-
intervalMs: options.progressIntervalMs,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
const result = await advanceTaskLifecycle(toUseCaseInput(repoRoot, taskId, options, progress?.observer));
|
|
300
|
+
const result = await advanceTaskLifecycle(toUseCaseInput(repoRoot, taskId, options));
|
|
328
301
|
const outcome = mapOutcome(result);
|
|
329
302
|
const gateStop = result.lifecycleState === "awaiting-write-set-approval" &&
|
|
330
303
|
result.blockers.length === 0;
|
|
@@ -360,7 +333,4 @@ export async function runTaskAdvance(repoRoot, args) {
|
|
|
360
333
|
writeOperatorJson(envelope);
|
|
361
334
|
process.exitCode = processExitCodeForOutcome(envelope.outcome);
|
|
362
335
|
}
|
|
363
|
-
finally {
|
|
364
|
-
progress?.dispose();
|
|
365
|
-
}
|
|
366
336
|
}
|
|
@@ -194,7 +194,7 @@ export function buildDagPiUserMessage(task, persona, step) {
|
|
|
194
194
|
if (isDagPiWriteTask(task)) {
|
|
195
195
|
const outcomeInstruction = task.writerOutcomePolicy
|
|
196
196
|
? [
|
|
197
|
-
"The first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed, IMPLEMENTATION_OUTCOME: already-satisfied, or IMPLEMENTATION_OUTCOME: blocked.
|
|
197
|
+
"The first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed, IMPLEMENTATION_OUTCOME: already-satisfied, or IMPLEMENTATION_OUTCOME: blocked. Use changed only after producing a non-empty bounded diff; use already-satisfied only when the contract is already met and no file changed; use blocked when implementation cannot proceed.",
|
|
198
198
|
task.writerOutcomePolicy.requireChangedFiles
|
|
199
199
|
? "This generation node requires a non-empty bounded diff; already-satisfied cannot complete it successfully."
|
|
200
200
|
: undefined,
|
|
@@ -206,8 +206,6 @@ export function buildDagPiUserMessage(task, persona, step) {
|
|
|
206
206
|
`You are executing hybrid DAG node "${task.id}" (role=${role}, piStep=${step}, writePolicy=${writePolicy}).`,
|
|
207
207
|
"The system prompt contains the full DAG envelope: objective, constraints, upstream context, and task.",
|
|
208
208
|
"Use write tools only for the bounded implementation requested by this node.",
|
|
209
|
-
"The upstream plan node's output is a BLUEPRINT, not a deliverable: reading it never satisfies this node. You MUST materialize the plan into actual working-tree changes by calling write/edit tools before claiming changed. A detailed plan that names every file, test, or snippet still requires you to write those files yourself.",
|
|
210
|
-
"Forbidden paths bound the writeSet, not the task: when the task only requires writing inside the writeSet (e.g. adding tests that call existing public APIs), a forbidden src/** does not make the task impossible — do NOT claim blocked merely because some path you never need to write is forbidden. blocked is only for when you genuinely cannot produce the required writeSet changes.",
|
|
211
209
|
outcomeInstruction,
|
|
212
210
|
`Allowed paths: ${(task.allowedPaths ?? []).join(", ") || "(none declared)"}.`,
|
|
213
211
|
`Write set: ${(task.writeSet ?? []).join(", ") || "(none declared)"}.`,
|
|
@@ -522,33 +520,6 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
522
520
|
}
|
|
523
521
|
let result;
|
|
524
522
|
try {
|
|
525
|
-
// Phase 5 (P1-9): capture the writeSet baseline BEFORE the writer provider
|
|
526
|
-
// call so a transient partial write can be rolled back and recovered via a
|
|
527
|
-
// staged child. Only frontend-implementation writers; a failed capture is a
|
|
528
|
-
// hard fail (no baseline → no rollback → no recovery). Dynamic import avoids
|
|
529
|
-
// an executor ↔ scheduler static cycle.
|
|
530
|
-
if (isWriteTask &&
|
|
531
|
-
(input.task.id === "frontend-implement-pi" ||
|
|
532
|
-
input.task.id === "frontend-repair-pi") &&
|
|
533
|
-
(input.task.writeSet?.length ?? 0) > 0) {
|
|
534
|
-
try {
|
|
535
|
-
const { captureFrontendWriterAttemptIntent } = await import("../workflows/dag/frontend-writer-recovery.js");
|
|
536
|
-
await captureFrontendWriterAttemptIntent({
|
|
537
|
-
cwd: input.cwd,
|
|
538
|
-
runDir: meta.runDir,
|
|
539
|
-
writeSet: input.task.writeSet,
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
catch (error) {
|
|
543
|
-
return {
|
|
544
|
-
ok: false,
|
|
545
|
-
stdout: "",
|
|
546
|
-
stderr: `frontend writer attempt baseline capture failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
547
|
-
failureCategory: "write-guard",
|
|
548
|
-
durationMs: Date.now() - started,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
523
|
result = await piStepFn({
|
|
553
524
|
attachedFiles: [],
|
|
554
525
|
modelConfig: resolveDagPiModelConfig(input.model, input.thinking ? { thinking: input.thinking } : undefined),
|
|
@@ -720,23 +691,6 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
720
691
|
const changedFiles = pathsChangedDuringRun(snapshotGitStatusPorcelain(beforeStatus), afterSnapshot, beforePathFingerprints, afterPathFingerprints);
|
|
721
692
|
changeManifestAfterStatus = afterStatus;
|
|
722
693
|
changeManifestChangedFiles = changedFiles;
|
|
723
|
-
// Phase 5: record the attempt changed paths/hashes into the rollback
|
|
724
|
-
// intent so a transient partial write can be CAS-restored.
|
|
725
|
-
if ((input.task.id === "frontend-implement-pi" ||
|
|
726
|
-
input.task.id === "frontend-repair-pi") &&
|
|
727
|
-
(input.task.writeSet?.length ?? 0) > 0) {
|
|
728
|
-
try {
|
|
729
|
-
const { recordFrontendWriterAttempt } = await import("../workflows/dag/frontend-writer-recovery.js");
|
|
730
|
-
await recordFrontendWriterAttempt({
|
|
731
|
-
cwd: input.cwd,
|
|
732
|
-
runDir: meta.runDir,
|
|
733
|
-
changedPaths: changedFiles,
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
catch {
|
|
737
|
-
// best-effort: a missing intent (no baseline captured) is a no-op
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
694
|
const guard = validateShellWriteGuardFromDiff({
|
|
741
695
|
changedFiles,
|
|
742
696
|
task: input.task,
|
|
@@ -977,6 +931,15 @@ function parseWriterImplementationOutcome(text) {
|
|
|
977
931
|
const lines = text.split(/\r?\n/);
|
|
978
932
|
const candidates = [];
|
|
979
933
|
for (const [lineIndex, line] of lines.entries()) {
|
|
934
|
+
const localizedOutcome = localizedWriterImplementationOutcome(line);
|
|
935
|
+
if (localizedOutcome) {
|
|
936
|
+
candidates.push({
|
|
937
|
+
lineIndex,
|
|
938
|
+
value: localizedOutcome,
|
|
939
|
+
outcome: localizedOutcome,
|
|
940
|
+
});
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
980
943
|
const nextLine = lines[lineIndex + 1];
|
|
981
944
|
const normalized = normalizeProtocolLine(line, WRITER_OUTCOME_PROTOCOL_LINE, nextLine);
|
|
982
945
|
if (normalized === undefined)
|
|
@@ -1005,6 +968,26 @@ function parseWriterImplementationOutcome(text) {
|
|
|
1005
968
|
candidates,
|
|
1006
969
|
};
|
|
1007
970
|
}
|
|
971
|
+
function localizedWriterImplementationOutcome(line) {
|
|
972
|
+
const normalized = normalizedProtocolValue(line);
|
|
973
|
+
const match = /^(?:实现|实施)结果\s*[::]\s*(已变更|已更改|已修改|已满足|无需变更|已阻塞|阻塞)$/u.exec(normalized);
|
|
974
|
+
if (!match)
|
|
975
|
+
return undefined;
|
|
976
|
+
switch (match[1]) {
|
|
977
|
+
case "已变更":
|
|
978
|
+
case "已更改":
|
|
979
|
+
case "已修改":
|
|
980
|
+
return "changed";
|
|
981
|
+
case "已满足":
|
|
982
|
+
case "无需变更":
|
|
983
|
+
return "already-satisfied";
|
|
984
|
+
case "已阻塞":
|
|
985
|
+
case "阻塞":
|
|
986
|
+
return "blocked";
|
|
987
|
+
default:
|
|
988
|
+
return undefined;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
1008
991
|
function isWriterImplementationOutcome(value) {
|
|
1009
992
|
return (value === "changed" || value === "already-satisfied" || value === "blocked");
|
|
1010
993
|
}
|
|
@@ -78,40 +78,6 @@ export async function checkPiSdkAvailability(_repoRoot) {
|
|
|
78
78
|
return { ok: false, detail: `pi SDK not available: ${message}` };
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Best-effort Pi SDK model catalog (credentialed providers first).
|
|
83
|
-
* Empty on SDK/auth/runtime failure — callers must fall back to harness tiers.
|
|
84
|
-
*/
|
|
85
|
-
export async function listPiSdkAvailableModels() {
|
|
86
|
-
try {
|
|
87
|
-
const imported = sdkImportOverrideForTests
|
|
88
|
-
? await sdkImportOverrideForTests()
|
|
89
|
-
: await loadPiSdkModule();
|
|
90
|
-
const sdk = imported;
|
|
91
|
-
const getAgentDir = sdk.getAgentDir;
|
|
92
|
-
const ModelRuntime = sdk.ModelRuntime;
|
|
93
|
-
if (typeof getAgentDir !== "function" || typeof ModelRuntime?.create !== "function") {
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
const agentDir = getAgentDir();
|
|
97
|
-
const runtime = await ModelRuntime.create({
|
|
98
|
-
authPath: path.join(agentDir, "auth.json"),
|
|
99
|
-
modelsPath: path.join(agentDir, "models.json"),
|
|
100
|
-
});
|
|
101
|
-
const snapshot = runtime.snapshot ?? {};
|
|
102
|
-
const configured = snapshot.configuredProviders;
|
|
103
|
-
const isConfigured = (provider) => configured instanceof Set ? configured.has(provider) : false;
|
|
104
|
-
return (snapshot.available ?? []).map((entry) => ({
|
|
105
|
-
provider: entry.provider,
|
|
106
|
-
id: entry.id,
|
|
107
|
-
name: entry.name,
|
|
108
|
-
hasCredentials: isConfigured(entry.provider),
|
|
109
|
-
}));
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
return [];
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
81
|
/** Fail-closed probe for the SDK-only structured custom-tool surface. */
|
|
116
82
|
export async function checkPiSdkCustomToolCapability(_repoRoot) {
|
|
117
83
|
if (sdkSessionFactoryOverride) {
|
|
@@ -31,7 +31,7 @@ import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorresp
|
|
|
31
31
|
import { materializeBackendTestResultFromPytestHtml, materializeBackendTestResultFromRunDir, parsePytestHtmlReport, } from "../workflows/dag/backend-test-result-contract.js";
|
|
32
32
|
import { collectBackendTestHumanCaseCatalog, collectBackendTestMappedPytestScripts, resolveBackendTestMappedPytestScripts, collectJacocoCoverage, hasBlockingBackendMarkdownSafetyFindings, hasBlockingBackendMarkdownModuleStemFindings, inspectBackendTestEnvironment, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, renderBackendTestL5Dashboard, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
|
|
33
33
|
import { applyDeterministicScenarioParamRepairs, assessBackendScenarioParamConsistency, classifyBackendTestFailureWithScenarioParam, readBackendScenarioParamFacts, renderBackendTestFailureAnalysis, writeBackendScenarioParamArtifacts, writeScenarioParamRepairAudit, } from "../workflows/dag/backend-test-scenario-param.js";
|
|
34
|
-
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
34
|
+
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
35
35
|
import { computeL5ReportMetrics } from "../workflows/dag/l5-report-metrics.js";
|
|
36
36
|
import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
|
|
37
37
|
import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
|
|
@@ -678,6 +678,42 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
678
678
|
fixtureResolution,
|
|
679
679
|
});
|
|
680
680
|
}
|
|
681
|
+
try {
|
|
682
|
+
const correspondence = await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
683
|
+
workspaceRoot: input.cwd,
|
|
684
|
+
taskId: meta.spec.sourceBinding?.taskId ?? "unbound",
|
|
685
|
+
});
|
|
686
|
+
const correspondenceReportPath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence-initial.md", correspondence.markdown);
|
|
687
|
+
const contractsDir = path.join(meta.runDir, "contracts");
|
|
688
|
+
await mkdir(contractsDir, { recursive: true });
|
|
689
|
+
const correspondenceFactsPath = path.join(contractsDir, "backend-test-markdown-pytest-correspondence-initial.json");
|
|
690
|
+
await writeFile(correspondenceFactsPath, JSON.stringify(correspondence.facts, null, 2), "utf8");
|
|
691
|
+
outputs.push(`initialCorrespondence=${correspondenceReportPath}`, `initialCorrespondenceFacts=${correspondenceFactsPath}`);
|
|
692
|
+
if (correspondence.facts.status === "FAIL" && facts.status === "PASS") {
|
|
693
|
+
const correspondenceRepairPaths = Array.from(new Set(correspondence.facts.entries
|
|
694
|
+
.filter((entry) => entry.status !== "EXACT_1_TO_1")
|
|
695
|
+
.flatMap((entry) => [entry.expectedScript, ...entry.actualScripts])
|
|
696
|
+
.filter((candidate) => candidate.startsWith("testcase/") && candidate.endsWith(".py")))).sort();
|
|
697
|
+
if (correspondenceRepairPaths.length > 0) {
|
|
698
|
+
facts.status = "REPAIRABLE";
|
|
699
|
+
facts.repairEligible = true;
|
|
700
|
+
facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...correspondenceRepairPaths])).sort();
|
|
701
|
+
facts.findings.push({
|
|
702
|
+
kind: "markdown-pytest-correspondence",
|
|
703
|
+
classification: "test-asset-defect",
|
|
704
|
+
repairability: "repairable",
|
|
705
|
+
detail: correspondence.facts.entries
|
|
706
|
+
.filter((entry) => entry.status !== "EXACT_1_TO_1")
|
|
707
|
+
.flatMap((entry) => entry.findings)
|
|
708
|
+
.join("; ")
|
|
709
|
+
.slice(0, 12_000) || "Markdown-to-pytest correspondence is incomplete",
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
outputs.push(`initialCorrespondence=UNAVAILABLE: ${error instanceof Error ? error.message : String(error)}`);
|
|
716
|
+
}
|
|
681
717
|
const artifacts = await writeBackendPytestCollectionArtifacts({
|
|
682
718
|
runDir: meta.runDir,
|
|
683
719
|
stem: "initial",
|
|
@@ -776,12 +812,56 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
776
812
|
facts: effective,
|
|
777
813
|
});
|
|
778
814
|
const scenarioFacts = await readBackendScenarioParamFacts(path.join(meta.runDir, "contracts", "backend-test-scenario-param-consistency-facts.json"));
|
|
815
|
+
const correspondence = meta.spec.sourceBinding
|
|
816
|
+
? await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
817
|
+
workspaceRoot: input.cwd,
|
|
818
|
+
taskId: meta.spec.sourceBinding.taskId,
|
|
819
|
+
})
|
|
820
|
+
: undefined;
|
|
821
|
+
const eligibility = correspondence
|
|
822
|
+
? buildBackendTestItemEligibility(effective.collectedItemIds, {
|
|
823
|
+
correspondenceEntries: correspondence.facts.entries,
|
|
824
|
+
scenarioEntries: scenarioFacts.entries,
|
|
825
|
+
})
|
|
826
|
+
: { eligibleItemIds: effective.collectedItemIds, excludedItems: [] };
|
|
827
|
+
const eligibilityInputHashes = Object.fromEntries((correspondence?.facts.inputFiles ?? []).map((item) => [item.path, item.sha256]));
|
|
828
|
+
const eligibilityFactsPath = path.join(meta.runDir, "contracts", "backend-test-execution-eligibility.json");
|
|
829
|
+
await mkdir(path.dirname(eligibilityFactsPath), { recursive: true });
|
|
830
|
+
await writeFile(eligibilityFactsPath, `${JSON.stringify({
|
|
831
|
+
schemaId: "backend-test-execution-eligibility-v1",
|
|
832
|
+
status: eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
|
|
833
|
+
collectedItemCount: effective.collectedItemIds.length,
|
|
834
|
+
eligibleItemCount: eligibility.eligibleItemIds.length,
|
|
835
|
+
excludedItemCount: eligibility.excludedItems.length,
|
|
836
|
+
eligibleItemIds: eligibility.eligibleItemIds,
|
|
837
|
+
excludedItems: eligibility.excludedItems,
|
|
838
|
+
inputHashes: eligibilityInputHashes,
|
|
839
|
+
}, null, 2)}\n`, "utf8");
|
|
840
|
+
const eligibilityReportPath = await writeRunReport(meta.runDir, "backend-test-execution-eligibility.md", [
|
|
841
|
+
"# Backend Test Execution Eligibility",
|
|
842
|
+
"",
|
|
843
|
+
"## Status",
|
|
844
|
+
"",
|
|
845
|
+
eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
|
|
846
|
+
"",
|
|
847
|
+
`- Collected items: ${effective.collectedItemIds.length}`,
|
|
848
|
+
`- Eligible items: ${eligibility.eligibleItemIds.length}`,
|
|
849
|
+
`- Excluded items: ${eligibility.excludedItems.length}`,
|
|
850
|
+
"",
|
|
851
|
+
"## Excluded Items",
|
|
852
|
+
"",
|
|
853
|
+
...(eligibility.excludedItems.length > 0 ? eligibility.excludedItems.map((item) => `- \`${item.itemId}\` (${item.caseId ?? "unknown Case"}): ${item.reasons.join("; ")}`) : ["- None"]),
|
|
854
|
+
"",
|
|
855
|
+
].join("\n"));
|
|
856
|
+
outputs.push(`executionEligibility=${eligibilityReportPath}`, `executionEligibilityFacts=${eligibilityFactsPath}`);
|
|
779
857
|
const readiness = await materializeBackendTestExecutionReadiness({
|
|
780
858
|
runDir: meta.runDir,
|
|
781
859
|
workspaceRoot: input.cwd,
|
|
782
860
|
effective,
|
|
783
861
|
scenarioParamStatus: scenarioFacts.overallStatus,
|
|
784
862
|
scenarioParamRepairAttempt: scenarioFacts.repairAttempt,
|
|
863
|
+
eligibility,
|
|
864
|
+
eligibilityInputHashes,
|
|
785
865
|
});
|
|
786
866
|
return {
|
|
787
867
|
ok: true,
|
|
@@ -792,6 +872,8 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
792
872
|
collectedItemCount: effective.collectedItemCount,
|
|
793
873
|
fixtureResolutionStatus: effective.fixtureResolutionStatus,
|
|
794
874
|
executionReadinessStatus: readiness.status,
|
|
875
|
+
eligibleItemCount: readiness.eligibleItemIds.length,
|
|
876
|
+
excludedItemCount: readiness.excludedItems.length,
|
|
795
877
|
factsPath: "contracts/backend-test-pytest-collection-effective.json",
|
|
796
878
|
readinessPath: "contracts/backend-test-execution-readiness.json",
|
|
797
879
|
reportPath: artifacts.reportPath,
|
|
@@ -889,8 +971,11 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
889
971
|
const executionReadiness = await readBackendTestExecutionReadiness(path.join(meta.runDir, "contracts", "backend-test-execution-readiness.json"));
|
|
890
972
|
await assertBackendTestExecutionReadinessFresh(input.cwd, executionReadiness);
|
|
891
973
|
const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd);
|
|
892
|
-
const
|
|
893
|
-
const
|
|
974
|
+
const eligibleItemIds = executionReadiness.eligibleItemIds;
|
|
975
|
+
const pytestTargetsRelative = "reports/backend-test-pytest-targets.txt";
|
|
976
|
+
const pytestTargetsAbsolute = path.join(meta.runDir, pytestTargetsRelative);
|
|
977
|
+
await writeFile(pytestTargetsAbsolute, `${eligibleItemIds.join("\n")}\n`, "utf8");
|
|
978
|
+
const pytestTargetsArg = `@\"\${HARNESS_DAG_RUN_DIR}/${pytestTargetsRelative}\"`;
|
|
894
979
|
// Split into short bash -c commands (aligned with markdown-environment).
|
|
895
980
|
// A single ultra-long compound command has been observed on Windows to
|
|
896
981
|
// exit 0xC0000142 (STATUS_DLL_INIT_FAILED) in ~40ms with empty stdout/
|
|
@@ -967,8 +1052,10 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
967
1052
|
'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
|
|
968
1053
|
'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
|
|
969
1054
|
'echo "STEP=pytest-run"',
|
|
970
|
-
`echo "- pytestCommand: python -m pytest
|
|
971
|
-
`
|
|
1055
|
+
`echo "- pytestCommand: python -m pytest <${eligibleItemIds.length} eligibility-authorized-items> -v -p no:cacheprovider --html=reports/backend-test.html --self-contained-html" >> "\${DIAG_FILE}"`,
|
|
1056
|
+
`echo "- eligibleItemCount: ${eligibleItemIds.length}" >> "\${DIAG_FILE}"`,
|
|
1057
|
+
`echo "- excludedItemCount: ${executionReadiness.excludedItems.length}" >> "\${DIAG_FILE}"`,
|
|
1058
|
+
`PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 "\${PYTHON_BIN}" -m pytest ${pytestTargetsArg} -v -p no:cacheprovider --html="\${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" --self-contained-html`,
|
|
972
1059
|
"STATUS=$?",
|
|
973
1060
|
'printf "%s" "${STATUS}" > "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt"',
|
|
974
1061
|
'echo "- pytestExitCode: ${STATUS}" >> "${DIAG_FILE}"',
|
|
@@ -1057,7 +1144,9 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1057
1144
|
const caseValidationSummary = await readRequiredRunReport(reportsDir, "backend-md-case-validation.md");
|
|
1058
1145
|
const caseCoverageSummary = await readAdvisoryRunReport(reportsDir, "backend-test-case-coverage-analysis.md", "Backend Test Case Coverage Analysis");
|
|
1059
1146
|
const traceabilitySummary = await readRequiredRunReport(reportsDir, "backend-test-traceability.md");
|
|
1060
|
-
const
|
|
1147
|
+
const correspondenceSummaryBase = await readAdvisoryRunReport(reportsDir, "backend-test-markdown-pytest-correspondence.md", "Backend Test Markdown → pytest Correspondence");
|
|
1148
|
+
const eligibilitySummary = await readAdvisoryRunReport(reportsDir, "backend-test-execution-eligibility.md", "Backend Test Execution Eligibility");
|
|
1149
|
+
const correspondenceSummary = `${correspondenceSummaryBase.trim()}\n\n${eligibilitySummary.trim()}\n`;
|
|
1061
1150
|
const htmlContent = renderBackendTestHtml({
|
|
1062
1151
|
title: meta.spec.title,
|
|
1063
1152
|
parsed,
|