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

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 (81) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +2 -0
  3. package/README.md +1 -1
  4. package/dist/build-stamp.json +2 -2
  5. package/dist/commands/dag-rerun.js +55 -1
  6. package/dist/shared/dag-prompt-override.js +28 -0
  7. package/dist/shared/operator/capabilities.js +25 -0
  8. package/dist/worker/console/operator-actions.js +116 -0
  9. package/dist/worker/console/routes.js +40 -2
  10. package/dist/worker/console/security.js +13 -7
  11. package/dist/worker/console/server.js +26 -1
  12. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DGn9iNrd.js → abnfDiagram-N423BO3Z-CiGOV5eG.js} +1 -1
  13. package/dist/worker/console/static/assets/{arc-Dc0C2Nyv.js → arc-Ds9Gyfd8.js} +1 -1
  14. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COnz_Bhe.js → architectureDiagram-T3A2C74G-C6V6uC98.js} +1 -1
  15. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-VhIP6vgf.js → blockDiagram-VBNYF7ZC-DezF7Ol9.js} +1 -1
  16. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D74lpmlb.js → c4Diagram-5PPSVZJV-BCWk3gCT.js} +1 -1
  17. package/dist/worker/console/static/assets/channel-DMvu1nmi.js +1 -0
  18. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BxmiogQE.js → chunk-2GRJ4B5K-DbYr-Cxy.js} +1 -1
  19. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C00Oi-2I.js → chunk-2Q5K7J3B-XSlusW4G.js} +1 -1
  20. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DTrT1Vze.js → chunk-5RXB4S5H-BtcKAj9i.js} +1 -1
  21. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DdKW7-fL.js → chunk-5VM5RSS4-BF57lhXj.js} +1 -1
  22. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-D0q1WN6w.js → chunk-6Q2QTUOP-DJIlR25o.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-DBa14MSI.js → chunk-GF5L2VYU-DklNc7dm.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DkP-pEox.js → chunk-JWPE2WC7-fZNtEdQZ.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-lgPogpcX.js → chunk-KBJHAD2P-BBgLqV4h.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-D-apZer0.js → chunk-RYQCIY6F-Dl9e5n_J.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-B1T2yAIe.js → chunk-XXDRQBXY-B0JK51NT.js} +1 -1
  28. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DVzM-02m.js +1 -0
  29. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DVzM-02m.js +1 -0
  30. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-0P6hi3jF.js → cose-bilkent-JH36ORCC-Ch-ajv4O.js} +1 -1
  31. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CntzMSR8.js → cynefin-VYW2F7L2-xn_NxcfB.js} +1 -1
  32. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C9zmXJtG.js → cynefinDiagram-MW4NZA55-DRD0fyga.js} +1 -1
  33. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-DfBCZqZw.js → dagre-VZM6K2ZE-COtnKp4B.js} +1 -1
  34. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DLp-bhGL.js → diagram-7IWD3JNH-BQp_CAqG.js} +1 -1
  35. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BksDcwZm.js → diagram-B4RE2ZJO-x0y-vNgq.js} +1 -1
  36. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Bdag7DYP.js → diagram-LBJQPF4R-N2ezwL31.js} +1 -1
  37. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CoY5uNbk.js → diagram-Q27KOJAE-CSaxRH6Q.js} +1 -1
  38. package/dist/worker/console/static/assets/{diagram-UB23O5K3-KcgrDXHt.js → diagram-UB23O5K3-6Ro7Kpbd.js} +1 -1
  39. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CikErwJb.js → ebnfDiagram-BXEA7PRR-CrIIQEAI.js} +1 -1
  40. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DqmwwzvZ.js → erDiagram-JOGREHBK-DivKcq8g.js} +1 -1
  41. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-35krh9WG.js → flowDiagram-UKHOOZJN-BXrfDo8y.js} +1 -1
  42. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CvTF1gJ4.js → ganttDiagram-PKOTCBZU-LTTQmMRJ.js} +1 -1
  43. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BiOWWClJ.js → gitGraphDiagram-DS77QQ5N-DP3o_5qd.js} +1 -1
  44. package/dist/worker/console/static/assets/{index-CqWlWE4l.js → index-C2jmYBfu.js} +71 -71
  45. package/dist/worker/console/static/assets/{index-Ya5FE7cD.css → index-C8rOIAJr.css} +1 -1
  46. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Crha5dcA.js → infoDiagram-6WML65LV-wVfSY1WH.js} +1 -1
  47. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DEtbscOZ.js → ishikawaDiagram-WSZJBQD7-BRKfFL0I.js} +1 -1
  48. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D2YYzx3z.js → journeyDiagram-NVQOT4AX-DEml3jLb.js} +1 -1
  49. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7x18ttQ.js → kanban-definition-27J2QSJJ-DTzBmOqN.js} +1 -1
  50. package/dist/worker/console/static/assets/{linear-CCLZkz45.js → linear-Cpy1d873.js} +1 -1
  51. package/dist/worker/console/static/assets/{mermaid.core-Dmy4lSGv.js → mermaid.core-C1cSavOy.js} +5 -5
  52. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CmvIol59.js → mindmap-definition-FAOFIHXS-BEBSKvYM.js} +1 -1
  53. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DVpEdirN.js → pegDiagram-VL7TDLO6-BrLs4Oac.js} +1 -1
  54. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-cKQy456v.js → pieDiagram-7S7Q4E2Y-B47AZcLa.js} +1 -1
  55. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-bx06I38m.js → quadrantDiagram-CIZ2JOQS-s0dVeYR2.js} +1 -1
  56. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BjfIVIJr.js → railroadDiagram-AXF67PYL-lW8aJ7Q6.js} +1 -1
  57. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DcuPGbKM.js → requirementDiagram-LRYGKXZP-BcHJPf6Q.js} +1 -1
  58. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DvgVeDoV.js → sankeyDiagram-W5VNT64P-DdegpUUf.js} +1 -1
  59. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DLBG9nm-.js → sequenceDiagram-SI44F4Z6-BxFWqNv8.js} +1 -1
  60. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-D33L7FBF.js → sizeCapture-X5ZJPWSS-cOdUDPbR.js} +1 -1
  61. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-hvqD9TpS.js → stateDiagram-OKZ733FA-DpxlYl8P.js} +1 -1
  62. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BwecGAkM.js +1 -0
  63. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BtjH81Mz.js → swimlanes-SLNWSIFB-DQOfurE0.js} +2 -2
  64. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D5WW_LbN.js +8 -0
  65. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CJ47Kraf.js → timeline-definition-Z64GVDOM-CIp3tC6V.js} +1 -1
  66. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-COVPl-wh.js → vennDiagram-T6HMQDX7-C-adIPKn.js} +1 -1
  67. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CCoQJLUY.js → wardleyDiagram-T6FBY63Y-CiYrar9M.js} +1 -1
  68. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DTWJvfAd.js → xychartDiagram-ELKLHX3M-WHrdTV0-.js} +1 -1
  69. package/dist/worker/console/static/index.html +2 -2
  70. package/dist/worker/observe/routes.js +12 -3
  71. package/dist/worker/observe/static/api.js +58 -0
  72. package/dist/worker/observe/static/styles.css +70 -0
  73. package/dist/worker/observe/static/views/dag-inspector.js +315 -2
  74. package/dist/workflows/dag/rerun-plan.js +112 -0
  75. package/dist/workflows/dag/rerun-run.js +58 -6
  76. package/package.json +1 -1
  77. package/dist/worker/console/static/assets/channel-DWcZjWB3.js +0 -1
  78. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-IjoI3dqU.js +0 -1
  79. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-IjoI3dqU.js +0 -1
  80. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-C6MII0D1.js +0 -1
  81. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D_tk0V8D.js +0 -8
