@tea-agent/loop-agent 0.39.0-beta.2 → 0.39.0-beta.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 (202) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +187 -92
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/recommendations.js +13 -2
  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 +429 -28
  15. package/dist/commands/inspect-next.js +8 -0
  16. package/dist/executors/dag-pi-executor.js +1691 -29
  17. package/dist/executors/pi-extension-resolver.js +233 -0
  18. package/dist/executors/pi-playwright-cli-tool.js +51 -12
  19. package/dist/executors/pi-sdk-executor.js +180 -33
  20. package/dist/executors/shell-executor.js +1152 -171
  21. package/dist/executors/shell-write-guard.js +14 -0
  22. package/dist/shared/dag-prompt-override.js +28 -0
  23. package/dist/shared/openspec-spec.js +70 -4
  24. package/dist/shared/operator/capabilities.js +32 -0
  25. package/dist/shared/playwright-cli-command-policy.js +15 -0
  26. package/dist/shared/update/console-notifier.js +100 -0
  27. package/dist/{cli → shared}/update/npm-client.js +41 -8
  28. package/dist/{cli → shared}/update/state.js +41 -13
  29. package/dist/task/config-types.js +19 -5
  30. package/dist/task/contract/project.js +3 -0
  31. package/dist/task/contract/schema.js +2 -1
  32. package/dist/task/frontend-project-capability.js +60 -17
  33. package/dist/task/source-prepare/parse-intent.js +24 -4
  34. package/dist/task/source-prepare/prepare.js +36 -0
  35. package/dist/worker/cli.js +31 -1
  36. package/dist/worker/console/chat/pi-console-config.js +81 -10
  37. package/dist/worker/console/chat/routes.js +1 -0
  38. package/dist/worker/console/dag-execution-receipt.js +33 -0
  39. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  40. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  41. package/dist/worker/console/index.js +3 -0
  42. package/dist/worker/console/operation-store.js +226 -6
  43. package/dist/worker/console/operator-actions.js +263 -0
  44. package/dist/worker/console/operator-user-error.js +2 -2
  45. package/dist/worker/console/routes.js +43 -2
  46. package/dist/worker/console/security.js +63 -9
  47. package/dist/worker/console/server.js +151 -6
  48. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-zsNyvGaH.js} +1 -1
  49. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BDjZ5kE1.js} +1 -1
  50. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-BkgaujnN.js} +1 -1
  51. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-CqxOJi0j.js} +1 -1
  52. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-C2JHLdRi.js} +1 -1
  53. package/dist/worker/console/static/assets/channel-BuqaGKAT.js +1 -0
  54. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-DF_YSvOS.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-D3Ort5Vo.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-XfAfZeUC.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-SLBibhf5.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-Dz29Ye_f.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DB2G4H8V.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-CIoGkxdu.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-CG78FGkn.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-ZDHAvXPd.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BKkkC4VA.js} +1 -1
  64. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DNdSHjH9.js +1 -0
  65. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DNdSHjH9.js +1 -0
  66. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-SposUZsO.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-BSCtQpD7.js} +1 -1
  68. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-DgFHrw9l.js} +1 -1
  69. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-BVBasYbj.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-6r_4rN_c.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-JmuaB41P.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-xXvAY3sk.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-D2B3lY2o.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-tK54tMEX.js} +1 -1
  75. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CNtELzV-.js} +1 -1
  76. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-C1xKMKcW.js} +1 -1
  77. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CZ7dlPMz.js} +1 -1
  78. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BqGb42Xe.js} +1 -1
  79. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-D_BlXEtt.js} +1 -1
  80. package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-D5zGX6fG.js} +68 -68
  81. package/dist/worker/console/static/assets/index-DBbhESQ_.css +1 -0
  82. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Vce2vmIe.js} +1 -1
  83. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DDwVBAvI.js} +1 -1
  84. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-y_Ew-wv4.js} +1 -1
  85. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-CjXJFp1K.js} +1 -1
  86. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-1itMst1Q.js} +1 -1
  87. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-C-D3ZRUa.js} +5 -5
  88. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BiYywmZc.js} +1 -1
  89. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-l2fydNWA.js} +1 -1
  90. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-Dz0hNbYW.js} +1 -1
  91. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-BujI_mLZ.js} +1 -1
  92. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-pWe84beb.js} +1 -1
  93. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-ByYu2frg.js} +1 -1
  94. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-CoPnilFP.js} +1 -1
  95. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-1Md2LnS0.js} +1 -1
  96. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DqqqAoSH.js} +1 -1
  97. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C4sGkf--.js} +1 -1
  98. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8rmop_9.js +1 -0
  99. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-ByxJrUzW.js} +2 -2
  100. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B0ky-kBM.js +8 -0
  101. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-D8MLgM0O.js} +1 -1
  102. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DnBis-wZ.js} +1 -1
  103. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-uuyYT7UO.js} +1 -1
  104. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-BjgabhwK.js} +1 -1
  105. package/dist/worker/console/static/index.html +2 -2
  106. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  107. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  108. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  109. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  110. package/dist/worker/observe/node-input.js +74 -3
  111. package/dist/worker/observe/node-process.js +377 -0
  112. package/dist/worker/observe/routes.js +36 -3
  113. package/dist/worker/observe/shell-handler-keys.js +34 -0
  114. package/dist/worker/observe/static/api.js +66 -0
  115. package/dist/worker/observe/static/constants.js +8 -1
  116. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  117. package/dist/worker/observe/static/format.js +23 -0
  118. package/dist/worker/observe/static/markdown-render.js +20 -1
  119. package/dist/worker/observe/static/operator-chrome.js +4 -0
  120. package/dist/worker/observe/static/state.js +13 -2
  121. package/dist/worker/observe/static/styles.css +73 -0
  122. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  123. package/dist/worker/observe/static/views/dag.js +8 -1
  124. package/dist/worker/observe/static/views/failures.js +5 -2
  125. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  126. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
  127. package/dist/workflows/dag/contract-output-registry.js +15 -0
  128. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  129. package/dist/workflows/dag/failure-category.js +12 -0
  130. package/dist/workflows/dag/frontend-closeout.js +221 -0
  131. package/dist/workflows/dag/frontend-design-policy.js +366 -0
  132. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  133. package/dist/workflows/dag/frontend-implementation-contract.js +378 -160
  134. package/dist/workflows/dag/frontend-prewrite-gate.js +343 -391
  135. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  136. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  137. package/dist/workflows/dag/frontend-recovery-controller.js +201 -0
  138. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  139. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  140. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  141. package/dist/workflows/dag/frontend-repair.js +7 -424
  142. package/dist/workflows/dag/frontend-review-context.js +195 -8
  143. package/dist/workflows/dag/frontend-risk.js +15 -2
  144. package/dist/workflows/dag/frontend-shadow-dual-write.js +665 -0
  145. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  146. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  147. package/dist/workflows/dag/frontend-shape.js +425 -0
  148. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  149. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  150. package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
  151. package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
  152. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  153. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  154. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  155. package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
  156. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  157. package/dist/workflows/dag/frontend-typed-event-store.js +439 -0
  158. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  159. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  160. package/dist/workflows/dag/frontend-worktree-diff.js +97 -18
  161. package/dist/workflows/dag/frontend-writer-admission.js +260 -0
  162. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  163. package/dist/workflows/dag/init-hybrid.js +572 -500
  164. package/dist/workflows/dag/node-execution.js +307 -70
  165. package/dist/workflows/dag/prompt.js +10 -18
  166. package/dist/workflows/dag/rerun-plan.js +144 -5
  167. package/dist/workflows/dag/rerun-run.js +58 -6
  168. package/dist/workflows/dag/retry-policy.js +228 -4
  169. package/dist/workflows/dag/runner.js +314 -119
  170. package/dist/workflows/dag/scheduler.js +114 -16
  171. package/dist/workflows/dag/structured-output-repair.js +712 -0
  172. package/dist/workflows/dag/types.js +235 -23
  173. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  174. package/dist/workflows/dag/validate.js +60 -10
  175. package/docs/init-surface.manifest.json +5 -0
  176. package/docs/operations/README.md +1 -1
  177. package/docs/templates/agent-dag.schema.json +44 -0
  178. package/docs/templates/frontend-test-dag.json +8 -10
  179. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  180. package/docs/templates/init-managed-agents.md +13 -3
  181. package/harness.json +3 -3
  182. package/package.json +5 -1
  183. package/scripts/next-info.mjs +356 -0
  184. package/scripts/next-publish-gate.mjs +238 -0
  185. package/scripts/release-source-binding.mjs +251 -0
  186. package/skills/codebase-scout/SKILL.md +1 -1
  187. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  188. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  189. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  190. package/skills/frontend-implementation/SKILL.md +4 -4
  191. package/skills/frontend-implementation/references/design-spec.md +11 -7
  192. package/skills/frontend-implementation/references/node-contracts.md +20 -11
  193. package/skills/loop-agent/references/command-reference.md +11 -4
  194. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  195. package/skills/playwright-cli/SKILL.md +22 -0
  196. package/skills/playwright-cli-case-generator/SKILL.md +20 -1
  197. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  198. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  199. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  200. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  201. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  202. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -90,6 +90,7 @@ import {
90
90
  closeDagInspector,
91
91
  selectDagNode,
92
92
  renderDagInspector,
93
+ mergeNodeProcess,
93
94
  } from "./dag-inspector.js";
