@tea-agent/loop-agent 0.39.0-next.27 → 0.39.0-next.29

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 (117) hide show
  1. package/AGENTS.md +3 -0
  2. package/CHANGELOG.md +150 -96
  3. package/README.md +8 -4
  4. package/dist/build-stamp.json +3 -3
  5. package/dist/cli/command-definitions.js +16 -5
  6. package/dist/cli/program.js +16 -2
  7. package/dist/cli/update/notifier.js +2 -2
  8. package/dist/cli.js +2 -1
  9. package/dist/commands/init-model-catalog.js +464 -0
  10. package/dist/commands/init-upgrade.js +265 -97
  11. package/dist/commands/init.js +428 -27
  12. package/dist/commands/inspect-next.js +8 -0
  13. package/dist/executors/dag-pi-executor.js +18 -9
  14. package/dist/executors/pi-playwright-cli-tool.js +8 -1
  15. package/dist/executors/shell-executor.js +209 -13
  16. package/dist/shared/operator/capabilities.js +7 -0
  17. package/dist/shared/update/console-notifier.js +100 -0
  18. package/dist/{cli → shared}/update/npm-client.js +41 -8
  19. package/dist/{cli → shared}/update/state.js +41 -13
  20. package/dist/worker/cli.js +31 -1
  21. package/dist/worker/console/chat/routes.js +1 -0
  22. package/dist/worker/console/operator-user-error.js +1 -1
  23. package/dist/worker/console/routes.js +3 -0
  24. package/dist/worker/console/security.js +53 -5
  25. package/dist/worker/console/server.js +10 -4
  26. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-DGn9iNrd.js} +1 -1
  27. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-Dc0C2Nyv.js} +1 -1
  28. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-COnz_Bhe.js} +1 -1
  29. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-VhIP6vgf.js} +1 -1
  30. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-D74lpmlb.js} +1 -1
  31. package/dist/worker/console/static/assets/channel-DWcZjWB3.js +1 -0
  32. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BxmiogQE.js} +1 -1
  33. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-C00Oi-2I.js} +1 -1
  34. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DTrT1Vze.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DdKW7-fL.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-D0q1WN6w.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DBa14MSI.js} +1 -1
  38. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-DkP-pEox.js} +1 -1
  39. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-lgPogpcX.js} +1 -1
  40. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-D-apZer0.js} +1 -1
  41. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-B1T2yAIe.js} +1 -1
  42. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-IjoI3dqU.js +1 -0
  43. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-IjoI3dqU.js +1 -0
  44. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-0P6hi3jF.js} +1 -1
  45. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CntzMSR8.js} +1 -1
  46. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-C9zmXJtG.js} +1 -1
  47. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-DfBCZqZw.js} +1 -1
  48. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DLp-bhGL.js} +1 -1
  49. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BksDcwZm.js} +1 -1
  50. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-Bdag7DYP.js} +1 -1
  51. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CoY5uNbk.js} +1 -1
  52. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-KcgrDXHt.js} +1 -1
  53. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CikErwJb.js} +1 -1
  54. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DqmwwzvZ.js} +1 -1
  55. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-35krh9WG.js} +1 -1
  56. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-CvTF1gJ4.js} +1 -1
  57. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BiOWWClJ.js} +1 -1
  58. package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-CqWlWE4l.js} +3 -3
  59. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Crha5dcA.js} +1 -1
  60. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DEtbscOZ.js} +1 -1
  61. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-D2YYzx3z.js} +1 -1
  62. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-C7x18ttQ.js} +1 -1
  63. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-CCLZkz45.js} +1 -1
  64. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-Dmy4lSGv.js} +5 -5
  65. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CmvIol59.js} +1 -1
  66. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-DVpEdirN.js} +1 -1
  67. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-cKQy456v.js} +1 -1
  68. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-bx06I38m.js} +1 -1
  69. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-BjfIVIJr.js} +1 -1
  70. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-DcuPGbKM.js} +1 -1
  71. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-DvgVeDoV.js} +1 -1
  72. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-DLBG9nm-.js} +1 -1
  73. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-D33L7FBF.js} +1 -1
  74. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-hvqD9TpS.js} +1 -1
  75. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-C6MII0D1.js +1 -0
  76. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-BtjH81Mz.js} +2 -2
  77. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D_tk0V8D.js +8 -0
  78. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-CJ47Kraf.js} +1 -1
  79. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-COVPl-wh.js} +1 -1
  80. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-CCoQJLUY.js} +1 -1
  81. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-DTWJvfAd.js} +1 -1
  82. package/dist/worker/console/static/index.html +1 -1
  83. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  84. package/dist/worker/observe/node-input.js +2 -0
  85. package/dist/worker/observe/node-process.js +377 -0
  86. package/dist/worker/observe/routes.js +24 -0
  87. package/dist/worker/observe/shell-handler-keys.js +34 -0
  88. package/dist/worker/observe/static/api.js +8 -0
  89. package/dist/worker/observe/static/constants.js +8 -1
  90. package/dist/worker/observe/static/format.js +23 -0
  91. package/dist/worker/observe/static/markdown-render.js +20 -1
  92. package/dist/worker/observe/static/operator-chrome.js +4 -0
  93. package/dist/worker/observe/static/state.js +11 -0
  94. package/dist/worker/observe/static/styles.css +3 -0
  95. package/dist/worker/observe/static/views/dag-inspector.js +247 -7
  96. package/dist/worker/observe/static/views/dag.js +8 -1
  97. package/dist/worker/observe/static/views/session-timeline.js +60 -4
  98. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
  99. package/dist/workflows/dag/failure-category.js +8 -0
  100. package/dist/workflows/dag/frontend-implementation-contract.js +5 -1
  101. package/dist/workflows/dag/init-hybrid.js +35 -10
  102. package/dist/workflows/dag/node-execution.js +20 -2
  103. package/dist/workflows/dag/retry-policy.js +91 -0
  104. package/dist/workflows/dag/types.js +9 -1
  105. package/docs/init-surface.manifest.json +5 -0
  106. package/docs/operations/README.md +1 -1
  107. package/docs/templates/init-managed-agents.md +13 -3
  108. package/package.json +5 -1
  109. package/scripts/next-info.mjs +356 -0
  110. package/scripts/next-publish-gate.mjs +238 -0
  111. package/scripts/release-source-binding.mjs +251 -0
  112. package/skills/loop-agent/references/command-reference.md +11 -4
  113. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  114. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  115. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  116. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  117. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -11,14 +11,15 @@ import {
11
11
  } from "../constants.js";
