@tea-agent/loop-agent 0.38.2 → 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.
Files changed (192) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +171 -96
  3. package/README.md +11 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +4 -0
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +16 -5
  8. package/dist/cli/program.js +16 -2
  9. package/dist/cli/update/notifier.js +2 -2
  10. package/dist/cli.js +2 -1
  11. package/dist/commands/dag-rerun.js +55 -1
  12. package/dist/commands/init-model-catalog.js +464 -0
  13. package/dist/commands/init-upgrade.js +265 -97
  14. package/dist/commands/init.js +428 -27
  15. package/dist/commands/inspect-next.js +8 -0
  16. package/dist/commands/task-advance.js +31 -0
  17. package/dist/executors/dag-pi-executor.js +76 -10
  18. package/dist/executors/pi-event-serializer.js +42 -1
  19. package/dist/executors/pi-executor.js +51 -0
  20. package/dist/executors/pi-extension-resolver.js +233 -0
  21. package/dist/executors/pi-playwright-cli-tool.js +51 -12
  22. package/dist/executors/pi-sdk-executor.js +187 -59
  23. package/dist/executors/shell-executor.js +265 -15
  24. package/dist/executors/shell-write-guard.js +7 -0
  25. package/dist/shared/dag-prompt-override.js +28 -0
  26. package/dist/shared/operator/capabilities.js +32 -0
  27. package/dist/shared/playwright-cli-command-policy.js +15 -0
  28. package/dist/shared/update/console-notifier.js +100 -0
  29. package/dist/{cli → shared}/update/npm-client.js +41 -8
  30. package/dist/{cli → shared}/update/state.js +41 -13
  31. package/dist/task/contract/constants.js +1 -0
  32. package/dist/task/contract/diff.js +26 -0
  33. package/dist/task/contract/project.js +5 -0
  34. package/dist/task/contract/schema.js +6 -1
  35. package/dist/task/source-prepare/build-draft.js +15 -0
  36. package/dist/task/source-prepare/parse-intent.js +24 -4
  37. package/dist/worker/cli.js +31 -1
  38. package/dist/worker/console/chat/routes.js +1 -0
  39. package/dist/worker/console/dag-execution-receipt.js +33 -0
  40. package/dist/worker/console/operator-actions.js +116 -0
  41. package/dist/worker/console/operator-user-error.js +2 -2
  42. package/dist/worker/console/pi-readiness.js +4 -4
  43. package/dist/worker/console/routes.js +43 -2
  44. package/dist/worker/console/security.js +63 -9
  45. package/dist/worker/console/server.js +36 -5
  46. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
  47. package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-pm3c2Rsl.js} +1 -1
  48. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
  49. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
  50. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
  51. package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
  52. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_Bq2ZqK.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
  62. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
  63. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
  64. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BC6Bgosf.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
  65. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
  67. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
  68. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
  73. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
  74. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
  75. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
  76. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
  77. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
  78. package/dist/worker/console/static/assets/index-C8rOIAJr.css +1 -0
  79. package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DEbyrEfW.js} +147 -68
  80. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
  81. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
  82. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
  83. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
  84. package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-CAyXjQjN.js} +1 -1
  85. package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-CcWKn6tS.js} +5 -5
  86. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
  87. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
  88. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
  89. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
  90. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
  91. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
  92. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
  93. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
  94. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
  95. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
  96. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
  97. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DOSnT7hb.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
  98. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
  99. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DVJQA7AN.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
  100. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
  101. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
  102. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
  103. package/dist/worker/console/static/index.html +2 -2
  104. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  105. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  106. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  107. package/dist/worker/observe/node-input.js +74 -3
  108. package/dist/worker/observe/node-process.js +377 -0
  109. package/dist/worker/observe/routes.js +36 -3
  110. package/dist/worker/observe/shell-handler-keys.js +34 -0
  111. package/dist/worker/observe/static/api.js +66 -0
  112. package/dist/worker/observe/static/constants.js +8 -1
  113. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  114. package/dist/worker/observe/static/format.js +23 -0
  115. package/dist/worker/observe/static/markdown-render.js +20 -1
  116. package/dist/worker/observe/static/operator-chrome.js +4 -0
  117. package/dist/worker/observe/static/state.js +13 -2
  118. package/dist/worker/observe/static/styles.css +73 -0
  119. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  120. package/dist/worker/observe/static/views/dag.js +8 -1
  121. package/dist/worker/observe/static/views/failures.js +5 -2
  122. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  123. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +76 -6
  124. package/dist/workflows/dag/backend-test-scenario-partitions.js +73 -1
  125. package/dist/workflows/dag/contract-output-registry.js +15 -0
  126. package/dist/workflows/dag/failure-category.js +12 -0
  127. package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
  128. package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
  129. package/dist/workflows/dag/frontend-repair.js +14 -0
  130. package/dist/workflows/dag/frontend-risk.js +15 -2
  131. package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
  132. package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
  133. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  134. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  135. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  136. package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
  137. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  138. package/dist/workflows/dag/frontend-verification-trace.js +29 -4
  139. package/dist/workflows/dag/init-hybrid.js +196 -117
  140. package/dist/workflows/dag/node-execution.js +139 -10
  141. package/dist/workflows/dag/prompt.js +15 -1
  142. package/dist/workflows/dag/rerun-plan.js +134 -3
  143. package/dist/workflows/dag/rerun-run.js +58 -6
  144. package/dist/workflows/dag/retry-policy.js +91 -0
  145. package/dist/workflows/dag/structured-output-repair.js +712 -0
  146. package/dist/workflows/dag/types.js +32 -1
  147. package/dist/workflows/dag/validate.js +41 -1
  148. package/docs/init-surface.manifest.json +5 -0
  149. package/docs/operations/README.md +1 -1
  150. package/docs/operations/local-development-environment.md +1 -5
  151. package/docs/skills/vetted-skill-registry.md +14 -0
  152. package/docs/templates/README.md +1 -1
  153. package/docs/templates/agent-dag.schema.json +44 -0
  154. package/docs/templates/backend-test-dag.json +2 -2
  155. package/docs/templates/frontend-test-dag.json +8 -10
  156. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  157. package/docs/templates/init-managed-agents.md +13 -3
  158. package/harness.json +2 -2
  159. package/package.json +12 -4
  160. package/scripts/next-info.mjs +356 -0
  161. package/scripts/next-publish-gate.mjs +238 -0
  162. package/scripts/release-source-binding.mjs +251 -0
  163. package/skills/analyze-product-requirements/SKILL.md +8 -5
  164. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  165. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  166. package/skills/analyze-product-requirements/references/example.md +50 -0
  167. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  168. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  169. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  170. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  171. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  172. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  173. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  174. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  175. package/skills/codebase-scout/SKILL.md +1 -1
  176. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  177. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  178. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  179. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  180. package/skills/improve-codebase-architecture/deepening.md +37 -0
  181. package/skills/improve-codebase-architecture/html-report.md +123 -0
  182. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  183. package/skills/improve-codebase-architecture/language.md +53 -0
  184. package/skills/loop-agent/references/command-reference.md +11 -4
  185. package/skills/playwright-cli/SKILL.md +22 -0
  186. package/skills/playwright-cli-case-generator/SKILL.md +20 -1
  187. package/dist/worker/console/static/assets/channel-R6oQIDkw.js +0 -1
  188. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +0 -1
  189. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +0 -1
  190. package/dist/worker/console/static/assets/index-CuH5CNFu.css +0 -1
  191. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +0 -1
  192. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +0 -8