94
95
  import { mergeSessionEvents } from "./session-timeline.js";
95
96
 
@@ -451,13 +452,19 @@ export async function renderDagDetail(dagRunId, initial = true) {
451
452
  nodesEl.scrollLeft = nodesScrollLeft;
452
453
 
453
454
  if (uiState.dagInspectorOpen && uiState.selectedDagNodeId) {
455
+ const selectedNode = nodes.find(
456
+ (node) => node.nodeId === uiState.selectedDagNodeId,
457
+ );
454
458
  if (!uiState.sessionEventIdentity) {
455
459
  uiState.sessionEventIdentity = makeSessionEventIdentity(
456
460
  dagRunId,
457
461
  uiState.selectedDagNodeId,
458
462
  );
459
463
  }
460
- await mergeSessionEvents(dagRunId, uiState.selectedDagNodeId);
464
+ await Promise.all([
465
+ mergeSessionEvents(dagRunId, uiState.selectedDagNodeId),
466
+ selectedNode ? mergeNodeProcess(dagRunId, selectedNode) : Promise.resolve(),
467
+ ]);
461
468
  }
462
469
  renderDagInspector(
463
470
  dagRunId,
@@ -101,11 +101,14 @@ export async function renderFailures() {
101
101
 
102
102
  if (tasks.length === 0) {
103
103
  clearNode(listEl);
104
+ // 2026-08-22 (B-FIND-02): 空态文案与静态 #failures-empty / 页面标题
105
+ // 「异常 Task」保持一致;旧文案「当前没有失败或阻塞的 Task。」与静态
106
+ // HTML 及回归目录 AC-FE-INS-004 漂移,导致 frontend-test 字面 find 0 命中。
104
107
  if (emptyEl) {
105
108
  emptyEl.hidden = false;
106
- mountViewState(emptyEl, "empty", "当前没有失败或阻塞的 Task");
109
+ mountViewState(emptyEl, "empty", "当前没有异常 Task");
107
110
  } else {
108
- mountViewState(listEl, "empty", "当前没有失败或阻塞的 Task");
111
+ mountViewState(listEl, "empty", "当前没有异常 Task");
109
112
  }
110
113
  return;
111
114
  }
@@ -250,6 +250,54 @@ export function buildSessionTimelineItems(events) {
250
250
  return items;
251
251
  }
252
252
 
253
+ /**
254
+ * Split a node's shared session-events log into per-attempt segments. Every
255
+ * attempt appends one agent run beginning with agent_start; events recorded
256
+ * before the first agent_start (defensive) stay in an implicit first segment.
257
+ */
258
+ export function splitSessionEventsByAttempt(events) {
259
+ const segments = [];
260
+ let current = null;
261
+ for (const event of events) {
262
+ if (typeof event?.type === "string" && event.type === "agent_start") {
263
+ current = [];
264
+ segments.push(current);
265
+ }
266
+ if (current === null) {
267
+ current = [];
268
+ segments.push(current);
269
+ }
270
+ current.push(event);
271
+ }
272
+ return segments;
273
+ }
274
+
275
+ /**
276
+ * Resolve the events visible in the「执行过程」tab for the selected execution
277
+ * key. attempt-N shows only that attempt's segment when the shared log carries
278
+ * multiple attempts; other keys (pass-N, null) keep the full stream.
279
+ */
280
+ export function resolveTimelineVisibleEvents(events, executionKey) {
281
+ const match =
282
+ typeof executionKey === "string"
283
+ ? /^attempt-(\d+)$/.exec(executionKey)
284
+ : null;
285
+ if (!match) return { events, note: null };
286
+ const segments = splitSessionEventsByAttempt(events);
287
+ if (segments.length <= 1) return { events, note: null };
288
+ const requested = Number(match[1]);
289
+ if (requested >= 1 && requested <= segments.length) {
290
+ return {
291
+ events: segments[requested - 1],
292
+ note: `已按第 ${requested} 次执行过滤 · 共 ${segments.length} 次执行`,
293
+ };
294
+ }
295
+ return {
296
+ events,
297
+ note: `未检测到第 ${requested} 次执行的边界(共 ${segments.length} 次执行),暂显示全部事件`,
298
+ };
299
+ }
300
+
253
301
  /**
254
302
  * Build rich timeline items: merge tool start/end by toolCallId, collapse
255
303
  * protocol noise, surface turn thinking/text without dumping agent_end.messages.
@@ -439,12 +487,25 @@ function ensureOpenDetailsSet() {
439
487
  return uiState.sessionTimelineOpenDetails;
440
488
  }
441
489
 
442
- function bindDetailsToggle(details, detailKey) {
490
+ function closedDetailKey(detailKey) {
491
+ return `${detailKey}::user-closed`;
492
+ }
493
+
494
+ function bindDetailsToggle(details, detailKey, defaultOpen = false) {
443
495
  const openSet = ensureOpenDetailsSet();
444
- if (openSet.has(detailKey)) details.open = true;
496
+ if (defaultOpen) {
497
+ if (!openSet.has(closedDetailKey(detailKey))) details.open = true;
498
+ } else if (openSet.has(detailKey)) {
499
+ details.open = true;
500
+ }
445
501
  details.addEventListener("toggle", () => {
446
- if (details.open) openSet.add(detailKey);
447
- else openSet.delete(detailKey);
502
+ if (details.open) {
503
+ openSet.add(detailKey);
504
+ openSet.delete(closedDetailKey(detailKey));
505
+ } else {
506
+ openSet.delete(detailKey);
507
+ openSet.add(closedDetailKey(detailKey));
508
+ }
448
509
  });
449
510
  }
450
511
 
@@ -599,7 +660,7 @@ function renderProtocolCard(item) {
599
660
  const details = document.createElement("details");
600
661
  details.className = "process-timeline-details";
601
662
  details.dataset.detailKey = item.detailKey;
602
- bindDetailsToggle(details, item.detailKey);
663
+ bindDetailsToggle(details, item.detailKey, true);
603
664
  const summary = document.createElement("summary");
604
665
  summary.className = "process-timeline-summary";
605
666
  summary.textContent = `消息收发 · ${item.protocolCount} 条协议事件`;
@@ -644,7 +705,7 @@ export async function mergeSessionEvents(dagRunId, nodeId) {
644
705
  }
645
706
  }
646
707
 
647
- export function renderSessionTimeline(content, nodeId) {
708
+ export function renderSessionTimeline(content, nodeId, options = {}) {
648
709
  content.dataset.dagRunId = uiState.currentDagRunId ?? "";
649
710
  content.dataset.nodeId = nodeId ?? "";
650
711
  content.classList.add("process-timeline-scroll");
@@ -653,6 +714,14 @@ export function renderSessionTimeline(content, nodeId) {
653
714
  return;
654
715
  }
655
716
 
717
+ const visible = resolveTimelineVisibleEvents(
718
+ uiState.sessionEvents,
719
+ options.executionKey,
720
+ );
721
+ if (visible.note) {
722
+ content.appendChild(el("p", "process-timeline-note", visible.note));
723
+ }
724
+
656
725
  const showProtocol = Boolean(uiState.sessionTimelineShowProtocol);
657
726
  const toolbar = el("div", "process-timeline-toolbar");
658
727
  const protocolToggle = document.createElement("button");
@@ -668,17 +737,17 @@ export function renderSessionTimeline(content, nodeId) {
668
737
  const scrollTop = parent.scrollTop;
669
738
  const scrollLeft = parent.scrollLeft;
670
739
  while (parent.firstChild) parent.removeChild(parent.firstChild);
671
- renderSessionTimeline(parent, nodeId);
740
+ renderSessionTimeline(parent, nodeId, options);
672
741
  parent.scrollTop = scrollTop;
673
742
  parent.scrollLeft = scrollLeft;
674
743
  });
675
744
  toolbar.appendChild(protocolToggle);
676
745
  content.appendChild(toolbar);
677
746
 
678
- const timelineItems = buildRichTimelineItems(uiState.sessionEvents, {
747
+ const timelineItems = buildRichTimelineItems(visible.events, {
679
748
  showProtocol,
680
749
  });
681
- const hasEventTimes = uiState.sessionEvents.some((event) =>
750
+ const hasEventTimes = visible.events.some((event) =>
682
751
  Boolean(sessionEventTimestamp(event)),
683
752
  );
684
753
  if (!hasEventTimes) {
@@ -1500,8 +1500,26 @@ print(json.dumps(out,ensure_ascii=True))
1500
1500
  // Windows Python may expose stdin as GBK; decoding generated Chinese/emoji
1501
1501
  // source through that locale can create surrogateescape code points and make
1502
1502
  // ast.parse fail with "surrogates not allowed".
1503
- const result = spawnSync("python", ["-c", program], { input: Buffer.from(source, "utf8"), encoding: "utf8", maxBuffer: 4 * 1024 * 1024, windowsHide: true });
1504
- if (result.status !== 0 || !result.stdout.trim())
1503
+ // POSIX images usually expose only `python3` (modern macOS has no `python`);
1504
+ // Windows often has a real `python` plus a Microsoft Store `python3` stub.
1505
+ // A missing interpreter must not silently degrade to empty payload shapes.
1506
+ const interpreterCandidates = process.platform === "win32" ? ["python", "python3"] : ["python3", "python"];
1507
+ let result;
1508
+ for (const interpreter of interpreterCandidates) {
1509
+ const attempt = spawnSync(interpreter, ["-c", program], {
1510
+ input: Buffer.from(source, "utf8"),
1511
+ encoding: "utf8",
1512
+ maxBuffer: 4 * 1024 * 1024,
1513
+ windowsHide: true,
1514
+ });
1515
+ // Interpreter missing/unspawnable → try the next candidate. A spawned
1516
+ // interpreter that exits nonzero analyzed real input; do not retry then.
1517
+ if (attempt.error)
1518
+ continue;
1519
+ result = attempt;
1520
+ break;
1521
+ }
1522
+ if (!result || result.status !== 0 || !result.stdout.trim())
1505
1523
  return new Map();
1506
1524
  try {
1507
1525
  const parsed = JSON.parse(result.stdout);
@@ -1,4 +1,19 @@
1
1
  import { structuredContractOutputSchemaIds, } from "./types.js";
2
+ /**
3
+ * Registry of node-level structured contract validators, keyed by the
4
+ * `structuredContractOutput.schemaId` declared on a DAG task.
5
+ *
6
+ * The executor looks the validator up here instead of hardcoding per-contract
7
+ * imports, so new contracts (backend-test manifests, coverage contracts, ...)
8
+ * can opt into producing-node self-validation without touching the executor.
9
+ */
10
+ export const structuredContractValidationClassifications = [
11
+ "format",
12
+ "schema",
13
+ "truncated",
14
+ "semantic-repair",
15
+ "governance-blocked",
16
+ ];
2
17
  const validators = new Map();
3
18
  export function registerStructuredContractValidator(schemaId, validator) {
4
19
  if (validators.has(schemaId)) {
@@ -3,8 +3,7 @@
3
3
  * its schemaId in the contract output registry. Imported once by the node
4
4
  * executor; keeps per-contract imports out of the executor.
5
5
  */
6
- import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, validateFrontendRevisionPatchNodeOutput, } from "./frontend-implementation-contract.js";
6
+ import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, } from "./frontend-implementation-contract.js";
7
7
  import { registerStructuredContractValidator } from "./contract-output-registry.js";
8
8
  registerStructuredContractValidator("frontend-implementation-contract-v1", validateFrontendContractNodeOutput);
9
9
  registerStructuredContractValidator("frontend-implementation-contract-plan-patch-v1", validateFrontendPlanPatchNodeOutput);
10
- registerStructuredContractValidator("frontend-implementation-contract-revision-patch-v1", validateFrontendRevisionPatchNodeOutput);
@@ -29,8 +29,13 @@ const RAW_TO_NORMALIZED = {
29
29
  "decision-envelope-invalid": "decision-envelope",
30
30
  "nonzero-exit": "shell-command",
31
31
  "spawn-error": "shell-command",
32
+ "test-timeout": "timeout",
32
33
  "static-error": "static-error",
33
34
  "invalid-output": "validation",
35
+ "structured-output-truncated": "validation",
36
+ "output-too-large": "validation",
37
+ "structured-repair-exhausted": "validation",
38
+ "governance-blocked": "validation",
34
39
  "path-violation": "validation",
35
40
  "empty-output": "validation",
36
41
  "verify-failure": "validation",
@@ -40,6 +45,13 @@ const RAW_TO_NORMALIZED = {
40
45
  quota: "executor",
41
46
  "rate-limit": "executor",
42
47
  unavailable: "executor",
48
+ // Deterministic capability/policy refusal: missing verified launcher JS
49
+ // entry, capability allowlist rejection, or missing bounded write tool
50
+ // profile. These are environment/config defects the operator repairs
51
+ // before rerunning; classify as executor so recovery recommends
52
+ // rerun-after-fix WITHOUT forcing a human-decision gate on
53
+ // standaloneTaskRerun (run-failed same-task rerun).
54
+ "tool-policy": "executor",
43
55
  unknown: "unknown",
44
56
  };
45
57
  function isFinishedStatus(status) {
@@ -0,0 +1,221 @@
1
+ import { z } from "zod";
2
+ export { buildFrontendShapeTransitionCapsule, computeFrontendShapeSourceDigest, evaluateFrontendShapeEscalation, FRONTEND_SHAPE_NODE_COUNTS, frontendShapeTransitionCapsuleSchema, frontendShapeTransitionReshapeCapsuleSchema, frontendShapeTransitionSplitCapsuleSchema, parseFrontendShapeTransitionCapsule, resolveFrontendTaskShape, } from "./frontend-shape.js";
3
+ /**
4
+ * Deterministic frontend closeout facts + renderer (M7).
5
+ *
6
+ * The functions here are pure (no I/O, no model, no side effects). The closeout
7
+ * renderer reads only committed facts, never model summaries or node prose, so
8
+ * a weak conclusion (failed / not-run / baseline-debt / mock-backed / pending)
9
+ * can never be rewritten into a stronger one (passed / real-integrated).
10
+ *
11
+ * Canonical shape routing and transition capsules live in `frontend-shape.ts`
12
+ * and are re-exported here for callers that also consume closeout contracts.
13
+ */
14
+ export const FRONTEND_CLOSEOUT_FACTS_SCHEMA_ID = "frontend-closeout-facts-v1";
15
+ /* ------------------------------------------------------------------ *
16
+ * Enums + facts schema *
17
+ * ------------------------------------------------------------------ */
18
+ export const frontendCoverageStatusSchema = z.enum([
19
+ "passed",
20
+ "failed",
21
+ "not-run",
22
+ "blocked",
23
+ "unavailable",
24
+ ]);
25
+ export const frontendLintStatusSchema = z.enum([
26
+ "passed",
27
+ "baseline-debt",
28
+ "failed",
29
+ "unavailable",
30
+ ]);
31
+ export const frontendIntegrationFactsSchema = z.enum([
32
+ "real-api",
33
+ "mock-backed",
34
+ "local-no-remote",
35
+ "unavailable",
36
+ ]);
37
+ export const frontendCloseoutFactsSchema = z
38
+ .object({
39
+ schemaVersion: z.literal(1),
40
+ schemaId: z.literal(FRONTEND_CLOSEOUT_FACTS_SCHEMA_ID),
41
+ coverageMatrix: z.array(z
42
+ .object({
43
+ verificationTargetId: z.string().min(1),
44
+ status: frontendCoverageStatusSchema,
45
+ })
46
+ .strict()),
47
+ lintStatus: frontendLintStatusSchema,
48
+ integrationFacts: frontendIntegrationFactsSchema,
49
+ reviewApproval: z
50
+ .object({
51
+ typedVerdict: z
52
+ .enum(["approve_review", "request_review_changes"])
53
+ .nullable(),
54
+ legacyVerdict: z.enum(["pass", "request-revision"]).nullable(),
55
+ match: z.boolean(),
56
+ })
57
+ .strict(),
58
+ cumulativeDiff: z
59
+ .object({
60
+ cumulativeDigest: z.string().min(1),
61
+ empty: z.boolean(),
62
+ })
63
+ .strict(),
64
+ notRun: z
65
+ .object({
66
+ browser: z.literal("not-run"),
67
+ visual: z.literal("not-run"),
68
+ })
69
+ .strict(),
70
+ pendingRealIntegration: z.boolean(),
71
+ residualRisks: z.array(z.string().min(1)).optional(),
72
+ evidenceGaps: z.array(z.string().min(1)).optional(),
73
+ })
74
+ .strict();
75
+ /* ------------------------------------------------------------------ *
76
+ * FrontendTestHandoff (A+B AC-010) *
77
+ * ------------------------------------------------------------------ */
78
+ export const FRONTEND_TEST_HANDOFF_SCHEMA_ID = "frontend-test-handoff-v1";
79
+ /**
80
+ * A+B (AC-010): deterministic frontend-test handoff. Bound to the task id,
81
+ * source digest, and candidate hash; browser/visual/runtime-a11y are always
82
+ * `not-run` and the handoff is always `deferred-to-frontend-test`, so a weak
83
+ * closeout can never block implementation and can never be rewritten as
84
+ * passed.
85
+ */
86
+ export const frontendTestHandoffSchema = z
87
+ .object({
88
+ schemaVersion: z.literal(1),
89
+ schemaId: z.literal(FRONTEND_TEST_HANDOFF_SCHEMA_ID),
90
+ taskId: z.string().min(1),
91
+ sourceDigest: z.string().regex(/^[a-f0-9]{64}$/),
92
+ candidateHash: z.string().regex(/^[a-f0-9]{64}$/),
93
+ executionStatus: z.literal("not-run"),
94
+ handoffStatus: z.literal("deferred-to-frontend-test"),
95
+ nonBlocking: z.literal(true),
96
+ })
97
+ .strict();
98
+ export function buildFrontendTestHandoff(input) {
99
+ return frontendTestHandoffSchema.parse({
100
+ schemaVersion: 1,
101
+ schemaId: FRONTEND_TEST_HANDOFF_SCHEMA_ID,
102
+ taskId: input.taskId,
103
+ sourceDigest: input.sourceDigest,
104
+ candidateHash: input.candidateHash,
105
+ executionStatus: "not-run",
106
+ handoffStatus: "deferred-to-frontend-test",
107
+ nonBlocking: true,
108
+ });
109
+ }
110
+ /**
111
+ * Deterministic, fail-closed mapping from structured evidence to integration
112
+ * facts. Never reads model text. `real-api` is the only value that may report
113
+ * real integration, and it requires both real passed evidence and satisfied
114
+ * required verification evidence. Anything else downgrades to pending.
115
+ */
116
+ export function deriveFrontendIntegrationFacts(input) {
117
+ const requiredEvidenceSatisfied = input.verificationTargets.length > 0 &&
118
+ input.verificationTargets.every((target) => target.status === "ok");
119
+ if (input.realApiEvidence.present &&
120
+ input.realApiEvidence.passed &&
121
+ requiredEvidenceSatisfied) {
122
+ return "real-api";
123
+ }
124
+ // Required evidence not satisfied → never report real-integrated or
125
+ // mock/locally-validated; evidence is unavailable.
126
+ if (!requiredEvidenceSatisfied) {
127
+ return "unavailable";
128
+ }
129
+ if (input.mockStrategy !== "not-needed" && input.mockEvidenceBound) {
130
+ return "mock-backed";
131
+ }
132
+ if (input.mockStrategy === "not-needed" && !input.realApiEvidence.present) {
133
+ return "local-no-remote";
134
+ }
135
+ return "unavailable";
136
+ }
137
+ /* ------------------------------------------------------------------ *
138
+ * Deterministic closeout renderer *
139
+ * ------------------------------------------------------------------ */
140
+ function bulletList(items) {
141
+ if (!items || items.length === 0)
142
+ return "";
143
+ return items.map((item) => `- ${item}`).join("\n");
144
+ }
145
+ function coverageLines(facts) {
146
+ const lines = facts.coverageMatrix.map((entry) => `- ${entry.verificationTargetId}: ${entry.status}`);
147
+ return lines.length > 0 ? lines.join("\n") : "_(none)_";
148
+ }
149
+ function frontendStatus(facts) {
150
+ switch (facts.integrationFacts) {
151
+ case "real-api":
152
+ return "Frontend status: real-integrated";
153
+ case "mock-backed":
154
+ return "Frontend status: mock-validated";
155
+ case "local-no-remote":
156
+ return "Frontend status: locally-validated";
157
+ case "unavailable":
158
+ return "Frontend status: unverified";
159
+ }
160
+ }
161
+ function integrationStatus(facts) {
162
+ return facts.integrationFacts === "real-api"
163
+ ? "Real integration: validated"
164
+ : `Real integration: pending (${facts.integrationFacts})`;
165
+ }
166
+ function reviewApprovalLines(facts) {
167
+ const { typedVerdict, legacyVerdict, match } = facts.reviewApproval;
168
+ return [
169
+ `typed: ${typedVerdict ?? "none"}`,
170
+ `legacy: ${legacyVerdict ?? "none"}`,
171
+ `match: ${match}`,
172
+ ].join("\n");
173
+ }
174
+ /**
175
+ * Render a deterministic closeout Markdown document from committed facts only.
176
+ * The same facts always render byte-identical output; weak statuses are
177
+ * emitted verbatim and never promoted to `passed` / real-integrated.
178
+ */
179
+ export function renderFrontendCloseout(facts) {
180
+ const lintLine = facts.lintStatus === "baseline-debt"
181
+ ? `${facts.lintStatus} (explicit debt; not passed)`
182
+ : facts.lintStatus;
183
+ return [
184
+ "# Frontend Closeout",
185
+ "",
186
+ "## Coverage Matrix",
187
+ coverageLines(facts),
188
+ "",
189
+ "## Lint Status",
190
+ lintLine,
191
+ "",
192
+ "## Integration Facts",
193
+ facts.integrationFacts,
194
+ "",
195
+ "## Frontend Status",
196
+ frontendStatus(facts),
197
+ "",
198
+ "## Real Integration Status",
199
+ integrationStatus(facts),
200
+ "",
201
+ "## Real Integration Pending",
202
+ facts.pendingRealIntegration ? "yes" : "no",
203
+ "",
204
+ "## Review Approval",
205
+ reviewApprovalLines(facts),
206
+ "",
207
+ "## Cumulative Diff",
208
+ `digest: ${facts.cumulativeDiff.cumulativeDigest}`,
209
+ `empty: ${facts.cumulativeDiff.empty}`,
210
+ "",
211
+ "## Browser / Visual",
212
+ `Browser: ${facts.notRun.browser}`,
213
+ `Visual: ${facts.notRun.visual}`,
214
+ "",
215
+ "## Residual Risks",
216
+ bulletList(facts.residualRisks) || "_(none)_",
217
+ "",
218
+ "## Evidence Gaps",
219
+ bulletList(facts.evidenceGaps) || "_(none)_",
220
+ ].join("\n") + "\n";
221
+ }