12
12
  import { clearNode, el } from "../dom.js";
13
13
  import { attemptFailureLabel } from "../dag-history-labels.js";
14
- import { fetchJson, fetchSpecEvidenceFile, fetchDagNodeInput } from "../api.js";
15
- import { badge, formatMs } from "../format.js";
16
- import { renderMarkdown } from "../markdown-render.js";
14
+ import { fetchJson, fetchSpecEvidenceFile, fetchDagNodeInput, fetchDagNodeProcess } from "../api.js";
15
+ import { badge, formatMs, prettyJsonBlock } from "../format.js";
16
+ import { renderMarkdown, renderInspectorDocument } from "../markdown-render.js";
17
17
  import {
18
18
  uiState,
19
19
  makeSessionEventIdentity,
20
20
  makeDagNodeInputIdentity,
21
21
  makeDagNodeExecutionOutputIdentity,
22
+ makeDagNodeProcessIdentity,
22
23
  getDagNodeExecutionOutputCache,
23
24
  setDagNodeExecutionOutputCache,
24
25
  clearDagNodeExecutionOutputCache,
@@ -68,6 +69,10 @@ function resetInspectorNodeSelection(dagRunId, nodeId) {
68
69
  uiState.dagNodeInputData = null;
69
70
  uiState.dagNodeInputError = null;
70
71
  uiState.dagNodeInputLoading = false;
72
+ uiState.dagNodeProcessData = null;
73
+ uiState.dagNodeProcessError = null;
74
+ uiState.dagNodeProcessLoading = false;
75
+ uiState.dagNodeProcessIdentity = null;
71
76
  // Drop execution-output cache/identity so node A's bodies never leak into B.
72
77
  clearDagNodeExecutionOutputCache();
73
78
  // Bump the session-event identity token so any in-flight response for the
@@ -748,6 +753,12 @@ function renderDagNodeInputDetail(content, data) {
748
753
  (shell.gates ?? []).join(", ") || "(none)",
749
754
  null,
750
755
  );
756
+ appendMetaRow(
757
+ shellDl,
758
+ "handlers",
759
+ (shell.handlers ?? []).join(", ") || "(none)",
760
+ null,
761
+ );
751
762
  section.appendChild(shellDl);
752
763
  if ((shell.commands ?? []).length > 0) {
753
764
  const list = el("ol", "node-input-command-list");
@@ -1305,7 +1316,7 @@ function appendOutputBody(content, outputText, errorText, options = null) {
1305
1316
  content.appendChild(el("p", "empty", UI_TEXT.noOutput));
1306
1317
  return;
1307
1318
  }
1308
- if (outputText) content.appendChild(renderMarkdown(outputText));
1319
+ if (outputText) content.appendChild(renderInspectorDocument(outputText));
1309
1320
  // Truncation note only for full/cached execution-output bodies (AC-003);
1310
1321
  // preview path never invents truncated/maxBytes.
1311
1322
  if (options?.truncated === true) {
@@ -1321,7 +1332,7 @@ function appendOutputBody(content, outputText, errorText, options = null) {
1321
1332
  if (errorText) {
1322
1333
  const error = el("section", "markdown-error");
1323
1334
  error.appendChild(el("p", "markdown-error-title", "错误输出"));
1324
- error.appendChild(renderMarkdown(errorText));
1335
+ error.appendChild(renderInspectorDocument(errorText));
1325
1336
  content.appendChild(error);
1326
1337
  }
1327
1338
  }
@@ -1455,6 +1466,228 @@ function repaintOutputTabBody(content, dagRunId, node) {
1455
1466
  restoreInspectorContentScroll(content, dagRunId, node.nodeId, "output");
1456
1467
  }
1457
1468
 
1469
+ function processEmptyMessage(body) {
1470
+ const reason = body?.emptyReason;
1471
+ if (reason === "static-no-process") return UI_TEXT.staticNoProcess;
1472
+ if (reason === "shell-receipt-missing") return UI_TEXT.noShellProcess;
1473
+ if (reason === "shell-preset-pending") return UI_TEXT.noPresetProcess;
1474
+ if (reason === "pi-session") return UI_TEXT.noSessionEvents;
1475
+ return UI_TEXT.noSessionEvents;
1476
+ }
1477
+
1478
+ function formatProcessBlock(text) {
1479
+ if (!text) return "";
1480
+ return prettyJsonBlock(text) ?? text;
1481
+ }
1482
+
1483
+ function appendProcessPre(parent, text) {
1484
+ if (!text) return;
1485
+ const pre = el("pre", "process-timeline-code");
1486
+ pre.textContent = formatProcessBlock(text);
1487
+ parent.appendChild(pre);
1488
+ }
1489
+
1490
+ function renderProcessCommandCard(command) {
1491
+ const failed = command.ok === false;
1492
+ const card = el(
1493
+ "div",
1494
+ `process-timeline-card process-timeline-tool-card${failed ? " is-error" : ""}`,
1495
+ );
1496
+ const details = document.createElement("details");
1497
+ details.className = "process-timeline-details";
1498
+ details.open = failed;
1499
+ const summary = el("summary", "process-timeline-summary");
1500
+ summary.appendChild(
1501
+ el("span", "process-timeline-summary-title", command.command || "command"),
1502
+ );
1503
+ summary.appendChild(
1504
+ el(
1505
+ "span",
1506
+ `process-timeline-summary-status status-${failed ? "failed" : "succeeded"}`,
1507
+ failed ? "失败" : command.reused ? "复用" : "成功",
1508
+ ),
1509
+ );
1510
+ details.appendChild(summary);
1511
+ const body = el("div", "process-timeline-card-body");
1512
+ const meta = [
1513
+ command.exitCode != null ? `exit ${command.exitCode}` : null,
1514
+ command.durationMs != null ? `耗时 ${formatMs(command.durationMs)}` : null,
1515
+ command.failureCategory ? command.failureCategory : null,
1516
+ ].filter(Boolean);
1517
+ if (meta.length > 0) {
1518
+ body.appendChild(el("div", "process-timeline-section-label", meta.join(" · ")));
1519
+ }
1520
+ if (command.stdout) {
1521
+ body.appendChild(el("div", "process-timeline-section-label", "stdout"));
1522
+ appendProcessPre(body, command.stdout);
1523
+ }
1524
+ if (command.stderr) {
1525
+ body.appendChild(el("div", "process-timeline-section-label", "stderr"));
1526
+ appendProcessPre(body, command.stderr);
1527
+ }
1528
+ if (command.truncated) {
1529
+ body.appendChild(el("p", "muted", "内容过长,已截断。"));
1530
+ }
1531
+ details.appendChild(body);
1532
+ card.appendChild(details);
1533
+ return card;
1534
+ }
1535
+
1536
+ function renderProcessStepCard(step) {
1537
+ const failed = step.ok === false;
1538
+ const card = el(
1539
+ "div",
1540
+ `process-timeline-card process-timeline-tool-card${failed ? " is-error" : ""}`,
1541
+ );
1542
+ const details = document.createElement("details");
1543
+ details.className = "process-timeline-details";
1544
+ details.open = true;
1545
+ const summary = el("summary", "process-timeline-summary");
1546
+ summary.appendChild(
1547
+ el("span", "process-timeline-summary-title", step.label || step.id || "step"),
1548
+ );
1549
+ summary.appendChild(
1550
+ el(
1551
+ "span",
1552
+ `process-timeline-summary-status status-${failed ? "failed" : "succeeded"}`,
1553
+ failed ? "失败" : "成功",
1554
+ ),
1555
+ );
1556
+ details.appendChild(summary);
1557
+ const body = el("div", "process-timeline-card-body");
1558
+ if (step.durationMs != null) {
1559
+ body.appendChild(
1560
+ el("div", "process-timeline-section-label", `耗时 ${formatMs(step.durationMs)}`),
1561
+ );
1562
+ }
1563
+ if (step.detail) appendProcessPre(body, step.detail);
1564
+ if (step.truncated) {
1565
+ body.appendChild(el("p", "muted", "内容过长,已截断。"));
1566
+ }
1567
+ details.appendChild(body);
1568
+ card.appendChild(details);
1569
+ return card;
1570
+ }
1571
+
1572
+ function renderNonPiProcess(content, body) {
1573
+ content.classList.add("process-timeline-scroll");
1574
+ if (!body || body.emptyReason || ((body.commands ?? []).length === 0 && (body.steps ?? []).length === 0)) {
1575
+ content.appendChild(el("p", "empty", processEmptyMessage(body)));
1576
+ return;
1577
+ }
1578
+ const heading =
1579
+ body.kind === "shell-preset"
1580
+ ? UI_TEXT.shellPresetHeading
1581
+ : UI_TEXT.shellCommandsHeading;
1582
+ if (body.handler) {
1583
+ content.appendChild(
1584
+ el("p", "process-timeline-note", `${heading} · ${body.handler}`),
1585
+ );
1586
+ }
1587
+ const ul = el("ul", "process-timeline process-timeline-rich");
1588
+ const items = (body.commands ?? []).length > 0 ? body.commands : body.steps ?? [];
1589
+ for (let index = 0; index < items.length; index += 1) {
1590
+ const item = items[index];
1591
+ const failed = item.ok === false;
1592
+ const li = el(
1593
+ "li",
1594
+ `process-timeline-item process-timeline-tool-end${failed ? " is-error" : ""}${index === 0 ? " is-first" : ""}${index === items.length - 1 ? " is-last" : ""}`,
1595
+ );
1596
+ const rail = el("span", "process-timeline-rail");
1597
+ rail.appendChild(el("span", "process-timeline-dot"));
1598
+ li.appendChild(rail);
1599
+ const cardBody = el("div", "process-timeline-body");
1600
+ cardBody.appendChild(
1601
+ body.kind === "shell-preset"
1602
+ ? renderProcessStepCard(item)
1603
+ : renderProcessCommandCard(item),
1604
+ );
1605
+ li.appendChild(cardBody);
1606
+ ul.appendChild(li);
1607
+ }
1608
+ content.appendChild(ul);
1609
+ }
1610
+
1611
+ export async function mergeNodeProcess(dagRunId, node) {
1612
+ if (!dagRunId || !node?.nodeId) return;
1613
+ const nodeId = node.nodeId;
1614
+ const key = effectiveExecutionKey(dagRunId, node);
1615
+ const identity = makeDagNodeProcessIdentity(dagRunId, nodeId, key);
1616
+ uiState.dagNodeProcessIdentity = identity;
1617
+ uiState.dagNodeProcessLoading = true;
1618
+ const result = await fetchDagNodeProcess(dagRunId, nodeId, key);
1619
+ if (uiState.dagNodeProcessIdentity !== identity) return;
1620
+ if (!result.ok || !result.body) {
1621
+ uiState.dagNodeProcessError =
1622
+ result.body?.error || UI_TEXT.processError;
1623
+ uiState.dagNodeProcessData = {
1624
+ schemaVersion: 1,
1625
+ dagRunId,
1626
+ nodeId,
1627
+ kind: "none",
1628
+ executor: node.executor ?? null,
1629
+ handler: null,
1630
+ emptyReason: "not-executed",
1631
+ key: key ?? null,
1632
+ commands: [],
1633
+ steps: [],
1634
+ };
1635
+ uiState.dagNodeProcessLoading = false;
1636
+ return;
1637
+ }
1638
+ uiState.dagNodeProcessError = null;
1639
+ uiState.dagNodeProcessData = result.body;
1640
+ uiState.dagNodeProcessLoading = false;
1641
+ }
1642
+
1643
+ function renderExecutorAwareTimeline(content, dagRunId, node) {
1644
+ content.dataset.dagRunId = String(dagRunId ?? "");
1645
+ content.dataset.nodeId = String(node.nodeId ?? "");
1646
+ const key = effectiveExecutionKey(dagRunId, node);
1647
+ const cached = uiState.dagNodeProcessData;
1648
+ const sameNode =
1649
+ cached &&
1650
+ cached.dagRunId === dagRunId &&
1651
+ cached.nodeId === node.nodeId &&
1652
+ (cached.key ?? null) === (key ?? null);
1653
+ if (sameNode && uiState.dagNodeProcessError) {
1654
+ content.appendChild(el("p", "empty", uiState.dagNodeProcessError));
1655
+ return;
1656
+ }
1657
+ if (sameNode && cached.kind === "pi-session") {
1658
+ renderSessionTimeline(content, node.nodeId, {
1659
+ executionKey: effectiveExecutionKey(dagRunId, node),
1660
+ });
1661
+ return;
1662
+ }
1663
+ if (sameNode && cached.kind !== "pi-session") {
1664
+ renderNonPiProcess(content, cached);
1665
+ return;
1666
+ }
1667
+ if ((node.executor ?? "pi") === "pi") {
1668
+ renderSessionTimeline(content, node.nodeId, {
1669
+ executionKey: effectiveExecutionKey(dagRunId, node),
1670
+ });
1671
+ return;
1672
+ }
1673
+ if (uiState.dagNodeProcessError && sameNode) {
1674
+ content.appendChild(el("p", "empty", uiState.dagNodeProcessError));
1675
+ return;
1676
+ }
1677
+ const loading = el("p", "muted", UI_TEXT.processLoading);
1678
+ loading.setAttribute("role", "status");
1679
+ content.appendChild(loading);
1680
+ void mergeNodeProcess(dagRunId, node).then(() => {
1681
+ if (
1682
+ uiState.dagInspectorTab === "timeline" &&
1683
+ uiState.selectedDagNodeId === node.nodeId &&
1684
+ uiState.currentDagRunId === dagRunId
1685
+ ) {
1686
+ renderDagInspectorForCurrent();
1687
+ }
1688
+ });
1689
+ }
1690
+
1458
1691
  function fillDagInspectorContent(content, dagRunId, node) {
1459
1692
  const previousRenderedNode = content.dataset.dagNodeId ?? "";
1460
1693
  const previousRenderedRun = content.dataset.dagRunId ?? "";
@@ -1504,7 +1737,7 @@ function fillDagInspectorContent(content, dagRunId, node) {
1504
1737
  } else if (activeTab === "timeline") {
1505
1738
  const attemptBuilt = buildAttemptBar(dagRunId, node);
1506
1739
  if (attemptBuilt?.bar) content.appendChild(attemptBuilt.bar);
1507
- renderSessionTimeline(content, node.nodeId);
1740
+ renderExecutorAwareTimeline(content, dagRunId, node);
1508
1741
  } else if (activeTab === "input") {
1509
1742
  void renderDagNodeInput(content, dagRunId, node);
1510
1743
  } else {
@@ -1657,7 +1890,14 @@ function dagInspectorSignature(dagRunId, node) {
1657
1890
  uiState.dagNodeExecutionOutputLoading ? "loading" : "idle",
1658
1891
  ]
1659
1892
  : tab === "timeline"
1660
- ? [uiState.sessionEventOffset]
1893
+ ? [
1894
+ uiState.sessionEventOffset,
1895
+ uiState.dagNodeProcessData?.kind ?? "",
1896
+ uiState.dagNodeProcessData?.emptyReason ?? "",
1897
+ (uiState.dagNodeProcessData?.commands ?? []).length,
1898
+ (uiState.dagNodeProcessData?.steps ?? []).length,
1899
+ uiState.dagNodeProcessLoading ? "loading" : "idle",
1900
+ ]
1661
1901
  : [node.status ?? ""];
1662
1902
  // Attempt dimension: selection identity + history shape. Poll refreshes keep
1663
1903
  // the DOM while selection/history are unchanged; switching attempt or a
@@ -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,
@@ -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.
@@ -657,7 +705,7 @@ export async function mergeSessionEvents(dagRunId, nodeId) {
657
705
  }
658
706
  }
659
707
 
660
- export function renderSessionTimeline(content, nodeId) {
708
+ export function renderSessionTimeline(content, nodeId, options = {}) {
661
709
  content.dataset.dagRunId = uiState.currentDagRunId ?? "";
662
710
  content.dataset.nodeId = nodeId ?? "";
663
711
  content.classList.add("process-timeline-scroll");
@@ -666,6 +714,14 @@ export function renderSessionTimeline(content, nodeId) {
666
714
  return;
667
715
  }
668
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
+
669
725
  const showProtocol = Boolean(uiState.sessionTimelineShowProtocol);
670
726
  const toolbar = el("div", "process-timeline-toolbar");
671
727
  const protocolToggle = document.createElement("button");
@@ -681,17 +737,17 @@ export function renderSessionTimeline(content, nodeId) {
681
737
  const scrollTop = parent.scrollTop;
682
738
  const scrollLeft = parent.scrollLeft;
683
739
  while (parent.firstChild) parent.removeChild(parent.firstChild);
684
- renderSessionTimeline(parent, nodeId);
740
+ renderSessionTimeline(parent, nodeId, options);
685
741
  parent.scrollTop = scrollTop;
686
742
  parent.scrollLeft = scrollLeft;
687
743
  });
688
744
  toolbar.appendChild(protocolToggle);
689
745
  content.appendChild(toolbar);
690
746
 
691
- const timelineItems = buildRichTimelineItems(uiState.sessionEvents, {
747
+ const timelineItems = buildRichTimelineItems(visible.events, {
692
748
  showProtocol,
693
749
  });
694
- const hasEventTimes = uiState.sessionEvents.some((event) =>
750
+ const hasEventTimes = visible.events.some((event) =>
695
751
  Boolean(sessionEventTimestamp(event)),
696
752
  );
697
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);
@@ -29,6 +29,7 @@ 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",
34
35
  "structured-output-truncated": "validation",
@@ -44,6 +45,13 @@ const RAW_TO_NORMALIZED = {
44
45
  quota: "executor",
45
46
  "rate-limit": "executor",
46
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",
47
55
  unknown: "unknown",
48
56
  };
49
57
  function isFinishedStatus(status) {
@@ -1723,7 +1723,11 @@ async function dropUnresolvedVerificationSymbols(input) {
1723
1723
  return changed;
1724
1724
  }
1725
1725
  function extractSingleOpenspecCitationsBlock(text) {
1726
- const blocks = text.match(/```openspec-citations[ \t]*\r?\n[\s\S]*?\r?\n```/g) ?? [];
1726
+ // Line-anchored so a directly closed empty fence (no candidate rows) still
1727
+ // counts as the one required block instead of "found 0" retry loops.
1728
+ const blocks = [
1729
+ ...text.matchAll(/^```openspec-citations[ \t]*\r?\n([\s\S]*?)^```[ \t]*$/gim),
1730
+ ].map((match) => match[0]);
1727
1731
  if (blocks.length !== 1) {
1728
1732
  throw new Error(`frontend plan patch output must include exactly one fenced openspec-citations block (found ${blocks.length})`);
1729
1733
  }
@@ -11,7 +11,7 @@ import { pathMatchesPattern } from "../../shared/git-progress.js";
11
11
  import { extractTaskSourceOpenspecPaths } from "../../shared/openspec-spec.js";
12
12
  import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
13
13
  import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
14
- import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
14
+ import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE, TARGET_TEMPLATE_WRITER_TRANSPORT_RETRY_POLICY, isCanonicalFinalVerifyShellRetryCandidate, isSafeReadOnlyPiRetryCandidate, isTargetTemplateImplementPi, isWriterTransportRetryCandidate, } from "./retry-policy.js";
15
15
  import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
16
16
  import { resolveAdapter } from "../../adapters/index.js";
17
17
  import { loadHarnessManifest } from "../../governance/harness.js";
@@ -1719,6 +1719,7 @@ export function buildStandardHybridDagFromTask(sources) {
1719
1719
  forbiddenPaths,
1720
1720
  outputContract: "Archived final shell verification stdout/stderr with exit codes; no worktree writes.",
1721
1721
  subtask_prompt: "Run the adapter-resolved final verification commands before read-only verification review.",
1722
+ transientRetryProfile: TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE,
1722
1723
  shell: {
1723
1724
  commands: verifyShellCommands,
1724
1725
  verifyEvidence: buildVerifyEvidence({
@@ -1839,6 +1840,7 @@ export function buildStandardHybridDagFromTask(sources) {
1839
1840
  allowedPaths: implementPaths.allowedPaths,
1840
1841
  forbiddenPaths,
1841
1842
  writerOutcomePolicy: { type: "implementation-outcome-v1" },
1843
+ transientRetryProfile: TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE,
1842
1844
  subtask_prompt: [
1843
1845
  "Implement the approved plan with minimal focused changes.",
1844
1846
  "Stay within writeSet. Do not write root artifacts/** unless artifacts paths are explicitly declared in writeSet.",
@@ -1898,6 +1900,7 @@ export function buildStandardHybridDagFromTask(sources) {
1898
1900
  ],
1899
1901
  };
1900
1902
  applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
1903
+ stampTargetTemplateTransientRetryProfile(spec);
1901
1904
  applyDefaultReadOnlyRetryPolicy(spec);
1902
1905
  parseDagSpec(spec);
1903
1906
  assertValidDagSpec(spec);
@@ -6442,7 +6445,12 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
6442
6445
  spec.sourceBinding = buildDagSourceBinding(sources);
6443
6446
  spec.taskContractBinding = taskContractBinding;
6444
6447
  assertNoGovernanceFlagOnDisallowedTemplate(spec, template);
6445
- parseDagSpec(spec);
6448
+ if (template === "standard-dag" ||
6449
+ template === "review-gated-dag" ||
6450
+ template === "supervised-implementation") {
6451
+ stampTargetTemplateTransientRetryProfile(spec);
6452
+ }
6453
+ spec = parseDagSpec(spec);
6446
6454
  assertValidDagSpec(spec);
6447
6455
  return spec;
6448
6456
  }
@@ -6493,14 +6501,6 @@ export async function buildHybridDagFromTask(sources, options = {}) {
6493
6501
  function cloneTask(task, patch = {}) {
6494
6502
  return { ...task, ...patch };
6495
6503
  }
6496
- /**
6497
- * Apply default Pi retry policies to generated DAG nodes:
6498
- * - safe read-only planner/scout/reviewer/verifier/supervisor/closeout
6499
- * - exclusive implementers get a single clean-timeout transport retry
6500
- * (provider stall with zero attributed writes only)
6501
- * Dynamic, shell, static, and decision-gate nodes are skipped. Idempotent:
6502
- * never overwrites an explicit retryPolicy a task already declares.
6503
- */
6504
6504
  /**
6505
6505
  * Backend-implementation Pi extension buckets (plan 2026-08-21 D3):
6506
6506
  * - read side: every non-writer, non-closeout Pi node gets navigation
@@ -6523,6 +6523,22 @@ function applyBackendPiExtensionBuckets(spec) {
6523
6523
  task.toolProfile === "write" ? ["pi-codegraph"] : ["pi-codegraph", "pi-lens"];
6524
6524
  }
6525
6525
  }
6526
+ function stampTargetTemplateTransientRetryProfile(spec) {
6527
+ for (const task of spec.tasks) {
6528
+ if (isTargetTemplateImplementPi(task) ||
6529
+ isCanonicalFinalVerifyShellRetryCandidate(task)) {
6530
+ task.transientRetryProfile = TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE;
6531
+ }
6532
+ }
6533
+ }
6534
+ /**
6535
+ * Apply default Pi retry policies to generated DAG nodes:
6536
+ * - safe read-only planner/scout/reviewer/verifier/supervisor/closeout
6537
+ * - exclusive implementers get a single clean-timeout transport retry
6538
+ * (provider stall with zero attributed writes only)
6539
+ * Dynamic, shell, static, and decision-gate nodes are skipped. Idempotent:
6540
+ * never overwrites an explicit retryPolicy a task already declares.
6541
+ */
6526
6542
  function applyDefaultReadOnlyRetryPolicy(spec) {
6527
6543
  for (const task of spec.tasks) {
6528
6544
  if (task.retryPolicy !== undefined)
@@ -6535,6 +6551,10 @@ function applyDefaultReadOnlyRetryPolicy(spec) {
6535
6551
  : DEFAULT_READ_ONLY_PI_RETRY_POLICY;
6536
6552
  continue;
6537
6553
  }
6554
+ if (isTargetTemplateImplementPi(task)) {
6555
+ task.retryPolicy = TARGET_TEMPLATE_WRITER_TRANSPORT_RETRY_POLICY;
6556
+ continue;
6557
+ }
6538
6558
  if (isWriterTransportRetryCandidate(task)) {
6539
6559
  task.retryPolicy = WRITER_TRANSPORT_RETRY_POLICY;
6540
6560
  }
@@ -6795,6 +6815,7 @@ function buildReviewGatedHybridDag(standard, sources) {
6795
6815
  }));
6796
6816
  spec.tasks.splice(spec.tasks.length - 1, 0, buildReviewNode(sources), buildReviewVerdictRecoveryNode(sources), buildReviewGateNode(sources));
6797
6817
  applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
6818
+ stampTargetTemplateTransientRetryProfile(spec);
6798
6819
  applyDefaultReadOnlyRetryPolicy(spec);
6799
6820
  parseDagSpec(spec);
6800
6821
  assertValidDagSpec(spec);
@@ -7126,6 +7147,9 @@ async function buildHardVerifyNode(sources) {
7126
7147
  forbiddenPaths: commonForbiddenPaths(sources),
7127
7148
  outputContract: "Archived hard verification stdout/stderr with exit codes; no worktree writes.",
7128
7149
  subtask_prompt: "Run hard verification after repair round.",
7150
+ transientRetryProfile: sources.verifyCommands && commands.length > 0
7151
+ ? TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE
7152
+ : undefined,
7129
7153
  shell: {
7130
7154
  commands,
7131
7155
  verifyEvidence: buildVerifyEvidence({
@@ -7296,6 +7320,7 @@ async function buildSupervisedHybridDag(standard, sources) {
7296
7320
  ],
7297
7321
  };
7298
7322
  applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
7323
+ stampTargetTemplateTransientRetryProfile(spec);
7299
7324
  applyDefaultReadOnlyRetryPolicy(spec);
7300
7325
  parseDagSpec(spec);
7301
7326
  assertValidDagSpec(spec);