@tea-agent/loop-agent 0.42.0-next.10 → 0.42.0-next.11

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.
@@ -16,6 +16,12 @@ import {
16
16
  describeTransferError,
17
17
  formatContextTransferReasonList,
18
18
  } from "../dag-context-reason-labels.js";
19
+ import {
20
+ describeInspectorMeta,
21
+ inspectorFieldLabel,
22
+ inspectorListValueLabel,
23
+ inspectorValueLabel,
24
+ } from "../dag-inspector-humanize.js";
19
25
  import {
20
26
  fetchJson,
21
27
  fetchSpecEvidenceFile,
@@ -31,7 +37,6 @@ import {
31
37
  } from "../api.js";
32
38
  import {
33
39
  badge,
34
- formatLedgerSummary,
35
40
  formatMs,
36
41
  prettyJsonBlock,
37
42
  } from "../format.js";
@@ -72,7 +77,7 @@ import { createCustomSelect } from "../custom-select.js";
72
77
  // reused inspector shell never keeps stale node closures: every render writes
73
78
  // the latest {dagRunId, node} here and tab clicks re-render from the same
74
79
  // reference, always resolving the currently selected node identity.
75
- const inspectorSelection = { dagRunId: null, node: null };
80
+ const inspectorSelection = { dagRunId: null, node: null, run: null };
76
81
 
77
82
  /**
78
83
  * 失败状态行统一入口:错误信息中嵌入的已知 reason code 先给中文语义
@@ -111,34 +116,95 @@ export const ARTIFACT_FILE_PREVIEW_SURFACES = Object.freeze({
111
116
  revisionSelection: "artifact-revision-current-preview",
112
117
  });
113
118
 
119
+ const INSPECTOR_PAGE_GUIDANCE = Object.freeze({
120
+ overview: {
121
+ eyebrow: "先看结论",
122
+ title: "这个节点做了什么",
123
+ body: "先看当前状态、执行方式和结果来源;需要追细节时,再进入后面的页面。",
124
+ },
125
+ input: {
126
+ eyebrow: "运行前",
127
+ title: "系统原本准备怎么做",
128
+ body: "这里是开始执行前保存的任务说明、依赖关系和读写范围,不代表这些内容后来一定被实际使用。",
129
+ },
130
+ context: {
131
+ eyebrow: "执行时",
132
+ title: "AI 实际拿到了哪些资料",
133
+ body: "这一页只展示系统在下发任务时真正记录到的内容;没有记录时会明确说明,不会用运行前设置代替。",
134
+ },
135
+ timeline: {
136
+ eyebrow: "执行过程",
137
+ title: "这一步实际发生了什么",
138
+ body: "按顺序查看 AI 的回复、工具调用或命令结果;失败时优先看标红步骤和错误输出。",
139
+ },
140
+ "spec-evidence": {
141
+ eyebrow: "需求依据",
142
+ title: "它有没有真的读过需求",
143
+ body: "只有成功读取文件或查询知识库的记录才算证据;模型自己提到文件名不算。",
144
+ },
145
+ output: {
146
+ eyebrow: "执行结果",
147
+ title: "它产出了什么,谁会用",
148
+ body: "先确认文件是否真实生成、能否替换以及后续谁会使用,再查看具体输出内容。",
149
+ },
150
+ lineage: {
151
+ eyebrow: "来源与影响",
152
+ title: "这一步从哪里来,会影响哪里",
153
+ body: "用于判断结果是本次生成还是沿用旧运行,以及替换或重跑会从哪个位置继续。",
154
+ },
155
+ });
156
+
157
+ function buildInspectorPageLead(tab) {
158
+ const copy = INSPECTOR_PAGE_GUIDANCE[tab];
159
+ if (!copy) return null;
160
+ const lead = el("section", "inspector-page-lead");
161
+ lead.dataset.page = tab;
162
+ lead.append(
163
+ el("span", "inspector-page-eyebrow", copy.eyebrow),
164
+ el("h4", null, copy.title),
165
+ el("p", null, copy.body),
166
+ );
167
+ return lead;
168
+ }
169
+
170
+ function ensureInspectorPageLead(content, tab) {
171
+ const existing = content.querySelector(
172
+ `.inspector-page-lead[data-page="${tab}"]`,
173
+ );
174
+ if (existing) return existing;
175
+ const lead = buildInspectorPageLead(tab);
176
+ if (lead) content.insertBefore(lead, content.firstChild);
177
+ return lead;
178
+ }
179
+
114
180
  export const CONTEXT_EXPORT_TARGETS = Object.freeze([
115
181
  {
116
182
  value: "generic",
117
- label: "通用 Agent 交接包",
118
- description: "适合交给其他 Agent 或 LLM 继续处理",
183
+ label: "通用 AI 交接包",
184
+ description: "适合交给其他 AI 工具继续处理",
119
185
  icon: "ri-robot-2-line",
120
- contents: "receipt · manifest · handoff.md",
186
+ contents: "执行时记录 · 文件清单 · 交接说明",
121
187
  },
122
188
  {
123
189
  value: "pi-session",
124
- label: "Pi 可重开会话",
125
- description: "兼容时附带可由 Pi 打开的 session JSONL",
190
+ label: "可在 Pi 中恢复的会话",
191
+ description: "如果记录兼容,会附带可直接恢复的会话文件",
126
192
  icon: "ri-terminal-box-line",
127
- contents: "receipt · manifest · session.jsonl(兼容时)",
193
+ contents: "执行时记录 · 文件清单 · 可恢复的 Pi 会话(兼容时)",
128
194
  },
129
195
  {
130
196
  value: "readable",
131
197
  label: "可读执行记录",
132
198
  description: "便于人工审阅,不承诺完整重现执行",
133
199
  icon: "ri-file-list-3-line",
134
- contents: "receipt · manifest · transcript.md",
200
+ contents: "执行时记录 · 文件清单 · 可读对话记录",
135
201
  },
136
202
  {
137
203
  value: "pi-handoff",
138
- label: "Pi Handoff 交接包",
139
- description: "面向 Pi Handoff 工作流的兼容格式",
204
+ label: "Pi 专用交接包",
205
+ description: "用于把资料交给 Pi 的继续处理流程",
140
206
  icon: "ri-share-forward-box-line",
141
- contents: "receipt · manifest · handoff.md",
207
+ contents: "执行时记录 · 文件清单 · Pi 交接说明",
142
208
  },
143
209
  ]);
144
210
 
@@ -398,7 +464,7 @@ function createPromptRestartPromptCard({
398
464
  const next = candidate ?? readCandidate?.();
399
465
  titleEl.textContent =
400
466
  next?.key === "frozen"
401
- ? "冻结原始提示词"
467
+ ? "运行开始时的提示词"
402
468
  : (next?.label ?? title);
403
469
  pre.textContent = next?.prompt ?? "";
404
470
  metaEl.textContent = formatPromptRestartMeta(next?.prompt ?? "");
@@ -415,6 +481,7 @@ function renderDagInspectorForCurrent() {
415
481
  inspectorSelection.dagRunId,
416
482
  inspectorSelection.node,
417
483
  false,
484
+ inspectorSelection.run,
418
485
  );
419
486
  }
420
487
 
@@ -628,6 +695,7 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
628
695
  restoreInspectorContentScroll(content, dagRunId, nodeId, "spec-evidence");
629
696
  return;
630
697
  }
698
+ ensureInspectorPageLead(content, "spec-evidence");
631
699
  if (!evidence) {
632
700
  content.appendChild(el("p", "empty", "无法加载规范证据。"));
633
701
  restoreInspectorContentScroll(content, dagRunId, nodeId, "spec-evidence");
@@ -638,8 +706,8 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
638
706
  const statusLabels = {
639
707
  "spec-read": "已读取规范",
640
708
  "source-bound": "已绑定任务源",
641
- "spec-injected": "仅注入 skill",
642
- "search-only": "仅执行检索",
709
+ "spec-injected": "已提供阅读能力,但没有读取记录",
710
+ "search-only": "只搜索过,没有读取内容",
643
711
  "kb-queried": "已观察知识库查询",
644
712
  "no-evidence": "未观察到读取",
645
713
  };
@@ -680,7 +748,7 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
680
748
  };
681
749
 
682
750
  appendListSection(
683
- "注入的 Skill",
751
+ "提供给节点的技能",
684
752
  evidence.skillInjection?.skills,
685
753
  (skill) => {
686
754
  const item = el("li", null);
@@ -705,9 +773,9 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
705
773
  icon.setAttribute("aria-hidden", "true");
706
774
  button.append(
707
775
  icon,
708
- document.createTextNode(` ${source.kind}: `),
776
+ document.createTextNode(` ${inspectorListValueLabel(source.kind)}: `),
709
777
  el("code", null, source.path),
710
- document.createTextNode(` · sha256 ${source.sha256.slice(0, 12)}…`),
778
+ document.createTextNode(` · 内容校验码 ${source.sha256.slice(0, 12)}…`),
711
779
  );
712
780
  button.addEventListener("click", () =>
713
781
  onSpecEvidenceFileClick(
@@ -767,7 +835,7 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
767
835
  item.appendChild(button);
768
836
  return item;
769
837
  },
770
- "unavailable:未观察到规范读取。",
838
+ "没有记录到节点读取规范文件。",
771
839
  );
772
840
  appendListSection(
773
841
  `规范检索(${evidence.specSearches?.length ?? 0})`,
@@ -782,7 +850,7 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
782
850
  );
783
851
  return item;
784
852
  },
785
- "unavailable:未观察到规范检索。",
853
+ "没有记录到节点检索规范。",
786
854
  );
787
855
  appendListSection(
788
856
  `知识库查询(${evidence.knowledgeBaseQueries?.length ?? 0})`,
@@ -794,7 +862,7 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
794
862
  item.append(icon, document.createTextNode(` ${query.connector}`));
795
863
  return item;
796
864
  },
797
- "unavailable:未观察到知识库查询;connector 可能未配置或未调用。",
865
+ "没有记录到知识库查询;可能未配置知识库连接,或节点没有使用它。",
798
866
  );
799
867
 
800
868
  if (
@@ -809,10 +877,10 @@ async function renderSpecEvidence(content, dagRunId, nodeId, cachedEvidence) {
809
877
  icon,
810
878
  document.createTextNode(
811
879
  evidence.status === "source-bound"
812
- ? "DAG 已绑定权威任务源及内容哈希,但尚未观察到成功的 read 工具调用;绑定证明上下文来源,read 事件才证明节点读取了完整文件。"
880
+ ? "系统已确认任务来源和文件版本,但没有记录到节点成功读取完整文件。仅绑定文件不能证明节点真的读过内容。"
813
881
  : evidence.status === "no-evidence"
814
- ? "未观察到规范读取。只有 session event 中成功配对的 read 工具调用才算已读取,模型总结或文件名命中不能作为证明。"
815
- : "规范 skill 已注入,但未观察到规范读取或知识库查询;grep/find/ls 只能证明检索,不能替代 read。",
882
+ ? "没有记录到节点读取规范。模型总结里提到文件名,不等于节点真正读取了文件。"
883
+ : "已把规范阅读能力提供给节点,但没有记录到它读取规范或查询知识库;只搜索到文件名不等于读过文件内容。",
816
884
  ),
817
885
  );
818
886
  content.appendChild(warning);
@@ -923,7 +991,7 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
923
991
  el(
924
992
  "span",
925
993
  "spec-evidence-detail-hash",
926
- `绑定 sha256 ${String(detail.data.sha256 ?? "").slice(0, 12)}… · 当前 ${detail.data.hashMatch ? "一致" : "不一致"}`,
994
+ `登记的内容校验码 ${String(detail.data.sha256 ?? "").slice(0, 12)}… · 当前文件${detail.data.hashMatch ? "一致" : "不一致"}`,
927
995
  ),
928
996
  );
929
997
  } else if (detail.source === "read" && detail.data?.readAt) {
@@ -969,15 +1037,22 @@ function renderSpecEvidenceDetail(content, dagRunId, nodeId) {
969
1037
 
970
1038
  function sourceLabel(source) {
971
1039
  if (source === "defaults") return "继承默认值";
972
- if (source === "schema-default") return "schema 默认";
973
- if (source === "merged") return "合并";
1040
+ if (source === "schema-default") return "系统默认值";
1041
+ if (source === "merged") return "合并后的值";
974
1042
  return null;
975
1043
  }
976
1044
 
977
1045
  function appendMetaRow(dl, label, value, source) {
978
1046
  if (value == null || value === "") return;
979
- const dt = el("dt", null, label);
980
- const dd = el("dd", null, String(value));
1047
+ const copy = describeInspectorMeta(label, value);
1048
+ const dt = el("dt", null, copy.label);
1049
+ const dd = el("dd", null, copy.value);
1050
+ const labelHelp = [
1051
+ copy.help ? `含义:${copy.help}` : null,
1052
+ copy.rawLabel ? `技术字段:${copy.rawLabel}` : null,
1053
+ ].filter(Boolean);
1054
+ if (labelHelp.length > 0) dt.setAttribute("title", labelHelp.join("\n"));
1055
+ if (copy.rawValue) dd.setAttribute("title", `原始值:${copy.rawValue}`);
981
1056
  const inherited = sourceLabel(source);
982
1057
  if (inherited) {
983
1058
  dd.appendChild(document.createTextNode(" "));
@@ -990,12 +1065,20 @@ function appendMetaRow(dl, label, value, source) {
990
1065
  function appendCodeList(parent, title, values) {
991
1066
  if (!values || values.length === 0) return;
992
1067
  const section = el("section", "node-input-section");
993
- section.appendChild(el("h4", null, title));
1068
+ const heading = el("h4", null, inspectorFieldLabel(title));
1069
+ if (inspectorFieldLabel(title) !== title) {
1070
+ heading.setAttribute("title", `技术字段:${title}`);
1071
+ }
1072
+ section.appendChild(heading);
994
1073
  const list = el("ul", "node-input-code-list");
995
1074
  for (const value of values) {
996
1075
  const item = document.createElement("li");
997
1076
  const code = document.createElement("code");
998
- code.textContent = String(value);
1077
+ const rawValue = String(value);
1078
+ code.textContent = inspectorListValueLabel(rawValue);
1079
+ if (code.textContent !== rawValue) {
1080
+ code.setAttribute("title", `原始值:${rawValue}`);
1081
+ }
999
1082
  item.appendChild(code);
1000
1083
  list.appendChild(item);
1001
1084
  }
@@ -1063,9 +1146,9 @@ function renderDagNodeInputDetail(content, data) {
1063
1146
  section.appendChild(el("h4", null, "任务正文"));
1064
1147
  if (data.prompt.source?.path) {
1065
1148
  const meta = el("p", "muted");
1066
- meta.textContent = `source: ${data.prompt.source.path}${
1149
+ meta.textContent = `任务说明来源:${data.prompt.source.path}${
1067
1150
  data.prompt.source.sha256Prefix
1068
- ? ` (sha256 ${data.prompt.source.sha256Prefix}…)`
1151
+ ? `(内容校验码 ${data.prompt.source.sha256Prefix}…)`
1069
1152
  : ""
1070
1153
  }`;
1071
1154
  section.appendChild(meta);
@@ -1085,7 +1168,7 @@ function renderDagNodeInputDetail(content, data) {
1085
1168
 
1086
1169
  if (data.executorInput) {
1087
1170
  const section = el("section", "node-input-section");
1088
- section.appendChild(el("h4", null, "执行器输入摘要"));
1171
+ section.appendChild(el("h4", null, "将如何执行"));
1089
1172
  const dl = el("dl", "node-input-summary");
1090
1173
  appendMetaRow(dl, "kind", data.executorInput.kind, null);
1091
1174
  if (data.executorInput.modelHint) {
@@ -1158,7 +1241,7 @@ function renderDagNodeInputDetail(content, data) {
1158
1241
 
1159
1242
  if (data.boundaries) {
1160
1243
  const section = el("section", "node-input-section");
1161
- section.appendChild(el("h4", null, "边界与输出契约"));
1244
+ section.appendChild(el("h4", null, "读写范围与输出要求"));
1162
1245
  const dl = el("dl", "node-input-summary");
1163
1246
  appendMetaRow(dl, "outputMode", data.boundaries.outputMode, null);
1164
1247
  appendMetaRow(
@@ -1169,7 +1252,7 @@ function renderDagNodeInputDetail(content, data) {
1169
1252
  );
1170
1253
  section.appendChild(dl);
1171
1254
  if (data.boundaries.outputContract) {
1172
- section.appendChild(el("h5", null, "outputContract"));
1255
+ section.appendChild(el("h5", null, "输出内容要求"));
1173
1256
  section.appendChild(renderMarkdown(data.boundaries.outputContract));
1174
1257
  }
1175
1258
  appendCodeList(section, "allowedPaths", data.boundaries.allowedPaths);
@@ -1181,7 +1264,7 @@ function renderDagNodeInputDetail(content, data) {
1181
1264
  if (data.piExtensionRunFacts) {
1182
1265
  const facts = data.piExtensionRunFacts;
1183
1266
  const section = el("section", "node-input-section");
1184
- section.appendChild(el("h4", null, "Pi 扩展运行事实"));
1267
+ section.appendChild(el("h4", null, "AI 扩展加载结果"));
1185
1268
  if (!facts.exists) {
1186
1269
  section.appendChild(
1187
1270
  el(
@@ -1222,7 +1305,7 @@ function renderDagNodeInputDetail(content, data) {
1222
1305
 
1223
1306
  if (data.promptFingerprint?.exists) {
1224
1307
  const section = el("section", "node-input-section");
1225
- section.appendChild(el("h4", null, "Assembled prompt 指纹"));
1308
+ section.appendChild(el("h4", null, "最终提示内容校验信息"));
1226
1309
  const dl = el("dl", "node-input-summary");
1227
1310
  appendMetaRow(
1228
1311
  dl,
@@ -1274,6 +1357,7 @@ async function renderDagNodeInput(content, dagRunId, node) {
1274
1357
  preserveIntent: true,
1275
1358
  });
1276
1359
  clearNode(content);
1360
+ ensureInspectorPageLead(content, "input");
1277
1361
  const loading = el("p", "muted", UI_TEXT.inputLoading);
1278
1362
  loading.setAttribute("role", "status");
1279
1363
  loading.setAttribute("aria-live", "polite");
@@ -1293,6 +1377,7 @@ async function renderDagNodeInput(content, dagRunId, node) {
1293
1377
  }
1294
1378
 
1295
1379
  clearNode(content);
1380
+ ensureInspectorPageLead(content, "input");
1296
1381
  const finishInputRender = () => {
1297
1382
  restoreInspectorContentScroll(content, dagRunId, nodeId, "input");
1298
1383
  uiState.dagNodeInputLoading = false;
@@ -1355,31 +1440,45 @@ export function frontendGateLedgerLines(input) {
1355
1440
  if (prewriteResult?.ok && prewriteResult.body) {
1356
1441
  const parsed = prewriteResult.body;
1357
1442
  if (parsed?.classification) {
1358
- lines.push(`classification: ${String(parsed.classification)}`);
1443
+ const classifications = {
1444
+ accepted: "通过,可以进入写入阶段",
1445
+ blocked: "未通过,已阻止写入",
1446
+ warning: "有提醒,需要留意",
1447
+ };
1448
+ lines.push(
1449
+ `写入前检查:${classifications[parsed.classification] ?? String(parsed.classification)}`,
1450
+ );
1359
1451
  if (parsed.failureCode) {
1360
- lines.push(`failureCode: ${String(parsed.failureCode)}`);
1452
+ lines.push(`问题编号:${String(parsed.failureCode)}`);
1361
1453
  }
1362
1454
  if (parsed.failureReason) {
1363
- lines.push(`failureReason: ${String(parsed.failureReason)}`);
1455
+ lines.push(`原因:${String(parsed.failureReason)}`);
1364
1456
  }
1365
1457
  } else {
1366
- lines.push("frontend-prewrite-result.json 内容无法解析为结构化结果。");
1458
+ lines.push("写入前检查结果无法解析,请查看运行记录。");
1367
1459
  }
1368
1460
  } else if (prewriteResult && prewriteResult.status === 404) {
1369
- lines.push("frontend-prewrite-result.json 尚未生成(门禁未执行或缺失)。");
1461
+ lines.push("写入前检查尚未执行,或没有留下检查结果。");
1370
1462
  } else if (prewriteResult) {
1371
1463
  lines.push(
1372
- "frontend-prewrite-result.json 读取失败(只读接口拒绝/服务异常,安全降级)。",
1464
+ "暂时无法读取写入前检查结果;为安全起见,不把它当成已通过。",
1373
1465
  );
1374
1466
  } else {
1375
- lines.push("frontend-prewrite-result.json 尚未生成(门禁未执行或缺失)。");
1467
+ lines.push("写入前检查尚未执行,或没有留下检查结果。");
1376
1468
  }
1377
1469
  if (ledgerBody && ledgerBody.present === true) {
1378
- lines.push(formatLedgerSummary(ledgerBody));
1470
+ const value = (candidate) =>
1471
+ typeof candidate === "number" ? candidate : "未知";
1472
+ lines.push(
1473
+ `需求覆盖记录:原始片段 ${value(ledgerBody.fragmentCount)} 个;整理后的需求 ${value(ledgerBody.canonicalRequirementCount)} 条;待处理 ${value(ledgerBody.unresolvedCount)} 条;冲突 ${value(ledgerBody.conflictCount)} 条。`,
1474
+ );
1475
+ if (ledgerBody.stale === true) {
1476
+ lines.push("提醒:任务来源后来发生过变化,这份需求覆盖记录可能已经过时。");
1477
+ }
1379
1478
  } else if (ledgerBody) {
1380
- lines.push("ledger: 无(任务无引用或 ledger 缺失,不伪造计数)");
1479
+ lines.push("没有需求覆盖记录;可能是任务没有引用资料,或记录尚未生成。不会猜测数量。");
1381
1480
  } else if (taskId) {
1382
- lines.push("ledger: 暂不可查(只读接口失败,安全降级)");
1481
+ lines.push("需求覆盖记录暂时无法查看;不会把读取失败当成没有问题。");
1383
1482
  }
1384
1483
  return lines;
1385
1484
  }
@@ -1426,7 +1525,7 @@ export async function fetchFrontendGateLedgerData(dagRunId) {
1426
1525
  async function renderFrontendGateLedger(content, dagRunId) {
1427
1526
  const section = el("section", "node-input-section");
1428
1527
  section.appendChild(
1429
- el("h4", null, "Frontend prewrite 门禁 / source fidelity ledger"),
1528
+ el("h4", null, "前端写入前检查与需求覆盖记录"),
1430
1529
  );
1431
1530
  const status = el("p", "muted", "加载中…");
1432
1531
  status.setAttribute("role", "status");
@@ -1472,11 +1571,11 @@ function buildContextExportSection(dagRunId, node) {
1472
1571
  const heading = el("div", "context-export-heading");
1473
1572
  const headingCopy = el("div", null);
1474
1573
  headingCopy.appendChild(el("span", "context-export-eyebrow", "上下文交接包"));
1475
- const title = el("h4", null, "把这个节点交给另一个 Agent");
1574
+ const title = el("h4", null, "把这个节点的资料交给另一个 AI");
1476
1575
  title.id = "context-export-title";
1477
1576
  headingCopy.appendChild(title);
1478
1577
  headingCopy.appendChild(
1479
- el("p", null, "选择接收方式和保护级别,系统会生成一个可核验的 ZIP 交接包。"),
1578
+ el("p", null, "选择接收方式和是否脱敏,系统会生成一个带来源校验信息的 ZIP 交接包。"),
1480
1579
  );
1481
1580
  const invariant = el("div", "context-export-invariant");
1482
1581
  const lockIcon = el("i", "ri-lock-2-line");
@@ -1560,10 +1659,10 @@ function buildContextExportSection(dagRunId, node) {
1560
1659
  ticket.setAttribute("aria-live", "polite");
1561
1660
  const ticketTop = el("div", "context-export-ticket-top");
1562
1661
  ticketTop.append(
1563
- el("span", "context-export-ticket-type", "CONTEXT / ZIP"),
1662
+ el("span", "context-export-ticket-type", "交接资料 / ZIP"),
1564
1663
  el("span", "context-export-ticket-safety", "已脱敏"),
1565
1664
  );
1566
- const ticketTitle = el("strong", "context-export-ticket-title", "通用 Agent 交接包");
1665
+ const ticketTitle = el("strong", "context-export-ticket-title", "通用 AI 交接包");
1567
1666
  const ticketRows = el("dl", "context-export-ticket-rows");
1568
1667
  const appendTicketRow = (label, value, className) => {
1569
1668
  const term = el("dt", null, label);
@@ -1571,12 +1670,12 @@ function buildContextExportSection(dagRunId, node) {
1571
1670
  ticketRows.append(term, detail);
1572
1671
  return detail;
1573
1672
  };
1574
- const recipientValue = appendTicketRow("用途", "适合交给其他 Agent 或 LLM 继续处理");
1673
+ const recipientValue = appendTicketRow("用途", "适合交给其他 AI 工具继续处理");
1575
1674
  const contentsValue = appendTicketRow("包含", CONTEXT_EXPORT_TARGETS[0].contents, "is-mono");
1576
1675
  const protectionValue = appendTicketRow("保护", "自动隐藏匹配到的疑似凭据");
1577
1676
  const attemptValue = appendTicketRow(
1578
1677
  "来源",
1579
- `${node.nodeId} · attempt ${selectedAttemptNumber(dagRunId, node) ?? 1}`,
1678
+ `${node.nodeId} · 第 ${selectedAttemptNumber(dagRunId, node) ?? 1} 次执行`,
1580
1679
  "is-mono",
1581
1680
  );
1582
1681
  ticket.append(
@@ -1584,7 +1683,7 @@ function buildContextExportSection(dagRunId, node) {
1584
1683
  el("span", "context-export-ticket-label", "即将生成"),
1585
1684
  ticketTitle,
1586
1685
  ticketRows,
1587
- el("p", "context-export-ticket-foot", "附带 manifest 哈希,可核对来源与完整性"),
1686
+ el("p", "context-export-ticket-foot", "附带文件清单和内容校验码,可以核对资料来源与完整性"),
1588
1687
  );
1589
1688
  workspace.appendChild(ticket);
1590
1689
  section.appendChild(workspace);
@@ -1604,7 +1703,7 @@ function buildContextExportSection(dagRunId, node) {
1604
1703
  recipientValue.textContent = target.description;
1605
1704
  contentsValue.textContent = target.contents;
1606
1705
  protectionValue.textContent = profile.description;
1607
- attemptValue.textContent = `${node.nodeId} · attempt ${selectedAttemptNumber(dagRunId, node) ?? 1}`;
1706
+ attemptValue.textContent = `${node.nodeId} · 第 ${selectedAttemptNumber(dagRunId, node) ?? 1} 次执行`;
1608
1707
  const safety = ticket.querySelector(".context-export-ticket-safety");
1609
1708
  if (safety) {
1610
1709
  safety.textContent = profileValue === "raw" ? "原始内容" : "已脱敏";
@@ -1647,7 +1746,7 @@ function buildContextExportSection(dagRunId, node) {
1647
1746
  }
1648
1747
  submit.disabled = true;
1649
1748
  try {
1650
- setStatus("正在生成可验证 Context Bundle…");
1749
+ setStatus("正在生成带来源校验信息的交接包…");
1651
1750
  const clientRequestId = `inspect-context-export-${Date.now()}-${Math.random()
1652
1751
  .toString(36)
1653
1752
  .slice(2, 10)}`;
@@ -1673,7 +1772,7 @@ function buildContextExportSection(dagRunId, node) {
1673
1772
  if (operation?.state !== "succeeded" || !operation.result?.downloadUrl) {
1674
1773
  throw new Error(operation?.errorMessage ?? "导出未成功完成");
1675
1774
  }
1676
- setStatus("Bundle 已生成,正在安全下载…");
1775
+ setStatus("交接包已生成,正在安全下载…");
1677
1776
  const downloaded = await downloadContextExport(operation.result.downloadUrl);
1678
1777
  const objectUrl = URL.createObjectURL(downloaded.blob);
1679
1778
  const link = document.createElement("a");
@@ -1684,7 +1783,7 @@ function buildContextExportSection(dagRunId, node) {
1684
1783
  link.remove();
1685
1784
  URL.revokeObjectURL(objectUrl);
1686
1785
  setStatus(
1687
- `下载完成 · ${operation.result.bundleId} · ${new Intl.NumberFormat("zh-CN").format(operation.result.bytes)} bytes\nSHA-256 ${operation.result.bundleSha256}`,
1786
+ `下载完成 · ${operation.result.bundleId} · ${new Intl.NumberFormat("zh-CN").format(operation.result.bytes)} 字节\n内容校验码 ${operation.result.bundleSha256}`,
1688
1787
  );
1689
1788
  } catch (error) {
1690
1789
  setStatus(transferErrorStatusText(error, "导出未成功完成"), true);
@@ -1816,14 +1915,14 @@ function createArtifactContentPreview(
1816
1915
  el(
1817
1916
  "span",
1818
1917
  null,
1819
- `${data.mediaType ?? "unknown"} · ${new Intl.NumberFormat("zh-CN").format(data.sourceBytes ?? 0)} bytes`,
1918
+ `${data.mediaType ?? "未知类型"} · ${new Intl.NumberFormat("zh-CN").format(data.sourceBytes ?? 0)} 字节`,
1820
1919
  ),
1821
1920
  ];
1822
1921
  if (typeof data.sha256 === "string" && data.sha256.length >= 12) {
1823
1922
  const sha = el(
1824
1923
  "span",
1825
1924
  null,
1826
- ` · sha256 ${data.sha256.slice(0, 12)}…`,
1925
+ ` · 内容校验码 ${data.sha256.slice(0, 12)}…`,
1827
1926
  );
1828
1927
  sha.title = data.sha256;
1829
1928
  metaChildren.push(sha);
@@ -1835,7 +1934,7 @@ function createArtifactContentPreview(
1835
1934
  el(
1836
1935
  "p",
1837
1936
  "artifact-content-preview-unavailable",
1838
- "该文件的媒体类型不支持安全内嵌显示;路径、大小和 SHA-256 已核验。",
1937
+ "该文件类型不适合直接在页面中显示;系统已经核对文件位置、大小和内容校验码。",
1839
1938
  ),
1840
1939
  );
1841
1940
  return;
@@ -1856,7 +1955,7 @@ function createArtifactContentPreview(
1856
1955
  if (data.redacted) notes.push("疑似凭据已脱敏");
1857
1956
  if (data.truncated) {
1858
1957
  notes.push(
1859
- `仅显示前 ${new Intl.NumberFormat("zh-CN").format(data.maxBytes ?? 0)} bytes`,
1958
+ `仅显示前 ${new Intl.NumberFormat("zh-CN").format(data.maxBytes ?? 0)} 字节`,
1860
1959
  );
1861
1960
  }
1862
1961
  if (notes.length > 0) {
@@ -1872,19 +1971,57 @@ function createArtifactContentPreview(
1872
1971
  return { element: details, reset };
1873
1972
  }
1874
1973
 
1875
- function buildArtifactRevisionSection(dagRunId, node) {
1974
+ export function artifactRevisionRunEligibility(run) {
1975
+ // File-level eligibility only says that a file has a known replacement
1976
+ // contract. Creating a derived run has stricter, run-level safeguards.
1977
+ // Keep this check here so the UI does not invite an action the server will
1978
+ // necessarily reject later.
1979
+ if (!run) return { eligible: true };
1980
+ const lifecycle = String(run.lifecycle ?? "").toLowerCase();
1981
+ const status = String(run.status ?? "").toLowerCase();
1982
+ if (lifecycle !== "completed") {
1983
+ return {
1984
+ eligible: false,
1985
+ title: "这次运行还没有完整结束",
1986
+ description:
1987
+ "请等待运行收尾为“已完成”。进行中、暂停中,或尚未收尾的报错,都不能从替换产出继续。",
1988
+ };
1989
+ }
1990
+ if (!new Set(["failed", "partial_failed"]).has(status)) {
1991
+ return {
1992
+ eligible: false,
1993
+ title: "这次运行不支持替换产出后继续",
1994
+ description:
1995
+ "只有已完整结束且结果为“失败”或“部分失败”的运行,才能换文件后继续。当前运行已经成功完成;文件本身可替换,不代表可以创建后续运行。",
1996
+ };
1997
+ }
1998
+ return { eligible: true };
1999
+ }
2000
+
2001
+ function buildArtifactRevisionSection(dagRunId, node, run) {
1876
2002
  const { section, body } = createDagActionCard({
1877
- eyebrow: "产出修订",
1878
- title: "替换产出后继续运行",
1879
- description: "上传一个候选新版本,先校验差异和影响范围,再决定是否创建新的运行。",
2003
+ eyebrow: "替换已有文件",
2004
+ title: "换掉这个节点的产出后继续",
2005
+ description: "上传新文件后,系统会先检查格式并告诉你哪些后续节点需要重做;确认前不会改动原运行。",
1880
2006
  icon: "ri-file-transfer-line",
1881
2007
  className: "artifact-revision-box",
1882
2008
  });
1883
2009
  section.dataset.actionSection = "artifact-revision";
2010
+ const runEligibility = artifactRevisionRunEligibility(run);
2011
+ if (!runEligibility.eligible) {
2012
+ body.appendChild(
2013
+ buildDagActionAvailability({
2014
+ tone: "blocked",
2015
+ title: runEligibility.title,
2016
+ description: runEligibility.description,
2017
+ }),
2018
+ );
2019
+ return section;
2020
+ }
1884
2021
  body.appendChild(
1885
2022
  buildDagActionAvailability({
1886
- title: "正在检查是否可以安全修订",
1887
- description: "读取已冻结的产出关系和修订规则。",
2023
+ title: "正在检查这个文件能不能安全替换",
2024
+ description: "系统正在读取运行开始前保存的文件关系和替换规则。",
1888
2025
  }),
1889
2026
  );
1890
2027
  void (async () => {
@@ -1894,7 +2031,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
1894
2031
  body.appendChild(
1895
2032
  buildDagActionAvailability({
1896
2033
  tone: "blocked",
1897
- title: "这个运行暂时不能安全修订产出",
2034
+ title: "这个运行暂时不能安全替换产出",
1898
2035
  description:
1899
2036
  "缺少结构化的产出关系,系统无法可靠判断哪些后续节点会受影响,也不会根据文件名或提示词猜测。",
1900
2037
  reasonCode:
@@ -1921,8 +2058,8 @@ function buildArtifactRevisionSection(dagRunId, node) {
1921
2058
  body.appendChild(
1922
2059
  buildDagActionAvailability({
1923
2060
  tone: "ready",
1924
- title: `可修订 ${eligible.length} 个产出`,
1925
- description: "父运行不会被修改;确认后会创建一条新的派生运行。",
2061
+ title: `可以替换 ${eligible.length} 个已生成文件`,
2062
+ description: "原运行不会被修改;确认后系统会另外创建一次新运行。",
1926
2063
  }),
1927
2064
  );
1928
2065
  const form = el("div", "dag-action-form artifact-revision-form");
@@ -1951,7 +2088,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
1951
2088
  form.appendChild(currentArtifactPreview.element);
1952
2089
  artifactSelect.addEventListener("change", currentArtifactPreview.reset);
1953
2090
  const fileLabel = el("label", "prompt-restart-field");
1954
- fileLabel.appendChild(el("span", "dag-action-field-label", "选择新文件(最多 10 MiB)"));
2091
+ fileLabel.appendChild(el("span", "dag-action-field-label", "选择新文件(最多 10 MB)"));
1955
2092
  const file = document.createElement("input");
1956
2093
  file.type = "file";
1957
2094
  file.className = "dag-action-file-input";
@@ -1963,7 +2100,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
1963
2100
  const reason = document.createElement("textarea");
1964
2101
  reason.className = "prompt-restart-textarea";
1965
2102
  reason.rows = 3;
1966
- reason.placeholder = "说明为什么要替换此产物";
2103
+ reason.placeholder = "例如:计划遗漏了一个验收条件,需要更正后继续";
1967
2104
  reason.dataset.testid = "artifact-revision-reason";
1968
2105
  reasonLabel.appendChild(reason);
1969
2106
  form.appendChild(reasonLabel);
@@ -1997,7 +2134,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
1997
2134
  return;
1998
2135
  }
1999
2136
  if (selectedFile.size > 10 * 1024 * 1024) {
2000
- setStatus("文件超过 10 MiB 限制。", true);
2137
+ setStatus("文件超过 10 MB 限制。", true);
2001
2138
  return;
2002
2139
  }
2003
2140
  if (!reason.value.trim()) {
@@ -2014,7 +2151,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
2014
2151
  reader.readAsDataURL(selectedFile);
2015
2152
  });
2016
2153
  const candidateBase64 = String(dataUrl).split(",", 2)[1] ?? "";
2017
- setStatus("正在校验候选字节、schema 与 diff…");
2154
+ setStatus("正在检查新文件格式并对比差异…");
2018
2155
  const stageId = `artifact-stage-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
2019
2156
  const staged = await postOperatorAction(
2020
2157
  "artifactRevisionStage",
@@ -2035,7 +2172,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
2035
2172
  if (stageOp?.state !== "succeeded" || !revision?.revisionId) {
2036
2173
  throw new Error(stageOp?.errorMessage ?? "修订校验失败");
2037
2174
  }
2038
- setStatus("校验通过,正在计算 direct consumers 与传递影响闭包…");
2175
+ setStatus("文件检查通过,正在计算哪些后续节点需要重新执行…");
2039
2176
  const planId = `artifact-plan-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
2040
2177
  const planned = await postOperatorAction(
2041
2178
  "artifactRevisionPlan",
@@ -2048,17 +2185,23 @@ function buildArtifactRevisionSection(dagRunId, node) {
2048
2185
  );
2049
2186
  const plan = planOp?.result?.plan;
2050
2187
  if (!plan) throw new Error(planOp?.errorMessage ?? "影响预览失败");
2051
- planHost.appendChild(el("h5", null, "修订与影响预览"));
2188
+ planHost.appendChild(el("h5", null, "替换结果与影响范围"));
2052
2189
  planHost.appendChild(
2053
2190
  transparencyMetaList([
2054
- ["修订 ID", revision.revisionId],
2055
- ["候选文件 SHA-256", revision.candidate?.sha256],
2056
- ["差异类型", revision.diff?.kind],
2057
- ["风险级别", plan.risk],
2058
- ["直接使用者", plan.impact?.directConsumerNodeIds?.join(", ") || "无"],
2059
- ["需要重跑", plan.impact?.affectedNodeIds?.join(", ") || "无"],
2060
- ["可以沿用", plan.impact?.importedNodeIds?.join(", ") || "无"],
2061
- ["计划哈希", plan.planHash],
2191
+ ["本次替换编号", revision.revisionId],
2192
+ ["新文件校验码", revision.candidate?.sha256],
2193
+ [
2194
+ "文件内容类型",
2195
+ inspectorValueLabel("差异类型", revision.diff?.kind),
2196
+ ],
2197
+ [
2198
+ "预计风险",
2199
+ inspectorValueLabel("风险级别", plan.risk),
2200
+ ],
2201
+ ["最先受影响的节点", plan.impact?.directConsumerNodeIds?.join(", ") || "无"],
2202
+ ["会重新执行的节点", plan.impact?.affectedNodeIds?.join(", ") || "无"],
2203
+ ["会沿用结果的节点", plan.impact?.importedNodeIds?.join(", ") || "无"],
2204
+ ["本次计划校验码", plan.planHash],
2062
2205
  ]),
2063
2206
  );
2064
2207
  if (revision.diff?.summary) {
@@ -2069,7 +2212,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
2069
2212
  }
2070
2213
  if (plan.eligible !== true) {
2071
2214
  setStatus(
2072
- `派生运行被阻断:${formatContextTransferReasonList(plan.blockedReasons) ?? "当前不满足继续运行的条件"}`,
2215
+ `暂时不能创建新运行:${formatContextTransferReasonList(plan.blockedReasons) ?? "当前不满足继续运行的条件"}`,
2073
2216
  true,
2074
2217
  );
2075
2218
  return;
@@ -2084,7 +2227,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
2084
2227
  void (async () => {
2085
2228
  execute.disabled = true;
2086
2229
  try {
2087
- setStatus("正在执行受影响闭包;父 run 保持不可变…");
2230
+ setStatus("正在创建新运行并重新执行受影响节点;原运行保持不变…");
2088
2231
  const executeId = `artifact-execute-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
2089
2232
  const accepted = await postOperatorAction(
2090
2233
  "artifactRevisionExecute",
@@ -2097,7 +2240,7 @@ function buildArtifactRevisionSection(dagRunId, node) {
2097
2240
  },
2098
2241
  executeId,
2099
2242
  );
2100
- if (!accepted.ok || !accepted.body?.operationId) throw new Error(accepted.body?.error?.message ?? "派生运行未被接受");
2243
+ if (!accepted.ok || !accepted.body?.operationId) throw new Error(accepted.body?.error?.message ?? "新运行未被接受");
2101
2244
  const operation = await pollOperationToTerminal(accepted.body.operationId, setStatus);
2102
2245
  const childRunId = operation?.result?.newRunId;
2103
2246
  if (childRunId) {
@@ -2115,17 +2258,17 @@ function buildArtifactRevisionSection(dagRunId, node) {
2115
2258
  planHost.appendChild(open);
2116
2259
  }
2117
2260
  if (operation?.state !== "succeeded" || !operation.result?.newRunId) {
2118
- throw new Error(operation?.errorMessage ?? operation?.result?.message ?? "派生运行失败");
2261
+ throw new Error(operation?.errorMessage ?? operation?.result?.message ?? "新运行失败");
2119
2262
  }
2120
- setStatus(`派生运行完成:${operation.result.newRunId}`);
2263
+ setStatus(`新运行完成:${operation.result.newRunId}`);
2121
2264
  } catch (error) {
2122
- setStatus(transferErrorStatusText(error, "派生运行失败"), true);
2265
+ setStatus(transferErrorStatusText(error, "新运行失败"), true);
2123
2266
  } finally {
2124
2267
  execute.disabled = false;
2125
2268
  }
2126
2269
  })();
2127
2270
  });
2128
- setStatus("影响预览已冻结;确认后将重新规划并校验 plan hash。" );
2271
+ setStatus("影响范围已经锁定;确认时系统会再次检查文件、运行和计划是否有变化。" );
2129
2272
  } catch (error) {
2130
2273
  setStatus(transferErrorStatusText(error, "影响预览失败"), true);
2131
2274
  } finally {
@@ -2149,20 +2292,26 @@ function buildDagActionIntro(node) {
2149
2292
  "重跑会创建新的运行;当前运行及其已经保存的事实始终保持不变。",
2150
2293
  ),
2151
2294
  );
2152
- const state = el("span", "dag-action-node-state", node.status ?? "unknown");
2295
+ const rawState = node.status ?? "unknown";
2296
+ const state = el(
2297
+ "span",
2298
+ "dag-action-node-state",
2299
+ inspectorValueLabel("status", rawState),
2300
+ );
2301
+ if (state.textContent !== rawState) state.setAttribute("title", `原始值:${rawState}`);
2153
2302
  intro.append(copy, state);
2154
2303
  return intro;
2155
2304
  }
2156
2305
 
2157
- async function renderPromptRestart(content, dagRunId, node) {
2306
+ async function renderPromptRestart(content, dagRunId, node, run) {
2158
2307
  const nodeId = node.nodeId;
2159
2308
  clearNode(content);
2160
2309
  const intro = buildDagActionIntro(node);
2161
- const revisionSection = buildArtifactRevisionSection(dagRunId, node);
2310
+ const revisionSection = buildArtifactRevisionSection(dagRunId, node, run);
2162
2311
  const { section: promptSection, body: promptBody } = createDagActionCard({
2163
2312
  eyebrow: "提示词重跑",
2164
2313
  title: "修改失败提示后重新执行",
2165
- description: "仅失败节点可编辑提示词并从当前节点继续;其他节点只提供冻结提示查看。",
2314
+ description: "只有执行失败的节点可以修改提示词并从这里继续;其他节点只能查看运行开始前保存的提示词。",
2166
2315
  icon: "ri-restart-line",
2167
2316
  className: "prompt-rerun-action-box",
2168
2317
  });
@@ -2170,7 +2319,7 @@ async function renderPromptRestart(content, dagRunId, node) {
2170
2319
  promptBody.appendChild(
2171
2320
  buildDagActionAvailability({
2172
2321
  title: "正在读取节点提示",
2173
- description: "检查冻结版本和历史失败候选。",
2322
+ description: "检查运行开始前保存的版本,以及每次失败时实际使用的版本。",
2174
2323
  }),
2175
2324
  );
2176
2325
  const exportSection = buildContextExportSection(dagRunId, node);
@@ -2199,7 +2348,7 @@ async function renderPromptRestart(content, dagRunId, node) {
2199
2348
  promptBody.appendChild(
2200
2349
  buildDagActionAvailability({
2201
2350
  tone: "blocked",
2202
- title: "无法读取这个节点的冻结提示",
2351
+ title: "无法读取这个节点原先保存的提示词",
2203
2352
  description: "当前不能安全预览或修改提示词。可以继续使用最下方的上下文导出。",
2204
2353
  }),
2205
2354
  );
@@ -2210,7 +2359,7 @@ async function renderPromptRestart(content, dagRunId, node) {
2210
2359
  if (!frozenBasePrompt) {
2211
2360
  promptBody.appendChild(
2212
2361
  buildDagActionAvailability({
2213
- title: "这个节点没有冻结提示",
2362
+ title: "这个节点没有保存原始提示词",
2214
2363
  description: "因此不能从这里修改提示词重跑。",
2215
2364
  }),
2216
2365
  );
@@ -2236,12 +2385,12 @@ async function renderPromptRestart(content, dagRunId, node) {
2236
2385
  createPromptRestartBanner({
2237
2386
  tone: "readonly",
2238
2387
  kicker: "只读",
2239
- title: `当前节点 ${node.status ?? "unknown"}`,
2240
- body: "仅失败(ERROR)节点支持编辑提示词并重启。以下可只读查看冻结提示词与历史失败候选。",
2388
+ title: `当前节点:${inspectorValueLabel("status", node.status ?? "unknown")}`,
2389
+ body: "只有执行失败的节点支持编辑提示词并重启。下面可以查看原始提示词,以及历次失败时实际使用的提示词。",
2241
2390
  }),
2242
2391
  );
2243
2392
  const promptCard = createPromptRestartPromptCard({
2244
- title: "冻结原始提示词",
2393
+ title: "运行开始前保存的提示词",
2245
2394
  badge: "只读",
2246
2395
  readCandidate: () => selectedCandidate,
2247
2396
  preTestId: "prompt-restart-readonly",
@@ -2253,9 +2402,9 @@ async function renderPromptRestart(content, dagRunId, node) {
2253
2402
  hint:
2254
2403
  promptCandidateOptions.length === 1
2255
2404
  ? historicalCandidatesMissing
2256
- ? "该历史运行未保存失败 attempt 的实际提示词;新运行会在此列出。"
2257
- : "该节点没有已保存的失败 attempt。"
2258
- : "可切换查看冻结底稿与历史失败候选。",
2405
+ ? "这个较早的运行没有保存失败时实际使用的提示词;新运行会在这里列出。"
2406
+ : "该节点没有保存过失败时使用的提示词。"
2407
+ : "可以切换查看原始版本和历次失败时使用的版本。",
2259
2408
  onChange: (nextKey) => {
2260
2409
  selectedCandidate =
2261
2410
  promptCandidateOptions.find((option) => option.key === nextKey) ??
@@ -2273,7 +2422,7 @@ async function renderPromptRestart(content, dagRunId, node) {
2273
2422
  promptBody.appendChild(
2274
2423
  buildDagActionAvailability({
2275
2424
  tone: "blocked",
2276
- title: "冻结提示不完整,无法安全编辑",
2425
+ title: "保存的提示词不完整,无法安全编辑",
2277
2426
  description:
2278
2427
  "为避免用截断文本覆盖完整提示,请改用 CLI:dag rerun --prompt-override。",
2279
2428
  }),
@@ -2287,11 +2436,11 @@ async function renderPromptRestart(content, dagRunId, node) {
2287
2436
  tone: "edit",
2288
2437
  kicker: "可编辑",
2289
2438
  title: "从失败节点编辑提示词并重启",
2290
- body: "父 run spec 保持冻结,将创建带修改提示词的续跑 run。",
2439
+ body: "原运行的设置和记录保持不变;系统会另建一次使用新提示词的运行。",
2291
2440
  }),
2292
2441
  );
2293
2442
  const promptCard = createPromptRestartPromptCard({
2294
- title: `冻结提示词(只读底稿 · ${nodeId})`,
2443
+ title: `原始提示词(只读 · ${nodeId})`,
2295
2444
  badge: "底稿",
2296
2445
  readCandidate: () => selectedCandidate,
2297
2446
  preTestId: "prompt-restart-base",
@@ -2304,10 +2453,10 @@ async function renderPromptRestart(content, dagRunId, node) {
2304
2453
  label: "提示词版本",
2305
2454
  hint:
2306
2455
  promptCandidateOptions.length > 1
2307
- ? "选择失败候选会载入全文替换;冻结原始提示词仍可使用追加模式。"
2456
+ ? "选择某次失败使用的版本后,会载入完整文本供你修改;原始提示词仍可选择只追加补充说明。"
2308
2457
  : historicalCandidatesMissing
2309
- ? "该历史运行未保存失败 attempt 的实际提示词;新运行会在此列出。"
2310
- : "该节点没有已保存的失败 attempt;当前仅有冻结原始提示词。",
2458
+ ? "这个较早的运行没有保存失败时实际使用的提示词;新运行会在这里列出。"
2459
+ : "该节点没有保存过失败时使用的提示词;当前只有原始提示词。",
2311
2460
  onChange: (nextKey) => {
2312
2461
  selectedCandidate =
2313
2462
  promptCandidateOptions.find((option) => option.key === nextKey) ??
@@ -2321,8 +2470,8 @@ async function renderPromptRestart(content, dagRunId, node) {
2321
2470
  picker.hintEl.textContent = selectedCandidate.truncated
2322
2471
  ? "该候选已截断,仅供查看,不能直接提交重启。"
2323
2472
  : selectedCandidate.key === "frozen"
2324
- ? "冻结原始提示词可使用追加或全文替换。"
2325
- : "已载入该失败候选;可在全文替换编辑器中修订后重启。";
2473
+ ? "原始提示词可以选择追加说明,也可以全文替换。"
2474
+ : "已载入这次失败时使用的提示词;修改完整文本后即可重启。";
2326
2475
  applyModeVisibility();
2327
2476
  },
2328
2477
  });
@@ -2344,7 +2493,7 @@ async function renderPromptRestart(content, dagRunId, node) {
2344
2493
  el(
2345
2494
  "p",
2346
2495
  "prompt-restart-hint",
2347
- "将包裹为 <operator_addendum> 块附加到原提示词末尾",
2496
+ "这段补充会加在原提示词末尾,不会改写原文(内部记录为 operator_addendum)。",
2348
2497
  ),
2349
2498
  );
2350
2499
  const addendumArea = document.createElement("textarea");
@@ -2379,14 +2528,14 @@ async function renderPromptRestart(content, dagRunId, node) {
2379
2528
  applyModeVisibility();
2380
2529
 
2381
2530
  const reasonLabel = el("label", "prompt-restart-field");
2382
- reasonLabel.appendChild(el("span", "prompt-restart-label", "重启原因"));
2531
+ reasonLabel.appendChild(el("span", "prompt-restart-label", "为什么要重启?"));
2383
2532
  reasonLabel.appendChild(
2384
- el("p", "prompt-restart-hint", "结构化说明,会写入续跑 provenance"),
2533
+ el("p", "prompt-restart-hint", "这段说明会随新运行一起保存,方便以后追溯。"),
2385
2534
  );
2386
2535
  const reasonArea = document.createElement("textarea");
2387
2536
  reasonArea.className = "prompt-restart-textarea prompt-restart-textarea-compact";
2388
2537
  reasonArea.rows = 3;
2389
- reasonArea.value = `operator-prompt-override / run=${dagRunId} / node=${nodeId} / 手工编辑失败节点提示词后重启`;
2538
+ reasonArea.value = `修改失败节点 ${nodeId} 的提示词后重新运行(来源运行:${dagRunId})`;
2390
2539
  reasonArea.setAttribute("data-testid", "prompt-restart-reason");
2391
2540
  reasonLabel.appendChild(reasonArea);
2392
2541
  box.appendChild(reasonLabel);
@@ -2398,7 +2547,7 @@ async function renderPromptRestart(content, dagRunId, node) {
2398
2547
  const submit = document.createElement("button");
2399
2548
  submit.type = "button";
2400
2549
  submit.className = "prompt-restart-submit";
2401
- submit.textContent = "资格预检并重启";
2550
+ submit.textContent = "检查是否可重跑并开始";
2402
2551
  submit.setAttribute("data-testid", "prompt-restart-submit");
2403
2552
  row.appendChild(status);
2404
2553
  row.appendChild(submit);
@@ -2441,7 +2590,7 @@ async function renderPromptRestart(content, dagRunId, node) {
2441
2590
  }
2442
2591
  submit.disabled = true;
2443
2592
  try {
2444
- setStatus("正在做重跑资格预检(dagRerunPlan)…");
2593
+ setStatus("正在检查这次重跑是否安全可行…");
2445
2594
  const overrides = [{ nodeId, prompt, mode }];
2446
2595
  const planRes = await postOperatorAction("dagRerunPlan", {
2447
2596
  runId: dagRunId,
@@ -2461,9 +2610,9 @@ async function renderPromptRestart(content, dagRunId, node) {
2461
2610
 
2462
2611
  const est = plan.estimatedExecution;
2463
2612
  const estText = est
2464
- ? `(将执行 ${est.nodeCount ?? "?"} 个节点 / ${est.piCallCount ?? "?"} 次 Pi 调用)`
2613
+ ? `(将执行 ${est.nodeCount ?? "?"} 个节点,其中 ${est.piCallCount ?? "?"} 个需要 AI 处理)`
2465
2614
  : "";
2466
- setStatus(`预检通过${estText},正在提交续跑(dagRerun)…`);
2615
+ setStatus(`检查通过${estText},正在创建新运行…`);
2467
2616
  const clientRequestId = `inspect-prompt-restart-${Date.now()}-${Math.random()
2468
2617
  .toString(36)
2469
2618
  .slice(2, 10)}`;
@@ -2491,21 +2640,21 @@ async function renderPromptRestart(content, dagRunId, node) {
2491
2640
  const newRunId =
2492
2641
  op.dagRunId ?? op.result?.newRunId ?? op.runSummary?.runId ?? null;
2493
2642
  if (newRunId) {
2494
- setStatus(`重启完成,新 run:${newRunId}`);
2643
+ setStatus(`重启完成,新运行:${newRunId}`);
2495
2644
  const open = document.createElement("button");
2496
2645
  open.type = "button";
2497
2646
  open.className = "prompt-restart-submit is-secondary";
2498
- open.textContent = "打开新 run";
2647
+ open.textContent = "打开新运行";
2499
2648
  open.addEventListener("click", () => {
2500
2649
  navigate(`/dag/${encodeURIComponent(String(newRunId))}`);
2501
2650
  });
2502
2651
  row.appendChild(open);
2503
2652
  } else {
2504
- setStatus("重启完成(未能解析新 run id,请在 DAG 历史中查看)。");
2653
+ setStatus("重启完成(暂时无法取得新运行编号,请在 DAG 历史中查看)。");
2505
2654
  }
2506
2655
  } else if (op) {
2507
2656
  setStatus(
2508
- `续跑操作终态:${op.state}${op.errorMessage ? ` — ${op.errorMessage}` : ""}`,
2657
+ `新运行最终状态:${inspectorValueLabel("status", op.state)}${op.errorMessage ? ` — ${op.errorMessage}` : ""}`,
2509
2658
  true,
2510
2659
  );
2511
2660
  } else {
@@ -2552,8 +2701,9 @@ function transparencyMetaList(rows) {
2552
2701
 
2553
2702
  function renderNodeOverview(content, dagRunId, node) {
2554
2703
  const root = el("div", "node-transparency-root");
2704
+ root.appendChild(buildInspectorPageLead("overview"));
2555
2705
  root.appendChild(
2556
- el("p", "node-input-semantics", "本页只展示当前运行已持久化的节点事实。"),
2706
+ el("p", "node-input-semantics", "这里展示系统为当前运行保存的真实记录;字段已转换为易懂名称,悬停可查看原始技术字段。"),
2557
2707
  );
2558
2708
  const summary = el("section", "node-input-section");
2559
2709
  summary.appendChild(el("h4", null, "节点事实"));
@@ -2571,12 +2721,12 @@ function renderNodeOverview(content, dagRunId, node) {
2571
2721
  );
2572
2722
  root.appendChild(summary);
2573
2723
  const semantics = el("section", "node-input-section");
2574
- semantics.appendChild(el("h4", null, "事实阶段"));
2724
+ semantics.appendChild(el("h4", null, "这些信息分别在什么时候产生"));
2575
2725
  semantics.appendChild(
2576
2726
  el(
2577
2727
  "p",
2578
2728
  "muted",
2579
- "输入页是 frozen declaration;有效上下文页是 dispatch-time receipt;产物页是执行后 materialization。缺失阶段会明确标记,不用预览冒充事实。",
2729
+ "“执行设置”是运行开始前保存的配置;“实际上下文”是下发任务时真正交给节点的内容;“产出”是执行结束后保存的文件。缺少哪个阶段会明确显示,预览内容不会被当成已经发生的事实。",
2580
2730
  ),
2581
2731
  );
2582
2732
  root.appendChild(semantics);
@@ -2591,6 +2741,7 @@ function selectedAttemptNumber(dagRunId, node) {
2591
2741
  }
2592
2742
 
2593
2743
  async function renderEffectiveContext(content, dagRunId, node) {
2744
+ ensureInspectorPageLead(content, "context");
2594
2745
  content.appendChild(el("p", "muted", "正在加载有效上下文事实…"));
2595
2746
  const result = await fetchDagNodeContext(
2596
2747
  dagRunId,
@@ -2605,6 +2756,7 @@ async function renderEffectiveContext(content, dagRunId, node) {
2605
2756
  )
2606
2757
  return;
2607
2758
  clearNode(content);
2759
+ ensureInspectorPageLead(content, "context");
2608
2760
  if (!result.ok || !result.body) {
2609
2761
  const error = el(
2610
2762
  "p",
@@ -2628,33 +2780,29 @@ async function renderEffectiveContext(content, dagRunId, node) {
2628
2780
  );
2629
2781
  if (receipt?.status !== "captured") {
2630
2782
  const missing = el("div", "node-transparency-omission");
2783
+ const reasonCode = receipt?.reasonCode ?? "CONTEXT_RECEIPT_MISSING";
2784
+ const missingLabel = contextTransferReasonLabel(reasonCode);
2631
2785
  missing.appendChild(
2632
2786
  el(
2633
2787
  "h4",
2634
2788
  null,
2635
- receipt?.status === "invalid" ? "Receipt 不可用" : "未捕获",
2789
+ receipt?.status === "invalid" ? "执行时记录不可用" : "没有执行时记录",
2636
2790
  ),
2637
2791
  );
2638
- missing.appendChild(
2639
- el("code", null, receipt?.reasonCode ?? "CONTEXT_RECEIPT_MISSING"),
2640
- );
2641
- const missingLabel = contextTransferReasonLabel(
2642
- receipt?.reasonCode ?? "CONTEXT_RECEIPT_MISSING",
2643
- );
2644
- if (missingLabel) {
2645
- missing.appendChild(el("p", "muted", missingLabel));
2646
- }
2792
+ const reason = el("p", "muted", missingLabel ?? reasonCode);
2793
+ if (missingLabel) reason.setAttribute("title", `技术代码:${reasonCode}`);
2794
+ missing.appendChild(reason);
2647
2795
  missing.appendChild(
2648
2796
  el(
2649
2797
  "p",
2650
2798
  "muted",
2651
- "当前只有 frozen input / prompt 指纹;它们不是 Effective Context。历史 run 不做事后回填。",
2799
+ "当前只有运行前设置和提示内容校验码,无法证明执行时真正收到了哪些内容。较早的运行不会事后补写这份记录。",
2652
2800
  ),
2653
2801
  );
2654
2802
  root.appendChild(missing);
2655
2803
  } else {
2656
2804
  const captured = el("section", "node-input-section");
2657
- captured.appendChild(el("h4", null, "Captured receipt"));
2805
+ captured.appendChild(el("h4", null, "这次执行实际收到的内容"));
2658
2806
  captured.appendChild(
2659
2807
  transparencyMetaList([
2660
2808
  ["context digest", receipt.contextDigest],
@@ -2690,21 +2838,20 @@ async function renderEffectiveContext(content, dagRunId, node) {
2690
2838
 
2691
2839
  function artifactInventorySection(data, dagRunId, nodeId) {
2692
2840
  const root = el("section", "node-input-section node-artifact-inventory");
2693
- root.appendChild(el("h4", null, "声明产物与消费关系"));
2841
+ root.appendChild(el("h4", null, "本节点生成和使用的文件"));
2694
2842
  if (!data?.binding?.available) {
2695
2843
  const warning = el("div", "node-transparency-omission");
2696
2844
  const bindingCode =
2697
2845
  data?.binding?.reasonCode ?? "ARTIFACT_BINDING_UNAVAILABLE";
2698
- warning.appendChild(el("code", null, bindingCode));
2699
2846
  const bindingLabel = contextTransferReasonLabel(bindingCode);
2700
- if (bindingLabel) {
2701
- warning.appendChild(el("p", "muted", bindingLabel));
2702
- }
2847
+ const reason = el("p", "muted", bindingLabel ?? bindingCode);
2848
+ if (bindingLabel) reason.setAttribute("title", `技术代码:${bindingCode}`);
2849
+ warning.appendChild(reason);
2703
2850
  warning.appendChild(
2704
2851
  el(
2705
2852
  "p",
2706
2853
  "muted",
2707
- "该历史 run 没有 frozen Artifact Binding;不会从 prompt、outputContract、路径或文件名推断 consumer。",
2854
+ "这个较早的运行没有保存明确的文件使用关系;系统不会根据提示词、输出要求或文件名猜测谁会使用它。",
2708
2855
  ),
2709
2856
  );
2710
2857
  root.appendChild(warning);
@@ -2742,7 +2889,7 @@ function artifactInventorySection(data, dagRunId, nodeId) {
2742
2889
  );
2743
2890
  appendCodeList(
2744
2891
  card,
2745
- "Direct consumers",
2892
+ "会直接使用此文件的节点",
2746
2893
  artifact.directConsumerNodeIds ?? [],
2747
2894
  );
2748
2895
  if (artifact.materialization?.status === "materialized") {
@@ -2756,7 +2903,7 @@ function artifactInventorySection(data, dagRunId, nodeId) {
2756
2903
  }
2757
2904
  for (const artifact of data.consumes ?? []) {
2758
2905
  const card = el("div", "node-artifact-card is-consumed");
2759
- card.appendChild(el("h5", null, `消费 ${artifact.artifactId}`));
2906
+ card.appendChild(el("h5", null, `使用 ${artifact.artifactId}`));
2760
2907
  card.appendChild(
2761
2908
  transparencyMetaList([
2762
2909
  ["producer", artifact.producerNodeId],
@@ -2775,7 +2922,7 @@ function artifactInventorySection(data, dagRunId, nodeId) {
2775
2922
  (data.produces ?? []).length === 0 &&
2776
2923
  (data.consumes ?? []).length === 0
2777
2924
  ) {
2778
- root.appendChild(el("p", "empty", "该节点没有声明的产物绑定。"));
2925
+ root.appendChild(el("p", "empty", "该节点没有登记会生成或使用的文件。"));
2779
2926
  }
2780
2927
  return root;
2781
2928
  }
@@ -2797,10 +2944,12 @@ async function prependArtifactInventory(content, dagRunId, node) {
2797
2944
  "node-input-error node-artifact-inventory",
2798
2945
  result.body?.error || "无法加载产物绑定事实。",
2799
2946
  );
2800
- content.insertBefore(section, content.firstChild);
2947
+ const lead = content.querySelector(".inspector-page-lead");
2948
+ content.insertBefore(section, lead?.nextSibling ?? content.firstChild);
2801
2949
  }
2802
2950
 
2803
2951
  async function renderRunLineage(content, dagRunId, node) {
2952
+ ensureInspectorPageLead(content, "lineage");
2804
2953
  content.appendChild(el("p", "muted", "正在加载来源与影响关系…"));
2805
2954
  const result = await fetchDagRunLineage(dagRunId);
2806
2955
  if (
@@ -2811,6 +2960,7 @@ async function renderRunLineage(content, dagRunId, node) {
2811
2960
  )
2812
2961
  return;
2813
2962
  clearNode(content);
2963
+ ensureInspectorPageLead(content, "lineage");
2814
2964
  if (!result.ok || !result.body) {
2815
2965
  content.appendChild(
2816
2966
  el("p", "node-input-error", result.body?.error || "无法加载来源与影响关系。"),
@@ -2819,21 +2969,11 @@ async function renderRunLineage(content, dagRunId, node) {
2819
2969
  }
2820
2970
  const data = result.body;
2821
2971
  const root = el("div", "node-transparency-root");
2822
- const intro = el("section", "node-lineage-intro");
2823
- intro.append(
2824
- el("h4", null, "这个节点从哪里来,会影响哪里"),
2825
- el(
2826
- "p",
2827
- "muted",
2828
- "这里展示派生运行继承、替换或重新执行了哪些节点。产出文件的直接使用者请到“产出”中查看。",
2829
- ),
2830
- );
2831
- root.appendChild(intro);
2832
2972
  root.appendChild(
2833
2973
  transparencyMetaList([
2834
2974
  ["运行类型", data.kind === "root" ? "首次运行" : "派生运行"],
2835
2975
  ["来源运行", data.parentRunId ?? null],
2836
- ["发起方式", data.trigger ?? null],
2976
+ ["发起方式", inspectorValueLabel("发起方式", data.trigger)],
2837
2977
  ["人工选择节点", data.operatorSelectedNodeId ?? null],
2838
2978
  ["从此节点重新执行", data.effectiveFromNodeId ?? null],
2839
2979
  ["重跑计划哈希", data.planHash ?? null],
@@ -3246,7 +3386,7 @@ function renderCachedOrPreviewBody(content, dagRunId, node, key, isLatest) {
3246
3386
  }
3247
3387
  if (cached && cached.ok === false) {
3248
3388
  const message =
3249
- cached.error || "该次执行输出暂不可用(artifact 缺失或无法读取)。";
3389
+ cached.error || "该次执行输出暂不可用(记录文件缺失或无法读取)。";
3250
3390
  const error = el("p", "empty", message);
3251
3391
  error.setAttribute("role", "status");
3252
3392
  content.appendChild(error);
@@ -3290,7 +3430,7 @@ async function loadAndRenderExecutionOutput(
3290
3430
  (result.status === 0
3291
3431
  ? "加载执行输出失败(网络错误)。"
3292
3432
  : result.status === 404
3293
- ? "该次执行输出暂不可用(artifact 缺失或无法读取)。"
3433
+ ? "该次执行输出暂不可用(记录文件缺失或无法读取)。"
3294
3434
  : `加载执行输出失败(HTTP ${result.status})。`);
3295
3435
  // Cache the miss for this key only — never wipe other valid bodies.
3296
3436
  setDagNodeExecutionOutputCache(dagRunId, nodeId, key, {
@@ -3328,7 +3468,8 @@ function repaintOutputTabBody(content, dagRunId, node) {
3328
3468
  // Remove everything after the attempt bar (or all if no bar).
3329
3469
  const inventory = content.querySelector(".node-artifact-inventory");
3330
3470
  const bar = content.querySelector(".dag-inspector-attempt-bar");
3331
- const keep = [inventory, bar].filter(Boolean);
3471
+ const lead = content.querySelector(".inspector-page-lead");
3472
+ const keep = [lead, inventory, bar].filter(Boolean);
3332
3473
  while (content.firstChild) content.removeChild(content.firstChild);
3333
3474
  for (const nodeEl of keep) content.appendChild(nodeEl);
3334
3475
 
@@ -3394,7 +3535,7 @@ function renderProcessCommandCard(command) {
3394
3535
  details.appendChild(summary);
3395
3536
  const body = el("div", "process-timeline-card-body");
3396
3537
  const meta = [
3397
- command.exitCode != null ? `exit ${command.exitCode}` : null,
3538
+ command.exitCode != null ? `退出码 ${command.exitCode}` : null,
3398
3539
  command.durationMs != null ? `耗时 ${formatMs(command.durationMs)}` : null,
3399
3540
  command.failureCategory ? command.failureCategory : null,
3400
3541
  ].filter(Boolean);
@@ -3404,11 +3545,11 @@ function renderProcessCommandCard(command) {
3404
3545
  );
3405
3546
  }
3406
3547
  if (command.stdout) {
3407
- body.appendChild(el("div", "process-timeline-section-label", "stdout"));
3548
+ body.appendChild(el("div", "process-timeline-section-label", "正常输出"));
3408
3549
  appendProcessPre(body, command.stdout);
3409
3550
  }
3410
3551
  if (command.stderr) {
3411
- body.appendChild(el("div", "process-timeline-section-label", "stderr"));
3552
+ body.appendChild(el("div", "process-timeline-section-label", "错误输出"));
3412
3553
  appendProcessPre(body, command.stderr);
3413
3554
  }
3414
3555
  if (command.truncated) {
@@ -3619,7 +3760,7 @@ function renderExecutorAwareTimeline(content, dagRunId, node) {
3619
3760
  });
3620
3761
  }
3621
3762
 
3622
- function fillDagInspectorContent(content, dagRunId, node) {
3763
+ function fillDagInspectorContent(content, dagRunId, node, run) {
3623
3764
  const previousRenderedNode = content.dataset.dagNodeId ?? "";
3624
3765
  const previousRenderedRun = content.dataset.dagRunId ?? "";
3625
3766
  const previousTab = content.dataset.inspectorTab ?? "";
@@ -3657,6 +3798,9 @@ function fillDagInspectorContent(content, dagRunId, node) {
3657
3798
  content.dataset.inspectorTab = activeTab;
3658
3799
  // Drop timeline-only classes when switching tabs so output layout stays clean.
3659
3800
  content.className = "dag-inspector-content";
3801
+ if (activeTab !== "prompt-restart") {
3802
+ content.classList.add("is-readable-page");
3803
+ }
3660
3804
  ensureInspectorContentScrollCapture(
3661
3805
  content,
3662
3806
  dagRunId,
@@ -3670,10 +3814,12 @@ function fillDagInspectorContent(content, dagRunId, node) {
3670
3814
  } else if (activeTab === "lineage") {
3671
3815
  void renderRunLineage(content, dagRunId, node);
3672
3816
  } else if (activeTab === "spec-evidence") {
3817
+ ensureInspectorPageLead(content, "spec-evidence");
3673
3818
  void renderSpecEvidence(content, dagRunId, node.nodeId);
3674
3819
  } else if (activeTab === "prompt-restart") {
3675
- void renderPromptRestart(content, dagRunId, node);
3820
+ void renderPromptRestart(content, dagRunId, node, run);
3676
3821
  } else if (activeTab === "timeline") {
3822
+ ensureInspectorPageLead(content, "timeline");
3677
3823
  const attemptBuilt = buildAttemptBar(dagRunId, node);
3678
3824
  if (attemptBuilt?.bar) content.appendChild(attemptBuilt.bar);
3679
3825
  renderExecutorAwareTimeline(content, dagRunId, node);
@@ -3684,6 +3830,7 @@ function fillDagInspectorContent(content, dagRunId, node) {
3684
3830
  void renderDagNodeInput(content, dagRunId, node);
3685
3831
  } else {
3686
3832
  // output tab: attempt bar + latest preview immediate / historical async body.
3833
+ ensureInspectorPageLead(content, "output");
3687
3834
  const attemptBuilt = buildAttemptBar(dagRunId, node);
3688
3835
  if (attemptBuilt?.bar) content.appendChild(attemptBuilt.bar);
3689
3836
  const key = effectiveExecutionKey(dagRunId, node);
@@ -3884,12 +4031,13 @@ function dagInspectorSignature(dagRunId, node) {
3884
4031
  ]);
3885
4032
  }
3886
4033
 
3887
- export function renderDagInspector(dagRunId, node, initial = true) {
4034
+ export function renderDagInspector(dagRunId, node, initial = true, run = null) {
3888
4035
  // Record the current selection before any early-return so tab handlers
3889
4036
  // (which reference the mutable inspectorSelection slot) always re-render
3890
4037
  // the currently selected node, never a stale build-time closure.
3891
4038
  inspectorSelection.dagRunId = dagRunId;
3892
4039
  inspectorSelection.node = node;
4040
+ inspectorSelection.run = run;
3893
4041
  const panel = document.getElementById("dag-inspector");
3894
4042
  if (!panel) return;
3895
4043
  captureDagTimelineViewportState(panel);
@@ -3939,7 +4087,7 @@ export function renderDagInspector(dagRunId, node, initial = true) {
3939
4087
  scrollTop: pre.scrollTop,
3940
4088
  }));
3941
4089
  clearNode(existingContent);
3942
- fillDagInspectorContent(existingContent, dagRunId, node);
4090
+ fillDagInspectorContent(existingContent, dagRunId, node, run);
3943
4091
  // Async tabs restore themselves after fetch; sync tabs restore here.
3944
4092
  if (activeTab === "output" || activeTab === "timeline") {
3945
4093
  restoreInspectorContentScroll(
@@ -3970,7 +4118,7 @@ export function renderDagInspector(dagRunId, node, initial = true) {
3970
4118
  panel.appendChild(buildDagInspectorHeader(dagRunId, node));
3971
4119
 
3972
4120
  const content = el("div", "dag-inspector-content");
3973
- fillDagInspectorContent(content, dagRunId, node);
4121
+ fillDagInspectorContent(content, dagRunId, node, run);
3974
4122
  panel.appendChild(content);
3975
4123
  panel.dataset.contentSignature = nextContentSignature;
3976
4124
  }