@@ -3,25 +3,48 @@ import { lstat, readFile, realpath } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import semver from "semver";
5
5
  const PACKAGE_NAME = "@tea-agent/loop-agent";
6
+ export const DEFAULT_NPM_QUERY_TIMEOUT_MS = 15_000;
6
7
  export function createNpmUpdateClient(options) {
7
8
  const npmCommand = options.npmCommand ?? "npm";
8
9
  const loopAgentCommand = options.loopAgentCommand ?? "loop-agent";
9
10
  const runner = options.runner ?? runCommand;
10
- const timeoutMs = options.timeoutMs ?? 1500;
11
+ const timeoutMs = options.timeoutMs ?? DEFAULT_NPM_QUERY_TIMEOUT_MS;
12
+ const cwd = options.cwd;
11
13
  const env = options.env;
14
+ const latestVersionCheck = async () => {
15
+ let result;
16
+ try {
17
+ result = await runner(npmCommand, ["view", PACKAGE_NAME, "dist-tags.latest", "--json"], { timeoutMs, cwd, env });
18
+ }
19
+ catch {
20
+ return { ok: false, reason: "spawn-failure" };
21
+ }
22
+ if (result.timedOut)
23
+ return { ok: false, reason: "timeout" };
24
+ if (result.exitCode === null) {
25
+ return { ok: false, reason: "spawn-failure" };
26
+ }
27
+ if (result.exitCode !== 0) {
28
+ return { ok: false, reason: "command-failed" };
29
+ }
30
+ const version = parseNpmViewLatest(result.stdout);
31
+ return version
32
+ ? { ok: true, version }
33
+ : { ok: false, reason: "invalid-response" };
34
+ };
12
35
  return {
13
36
  async latestVersion() {
14
- const result = await runner(npmCommand, ["view", PACKAGE_NAME, "dist-tags.latest", "--json"], { timeoutMs, env });
15
- if (result.exitCode !== 0 || result.timedOut)
16
- return undefined;
17
- return parseNpmViewLatest(result.stdout);
37
+ const result = await latestVersionCheck();
38
+ return result.ok ? result.version : undefined;
18
39
  },
40
+ latestVersionCheck,
19
41
  async canAutoInstall() {
20
42
  return proveGlobalNpmInstall({
21
43
  currentVersion: options.currentVersion,
22
44
  packageRoot: options.packageRoot,
23
45
  npmCommand,
24
46
  runner,
47
+ cwd,
25
48
  env,
26
49
  timeoutMs,
27
50
  });
@@ -29,17 +52,25 @@ export function createNpmUpdateClient(options) {
29
52
  async installExact(version) {
30
53
  if (!isStrictRelease(version))
31
54
  return { ok: false, reason: "invalid-version" };
32
- const result = await runner(npmCommand, buildExactInstallArgs(version), { timeoutMs: Math.max(timeoutMs, 120000), env });
55
+ const result = await runner(npmCommand, buildExactInstallArgs(version), {
56
+ timeoutMs: Math.max(timeoutMs, 120000),
57
+ cwd,
58
+ env,
59
+ });
33
60
  if (result.exitCode !== 0 || result.timedOut)
34
61
  return { ok: false, reason: "install-failed" };
35
62
  return { ok: true };
36
63
  },
37
64
  async verifyInstalled(version) {
38
- const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, env });
65
+ const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, cwd, env });
39
66
  if (list.exitCode !== 0 || !verifyNpmListVersion(list.stdout, version)) {
40
67
  return { ok: false, reason: "npm-list-version-mismatch" };
41
68
  }
42
- const pathVersion = await runner(loopAgentCommand, ["--version"], { timeoutMs, env });
69
+ const pathVersion = await runner(loopAgentCommand, ["--version"], {
70
+ timeoutMs,
71
+ cwd,
72
+ env,
73
+ });
43
74
  if (pathVersion.exitCode !== 0 || !verifyPathVersion(pathVersion.stdout, version)) {
44
75
  return { ok: false, reason: "path-version-mismatch" };
45
76
  }
@@ -80,6 +111,7 @@ export function verifyPathVersion(stdout, expectedVersion) {
80
111
  async function proveGlobalNpmInstall(options) {
81
112
  const root = await options.runner(options.npmCommand, ["root", "-g"], {
82
113
  timeoutMs: options.timeoutMs,
114
+ cwd: options.cwd,
83
115
  env: options.env,
84
116
  });
85
117
  if (root.exitCode !== 0 || root.timedOut)
@@ -113,6 +145,7 @@ function isStrictRelease(version) {
113
145
  async function runCommand(command, args, options) {
114
146
  return new Promise((resolve) => {
115
147
  const child = spawn(command, [...args], {
148
+ cwd: options.cwd,
116
149
  env: options.env ? { ...process.env, ...options.env } : process.env,
117
150
  stdio: ["ignore", "pipe", "pipe"],
118
151
  shell: false,
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  export function updateStatePath(homeDir) {
4
4
  return path.join(homeDir, ".loop-agent", "update-state.json");
@@ -18,6 +18,7 @@ export async function readUpdateState(statePath) {
18
18
  latestVersion: stringOrUndefined(parsed.latestVersion),
19
19
  dismissedVersion: stringOrUndefined(parsed.dismissedVersion),
20
20
  retryAfter: stringOrUndefined(parsed.retryAfter),
21
+ lastPromptedLocalDate: stringOrUndefined(parsed.lastPromptedLocalDate),
21
22
  };
22
23
  }
23
24
  catch {
@@ -36,28 +37,55 @@ export async function acquireUpdateLock(lockPath, options = {}) {
36
37
  await mkdir(path.dirname(lockPath), { recursive: true });
37
38
  try {
38
39
  await mkdir(lockPath);
40
+ return await claimLock(lockPath, now);
41
+ }
42
+ catch {
43
+ if (await isFreshLock(lockPath, now, staleMs))
44
+ return undefined;
45
+ try {
46
+ await rm(lockPath, { recursive: true, force: true });
47
+ await mkdir(lockPath);
48
+ return await claimLock(lockPath, now);
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ }
54
+ }
55
+ async function claimLock(lockPath, now) {
56
+ try {
39
57
  await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
40
58
  return async () => {
41
59
  await rm(lockPath, { recursive: true, force: true });
42
60
  };
43
61
  }
62
+ catch {
63
+ await rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
64
+ return undefined;
65
+ }
66
+ }
67
+ async function isFreshLock(lockPath, now, staleMs) {
68
+ try {
69
+ const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
70
+ const owner = JSON.parse(ownerRaw);
71
+ if (typeof owner.at !== "string")
72
+ return false;
73
+ const at = Date.parse(owner.at);
74
+ if (!Number.isFinite(at))
75
+ return false;
76
+ return now().getTime() - at <= staleMs;
77
+ }
44
78
  catch {
45
79
  try {
46
- const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
47
- const owner = JSON.parse(ownerRaw);
48
- if (owner.at && now().getTime() - Date.parse(owner.at) > staleMs) {
49
- await rm(lockPath, { recursive: true, force: true });
50
- await mkdir(lockPath);
51
- await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
52
- return async () => {
53
- await rm(lockPath, { recursive: true, force: true });
54
- };
55
- }
80
+ const info = await stat(lockPath);
81
+ const mtime = Number.isFinite(info.mtimeMs)
82
+ ? info.mtimeMs
83
+ : info.mtime.getTime();
84
+ return now().getTime() - mtime <= staleMs;
56
85
  }
57
86
  catch {
58
- return undefined;
87
+ return false;
59
88
  }
60
- return undefined;
61
89
  }
62
90
  }
63
91
  function emptyState() {
@@ -17,6 +17,7 @@ export const MANAGED_TASK_CONFIG_FIELDS = [
17
17
  "forbiddenPaths",
18
18
  "hardConstraints",
19
19
  "verifyCommands",
20
+ "backendTest",
20
21
  ];
21
22
  export const CONTRACT_FILE_NAME = "contract.json";
22
23
  export const TRANSACTIONS_DIR_NAME = ".transactions";
@@ -1,3 +1,4 @@
1
+ import { canonicalizeJsonValue } from "./canonicalize.js";
1
2
  import { MANAGED_TASK_CONFIG_FIELDS } from "./constants.js";
2
3
  function maxRisk(a, b) {
3
4
  const order = ["routine", "elevated", "blocking"];
@@ -139,6 +140,31 @@ export function diffDraftAgainstCurrent(input) {
139
140
  path: "task.json#featureId",
140
141
  });
141
142
  }
143
+ // backendTest is managed whole-object: an absent draft value means
144
+ // "keep existing" and never diffs; declared values compare canonically.
145
+ if (input.draft.backendTest !== undefined) {
146
+ const before = cfg.backendTest ?? {};
147
+ const after = input.draft.backendTest;
148
+ if (canonicalizeJsonValue(before) !== canonicalizeJsonValue(after)) {
149
+ changedManagedFields.push("backendTest");
150
+ risks.push({
151
+ code: "BACKEND_TEST_CONFIG_CHANGED",
152
+ level: "routine",
153
+ message: "backendTest configuration changed",
154
+ path: "task.json#backendTest",
155
+ });
156
+ const afterKeys = new Set(Object.keys(after));
157
+ const droppedKeys = Object.keys(before).filter((key) => !afterKeys.has(key));
158
+ if (droppedKeys.length > 0) {
159
+ risks.push({
160
+ code: "BACKEND_TEST_CONFIG_WEAKENED",
161
+ level: "elevated",
162
+ message: `drop backendTest keys: ${droppedKeys.sort().join(", ")}`,
163
+ path: "task.json#backendTest",
164
+ });
165
+ }
166
+ }
167
+ }
142
168
  }
143
169
  else {
144
170
  for (const field of MANAGED_TASK_CONFIG_FIELDS) {
@@ -174,6 +174,11 @@ export function mergeManagedTaskConfigFields(existing, draft) {
174
174
  if (draft.featureId !== undefined) {
175
175
  next.featureId = draft.featureId;
176
176
  }
177
+ // backendTest is whole-object replace when the draft declares it (`{}` is a
178
+ // legal explicit clear back to the default layout); absent keeps existing.
179
+ if (draft.backendTest !== undefined) {
180
+ next.backendTest = draft.backendTest;
181
+ }
177
182
  return next;
178
183
  }
179
184
  /**
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { taskKindSchema, taskVerifyCommandSchema } from "../config-types.js";
2
+ import { backendTestConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
3
3
  import { TASK_CONFIG_SCHEMA_VERSION, TASK_CONTRACT_CANONICALIZER_VERSION, TASK_CONTRACT_DRAFT_SCHEMA_VERSION, TASK_CONTRACT_PROJECTION_VERSION, TASK_CONTRACT_REF_SCHEMA_VERSION, } from "./constants.js";
4
4
  const sha256Schema = z
5
5
  .string()
@@ -62,6 +62,11 @@ export const taskContractDraftSchema = z
62
62
  requirement: taskContractDraftRequirementSchema,
63
63
  constraints: taskContractDraftConstraintsSchema,
64
64
  verification: taskContractDraftVerificationSchema,
65
+ /**
66
+ * Managed backend-test layout / pipeline configuration projected into
67
+ * task.json#backendTest. Optional: absent means "keep existing".
68
+ */
69
+ backendTest: backendTestConfigSchema,
65
70
  references: z.array(taskContractDraftReferenceSchema).optional(),
66
71
  sourceResolutions: z
67
72
  .array(taskContractDraftSourceResolutionSchema)
@@ -22,6 +22,18 @@ function resolveTaskKindExplicit(flags, baseDraft) {
22
22
  return flags.taskKindExplicit;
23
23
  return baseDraft?.taskKindExplicit ?? false;
24
24
  }
25
+ /**
26
+ * Resolve managed backend-test layout config.
27
+ * Precedence: explicit CLI flags > --from-draft base > existing task.json
28
+ * (the last tier self-heals tasks whose backendTest was hand-written).
29
+ */
30
+ function resolveBackendTest(flags, baseDraft, existing) {
31
+ if (flags.backendTest !== undefined)
32
+ return flags.backendTest;
33
+ if (baseDraft?.backendTest !== undefined)
34
+ return baseDraft.backendTest;
35
+ return existing?.backendTest ?? undefined;
36
+ }
25
37
  function resolveTitle(taskId, flags, facts, baseDraft, existing) {
26
38
  return (trimNonEmpty(flags.title) ??
27
39
  trimNonEmpty(baseDraft?.title) ??
@@ -205,6 +217,9 @@ export function buildPrepareDraft(input) {
205
217
  };
206
218
  if (featureId)
207
219
  draft.featureId = featureId;
220
+ const backendTest = resolveBackendTest(flags, baseDraft, existingTaskConfig);
221
+ if (backendTest !== undefined)
222
+ draft.backendTest = backendTest;
208
223
  if (references && references.length > 0) {
209
224
  draft.references = references.map((ref) => ({
210
225
  role: ref.role,
@@ -35,8 +35,15 @@ const HEADING_ALIASES = {
35
35
  assumptions: new Set(["假设", "assumptions"]),
36
36
  };
37
37
  const OBJECTIVE_MAX_CHARS = 1200;
38
- /** Product Requirement V3/V4 style embedded AC heading, e.g. AC-FE-001 / AC-BE-12 / AC-001. */
39
- const EMBEDDED_AC_HEADING = /^(AC(?:[-_](?:FE|BE|UI|API))?[-_]?\d+(?:[-_]\d+)?)\s*[::]?\s*(.*)$/iu;
38
+ /**
39
+ * Product Requirement V3/V4 style embedded AC heading, e.g. AC-FE-001 /
40
+ * AC-BE-12 / AC-001 — and catalog-qualified feature segments such as
41
+ * AC-FE-INS-001 / AC-FE-CHAT-005 (uppercase letter/digit segments).
42
+ * Catalog ids embedded in bold (“**AC-FE-INS-001**:…”) must bind as-is;
43
+ * falling through to a minted AC-00N historically produced dual ids and
44
+ * acceptanceCoverage.missing noise (2026-08-23 fe-test loop finding 5).
45
+ */
46
+ const EMBEDDED_AC_HEADING = /^(AC(?:[-_](?:FE|BE|UI|API))?[-_]?[A-Z0-9]+(?:[-_][A-Z0-9]+)*)\s*[::]?\s*(.*)$/u;
40
47
  function stripFencedAndComments(text) {
41
48
  // Remove fenced code blocks and HTML comments so headings inside them are ignored.
42
49
  return text
@@ -52,12 +59,25 @@ function normalizeAcceptanceId(raw) {
52
59
  .replace(/^(AC)(FE|BE|UI|API)(?=-|\d)/u, "$1-$2");
53
60
  }
54
61
  function parseEmbeddedAcHeading(headingText) {
55
- const normalized = headingText.trim().replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "");
62
+ const normalized = headingText
63
+ .trim()
64
+ // Strip leading section numbers ("2.1、" style) and bold/emphasis
65
+ // wrappers ("**AC-FE-INS-001**") so embedded catalog ids bind.
66
+ .replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
67
+ .replace(/^\*\*|\*\*$/g, "")
68
+ .replace(/^__|__$/g, "")
69
+ .trim();
56
70
  const match = EMBEDDED_AC_HEADING.exec(normalized);
57
71
  if (!match)
58
72
  return null;
59
73
  const id = normalizeAcceptanceId(match[1]);
60
- const text = match[2]?.trim() || id;
74
+ // Bold-wrapped ids leave their closing marker inside the captured text
75
+ // ("**AC-FE-INS-001**:xxx" captures "**:xxx"); strip emphasis remnants
76
+ // and leading separators so the bound text is clean prose.
77
+ const text = (match[2] ?? "")
78
+ .replace(/^[*_]+\s*/, "")
79
+ .replace(/^\s*[::]\s*/, "")
80
+ .trim() || id;
61
81
  return { id, text };
62
82
  }
63
83
  function normalizeHeadingText(raw) {
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile } from "node:fs/promises";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { Command } from "commander";
7
+ import { createNpmUpdateClient } from "../shared/update/npm-client.js";
8
+ import { runConsoleDailyUpdateCheck } from "../shared/update/console-notifier.js";
6
9
  import { readPackageVersion } from "../shared/package-metadata.js";
7
10
  import YAML from "yaml";
8
11
  import { LoopAgentClient } from "./loop-agent/loop-agent-client.js";
@@ -838,10 +841,33 @@ function parseOptionalTimeoutMs(raw) {
838
841
  return value;
839
842
  }
840
843
  /** Console HTTP entry for bare `agent-worker console` (listen first, then optional browser open). */
841
- async function runConsoleServe(options) {
844
+ export async function runConsoleServe(options) {
842
845
  const { createConsoleServer } = await import("./console/server.js");
843
846
  const { maybeOpenConsoleBrowser } = await import("./console/open-browser.js");
844
847
  try {
848
+ try {
849
+ const packageRoot = path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))));
850
+ const currentVersion = readPackageVersion(packageRoot) ?? "0.0.0";
851
+ await runConsoleDailyUpdateCheck({
852
+ currentVersion,
853
+ packageRoot,
854
+ homeDir: os.homedir(),
855
+ cwd: options.repoRoot,
856
+ env: process.env,
857
+ stdinIsTTY: process.stdin.isTTY === true,
858
+ stderrIsTTY: process.stderr.isTTY === true,
859
+ client: createNpmUpdateClient({
860
+ currentVersion,
861
+ packageRoot,
862
+ cwd: options.repoRoot,
863
+ env: process.env,
864
+ }),
865
+ });
866
+ }
867
+ catch (error) {
868
+ const detail = error instanceof Error ? error.message : String(error);
869
+ process.stderr.write(`loop-agent 更新检查已跳过:${detail}\n`);
870
+ }
845
871
  const server = await createConsoleServer({
846
872
  repoRoot: options.repoRoot,
847
873
  host: options.host,
@@ -857,6 +883,10 @@ async function runConsoleServe(options) {
857
883
  noOpen: options.noOpen === true,
858
884
  serverUrl: server.url,
859
885
  });
886
+ if (options.holdUntilSignal === false) {
887
+ await server.close();
888
+ return;
889
+ }
860
890
  await new Promise((resolve) => {
861
891
  const shutdown = () => resolve();
862
892
  process.once("SIGINT", shutdown);
@@ -270,6 +270,7 @@ export function parseChatImages(value) {
270
270
  }
271
271
  function gateMutation(req, deps) {
272
272
  const failure = evaluateMutationGate(req, {
273
+ cookieName: deps.bootToken.cookieName,
273
274
  bootToken: deps.bootToken.value,
274
275
  confirmationToken: deps.bootToken.confirmationToken,
275
276
  consoleOrigin: deps.getConsoleOrigin(),
@@ -35,6 +35,32 @@ import { hashDagBytes, } from "./dag-confirmation.js";
35
35
  * framework invocations that don't embed the word "verify".
36
36
  */
37
37
  export const STRUCTURED_VERIFICATION_COMMAND_RE = /\b(vitest|jest|playwright|cypress|pytest|py-test|go\s+test|cargo\s+test|npm\s+run\s+(lint|typecheck|test|verify)|npm\s+(test|run\s+test)|check-repo\.sh|loop-agent-standard-verify)\b/i;
38
+ /**
39
+ * Deterministic shell preset keys whose executors are themselves structured
40
+ * verification/gate materializers: their shell.commands[] is empty and the
41
+ * preset alone carries the verification authority (hash-bound artifact
42
+ * gates, evidence validators, report finalize). Mirrors the named-shell
43
+ * handler registry in shell-executor.ts so preset-only workflows
44
+ * (frontend-test finalize/reports, backend-test pipeline, verify-evidence
45
+ * gates) pass the G2 autonomous execution gate without depending on node-id
46
+ * naming conventions.
47
+ */
48
+ export const STRUCTURED_VERIFICATION_SHELL_PRESETS = new Set([
49
+ "frontendTestCaseChecklist",
50
+ "frontendTestCaseManifest",
51
+ "frontendTestEvidenceValidation",
52
+ "frontendTestResultFinalize",
53
+ "frontendTestL5Report",
54
+ "frontendTestHtmlReport",
55
+ "frontendTestReports",
56
+ "backendTestPipeline",
57
+ "verifyEvidence",
58
+ "repairArtifactGate",
59
+ "jacocoCoverage",
60
+ "requirementCoverageGate",
61
+ "jsonArtifactGate",
62
+ "verdictGate",
63
+ ]);
38
64
  export const DEFAULT_EXECUTION_RECEIPT_TTL_MS = 30 * 60 * 1000;
39
65
  function normalizePathEntry(entry) {
40
66
  return entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
@@ -157,6 +183,13 @@ export function deriveEligibilityFactsFromDagSpec(input) {
157
183
  return false;
158
184
  if (/verify|verification/i.test(task.id ?? ""))
159
185
  return true;
186
+ // Preset-only verification shells (empty commands[]): the preset key is
187
+ // the verification authority (frontend-test finalize, backend-test
188
+ // pipeline, evidence/artifact gates).
189
+ const shellRecord = task.shell;
190
+ const hasVerificationPreset = Object.keys(shellRecord).some((key) => STRUCTURED_VERIFICATION_SHELL_PRESETS.has(key));
191
+ if (hasVerificationPreset)
192
+ return true;
160
193
  const commands = stringList(task.shell.commands);
161
194
  return commands.some((command) => STRUCTURED_VERIFICATION_COMMAND_RE.test(command));
162
195
  });
@@ -1,5 +1,6 @@
1
1
  import { access, readFile } from "node:fs/promises";
2
2
  import { buildOperatorCapabilitiesDocument, OFFICIAL_TAIL_ACTION_IDS, } from "../../shared/operator/capabilities.js";
3
+ import { DAG_RERUN_PROMPT_OVERRIDE_ENV, isDagRerunPromptOverrideEnabled, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
3
4
  import { buildOperatorResult, operatorFailed, operatorSucceeded, } from "../../shared/operator/envelope.js";
4
5
  import { CANONICAL_TASK_ID_PATTERN, formatLocalBusinessDate, getTaskPaths, loadTaskConfig, } from "../../task/runtime.js";
5
6
  import { diffTaskContract, validateDraftForTask, } from "../../task/contract/index.js";
@@ -401,6 +402,85 @@ async function runReadCli(ctx, args, command) {
401
402
  });
402
403
  }
403
404
  }
405
+ /**
406
+ * Parse and bound the experimental promptOverrides action param. The env
407
+ * switch is checked separately so disabled deployments fail closed before any
408
+ * staging happens. `mode` defaults to "replace"; "append" keeps the frozen
409
+ * base prompt and only appends the operator text as a marked addendum.
410
+ */
411
+ function parsePromptOverrideParams(raw) {
412
+ if (raw === undefined || raw === null)
413
+ return { kind: "ok", overrides: [] };
414
+ if (!Array.isArray(raw)) {
415
+ return {
416
+ kind: "error",
417
+ message: "promptOverrides must be an array of {nodeId, prompt, mode?}",
418
+ };
419
+ }
420
+ if (raw.length === 0)
421
+ return { kind: "ok", overrides: [] };
422
+ if (raw.length > 8) {
423
+ return { kind: "error", message: "promptOverrides supports at most 8 nodes" };
424
+ }
425
+ const overrides = [];
426
+ const seen = new Set();
427
+ for (const item of raw) {
428
+ if (!item || typeof item !== "object") {
429
+ return {
430
+ kind: "error",
431
+ message: "promptOverrides entries must be objects {nodeId, prompt, mode?}",
432
+ };
433
+ }
434
+ const record = item;
435
+ const nodeId = typeof record.nodeId === "string" ? record.nodeId.trim() : "";
436
+ const prompt = typeof record.prompt === "string" ? record.prompt : "";
437
+ const mode = record.mode === undefined || record.mode === null
438
+ ? undefined
439
+ : typeof record.mode === "string"
440
+ ? record.mode
441
+ : "invalid";
442
+ if (!nodeId) {
443
+ return { kind: "error", message: "promptOverrides nodeId is required" };
444
+ }
445
+ if (seen.has(nodeId)) {
446
+ return {
447
+ kind: "error",
448
+ message: `promptOverrides contains duplicate nodeId: ${nodeId}`,
449
+ };
450
+ }
451
+ seen.add(nodeId);
452
+ if (mode !== undefined && mode !== "append" && mode !== "replace") {
453
+ return {
454
+ kind: "error",
455
+ message: `promptOverrides mode for ${nodeId} must be append or replace`,
456
+ };
457
+ }
458
+ if (!prompt.trim() || prompt.length > MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS) {
459
+ return {
460
+ kind: "error",
461
+ message: `promptOverrides prompt for ${nodeId} must be 1-${MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS} characters`,
462
+ };
463
+ }
464
+ overrides.push(mode === undefined ? { nodeId, prompt } : { nodeId, prompt, mode });
465
+ }
466
+ return { kind: "ok", overrides };
467
+ }
468
+ /** Stage override bodies under app-data and build `--prompt-override` CLI
469
+ * args. The flag carries the mode: `--prompt-override-append` for append. */
470
+ async function buildPromptOverrideCliArgs(ctx, overrides) {
471
+ const args = [];
472
+ for (const override of overrides) {
473
+ const staged = await stageUtf8Text(ctx.appData, override.prompt, {
474
+ extension: ".md",
475
+ label: `prompt-override-${override.nodeId}`,
476
+ });
477
+ const flag = override.mode === "append"
478
+ ? "--prompt-override-append"
479
+ : "--prompt-override";
480
+ args.push(flag, `${override.nodeId}@${staged.path}`);
481
+ }
482
+ return args;
483
+ }
404
484
  async function acceptOperation(ctx, input) {
405
485
  const created = await ctx.operations.create({
406
486
  clientRequestId: input.clientRequestId,
@@ -2078,12 +2158,35 @@ export async function dispatchOperatorAction(ctx, req) {
2078
2158
  }
2079
2159
  return accepted;
2080
2160
  }
2161
+ case "dagPromptOverrideConfig": {
2162
+ // Experimental internal switch; the browser UI hides the prompt-edit
2163
+ // entry unless this reports enabled=true.
2164
+ return {
2165
+ kind: "sync",
2166
+ status: 200,
2167
+ body: operatorSucceeded(action, {
2168
+ enabled: isDagRerunPromptOverrideEnabled(),
2169
+ envVar: DAG_RERUN_PROMPT_OVERRIDE_ENV,
2170
+ }),
2171
+ };
2172
+ }
2081
2173
  case "dagRerunPlan": {
2082
2174
  const runId = str(p.runId);
2083
2175
  const fromNode = str(p.fromNode);
2084
2176
  if (!runId || !fromNode) {
2085
2177
  return invalid(action, "runId and fromNode are required");
2086
2178
  }
2179
+ const parsedOverrides = parsePromptOverrideParams(p.promptOverrides);
2180
+ if (parsedOverrides.kind === "error") {
2181
+ return invalid(action, parsedOverrides.message);
2182
+ }
2183
+ if (parsedOverrides.overrides.length > 0 &&
2184
+ !isDagRerunPromptOverrideEnabled()) {
2185
+ return invalid(action, `prompt override rerun is disabled; set ${DAG_RERUN_PROMPT_OVERRIDE_ENV}=1 to enable this experimental capability`);
2186
+ }
2187
+ const overrideArgs = parsedOverrides.overrides.length > 0
2188
+ ? await buildPromptOverrideCliArgs(ctx, parsedOverrides.overrides)
2189
+ : [];
2087
2190
  const body = await runReadCli(ctx, [
2088
2191
  "dag",
2089
2192
  "rerun",
@@ -2092,6 +2195,7 @@ export async function dispatchOperatorAction(ctx, req) {
2092
2195
  "--from-node",
2093
2196
  fromNode,
2094
2197
  "--plan",
2198
+ ...overrideArgs,
2095
2199
  "--json",
2096
2200
  ], "dag rerun plan");
2097
2201
  return { kind: "sync", status: body.ok ? 200 : 400, body };
@@ -2105,6 +2209,17 @@ export async function dispatchOperatorAction(ctx, req) {
2105
2209
  if (!runId || !fromNode || !planHash || !reason || !clientRequestId) {
2106
2210
  return invalid(action, "runId, fromNode, planHash, reason, and clientRequestId are required");
2107
2211
  }
2212
+ const parsedOverrides = parsePromptOverrideParams(p.promptOverrides);
2213
+ if (parsedOverrides.kind === "error") {
2214
+ return invalid(action, parsedOverrides.message);
2215
+ }
2216
+ if (parsedOverrides.overrides.length > 0 &&
2217
+ !isDagRerunPromptOverrideEnabled()) {
2218
+ return invalid(action, `prompt override rerun is disabled; set ${DAG_RERUN_PROMPT_OVERRIDE_ENV}=1 to enable this experimental capability`);
2219
+ }
2220
+ const overrideArgs = parsedOverrides.overrides.length > 0
2221
+ ? await buildPromptOverrideCliArgs(ctx, parsedOverrides.overrides)
2222
+ : [];
2108
2223
  return acceptOperation(ctx, {
2109
2224
  action,
2110
2225
  actionParams: p,
@@ -2122,6 +2237,7 @@ export async function dispatchOperatorAction(ctx, req) {
2122
2237
  clientRequestId,
2123
2238
  "--reason",
2124
2239
  reason,
2240
+ ...overrideArgs,
2125
2241
  "--json",
2126
2242
  ],
2127
2243
  });
@@ -3,7 +3,7 @@ import { rewriteRecoveryOperatorError } from "./recovery-error-copy.js";
3
3
  const MUTATION_GATE_COPY = [
4
4
  {
5
5
  pattern: /invalid (boot capability|confirmation header) token/i,
6
- copy: "页面已过期(控制台已重启),请刷新页面(F5)后重试。",
6
+ copy: "页面凭证已失效(控制台可能已重启),请刷新页面(F5)后重试。",
7
7
  },
8
8
  {
9
9
  pattern: /mutation requires boot capability cookie|mutation requires confirmation header token/i,
@@ -23,7 +23,7 @@ export function formatOperatorUserError(message) {
23
23
  if (recovery)
24
24
  return recovery;
25
25
  if (/controller identity changed|package fingerprint changed|entry binary sha256 changed/i.test(raw)) {
26
- return "控制器版本已变更(Console 启动后代码被重新编译)。请重启 Console 后再试,一次会话中途不要升级/重建控制器。";
26
+ return "控制器版本已变更(Console 启动后代码被重新编译,或提交了 package.json / bin / dist / skills 等指纹范围文件)。请重启 Console 进程使其重新加载当前构建后再重试本操作;一次会话中途不要重建控制器。";
27
27
  }
28
28
  if (/Pi session file missing or not openable|cannot reopen chat session .* no persisted Pi session file/i.test(raw)) {
29
29
  return "该对话的 Pi 会话文件已丢失,无法恢复上下文。请新建对话继续(历史记录无法重开)。";
@@ -85,7 +85,7 @@ function hasProviderConfig(snapshot) {
85
85
  /**
86
86
  * Discover live Pi SDK / auth / model readiness via ModelRuntime.
87
87
  * Kept inside worker/console (dynamic import) so Worker does not depend on
88
- * executors; contract matches pi-sdk-executor 0.80.10.
88
+ * executors; contract matches pi-sdk-executor 0.83.0.
89
89
  */
90
90
  export async function discoverPiReadinessProbe(options) {
91
91
  const repoRoot = options?.repoRoot ?? process.cwd();
@@ -118,7 +118,7 @@ export async function discoverPiReadinessProbe(options) {
118
118
  modelsAvailable: false,
119
119
  interviewSessionOk: false,
120
120
  details: {
121
- sdkMessage: "Pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.80.10)",
121
+ sdkMessage: "Pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.83.0)",
122
122
  authMessage: "skipped (sdk incompatible)",
123
123
  modelsMessage: "skipped (sdk incompatible)",
124
124
  sessionMessage: "skipped (sdk incompatible)",
@@ -159,7 +159,7 @@ export async function discoverPiReadinessProbe(options) {
159
159
  modelsAvailable: false,
160
160
  interviewSessionOk: false,
161
161
  details: {
162
- sdkMessage: "Pi SDK 0.80.10 contract available",
162
+ sdkMessage: "Pi SDK 0.83.0 contract available",
163
163
  authMessage: `ModelRuntime.create failed: ${message}`,
164
164
  modelsMessage: "skipped (runtime init failed)",
165
165
  sessionMessage: "skipped (runtime init failed)",
@@ -224,7 +224,7 @@ export async function discoverPiReadinessProbe(options) {
224
224
  interviewSessionOk,
225
225
  piAvailableModels: availableModels,
226
226
  details: {
227
- sdkMessage: `Pi SDK 0.80.10 contract available (agentDir=${agentDir})`,
227
+ sdkMessage: `Pi SDK 0.83.0 contract available (agentDir=${agentDir})`,
228
228
  authMessage: authReady
229
229
  ? `credentials/providers ready (available=${availableCount}, providerConfig=${providerConfigured})`
230
230
  : "no available models and no stored/configured provider credentials (set Pi auth or provider API keys)",