@tea-agent/loop-agent 0.29.1 → 0.29.3

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +36 -16
  2. package/bin/agent-worker.js +0 -0
  3. package/dist/commands/client-recovery.js +2 -10
  4. package/dist/commands/init.js +1 -1
  5. package/dist/executors/dag-pi-executor.js +35 -10
  6. package/dist/executors/pi-executor.js +5 -3
  7. package/dist/executors/pi-playwright-cli-tool.js +9 -14
  8. package/dist/executors/pi-sdk-executor.js +16 -0
  9. package/dist/shared/operator/capabilities.js +9 -9
  10. package/dist/shared/pi-retry-settings.js +23 -0
  11. package/dist/worker/console/chat/pi-runtime.js +167 -41
  12. package/dist/worker/console/chat/routes.js +21 -2
  13. package/dist/worker/console/chat/runtime-context.js +50 -8
  14. package/dist/worker/console/chat/tool-preview.js +90 -0
  15. package/dist/worker/console/chat/turn-process.js +178 -0
  16. package/dist/worker/console/chat/usage.js +144 -16
  17. package/dist/worker/console/night-aux-ticker.js +141 -0
  18. package/dist/worker/console/operation-runner.js +21 -4
  19. package/dist/worker/console/operator-user-error.js +12 -0
  20. package/dist/worker/console/recovery-cta.js +6 -6
  21. package/dist/worker/console/routes.js +61 -0
  22. package/dist/worker/console/server.js +7 -0
  23. package/dist/worker/console/static/assets/index-D9gnJn_l.js +29 -0
  24. package/dist/worker/console/static/assets/index-rajoXwkM.css +1 -0
  25. package/dist/worker/console/static/index.html +2 -2
  26. package/dist/worker/console/static-src/night-prepare-result.js +118 -0
  27. package/dist/worker/scheduler/clock-install/win32-schtasks.js +70 -15
  28. package/dist/workflows/dag/backend-test-writer-completeness.js +98 -14
  29. package/dist/workflows/dag/frontend-test-case-checklist.js +3 -3
  30. package/dist/workflows/dag/init-hybrid.js +21 -12
  31. package/dist/workflows/dag/node-execution.js +41 -2
  32. package/docs/templates/frontend-test-case-checklist.md +1 -1
  33. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +1 -1
  34. package/docs/templates/frontend-test-dag.json +1 -1
  35. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  36. package/docs/templates/frontend-test-dag.review-cases.prompt.md +1 -1
  37. package/docs/templates/init-managed-agents.md +6 -3
  38. package/package.json +1 -1
  39. package/skills/playwright-cli/SKILL.md +1 -1
  40. package/skills/playwright-cli-case-generator/SKILL.md +1 -1
  41. package/dist/worker/console/static/assets/index-Cwx-ZVEQ.js +0 -29
  42. package/dist/worker/console/static/assets/index-Yyn3ynVv.css +0 -1