@@ -11,7 +11,14 @@ 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, fetchDagNodeProcess } from "../api.js";
14
+ import {
15
+ fetchJson,
16
+ fetchSpecEvidenceFile,
17
+ fetchDagNodeInput,
18
+ fetchDagNodeProcess,
19
+ postOperatorAction,
20
+ fetchOperatorOperation,
21
+ } from "../api.js";
15
22
  import { badge, formatMs, prettyJsonBlock } from "../format.js";
16
23
  import { renderMarkdown, renderInspectorDocument } from "../markdown-render.js";
17
24
  import {
@@ -978,8 +985,304 @@ async function renderDagNodeInput(content, dagRunId, node) {
978
985
  finishInputRender();
979
986
  }
980
987
 
988
+ /**
989
+ * Experimental prompt-override rerun tab. Only reachable when the server
990
+ * injected the mutation token (switch on). Every node shows its frozen
991
+ * subtask_prompt here; only FAILED (ERROR) nodes additionally get the editor +
992
+ * restart flow: preflight (dagRerunPlan) binds the edit into planHash, dagRerun
993
+ * executes it as a Console operation, and on success we jump to the new run.
994
+ */
995
+ async function renderPromptRestart(content, dagRunId, node) {
996
+ const nodeId = node.nodeId;
997
+ clearNode(content);
998
+
999
+ const loading = el("p", "muted", "正在加载冻结提示词…");
1000
+ loading.setAttribute("role", "status");
1001
+ loading.setAttribute("aria-live", "polite");
1002
+ content.appendChild(loading);
1003
+ const input = await fetchDagNodeInput(dagRunId, nodeId);
1004
+ if (
1005
+ !content.isConnected ||
1006
+ content.dataset.dagNodeId !== String(nodeId) ||
1007
+ content.dataset.dagRunId !== String(dagRunId) ||
1008
+ activeInspectorTab() !== "prompt-restart"
1009
+ ) {
1010
+ return;
1011
+ }
1012
+ clearNode(content);
1013
+
1014
+ if (!input.ok || !input.body?.available) {
1015
+ const error = el("p", "node-input-error", "无法读取该节点的冻结提示词。");
1016
+ error.setAttribute("role", "alert");
1017
+ content.appendChild(error);
1018
+ return;
1019
+ }
1020
+ const basePrompt =
1021
+ typeof input.body.prompt?.text === "string" ? input.body.prompt.text : "";
1022
+ if (!basePrompt) {
1023
+ const error = el("p", "node-input-error", "该节点没有冻结提示词。");
1024
+ error.setAttribute("role", "alert");
1025
+ content.appendChild(error);
1026
+ return;
1027
+ }
1028
+
1029
+ if (node.status !== "ERROR") {
1030
+ // Non-failed nodes still show their frozen prompt — read-only.
1031
+ const hint = el(
1032
+ "p",
1033
+ "muted",
1034
+ `当前节点状态为 ${node.status ?? "unknown"};仅失败(ERROR)节点支持编辑提示词并重启,以下为该节点冻结提示词(只读)。`,
1035
+ );
1036
+ hint.setAttribute("role", "status");
1037
+ content.appendChild(hint);
1038
+ const pre = document.createElement("pre");
1039
+ pre.className = "prompt-restart-readonly";
1040
+ pre.setAttribute("data-testid", "prompt-restart-readonly");
1041
+ pre.textContent = basePrompt;
1042
+ content.appendChild(pre);
1043
+ return;
1044
+ }
1045
+
1046
+ if (input.body.prompt?.truncated === true) {
1047
+ const error = el(
1048
+ "p",
1049
+ "node-input-error",
1050
+ "冻结提示词被截断,为避免误替换请改用 CLI(dag rerun --prompt-override)。",
1051
+ );
1052
+ error.setAttribute("role", "alert");
1053
+ content.appendChild(error);
1054
+ return;
1055
+ }
1056
+
1057
+ const box = el("div", "prompt-restart-box");
1058
+ box.appendChild(
1059
+ el(
1060
+ "p",
1061
+ "muted",
1062
+ "编辑该失败节点的提示词并从它重启:父 run spec 保持冻结,将创建带修改提示词的续跑 run(实验能力,仅 ERROR 节点)。",
1063
+ ),
1064
+ );
1065
+
1066
+ const modeRow = el("div", "prompt-restart-mode");
1067
+ modeRow.appendChild(el("span", "muted", "编辑模式"));
1068
+ const modeSelect = document.createElement("select");
1069
+ modeSelect.className = "prompt-restart-mode-select";
1070
+ modeSelect.setAttribute("data-testid", "prompt-restart-mode");
1071
+ const appendOption = document.createElement("option");
1072
+ appendOption.value = "append";
1073
+ appendOption.textContent =
1074
+ "追加指令(推荐):保留原提示词,在末尾追加 operator_addendum 块";
1075
+ const replaceOption = document.createElement("option");
1076
+ replaceOption.value = "replace";
1077
+ replaceOption.textContent = "全文替换(高级):替换整个提示词";
1078
+ modeSelect.appendChild(appendOption);
1079
+ modeSelect.appendChild(replaceOption);
1080
+ modeSelect.value = "append";
1081
+ modeRow.appendChild(modeSelect);
1082
+ box.appendChild(modeRow);
1083
+
1084
+ const baseLabel = el("label", "prompt-restart-field");
1085
+ baseLabel.appendChild(
1086
+ el("span", "muted", `冻结提示词(只读底稿 · ${nodeId})`),
1087
+ );
1088
+ const baseBlock = document.createElement("pre");
1089
+ baseBlock.className = "prompt-restart-readonly";
1090
+ baseBlock.setAttribute("data-testid", "prompt-restart-base");
1091
+ baseBlock.textContent = basePrompt;
1092
+ baseLabel.appendChild(baseBlock);
1093
+ box.appendChild(baseLabel);
1094
+
1095
+ const addendumLabel = el("label", "prompt-restart-field");
1096
+ addendumLabel.appendChild(
1097
+ el(
1098
+ "span",
1099
+ "muted",
1100
+ "追加指令(将包裹为 <operator_addendum> 块附加到原提示词末尾)",
1101
+ ),
1102
+ );
1103
+ const addendumArea = document.createElement("textarea");
1104
+ addendumArea.className = "prompt-restart-textarea";
1105
+ addendumArea.rows = 6;
1106
+ addendumArea.placeholder = "例如:重点检查 X 边界;输出前先自检 Y 约束…";
1107
+ addendumArea.setAttribute("data-testid", "prompt-restart-addendum");
1108
+ addendumLabel.appendChild(addendumArea);
1109
+ box.appendChild(addendumLabel);
1110
+
1111
+ const replaceLabel = el("label", "prompt-restart-field");
1112
+ replaceLabel.appendChild(el("span", "muted", `完整提示词(${nodeId})`));
1113
+ const replaceArea = document.createElement("textarea");
1114
+ replaceArea.className = "prompt-restart-textarea";
1115
+ replaceArea.rows = 16;
1116
+ replaceArea.value = basePrompt;
1117
+ replaceArea.setAttribute("data-testid", "prompt-restart-textarea");
1118
+ replaceLabel.appendChild(replaceArea);
1119
+ box.appendChild(replaceLabel);
1120
+
1121
+ const applyModeVisibility = () => {
1122
+ const append = modeSelect.value === "append";
1123
+ baseLabel.style.display = append ? "" : "none";
1124
+ addendumLabel.style.display = append ? "" : "none";
1125
+ replaceLabel.style.display = append ? "none" : "";
1126
+ };
1127
+ modeSelect.addEventListener("change", applyModeVisibility);
1128
+ applyModeVisibility();
1129
+
1130
+ const reasonLabel = el("label", "prompt-restart-field");
1131
+ reasonLabel.appendChild(el("span", "muted", "重启原因(结构化说明)"));
1132
+ const reasonArea = document.createElement("textarea");
1133
+ reasonArea.className = "prompt-restart-textarea";
1134
+ reasonArea.rows = 3;
1135
+ reasonArea.value = `operator-prompt-override / run=${dagRunId} / node=${nodeId} / 手工编辑失败节点提示词后重启`;
1136
+ reasonArea.setAttribute("data-testid", "prompt-restart-reason");
1137
+ reasonLabel.appendChild(reasonArea);
1138
+ box.appendChild(reasonLabel);
1139
+
1140
+ const row = el("div", "prompt-restart-actions");
1141
+ const submit = document.createElement("button");
1142
+ submit.type = "button";
1143
+ submit.className = "prompt-restart-submit";
1144
+ submit.textContent = "资格预检并重启";
1145
+ submit.setAttribute("data-testid", "prompt-restart-submit");
1146
+ row.appendChild(submit);
1147
+ box.appendChild(row);
1148
+
1149
+ const status = el("p", "prompt-restart-status", "");
1150
+ status.setAttribute("role", "status");
1151
+ status.setAttribute("aria-live", "polite");
1152
+ box.appendChild(status);
1153
+
1154
+ content.appendChild(box);
1155
+
1156
+ const setStatus = (text, isError = false) => {
1157
+ status.textContent = text ?? "";
1158
+ status.classList.toggle("prompt-restart-error", isError);
1159
+ };
1160
+
1161
+ submit.addEventListener("click", () => {
1162
+ void (async () => {
1163
+ const mode = modeSelect.value === "replace" ? "replace" : "append";
1164
+ const prompt =
1165
+ mode === "append" ? addendumArea.value : replaceArea.value;
1166
+ const reason = reasonArea.value.trim();
1167
+ if (!prompt.trim()) {
1168
+ setStatus(
1169
+ mode === "append" ? "追加指令不能为空。" : "提示词不能为空。",
1170
+ true,
1171
+ );
1172
+ return;
1173
+ }
1174
+ if (mode === "replace" && prompt === basePrompt) {
1175
+ setStatus("提示词未修改;如需按原提示词重跑请使用恢复工作台。", true);
1176
+ return;
1177
+ }
1178
+ if (!reason) {
1179
+ setStatus("需要填写重启原因。", true);
1180
+ return;
1181
+ }
1182
+ submit.disabled = true;
1183
+ try {
1184
+ setStatus("正在做重跑资格预检(dagRerunPlan)…");
1185
+ const overrides = [{ nodeId, prompt, mode }];
1186
+ const planRes = await postOperatorAction("dagRerunPlan", {
1187
+ runId: dagRunId,
1188
+ fromNode: nodeId,
1189
+ promptOverrides: overrides,
1190
+ });
1191
+ const plan = planRes.body?.result ?? null;
1192
+ if (!planRes.ok || plan?.eligible !== true || !plan?.planHash) {
1193
+ setStatus(
1194
+ planRes.body?.error?.message ??
1195
+ plan?.blockedReasons?.join(", ") ??
1196
+ "资格预检未通过。",
1197
+ true,
1198
+ );
1199
+ return;
1200
+ }
1201
+
1202
+ const est = plan.estimatedExecution;
1203
+ const estText = est
1204
+ ? `(将执行 ${est.nodeCount ?? "?"} 个节点 / ${est.piCallCount ?? "?"} 次 Pi 调用)`
1205
+ : "";
1206
+ setStatus(`预检通过${estText},正在提交续跑(dagRerun)…`);
1207
+ const clientRequestId = `inspect-prompt-restart-${Date.now()}-${Math.random()
1208
+ .toString(36)
1209
+ .slice(2, 10)}`;
1210
+ const runRes = await postOperatorAction(
1211
+ "dagRerun",
1212
+ {
1213
+ runId: dagRunId,
1214
+ fromNode: nodeId,
1215
+ planHash: plan.planHash,
1216
+ reason,
1217
+ promptOverrides: overrides,
1218
+ },
1219
+ clientRequestId,
1220
+ );
1221
+ const operationId = runRes.body?.operationId;
1222
+ if (!runRes.ok || !operationId) {
1223
+ setStatus(runRes.body?.error?.message ?? "提交续跑失败。", true);
1224
+ return;
1225
+ }
1226
+
1227
+ const op = await pollOperationToTerminal(operationId, (text) =>
1228
+ setStatus(text),
1229
+ );
1230
+ if (op?.state === "succeeded") {
1231
+ const newRunId =
1232
+ op.dagRunId ?? op.result?.newRunId ?? op.runSummary?.runId ?? null;
1233
+ if (newRunId) {
1234
+ setStatus(`重启完成,新 run:${newRunId}`);
1235
+ const open = document.createElement("button");
1236
+ open.type = "button";
1237
+ open.className = "prompt-restart-submit";
1238
+ open.textContent = "打开新 run";
1239
+ open.addEventListener("click", () => {
1240
+ window.location.hash = `#/dag/${encodeURIComponent(String(newRunId))}`;
1241
+ });
1242
+ row.appendChild(open);
1243
+ } else {
1244
+ setStatus("重启完成(未能解析新 run id,请在 DAG 历史中查看)。");
1245
+ }
1246
+ } else if (op) {
1247
+ setStatus(
1248
+ `续跑操作终态:${op.state}${op.errorMessage ? ` — ${op.errorMessage}` : ""}`,
1249
+ true,
1250
+ );
1251
+ } else {
1252
+ setStatus("等待超时:操作仍在运行,可在操作台查看进度。", true);
1253
+ }
1254
+ } catch (err) {
1255
+ setStatus(err instanceof Error ? err.message : String(err), true);
1256
+ } finally {
1257
+ submit.disabled = false;
1258
+ }
1259
+ })();
1260
+ });
1261
+ }
1262
+
1263
+ /** Poll a Console operation until a terminal state; null on poll budget end. */
1264
+ async function pollOperationToTerminal(operationId, onProgress) {
1265
+ const terminal = new Set([
1266
+ "succeeded",
1267
+ "failed",
1268
+ "timed-out",
1269
+ "needs-reconcile",
1270
+ ]);
1271
+ for (let attempt = 0; attempt < 1200; attempt += 1) {
1272
+ const res = await fetchOperatorOperation(operationId);
1273
+ const op = res.body?.operation ?? res.body;
1274
+ const state = op?.state ?? "";
1275
+ if (res.ok && terminal.has(state)) {
1276
+ return op;
1277
+ }
1278
+ onProgress(`续跑执行中…(${state || "queued"})`);
1279
+ await new Promise((resolve) => setTimeout(resolve, 3000));
1280
+ }
1281
+ return null;
1282
+ }
1283
+
981
1284
  function activeInspectorTab() {
982
- return ["input", "timeline", "spec-evidence"].includes(
1285
+ return ["input", "timeline", "spec-evidence", "prompt-restart"].includes(
983
1286
  uiState.dagInspectorTab,
984
1287
  )
985
1288
  ? uiState.dagInspectorTab
@@ -1734,6 +2037,8 @@ function fillDagInspectorContent(content, dagRunId, node) {
1734
2037
  );
1735
2038
  if (activeTab === "spec-evidence") {
1736
2039
  void renderSpecEvidence(content, dagRunId, node.nodeId);
2040
+ } else if (activeTab === "prompt-restart") {
2041
+ void renderPromptRestart(content, dagRunId, node);
1737
2042
  } else if (activeTab === "timeline") {
1738
2043
  const attemptBuilt = buildAttemptBar(dagRunId, node);
1739
2044
  if (attemptBuilt?.bar) content.appendChild(attemptBuilt.bar);
@@ -1801,6 +2106,14 @@ function buildDagInspectorHeader(dagRunId, node) {
1801
2106
  ["timeline", "执行过程", "ri-route-line"],
1802
2107
  ["spec-evidence", "规范证据", "ri-book-open-line"],
1803
2108
  ];
2109
+ // Experimental prompt-override rerun: the server injects the SCOPED
2110
+ // inspect mutation token into /inspect HTML only while
2111
+ // HARNESS_DAG_RERUN_PROMPT_OVERRIDE is enabled (and it only authorizes
2112
+ // dagRerunPlan/dagRerun). No token → no tab → the inspector stays
2113
+ // strictly read-only.
2114
+ if (globalThis.__INSPECT_MUTATION_TOKEN__) {
2115
+ tabDefs.push(["prompt-restart", "提示词重启", "ri-restart-line"]);
2116
+ }
1804
2117
  const tabs = el("div", "dag-inspector-tabs");
1805
2118
  tabs.setAttribute("role", "tablist");
1806
2119
  tabs.setAttribute("aria-label", "节点检查器面板");
@@ -3,6 +3,7 @@ import { readFile } from "node:fs/promises";
3
3
  import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
4
4
  import { isSafeReadOnlyPiRetryCandidate, isTransportCrashedExclusiveWriter } from "./retry-policy.js";
5
5
  import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
6
+ import { composeDagPromptOverridePrompt, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
6
7
  const REVIEW_VERDICT_RECOVERY_NODE_ID = "review-verdict-recovery-pi";
7
8
  const SAFE_SHELL_GATE_KEYS = [
8
9
  "verdictGate",
@@ -381,6 +382,100 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
381
382
  reasonCodes: [...new Set(reasonCodes)],
382
383
  };
383
384
  }
385
+ /**
386
+ * Experimental prompt-override admission (fail-closed):
387
+ * - requires the internal switch (see shared/dag-prompt-override.ts);
388
+ * - the target must be a failed (ERROR) node inside the reset closure, so an
389
+ * override can only adjust how an already-failed node retries — never a
390
+ * finished, skipped, or not-yet-started node;
391
+ * - only `subtask_prompt` semantics change: "append" keeps the frozen base
392
+ * and adds a marked addendum block, "replace" swaps the whole prompt;
393
+ * base/final sha256 are recorded for provenance and bound into planHash.
394
+ */
395
+ function assessPromptOverrides(input) {
396
+ const blockingNodes = [];
397
+ const reasonCodes = [];
398
+ const tasks = taskById(input.spec);
399
+ const resetSet = new Set(input.resetNodeIds);
400
+ const seen = new Set();
401
+ const entries = [];
402
+ if (!input.enabled) {
403
+ reasonCodes.push("prompt-override-disabled");
404
+ }
405
+ for (const override of input.overrides) {
406
+ const nodeId = override.nodeId?.trim() ?? "";
407
+ if (seen.has(nodeId)) {
408
+ blockingNodes.push({
409
+ nodeId,
410
+ reasonCode: "prompt-override-duplicate-node",
411
+ });
412
+ reasonCodes.push("prompt-override-duplicate-node");
413
+ continue;
414
+ }
415
+ seen.add(nodeId);
416
+ const task = tasks.get(nodeId);
417
+ if (!task) {
418
+ blockingNodes.push({
419
+ nodeId,
420
+ reasonCode: "prompt-override-node-missing",
421
+ });
422
+ reasonCodes.push("prompt-override-node-missing");
423
+ continue;
424
+ }
425
+ if (!resetSet.has(nodeId)) {
426
+ blockingNodes.push({
427
+ nodeId,
428
+ reasonCode: "prompt-override-outside-reset-closure",
429
+ });
430
+ reasonCodes.push("prompt-override-outside-reset-closure");
431
+ continue;
432
+ }
433
+ if (input.state.nodes[nodeId]?.status !== "ERROR") {
434
+ blockingNodes.push({
435
+ nodeId,
436
+ reasonCode: "prompt-override-node-not-failed",
437
+ });
438
+ reasonCodes.push("prompt-override-node-not-failed");
439
+ continue;
440
+ }
441
+ const mode = override.mode === "append" ? "append" : "replace";
442
+ const prompt = override.prompt ?? "";
443
+ if (!prompt.trim() ||
444
+ prompt.length > MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS) {
445
+ blockingNodes.push({
446
+ nodeId,
447
+ reasonCode: "prompt-override-invalid-prompt",
448
+ });
449
+ reasonCodes.push("prompt-override-invalid-prompt");
450
+ continue;
451
+ }
452
+ const basePromptSha256 = createHash("sha256")
453
+ .update(task.subtask_prompt, "utf8")
454
+ .digest("hex");
455
+ const finalPrompt = composeDagPromptOverridePrompt({
456
+ basePrompt: task.subtask_prompt,
457
+ text: prompt,
458
+ mode,
459
+ });
460
+ const promptSha256 = createHash("sha256")
461
+ .update(finalPrompt, "utf8")
462
+ .digest("hex");
463
+ if (promptSha256 === basePromptSha256) {
464
+ blockingNodes.push({
465
+ nodeId,
466
+ reasonCode: "prompt-override-unchanged",
467
+ });
468
+ reasonCodes.push("prompt-override-unchanged");
469
+ continue;
470
+ }
471
+ entries.push({ nodeId, mode, basePromptSha256, promptSha256 });
472
+ }
473
+ return {
474
+ entries: entries.length > 0 ? entries : undefined,
475
+ blockingNodes,
476
+ reasonCodes: [...new Set(reasonCodes)],
477
+ };
478
+ }
384
479
  export function computePlanHash(planWithoutHash) {
385
480
  const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
386
481
  return createHash("sha256").update(canonical, "utf8").digest("hex");
@@ -648,6 +743,22 @@ export async function evaluateDagRerunPlan(input) {
648
743
  blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
649
744
  }
650
745
  }
746
+ let promptOverrideEntries;
747
+ if (input.promptOverrides !== undefined && input.promptOverrides.length > 0) {
748
+ const overrideAssessment = assessPromptOverrides({
749
+ spec: input.parentSpec,
750
+ state: input.parentState,
751
+ resetNodeIds,
752
+ overrides: input.promptOverrides,
753
+ enabled: input.promptOverrideEnabled === true,
754
+ });
755
+ blockingNodes.push(...overrideAssessment.blockingNodes);
756
+ for (const code of overrideAssessment.reasonCodes) {
757
+ reasonCodes.push(code);
758
+ blockedReasons.push(code);
759
+ }
760
+ promptOverrideEntries = overrideAssessment.entries;
761
+ }
651
762
  const uniqueReasonCodes = [...new Set(reasonCodes)];
652
763
  const uniqueBlockedReasons = [...new Set(blockedReasons)];
653
764
  const eligible = uniqueBlockedReasons.length === 0 &&
@@ -691,6 +802,7 @@ export async function evaluateDagRerunPlan(input) {
691
802
  parentLifecycle: input.parentLifecycle,
692
803
  workerManaged: input.workerManaged,
693
804
  }),
805
+ ...(promptOverrideEntries ? { promptOverrides: promptOverrideEntries } : {}),
694
806
  };
695
807
  return {
696
808
  ...planWithoutHash,
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { access, copyFile, mkdir, readdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
5
+ import { composeDagPromptOverridePrompt, isDagRerunPromptOverrideEnabled, } from "../../shared/dag-prompt-override.js";
5
6
  import { resolveRunningControllerIdentity } from "../../shared/package-metadata.js";
6
7
  import { readControllerIdentityArtifact, captureControllerIdentity } from "./controller-identity.js";
7
8
  import { DAG_RUNS_DIR, getDagRunDir, isDagNonExecutionLifecycle, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
@@ -190,6 +191,8 @@ export async function planDagRerun(input) {
190
191
  parentSpec: nonCompletedSpec,
191
192
  parentState: nonCompletedState,
192
193
  parentLifecycle,
194
+ promptOverrides: input.promptOverrides,
195
+ promptOverrideEnabled: isDagRerunPromptOverrideEnabled(),
193
196
  workerManaged: nonCompletedWorkerHints.workerManaged,
194
197
  workerAssociation: nonCompletedWorkerHints.workerManaged
195
198
  ? {
@@ -229,6 +232,8 @@ export async function planDagRerun(input) {
229
232
  parentTerminalWorkspace,
230
233
  currentWorkspace,
231
234
  skillSnapshotOk,
235
+ promptOverrides: input.promptOverrides,
236
+ promptOverrideEnabled: isDagRerunPromptOverrideEnabled(),
232
237
  workerManaged: workerHints.workerManaged,
233
238
  workerAssociation: workerHints.workerManaged
234
239
  ? {
@@ -384,15 +389,57 @@ function buildPendingNodeRecord(task) {
384
389
  ...(task.outputMode ? { outputMode: task.outputMode } : {}),
385
390
  };
386
391
  }
392
+ /**
393
+ * Stage the continuation spec with operator prompt overrides applied. The
394
+ * parent spec stays frozen; every override must match the plan entry (sha256
395
+ * of the composed final prompt and of the parent base prompt) so the
396
+ * two-phase planHash remains the single source of truth for what changed.
397
+ * "append" entries keep the frozen base and append the operator addendum in a
398
+ * marked block; "replace" entries swap the whole prompt.
399
+ */
400
+ function applyPromptOverridesToSpec(input) {
401
+ const planned = new Map((input.plan.promptOverrides ?? []).map((entry) => [entry.nodeId, entry]));
402
+ const spec = structuredClone(input.spec);
403
+ for (const override of input.overrides) {
404
+ const entry = planned.get(override.nodeId);
405
+ if (!entry) {
406
+ throw new Error(`prompt override is not present in the rerun plan: ${override.nodeId}`);
407
+ }
408
+ const task = spec.tasks.find((candidate) => candidate.id === override.nodeId);
409
+ if (!task) {
410
+ throw new Error(`prompt override node missing from spec: ${override.nodeId}`);
411
+ }
412
+ if (sha256Hex(task.subtask_prompt) !== entry.basePromptSha256) {
413
+ throw new Error(`prompt override base prompt drifted from plan: ${override.nodeId}`);
414
+ }
415
+ const finalPrompt = composeDagPromptOverridePrompt({
416
+ basePrompt: task.subtask_prompt,
417
+ text: override.prompt,
418
+ mode: override.mode === "append" ? "append" : "replace",
419
+ });
420
+ if (sha256Hex(finalPrompt) !== entry.promptSha256) {
421
+ throw new Error(`prompt override text does not match the planned sha256: ${override.nodeId}`);
422
+ }
423
+ task.subtask_prompt = finalPrompt;
424
+ }
425
+ return spec;
426
+ }
387
427
  async function stageContinuationRun(input) {
388
- const { ranks } = topoSortToRanks(input.parentSpec);
389
- const runId = await buildDagRunId(input.parentSpec, { cwd: input.cwd });
428
+ const specForRun = input.promptOverrides !== undefined && input.promptOverrides.length > 0
429
+ ? applyPromptOverridesToSpec({
430
+ spec: input.parentSpec,
431
+ plan: input.plan,
432
+ overrides: input.promptOverrides,
433
+ })
434
+ : input.parentSpec;
435
+ const { ranks } = topoSortToRanks(specForRun);
436
+ const runId = await buildDagRunId(specForRun, { cwd: input.cwd });
390
437
  const runDir = getDagRunDir(input.cwd, "active", runId);
391
438
  await prepareActiveRunDir(runDir);
392
- await writeRunSpec(runDir, input.parentSpec);
439
+ await writeRunSpec(runDir, specForRun);
393
440
  const resetSet = new Set(input.plan.resetNodeIds);
394
441
  const importedSet = new Set(input.plan.importedNodeIds);
395
- const tasksById = new Map(input.parentSpec.tasks.map((task) => [task.id, task]));
442
+ const tasksById = new Map(specForRun.tasks.map((task) => [task.id, task]));
396
443
  const importManifestNodes = [];
397
444
  const nodes = {};
398
445
  for (const nodeId of input.plan.importedNodeIds) {
@@ -433,6 +480,9 @@ async function stageContinuationRun(input) {
433
480
  reason: input.reason,
434
481
  requestId: input.requestId,
435
482
  planHash: input.plan.planHash,
483
+ ...(input.plan.promptOverrides
484
+ ? { promptOverrides: input.plan.promptOverrides }
485
+ : {}),
436
486
  createdAt,
437
487
  };
438
488
  const state = {
@@ -482,7 +532,7 @@ async function stageContinuationRun(input) {
482
532
  await writeJsonAtomic(path.join(runDir, IMPORT_MANIFEST_REL_PATH), importManifest);
483
533
  await writeJsonAtomic(path.join(runDir, RERUN_PLAN_REL_PATH), input.plan);
484
534
  await writeRunState(runDir, state);
485
- return { runId, runDir, state };
535
+ return { runId, runDir, state, spec: specForRun };
486
536
  }
487
537
  export async function executeDagRerun(input) {
488
538
  const existing = await readExistingOperatorRequest(input.cwd, input.requestId);
@@ -493,6 +543,7 @@ export async function executeDagRerun(input) {
493
543
  cwd: input.cwd,
494
544
  parentRunId: input.parentRunId,
495
545
  selectedNodeId: input.selectedNodeId,
546
+ promptOverrides: input.promptOverrides,
496
547
  });
497
548
  if (plan.planHash !== input.planHash) {
498
549
  return {
@@ -530,10 +581,11 @@ export async function executeDagRerun(input) {
530
581
  plan,
531
582
  reason: input.reason,
532
583
  requestId: input.requestId,
584
+ promptOverrides: input.promptOverrides,
533
585
  });
534
586
  const continuationOptions = {
535
587
  cwd: input.cwd,
536
- spec: parentSpec,
588
+ spec: staged.spec,
537
589
  state: staged.state,
538
590
  runDir: staged.runDir,
539
591
  ...(input.maxConcurrent !== undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.39.0-next.29",
3
+ "version": "0.39.0-next.30",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-Dmy4lSGv.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-DBa14MSI.js";import{_ as i}from"./mermaid.core-Dmy4lSGv.js";import"./chunk-5VM5RSS4-DdKW7-fL.js";import"./chunk-XXDRQBXY-B1T2yAIe.js";import"./chunk-KBJHAD2P-lgPogpcX.js";import"./chunk-2GRJ4B5K-BxmiogQE.js";import"./index-CqWlWE4l.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-DBa14MSI.js";import{_ as i}from"./mermaid.core-Dmy4lSGv.js";import"./chunk-5VM5RSS4-DdKW7-fL.js";import"./chunk-XXDRQBXY-B1T2yAIe.js";import"./chunk-KBJHAD2P-lgPogpcX.js";import"./chunk-2GRJ4B5K-BxmiogQE.js";import"./index-CqWlWE4l.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as r,b as e,a,S as s}from"./chunk-5RXB4S5H-DTrT1Vze.js";import{_ as i}from"./mermaid.core-Dmy4lSGv.js";import"./chunk-XXDRQBXY-B1T2yAIe.js";import"./chunk-KBJHAD2P-lgPogpcX.js";import"./chunk-2GRJ4B5K-BxmiogQE.js";import"./index-CqWlWE4l.js";var u={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-UKHOOZJN-35krh9WG.js";import{_ as a}from"./mermaid.core-Dmy4lSGv.js";import"./chunk-5VM5RSS4-DdKW7-fL.js";import"./chunk-XXDRQBXY-B1T2yAIe.js";import"./chunk-KBJHAD2P-lgPogpcX.js";import"./chunk-2GRJ4B5K-BxmiogQE.js";import"./channel-DWcZjWB3.js";import"./index-CqWlWE4l.js";var o=a(t=>`${e(t)}
2
- .swimlane.cluster rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),s=o,n=r({defaultLayout:"swimlane",styles:s});export{n as diagram};