@@ -1433,13 +1433,21 @@ function buildSourceContextBlock(sources) {
1433
1433
  const requirementExcerpt = excerptMarkdown(sources.requirementMarkdown, {
1434
1434
  sourceRef: requirementRef,
1435
1435
  });
1436
- const parts = ["## Task source: 需求.md", requirementExcerpt.text];
1436
+ const boundReadPaths = [
1437
+ `- requirement: ${requirementRef}`,
1438
+ ];
1439
+ const parts = [
1440
+ `## Task source: 需求.md`,
1441
+ `Bound readPath (use for Pi read-tool calls): ${requirementRef}`,
1442
+ requirementExcerpt.text,
1443
+ ];
1437
1444
  if (sources.constraintMarkdown) {
1438
1445
  const constraintRef = toDagSourcePath(sources, sources.constraintPath);
1439
1446
  const constraintExcerpt = excerptMarkdown(sources.constraintMarkdown, {
1440
1447
  sourceRef: constraintRef,
1441
1448
  });
1442
- parts.push("## Task source: 执行约束.md", constraintExcerpt.text);
1449
+ boundReadPaths.push(`- constraints: ${constraintRef}`);
1450
+ parts.push("## Task source: 执行约束.md", `Bound readPath (use for Pi read-tool calls): ${constraintRef}`, constraintExcerpt.text);
1443
1451
  }
1444
1452
  for (const reference of (sources.referenceDocuments ?? []).slice(0, MAX_INLINE_SOURCE_REFERENCE_DOCUMENTS)) {
1445
1453
  const relativePath = path
@@ -1449,9 +1457,10 @@ function buildSourceContextBlock(sources) {
1449
1457
  const referenceExcerpt = excerptMarkdown(reference.markdown, {
1450
1458
  sourceRef: referenceRef,
1451
1459
  });
1452
- parts.push(`## Task source reference: ${relativePath}`, referenceExcerpt.text);
1460
+ boundReadPaths.push(`- reference ${relativePath}: ${referenceRef}`);
1461
+ parts.push(`## Task source reference: ${relativePath}`, `Bound readPath (use for Pi read-tool calls): ${referenceRef}`, referenceExcerpt.text);
1453
1462
  }
1454
- parts.push("## Task config summary", `- taskId: ${sources.taskConfig.taskId}`, `- flow: ${sources.taskConfig.flow}`, `- complexity: ${sources.taskConfig.complexity}`, `- contextProfile: ${sources.taskConfig.contextProfile}`, `- allowedPaths: ${sources.taskConfig.allowedPaths.join(", ") || "(none — review before execute)"}`, `- forbiddenPaths: ${sources.taskConfig.forbiddenPaths.join(", ") || "(none)"}`, '- Pi DAG nodes are read-only unless toolProfile="write" is explicitly selected for a bounded writer node.', "- Agent DAG read-only nodes must not write root artifacts/**; root artifacts/ is not a per-node scratchpad.", "- source/references/* are immutable user/source facts; source/需求.md is the derived execution contract.");
1463
+ parts.push("## Bound source read paths", ...boundReadPaths, "Use these repository-readable paths for any Pi read-tool calls. Bound files under `.harness/tasks/<taskId>/source/**` are read-only inputs: reading them is allowed even though writing `.harness/**` is forbidden.", "Never resolve task-relative citations such as `source/需求.md` or `source/references/*` against the repository root, invent `source/<taskId>/...`, search for substitutes, or fall back to `docs/**` when a bound read fails.", "## Task config summary", `- taskId: ${sources.taskConfig.taskId}`, `- flow: ${sources.taskConfig.flow}`, `- complexity: ${sources.taskConfig.complexity}`, `- contextProfile: ${sources.taskConfig.contextProfile}`, `- allowedPaths: ${sources.taskConfig.allowedPaths.join(", ") || "(none — review before execute)"}`, `- forbiddenPaths: ${sources.taskConfig.forbiddenPaths.join(", ") || "(none)"}`, '- Pi DAG nodes are read-only unless toolProfile="write" is explicitly selected for a bounded writer node.', "- Agent DAG read-only nodes must not write root artifacts/**; root artifacts/ is not a per-node scratchpad.", `- Derived execution contract and immutable references live under the Bound source read paths above (not as repo-root \`source/...\`).`);
1455
1464
  if (sources.taskConfig.hardConstraints.length > 0) {
1456
1465
  parts.push("- hardConstraints:", ...sources.taskConfig.hardConstraints.map((c) => ` - ${c}`));
1457
1466
  }
@@ -4026,7 +4035,7 @@ function buildFrontendTestHybridDag(sources) {
4026
4035
  "if(!Array.isArray(manifest.cases)||manifest.cases.length===0)throw new Error('checklist: empty cases');",
4027
4036
  `const declaredAc=new Set(${declaredAcIdsLiteral});`,
4028
4037
  "const issues=[];",
4029
- "const openRe=/playwright-cli\\s+open\\s+--browser=chrome\\s+--headless\\s+https?:\\/\\/\\S+/i;",
4038
+ "const openRe=/playwright-cli\\s+open\\s+--browser=chrome\\s+https?:\\/\\/\\S+/i;",
4030
4039
  "const prodRe=/(?:^|\\/\\/)(?:www\\.)?[^\\s\\/]*(?:prod|production)/i;",
4031
4040
  "const caseIdRe=/^FE-[A-Za-z0-9][A-Za-z0-9-]*$/;",
4032
4041
  ,
@@ -4039,9 +4048,9 @@ function buildFrontendTestHybridDag(sources) {
4039
4048
  " if(!casePath||!fs.existsSync(casePath)){issues.push({ruleId:'case-file-missing',caseId:id,detail:String(casePath)});continue;}",
4040
4049
  " if(typeof c.caseId==='string'&&casePath!=='testcase/frontend/cases/'+c.caseId+'.md')issues.push({ruleId:'case-path-mismatch',caseId:id,detail:casePath+' must equal testcase/frontend/cases/'+c.caseId+'.md'});",
4041
4050
  " const body=fs.readFileSync(casePath,'utf8');",
4042
- " if(!openRe.test(body))issues.push({ruleId:'open-prefix',caseId:id,detail:'missing playwright-cli open --browser=chrome --headless <absolute-url>; playwright-cli is strongly recommended for browser execution'});",
4051
+ " if(!openRe.test(body))issues.push({ruleId:'open-prefix',caseId:id,detail:'missing playwright-cli open --browser=chrome <absolute-url>; playwright-cli is strongly recommended for browser execution'});",
4043
4052
  ,
4044
- " const m=body.match(/playwright-cli\\s+open\\s+--browser=chrome\\s+--headless\\s+(https?:\\/\\/\\S+)/i);",
4053
+ " const m=body.match(/playwright-cli\\s+open\\s+--browser=chrome\\s+(https?:\\/\\/\\S+)/i);",
4045
4054
  " if(m){const url=m[1].replace(/[)\\]},.\"']+$/,''); if(prodRe.test(url))issues.push({ruleId:'production-url',caseId:id,detail:url});}",
4046
4055
  " if(!Array.isArray(c.acIds)||c.acIds.length===0)issues.push({ruleId:'ac-mapping',caseId:id,detail:'acIds required (AC-FE-* acceptance ids, not caseId)'});",
4047
4056
  " else {",
@@ -4174,7 +4183,7 @@ function buildFrontendTestHybridDag(sources) {
4174
4183
  "Build the frontend test RAG package (keep it short).",
4175
4184
  "Read task source, routes/components/API or Mock facts, and execution contract. Write only testcase/frontend/rag/context.md and coverage-map.md.",
4176
4185
  "Prefer fixed fields: baseUrl, baseUrlSource, environmentProbe, AC table, capability matrix (backend real/mock, pagination data, HTTP observation, error injection), risks, forbidden hosts. Do not paste large implementation dumps.",
4177
- `Controller-frozen origin (required, not model-selectable): write exactly \`baseUrl: ${controllerFrontend.baseUrl}\` and \`baseUrlSource: ${controllerFrontend.baseUrlSource}\`. Do not derive, replace, or override the origin from model reasoning or other repository text. Write \`environmentProbe: pending\`. Include exact start prefix: playwright-cli open --browser=chrome --headless ${controllerFrontend.baseUrl}.`,
4186
+ `Controller-frozen origin (required, not model-selectable): write exactly \`baseUrl: ${controllerFrontend.baseUrl}\` and \`baseUrlSource: ${controllerFrontend.baseUrlSource}\`. Do not derive, replace, or override the origin from model reasoning or other repository text. Write \`environmentProbe: pending\`. Include exact start prefix: playwright-cli open --browser=chrome ${controllerFrontend.baseUrl}.`,
4178
4187
  buildSourceContextBlock(sources),
4179
4188
  ].join("\n\n"),
4180
4189
  },
@@ -4227,7 +4236,7 @@ function buildFrontendTestHybridDag(sources) {
4227
4236
  "dimensions: core|boundary|flow|backend only.",
4228
4237
  "Prefer a small smoke suite (default max roughly 4-8 cases unless task frontendTest.maxCasesPerBatch is higher). Never invent unavailable API fields or credentials. Do not create pytest or Playwright source.",
4229
4238
  "HARD playwright-cli-only: every browser step must use repo skill playwright-cli declared commands only. Forbidden: bare `playwright`, `npx playwright`, `playwright test`, `@playwright/test`, Node Playwright API, or generating Playwright/Pytest source. No fallback when playwright-cli is unavailable - case must instruct blocked evidence playwright-cli-unavailable.",
4230
- `Use only the controller-frozen baseUrl ${controllerFrontend.baseUrl} from ${controllerFrontend.baseUrlSource}; context.md may reference it but cannot establish or override it. Every browser start command must be exactly: playwright-cli open --browser=chrome --headless ${controllerFrontend.baseUrl}. Never leave a base-url placeholder. Use default browser session only; never write -s=<case-id>.`,
4239
+ `Use only the controller-frozen baseUrl ${controllerFrontend.baseUrl} from ${controllerFrontend.baseUrlSource}; context.md may reference it but cannot establish or override it. Every browser start command must be exactly: playwright-cli open --browser=chrome ${controllerFrontend.baseUrl}. Never leave a base-url placeholder. Use default browser session only; never write -s=<case-id>.`,
4231
4240
  "HARD dynamic refs: executable playwright-cli lines must never contain an angle-bracket token such as <fresh-ref> or descriptive <...> placeholder. Use only shell-safe documentation placeholders `eX`, `eY`, ...; each means the real `eNN` ref parsed from the immediately preceding latest `snapshot`. Write a fresh snapshot before every element reference. eX/eY are never literal structured-tool arguments; a later snapshot invalidates prior refs, so never reuse stale refs.",
4232
4241
  "HARD file-output argv: use canonical `--filename` only. Screenshot uses `playwright-cli screenshot --filename final.png` (a real ref may precede the flag); PDF uses `playwright-cli pdf --filename final.pdf`; snapshot without filename is response-only and a snapshot file uses `playwright-cli snapshot --filename snapshot.txt`. Never generate `playwright-cli screenshot <path>`, use `--path`, `--output`, or `--file`, or pass an output path as a positional target.",
4233
4242
  "Each case must be independently reproducible with fixture/reset, UI reset, snapshot-before-ref, evidence write point under testcase/frontend/evidence/<case-id>/. If the isolated environment is unavailable, require writing blocked evidence before any browser command.",
@@ -4376,7 +4385,7 @@ function buildFrontendTestHybridDag(sources) {
4376
4385
  subtaskPromptTemplate: [
4377
4386
  "Primary job: EXECUTE case {{case.caseId}} from {{case.casePath}} with skill playwright-cli (fresh Pi session; do not use /new). playwright-cli-only: never bare Playwright CLI/API/test runner; no fallback.",
4378
4387
  "Use the structured playwright_cli tool for every browser action. Do not request or search for bash. Do not execute raw shell commands. Translate each playwright-cli line in the case Markdown into one playwright_cli tool call ({command, args?, timeoutSeconds?}).",
4379
- `1) The controller-frozen baseUrl is ${controllerFrontend.baseUrl} from ${controllerFrontend.baseUrlSource}; model-authored context/case text cannot establish or override it. 2) Start browser ONLY via playwright_cli command=open with args [--browser=chrome, --headless, ${controllerFrontend.baseUrl}] (default session only; no -s=). 3) Dynamic refs: eX/eY in case Markdown are documentation placeholders, never tool args. Immediately before every structured playwright_cli call that references an element, parse the current real eNN from the immediately preceding latest snapshot and pass only that real eNN; never send literal \`eX\`/\`eY\`. A new snapshot invalidates prior refs, so never reuse stale refs. File outputs are canonical: screenshot args [--filename, final.png] (or [e5, --filename, final.png] for a real target), PDF args [--filename, final.pdf], and snapshot writes a file only with [--filename, snapshot.txt]; snapshot without filename is response-only. Never use --path, --output, --file, or any output path as a positional target. Follow case steps with snapshot before element refs using only playwright_cli. A passed case requires this same child receipt order: successful open → successful find → controller post-execution cleanup. Only successful find is a meaningful assertion; snapshot, goto, screenshot, request/console, click/fill and other ordinary interactions cannot establish passed authority. 4) Only when preflight or playwright_cli tool explicitly fails may you write blocked evidence (blockedReason playwright-cli-unavailable | frontend-base-url-unreachable); never invent CLI-unavailable solely because bash is absent. 5) For U/D: enforce current-user ownership / create-or-mock-or-blocked; never mutate other users' data.`,
4388
+ `1) The controller-frozen baseUrl is ${controllerFrontend.baseUrl} from ${controllerFrontend.baseUrlSource}; model-authored context/case text cannot establish or override it. 2) Start browser ONLY via playwright_cli command=open with args [--browser=chrome, ${controllerFrontend.baseUrl}] (default session only; no -s=). 3) Dynamic refs: eX/eY in case Markdown are documentation placeholders, never tool args. Immediately before every structured playwright_cli call that references an element, parse the current real eNN from the immediately preceding latest snapshot and pass only that real eNN; never send literal \`eX\`/\`eY\`. A new snapshot invalidates prior refs, so never reuse stale refs. File outputs are canonical: screenshot args [--filename, final.png] (or [e5, --filename, final.png] for a real target), PDF args [--filename, final.pdf], and snapshot writes a file only with [--filename, snapshot.txt]; snapshot without filename is response-only. Never use --path, --output, --file, or any output path as a positional target. Follow case steps with snapshot before element refs using only playwright_cli. A passed case requires this same child receipt order: successful open → successful find → controller post-execution cleanup. Only successful find is a meaningful assertion; snapshot, goto, screenshot, request/console, click/fill and other ordinary interactions cannot establish passed authority. 4) Only when preflight or playwright_cli tool explicitly fails may you write blocked evidence (blockedReason playwright-cli-unavailable | frontend-base-url-unreachable); never invent CLI-unavailable solely because bash is absent. 5) For U/D: enforce current-user ownership / create-or-mock-or-blocked; never mutate other users' data.`,
4380
4389
  "Always write {{case.evidenceDir}}execution.md and {{case.evidenceDir}}case-result.json with caseId={{case.caseId}}, status passed|failed|blocked, evidencePaths (relative under evidenceDir). blocked needs non-empty blockedReason. After writing, self-check the same contract; if self-check fails, rewrite both files as status=blocked blockedReason=invalid-evidence-shape (never leave missing/malformed evidence).",
4381
4390
  "Business failed/blocked is a recorded result, not a node failure. Close browser via playwright_cli command=close. Return compact JSON (<=1200 chars): {caseId,status,evidencePaths,errorSummary,tokens}.",
4382
4391
  ].join("\n\n"),
@@ -4453,7 +4462,7 @@ function buildFrontendTestHybridDag(sources) {
4453
4462
  subtaskPromptTemplate: [
4454
4463
  "RERUN attempt {{case.rerunAttempt}} for {{case.caseId}} (reason={{case.reason}}). Rewrite authoritative evidence; set rerunAttempt={{case.rerunAttempt}}.",
4455
4464
  "Primary job: EXECUTE case {{case.caseId}} from {{case.casePath}} with skill playwright-cli (fresh Pi session). Use structured playwright_cli only; headless open.",
4456
- `Start via playwright_cli command=open with args [--browser=chrome, --headless, ${controllerFrontend.baseUrl}]. Passed requires open → find → cleanup receipts.`,
4465
+ `Start via playwright_cli command=open with args [--browser=chrome, ${controllerFrontend.baseUrl}]. Passed requires open → find → cleanup receipts.`,
4457
4466
  "Always write {{case.evidenceDir}}execution.md and {{case.evidenceDir}}case-result.json with caseId, status, evidencePaths, rerunAttempt={{case.rerunAttempt}}.",
4458
4467
  ].join("\n\n"),
4459
4468
  },
@@ -4543,7 +4552,7 @@ function buildFrontendTestHybridDag(sources) {
4543
4552
  "frontend-test-dag generates Markdown cases and browser evidence only; it must not generate pytest or Playwright test source code.",
4544
4553
  "Each browser case runs serially in a fresh Pi execution boundary. Persist its evidence before starting the next case.",
4545
4554
  "Use only the declared isolated test environment. Production URLs, real credentials, and unauthorized data are blocked.",
4546
- `Browser startup for generated cases must be playwright-cli open --browser=chrome --headless ${controllerFrontend.baseUrl}; this origin is frozen by the controller from ${controllerFrontend.baseUrlSource}, and model-authored files cannot establish or override it; generated operations stay in the default browser session and must not use unverified named-session flags.`,
4555
+ `Browser startup for generated cases must be playwright-cli open --browser=chrome ${controllerFrontend.baseUrl}; this origin is frozen by the controller from ${controllerFrontend.baseUrlSource}, and model-authored files cannot establish or override it; generated operations stay in the default browser session and must not use unverified named-session flags.`,
4547
4556
  "Browser-tool preflight runs before any frontend-test Pi node; cli-only rollback, missing/incompatible Pi SDK custom-tool capability, missing verified playwright-cli launcher, or incompatible --help fails with zero Pi calls.",
4548
4557
  "Browser case children use commandPolicy capability-allowlist playwright-cli and structured playwright_cli tool; ordinary writers remain without Bash.",
4549
4558
  "Passed cases require same-child ordered controller receipts: successful open → successful find → successful post-execution cleanup. Pre-start cleanup, snapshot/goto/screenshot/request/console and ordinary interactions are insufficient; missing or unordered receipts convert to blocked (browser-command-evidence-missing).",
@@ -8,6 +8,7 @@ import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
8
8
  import { resolveContextPolicy } from "./context-policy.js";
9
9
  import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
10
10
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
11
+ import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
11
12
  import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, } from "./retry-policy.js";
12
13
  import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
13
14
  import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
@@ -125,7 +126,7 @@ export function buildNodePrompt(spec, task, upstream, options) {
125
126
  convergenceFeedback: options?.convergenceFeedback,
126
127
  });
127
128
  }
128
- function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason) {
129
+ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths) {
129
130
  if (attemptNumber <= 1)
130
131
  return basePrompt;
131
132
  if (previousFailureCategory === "protocol-invalid" &&
@@ -138,6 +139,22 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
138
139
  ].join("\n");
139
140
  }
140
141
  if (previousFailureCategory === "writer-empty-diff") {
142
+ const maxAttempts = task.retryPolicy?.maxAttempts ?? 3;
143
+ // When a completeness progress exists for this writer, fold the concrete
144
+ // target paths into the empty-diff retry so the model does not guess and
145
+ // does not need to read a forbidden `.harness/**` evidence file.
146
+ if (recoveryTargetPaths && recoveryTargetPaths.length > 0) {
147
+ return [
148
+ basePrompt,
149
+ "",
150
+ buildOutputLimitRecoverySection({
151
+ attempt: attemptNumber,
152
+ maxAttempts,
153
+ reason: "T4",
154
+ targetPaths: recoveryTargetPaths,
155
+ }),
156
+ ].join("\n");
157
+ }
141
158
  return [
142
159
  basePrompt,
143
160
  "",
@@ -150,6 +167,22 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
150
167
  }
151
168
  if (previousFailureCategory === "incomplete-write-set") {
152
169
  const maxAttempts = task.retryPolicy?.maxAttempts ?? 3;
170
+ // Embed concrete missing/broken paths from the run-owned progress facts
171
+ // so the continuation attempt is fully self-contained and never reads
172
+ // a forbidden `.harness/**` evidence file. When the loader finds no
173
+ // progress facts yet (rare), fall back to the path-pointing contract.
174
+ if (recoveryTargetPaths) {
175
+ return [
176
+ basePrompt,
177
+ "",
178
+ buildOutputLimitRecoverySection({
179
+ attempt: attemptNumber,
180
+ maxAttempts,
181
+ reason: "T3_or_T5",
182
+ targetPaths: recoveryTargetPaths,
183
+ }),
184
+ ].join("\n");
185
+ }
153
186
  return [
154
187
  basePrompt,
155
188
  "",
@@ -597,12 +630,18 @@ export async function executeDagNode(input) {
597
630
  tasksById,
598
631
  state,
599
632
  });
633
+ // For backend-test generation writers, load the most recent
634
+ // completeness progress from the run dir so the next attempt's
635
+ // prompt embeds concrete target paths. Undefined for non-writers
636
+ // or when no progress facts exist yet (no-op).
637
+ const recoveryProgress = await loadBackendTestWriterProgressForRetry(runDir, task.id);
638
+ const recoveryTargetPaths = recoveryProgress?.targetPaths;
600
639
  result = await executeNode({
601
640
  task,
602
641
  cwd,
603
642
  model,
604
643
  ...(thinking ? { thinking } : {}),
605
- prompt: buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason),
644
+ prompt: buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths),
606
645
  attempt: attemptNumber,
607
646
  reportActivity,
608
647
  timeoutMs: livenessPolicy.absoluteMaxWallClockMs,
@@ -7,7 +7,7 @@ LLM review (when `frontendTest.reviewMode=blocking`) must not invent blocking ru
7
7
 
8
8
  | ruleId | Rule |
9
9
  |---|---|
10
- | `open-prefix` | Each case body includes `playwright-cli open --browser=chrome --headless <absolute-http(s)-url>` |
10
+ | `open-prefix` | Each case body includes `playwright-cli open --browser=chrome <absolute-http(s)-url>` |
11
11
  | `production-url` | Open URL must not look like a production host |
12
12
  | `ac-mapping` | Manifest entry has non-empty `acIds` (**acceptance** ids, not case ids) |
13
13
  | `ac-id-shape` | Each `acIds[]` entry matches `AC-*` / `AC-FE-*` |
@@ -18,7 +18,7 @@ Each case is independently executable and includes AC mapping (`acIds`), precond
18
18
 
19
19
  Every case must copy the **controller-frozen** absolute base URL supplied by the DAG. `testcase/frontend/rag/context.md` may reference that value, but model-authored context/case prose cannot establish or override the origin. Do not leave a base-url placeholder.
20
20
 
21
- Every case must start with `playwright-cli open --browser=chrome --headless` followed by the concrete controller-frozen URL supplied by the DAG. Do not copy an angle-bracket URL placeholder into a generated executable case line.
21
+ Every case must start with `playwright-cli open --browser=chrome` followed by the concrete controller-frozen URL supplied by the DAG. Do not copy an angle-bracket URL placeholder into a generated executable case line.
22
22
 
23
23
  ### Dynamic element refs (hard)
24
24
 
@@ -11,7 +11,7 @@
11
11
  "globalConstraints": [
12
12
  "Do not generate pytest or Playwright source code.",
13
13
  "Only use declared isolated test environments; production URLs and real credentials are blocked.",
14
- "Every generated browser start command uses playwright-cli open --browser=chrome --headless followed by the concrete controller-resolved URL (from task source config.md when present, else http://localhost:5173); executable case lines never retain an angle-bracket URL/ref placeholder; subsequent commands stay in that default session and must not use unverified named-session flags.",
14
+ "Every generated browser start command uses playwright-cli open --browser=chrome followed by the concrete controller-resolved URL (from task source config.md when present, else http://localhost:5173); executable case lines never retain an angle-bracket URL/ref placeholder; subsequent commands stay in that default session and must not use unverified named-session flags.",
15
15
  "Case children execute serially. Persist each case result, logs and browser evidence before the next child starts.",
16
16
  "A token threshold is a post-case stop check, not a model hard token cap; unstarted cases must be recorded as blocked: token-budget-exhausted.",
17
17
  "Default pipeline acceptance is the final frontend-test-result-v1 plus testcase/frontend/reports/frontend-test-report.html",
@@ -10,7 +10,7 @@ The preflight controller resolves and freezes one absolute browser base URL from
10
10
  - Never use production hosts or credentials.
11
11
  - Write `environmentProbe: pending`. The environment preflight shell will replace this with `reachable`, `unreachable`, or `curl-unavailable` plus a structured `blockedReason` (for example `frontend-base-url-unreachable`).
12
12
  - Include the exact browser start prefix using the supplied controller-frozen URL:
13
- `playwright-cli open --browser=chrome --headless <controller-frozen-base-url>`.
13
+ `playwright-cli open --browser=chrome <controller-frozen-base-url>`.
14
14
 
15
15
  Do not claim the environment is reachable until preflight completes. Preflight does not start the application.
16
16
 
@@ -2,4 +2,4 @@
2
2
 
3
3
  Read the RAG files and Markdown cases only. First line must be `VERDICT: pass` or `VERDICT: request-revision`. Report AC coverage, case independence, evidence completeness, unsafe environment/data dependencies, and manifest issues. This verdict is a deterministic safety gate: `request-revision` blocks manifest materialization and browser execution.
4
4
 
5
- Every case must retain the exact browser-start command prefix with the resolved absolute baseUrl from `testcase/frontend/rag/context.md` (prefer task source `config.md`, else `http://localhost:5173`): `playwright-cli open --browser=chrome --headless <resolved-base-url>`; session flags must not precede `open`, and subsequent commands must remain in its default session without `-s=` or assumed named-session binding. Verify every executable sub-scenario specifies fixture/reset, UI reset, fresh snapshot before element refs, a successful `playwright-cli find ...` semantic assertion after `open`, and an evidence write point. `snapshot`, `goto`, `screenshot`, `request`/`console`, and ordinary interactions cannot independently authorize passed. Verify each case requires both `execution.md` and `case-result.json` under its own evidence directory. The JSON result must contain matching `caseId`, `status` (`passed`, `failed`, or `blocked`) and `evidencePaths`; blocked cases must name a non-empty `blockedReason` and cannot count as passed.
5
+ Every case must retain the exact browser-start command prefix with the resolved absolute baseUrl from `testcase/frontend/rag/context.md` (prefer task source `config.md`, else `http://localhost:5173`): `playwright-cli open --browser=chrome <resolved-base-url>`; session flags must not precede `open`, and subsequent commands must remain in its default session without `-s=` or assumed named-session binding. Verify every executable sub-scenario specifies fixture/reset, UI reset, fresh snapshot before element refs, a successful `playwright-cli find ...` semantic assertion after `open`, and an evidence write point. `snapshot`, `goto`, `screenshot`, `request`/`console`, and ordinary interactions cannot independently authorize passed. Verify each case requires both `execution.md` and `case-result.json` under its own evidence directory. The JSON result must contain matching `caseId`, `status` (`passed`, `failed`, or `blocked`) and `evidencePaths`; blocked cases must name a non-empty `blockedReason` and cannot count as passed.
@@ -32,10 +32,10 @@
32
32
  | --- | --- |
33
33
  | **允许** | 已发布 `loop-agent` / `agent-worker` CLI;只读 status/doctor/report/inspect/observe;准备 `source/*` 与 `task.json` 边界;human gate;shell 验证与 handoff。 |
34
34
  | **禁止** | 绕过 CLI 用宿主 Edit/Write/ApplyPatch 直接改业务实现;CLI/DAG 失败后「救火改文件」;用聊天自述代替 shell 验证。 |
35
- | **失败时** | `dag doctor` / `dag report` / `dag reconcile-run`(及适用 worker reconcile);修正任务源/`task.json`/DAG 后经 CLI 重跑。 |
35
+ | **失败时** | `dag report` / `dag doctor`;优先 `dag rerun --from-node` 安全子图续跑;仅契约/源真变或 plan 不合格时同 task 重 advance / `dag rerun-task`;paused approve→`dag resume`。 |
36
36
  | **实现写入** | 业务代码 **只** 经受治理 DAG writer(`implement-pi` / `repair-pi`)经 `task advance`(批准 writeSet gate 后长跑)。 |
37
37
 
38
- **永远不要**:`loop-agent` / `agent-worker` 失败 ⇒ 主会话直接改仓库实现。
38
+ **永远不要**:`loop-agent` / `agent-worker` 失败 ⇒ 主会话直接改仓库实现;也勿因 provider 抖动或只读节点失败**新建无关 task-id**。
39
39
 
40
40
  ### 自然语言入口路由
41
41
 
@@ -47,6 +47,7 @@
47
47
  | 初始化对齐 / 升级后对齐 / reconcile 初始化 / loop-agent 初始化对齐 | 初始化升级闭环 | 直接运行 `init upgrade`,而非拼接低层 safe-update 命令 |
48
48
  | 初始化安全更新 / 应用初始化更新 / loop-agent 初始化安全更新 / loop agent初始化安全更新 | 初始化升级闭环 | 直接运行 `init upgrade`,由 controller 处理安全更新与验证 |
49
49
  | loop-agent 帮我完成 / 帮我实现 / 帮我修复 / 帮我开发 <需求>;使用 loop-agent 完成 <X>;按 loop-agent 流程处理 <X> | 通用需求实现 | 先提供 PRD 与 `allowedPaths` / `forbiddenPaths` / verify,再 `task advance` → 审查 writeSet gate → `task advance --approve-gate`;主会话不得直接修改业务实现 |
50
+ | 从失败节点继续 / 续跑 / 不要重开任务 / 从 plan 或 code-review 接着跑 | DAG 中途恢复 | **不是**新 `task advance`:`dag report` → `dag rerun --from-node … --plan` → 带 `plan-hash` 执行(见失败默认恢复序) |
50
51
 
51
52
  **初始化升级闭环(写入型)**:主会话收到上述写入型表达时自动调用统一入口,并在 controller 返回的具体单文件 `allowedPaths` 内完成必要语义合并后使用 `--continue`。不得停在 `check-update`、`needs-safe-update`、`needs-model-merge` 或 `verification-pending`。默认安装/合并项目 `.opencode/plugins/`、`.pi/extensions/` 与 `.pi/settings.json`;Pi 必须 trust 项目才能加载,默认严禁写 `~/.pi/agent/settings.json`。
52
53
 
@@ -136,7 +137,9 @@ agent-worker console serve --repo . --port 8790 # 兼容入口,等价于上
136
137
 
137
138
  live run 先用 `loop-agent dag status --run-id <run-id>` 看 lifecycle 与 liveness;用 `loop-agent dag report --run-id <run-id> --markdown` 读 facts;失败/paused 用 `loop-agent dag doctor --run-id <run-id> --markdown`。生命周期对齐先只读运行 `loop-agent dag reconcile-run --run-id <run-id>`;只有 runner 已停止且 operator 明确提供 `--action supersede|abandon --reason "..."` 时才允许变更。失败 run 用 `loop-agent dag closeout-draft --run-id <run-id>` 生成 failure handoff,不要写成成功 closeout。
138
139
 
139
- Operator 须持续监控 live run,直到 controller 报告 run 已结束(节点/流程终态如 `FINISHED`、`FAILED` `partial_failed`),或 Decision Gate **需要 approve**;不要在节点仍运行时假定完成。持续监视过程中,主会话可在合适节点(例如节点/rank 状态变化、进入 verify/closeout、出现 stall 嫌疑或需要 approve 时)向用户做简短进度汇报(当前节点、状态、是否有风险),避免长时间静默;汇报是告知,不是请求确认,不得因此停下流程。判活须组合 runner heartbeat、session events 与 `dag doctor` liveness/provider meaningful progress;Runner heartbeat 只证明 lease,alone ≠ progress,不得仅凭运行时长结束节点。exclusive writer(如 `implement-pi` / `repair-pi`)运行期间:主会话与其他 writer **不得并发修改工作区**,以免 write-guard 错误归因;只读 status/doctor/report/observe 与 approve/reject/resume CLI 仍允许。恢复:status/doctor/report → classify → reconcile/replan → CLI 重跑 → shell verify。**禁止**把主会话直接 Edit 业务代码当作恢复手段。
140
+ **失败默认恢复序**:`dag report`/`dag doctor` 优先 `dag rerun --from-node <node> --plan` 再带 `--plan-hash`(provider 抖动、plan/review/verify 安全下游;writer/decision/fingerprint 不合格勿硬跑)→ paused:approve→`dag resume` 契约/源真变或 R1 不合格:同 task `task advance` / `dag rerun-task`(禁无理由新建 task-id)→ Worker-owned:`agent-worker task retry`。
141
+
142
+ Operator 须监控 live run 至终态(FINISHED / FAILED / partial_failed)或 Decision Gate 需要 approve;可在节点/rank 变化、verify/closeout、stall 或需 approve 时简短汇报(告知非请求确认)。判活须组合 runner heartbeat、session events 与 `dag doctor` liveness/provider meaningful progress(heartbeat alone ≠ progress)。bounded writer 运行期间不得并发修改工作区(write guard / write-guard 会把越界 diff 错误归因到 writer);只读 status/doctor/report 与 approve/reject/resume 仍允许。恢复:report/doctor → **优先** `dag rerun --from-node` → 必要时 rerun-task/同 task advance → shell verify。**禁止**把主会话直接 Edit 业务代码当作恢复手段。
140
143
 
141
144
  ### 运行态与验证
142
145
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.29.1",
3
+ "version": "0.29.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -58,7 +58,7 @@ request
58
58
  获取 snapshot,并只使用 snapshot 中可见的 ref 或已知安全 locator。
59
59
 
60
60
  ```text
61
- playwright-cli open --browser=chrome --headless http://localhost:5173
61
+ playwright-cli open --browser=chrome http://localhost:5173
62
62
  playwright-cli snapshot
63
63
  playwright-cli find "Sign in"
64
64
  playwright-cli fill e1 "user@example.com"
@@ -41,7 +41,7 @@ description: 根据 FE-test RAG 知识包生成可由受限 playwright_cli runti
41
41
  `-s=<case-id>` 建立 named session。
42
42
  3. 可独立执行的命令序列:使用 RAG `context.md` 中已解析的绝对 `baseUrl`(优先来自任务源
43
43
  `config.md`;缺失时默认 `http://localhost:5173`),必须以
44
- `playwright-cli open --browser=chrome --headless` 后接解析出的具体 base URL 开始;不得保留
44
+ `playwright-cli open --browser=chrome` 后接解析出的具体 base URL 开始;不得保留
45
45
  base-url 占位符,也不得使用 `-s=<case-id>` 或其他 named session;再按需登录、数据准备、
46
46
  `snapshot` 后优先使用元素引用和操作。每个拟通过场景必须在 `open` 后以成功的 `find` 作为
47
47
  唯一结构化 UI 语义断言,再由 controller 执行 post-execution cleanup。`snapshot`、`goto`、