@tea-agent/loop-agent 0.40.0-next.2 → 0.40.0-next.3
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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +8 -2
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +129 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +3 -0
- package/dist/worker/console/server.js +73 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DLMldIE8.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DVOg1dSW.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-715v7joo.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BtMEGtAQ.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-qUxXsdxu.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BYMag72f.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5jt5rtx.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DvZghfS2.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CXutxAmY.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BkANIDWk.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bwy1wwlr.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3pWYVCG.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DRzjzmFD.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C4xXDxXL.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BngIZ90b.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-B40ACoFn.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BE-1ZRAI.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BoYbwVTh.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-B5-KA0fK.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DJpCToT5.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-De8R04Ng.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BU1N8DiA.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BUUUzSYI.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-zLoT1RC4.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-_q4-4N8c.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CTqzoNSL.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMKSYKJM.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Dj6U8Sb7.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BfoxVYky.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/{index-CA0nQEbb.js → index-CW88Fjbw.js} +81 -81
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-T5NfvHGV.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BEhUMkut.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-B2Ld6nNt.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-nV9T85yS.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIO1zDYh.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-B_ucAqLq.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BIA3VNcZ.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CNzLjNV3.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-PWytzfc_.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CS0wGoIq.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-C3Wsqph8.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DF6FDTYg.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BdQUJm6y.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-Bv9rppGg.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C3Hpvb_B.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CZOWLpno.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CPQRIZx5.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CkwM9BgA.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-wGSoDzYc.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-GNVas2gx.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-C5USnHw7.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +5 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/static/api.js +3 -3
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/styles.css +338 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +531 -78
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +4 -3
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +125 -4
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +42 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/package.json +1 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-BG6TLen_.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GuyUqs0h.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dgsvn63q.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DaNfG1BJ.js +0 -8
|
@@ -19,8 +19,11 @@ import {
|
|
|
19
19
|
postOperatorAction,
|
|
20
20
|
fetchOperatorOperation,
|
|
21
21
|
} from "../api.js";
|
|
22
|
-
import { badge, formatMs, prettyJsonBlock } from "../format.js";
|
|
22
|
+
import { badge, formatLedgerSummary, formatMs, prettyJsonBlock } from "../format.js";
|
|
23
23
|
import { renderMarkdown, renderInspectorDocument } from "../markdown-render.js";
|
|
24
|
+
import { describeDagNodePurpose } from "../dag-node-purpose.js";
|
|
25
|
+
import { buildPromptRestartCandidateOptions } from "../prompt-restart-candidates.js";
|
|
26
|
+
import { copyText } from "../copy.js";
|
|
24
27
|
import {
|
|
25
28
|
uiState,
|
|
26
29
|
makeSessionEventIdentity,
|
|
@@ -52,6 +55,254 @@ import { createCustomSelect } from "../custom-select.js";
|
|
|
52
55
|
// reference, always resolving the currently selected node identity.
|
|
53
56
|
const inspectorSelection = { dagRunId: null, node: null };
|
|
54
57
|
|
|
58
|
+
function createPromptRestartCopyButton(readCandidate) {
|
|
59
|
+
const buildIcon = (kind) => {
|
|
60
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
61
|
+
svg.setAttribute(
|
|
62
|
+
"class",
|
|
63
|
+
`dags-copy-icon dags-copy-icon-${kind === "check" ? "check" : "rects"}`,
|
|
64
|
+
);
|
|
65
|
+
svg.setAttribute("viewBox", "0 0 16 16");
|
|
66
|
+
svg.setAttribute("width", "16");
|
|
67
|
+
svg.setAttribute("height", "16");
|
|
68
|
+
svg.setAttribute("aria-hidden", "true");
|
|
69
|
+
svg.setAttribute("focusable", "false");
|
|
70
|
+
if (kind === "check") {
|
|
71
|
+
const check = document.createElementNS(
|
|
72
|
+
"http://www.w3.org/2000/svg",
|
|
73
|
+
"path",
|
|
74
|
+
);
|
|
75
|
+
check.setAttribute("d", "M3.5 8.5 6.5 11.5 12.5 4.5");
|
|
76
|
+
check.setAttribute("fill", "none");
|
|
77
|
+
check.setAttribute("stroke", "currentColor");
|
|
78
|
+
check.setAttribute("stroke-width", "1.8");
|
|
79
|
+
check.setAttribute("stroke-linecap", "round");
|
|
80
|
+
check.setAttribute("stroke-linejoin", "round");
|
|
81
|
+
svg.appendChild(check);
|
|
82
|
+
return svg;
|
|
83
|
+
}
|
|
84
|
+
for (const [x, y] of [
|
|
85
|
+
[2, 2],
|
|
86
|
+
[5, 5],
|
|
87
|
+
]) {
|
|
88
|
+
const rect = document.createElementNS(
|
|
89
|
+
"http://www.w3.org/2000/svg",
|
|
90
|
+
"rect",
|
|
91
|
+
);
|
|
92
|
+
rect.setAttribute("x", String(x));
|
|
93
|
+
rect.setAttribute("y", String(y));
|
|
94
|
+
rect.setAttribute("width", "9");
|
|
95
|
+
rect.setAttribute("height", "9");
|
|
96
|
+
rect.setAttribute("rx", "1.5");
|
|
97
|
+
rect.setAttribute("fill", "none");
|
|
98
|
+
rect.setAttribute("stroke", "currentColor");
|
|
99
|
+
rect.setAttribute("stroke-width", "1.5");
|
|
100
|
+
svg.appendChild(rect);
|
|
101
|
+
}
|
|
102
|
+
return svg;
|
|
103
|
+
};
|
|
104
|
+
const button = el(
|
|
105
|
+
"button",
|
|
106
|
+
"dags-copy-btn dags-copy-btn-icon prompt-restart-copy-icon",
|
|
107
|
+
);
|
|
108
|
+
button.type = "button";
|
|
109
|
+
button.setAttribute("data-testid", "prompt-restart-copy");
|
|
110
|
+
button.setAttribute("aria-label", "复制当前提示词候选");
|
|
111
|
+
button.setAttribute("title", "复制当前提示词候选");
|
|
112
|
+
button.appendChild(buildIcon("copy"));
|
|
113
|
+
button.appendChild(buildIcon("check"));
|
|
114
|
+
button.addEventListener("click", (event) => {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
event.stopPropagation();
|
|
117
|
+
const candidate = readCandidate();
|
|
118
|
+
void copyText(candidate?.prompt ?? "").then((ok) => {
|
|
119
|
+
const feedback = ok
|
|
120
|
+
? candidate?.truncated
|
|
121
|
+
? "已复制截断预览"
|
|
122
|
+
: "已复制"
|
|
123
|
+
: "复制失败";
|
|
124
|
+
button.classList.toggle("is-copied", ok);
|
|
125
|
+
button.setAttribute("aria-label", feedback);
|
|
126
|
+
button.setAttribute("title", feedback);
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
button.classList.remove("is-copied");
|
|
129
|
+
button.setAttribute("aria-label", "复制当前提示词候选");
|
|
130
|
+
button.setAttribute("title", "复制当前提示词候选");
|
|
131
|
+
}, 1500);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
return button;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function formatPromptRestartMeta(text) {
|
|
138
|
+
const value = String(text ?? "");
|
|
139
|
+
const chars = [...value].length;
|
|
140
|
+
const lines = value.length === 0 ? 0 : value.split("\n").length;
|
|
141
|
+
return `${lines} 行 · ${chars.toLocaleString("zh-CN")} 字`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createPromptRestartBanner({ tone, kicker, title, body }) {
|
|
145
|
+
const banner = el("div", `prompt-restart-banner is-${tone}`);
|
|
146
|
+
banner.setAttribute("role", "status");
|
|
147
|
+
banner.appendChild(el("span", "prompt-restart-banner-kicker", kicker));
|
|
148
|
+
banner.appendChild(el("p", "prompt-restart-banner-title", title));
|
|
149
|
+
banner.appendChild(el("p", "prompt-restart-banner-body", body));
|
|
150
|
+
return banner;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function createPromptRestartCandidatePicker({
|
|
154
|
+
options,
|
|
155
|
+
value,
|
|
156
|
+
onChange,
|
|
157
|
+
label,
|
|
158
|
+
hint,
|
|
159
|
+
}) {
|
|
160
|
+
const field = el("div", "prompt-restart-field");
|
|
161
|
+
field.appendChild(el("span", "prompt-restart-label", label));
|
|
162
|
+
const select = createCustomSelect({
|
|
163
|
+
ariaLabel: label,
|
|
164
|
+
className: "prompt-restart-candidate-select",
|
|
165
|
+
value,
|
|
166
|
+
disabled: options.length < 2,
|
|
167
|
+
options: options.map((option) => ({
|
|
168
|
+
value: option.key,
|
|
169
|
+
label: option.label,
|
|
170
|
+
})),
|
|
171
|
+
onChange: (nextKey) => {
|
|
172
|
+
onChange(String(nextKey));
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
select.setAttribute("data-testid", "prompt-restart-candidate");
|
|
176
|
+
field.appendChild(select);
|
|
177
|
+
const hintEl = el("p", "prompt-restart-hint", hint ?? "");
|
|
178
|
+
if (!hint) hintEl.hidden = true;
|
|
179
|
+
field.appendChild(hintEl);
|
|
180
|
+
return { field, select, hintEl };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function createPromptRestartModeSwitch({ value = "append", onChange }) {
|
|
184
|
+
const field = el("div", "prompt-restart-field prompt-restart-mode-field");
|
|
185
|
+
field.appendChild(el("span", "prompt-restart-label", "编辑模式"));
|
|
186
|
+
const group = el("div", "prompt-restart-mode-switch");
|
|
187
|
+
group.setAttribute("role", "radiogroup");
|
|
188
|
+
group.setAttribute("aria-label", "编辑模式");
|
|
189
|
+
group.setAttribute("data-testid", "prompt-restart-mode");
|
|
190
|
+
let current = value === "replace" ? "replace" : "append";
|
|
191
|
+
const buttons = [];
|
|
192
|
+
const sync = () => {
|
|
193
|
+
for (const btn of buttons) {
|
|
194
|
+
const selected = btn.dataset.mode === current;
|
|
195
|
+
btn.setAttribute("aria-checked", selected ? "true" : "false");
|
|
196
|
+
btn.classList.toggle("is-selected", selected);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
for (const mode of [
|
|
200
|
+
{ value: "append", label: "追加指令" },
|
|
201
|
+
{ value: "replace", label: "全文替换" },
|
|
202
|
+
]) {
|
|
203
|
+
const btn = document.createElement("button");
|
|
204
|
+
btn.type = "button";
|
|
205
|
+
btn.className = "prompt-restart-mode-option";
|
|
206
|
+
btn.dataset.mode = mode.value;
|
|
207
|
+
btn.setAttribute("role", "radio");
|
|
208
|
+
btn.setAttribute("aria-label", mode.label);
|
|
209
|
+
btn.textContent = mode.label;
|
|
210
|
+
btn.addEventListener("click", () => {
|
|
211
|
+
if (current === mode.value) return;
|
|
212
|
+
current = mode.value;
|
|
213
|
+
sync();
|
|
214
|
+
onChange?.(current);
|
|
215
|
+
});
|
|
216
|
+
buttons.push(btn);
|
|
217
|
+
group.appendChild(btn);
|
|
218
|
+
}
|
|
219
|
+
Object.defineProperty(group, "value", {
|
|
220
|
+
configurable: true,
|
|
221
|
+
enumerable: true,
|
|
222
|
+
get() {
|
|
223
|
+
return current;
|
|
224
|
+
},
|
|
225
|
+
set(next) {
|
|
226
|
+
current = next === "replace" ? "replace" : "append";
|
|
227
|
+
sync();
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
sync();
|
|
231
|
+
field.appendChild(group);
|
|
232
|
+
const hint = el(
|
|
233
|
+
"p",
|
|
234
|
+
"prompt-restart-hint",
|
|
235
|
+
"追加会保留原提示词;全文替换会改写整份底稿。",
|
|
236
|
+
);
|
|
237
|
+
field.appendChild(hint);
|
|
238
|
+
return { field, group, hint };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function createPromptRestartPromptCard({
|
|
242
|
+
title,
|
|
243
|
+
badge,
|
|
244
|
+
readCandidate,
|
|
245
|
+
preTestId,
|
|
246
|
+
collapsible = false,
|
|
247
|
+
startCollapsed = false,
|
|
248
|
+
}) {
|
|
249
|
+
const card = el("div", "prompt-restart-prompt-card");
|
|
250
|
+
const head = el("div", "prompt-restart-prompt-head");
|
|
251
|
+
const titles = el("div", "prompt-restart-prompt-titles");
|
|
252
|
+
const titleEl = el("span", "prompt-restart-prompt-title", title);
|
|
253
|
+
titles.appendChild(titleEl);
|
|
254
|
+
if (badge) {
|
|
255
|
+
titles.appendChild(el("span", "prompt-restart-prompt-badge", badge));
|
|
256
|
+
}
|
|
257
|
+
const metaEl = el("span", "prompt-restart-prompt-meta", "");
|
|
258
|
+
titles.appendChild(metaEl);
|
|
259
|
+
head.appendChild(titles);
|
|
260
|
+
const headActions = el("div", "prompt-restart-prompt-actions");
|
|
261
|
+
let fold = null;
|
|
262
|
+
let collapsed = Boolean(collapsible && startCollapsed);
|
|
263
|
+
const syncFold = () => {
|
|
264
|
+
if (!fold) return;
|
|
265
|
+
card.classList.toggle("is-collapsed", collapsed);
|
|
266
|
+
fold.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
|
267
|
+
fold.textContent = collapsed ? "展开" : "收起";
|
|
268
|
+
};
|
|
269
|
+
if (collapsible) {
|
|
270
|
+
card.classList.add("is-collapsible");
|
|
271
|
+
fold = document.createElement("button");
|
|
272
|
+
fold.type = "button";
|
|
273
|
+
fold.className = "prompt-restart-prompt-fold";
|
|
274
|
+
fold.setAttribute("aria-controls", preTestId);
|
|
275
|
+
fold.addEventListener("click", (event) => {
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
event.stopPropagation();
|
|
278
|
+
collapsed = !collapsed;
|
|
279
|
+
syncFold();
|
|
280
|
+
});
|
|
281
|
+
headActions.appendChild(fold);
|
|
282
|
+
}
|
|
283
|
+
headActions.appendChild(createPromptRestartCopyButton(readCandidate));
|
|
284
|
+
head.appendChild(headActions);
|
|
285
|
+
card.appendChild(head);
|
|
286
|
+
const pre = document.createElement("pre");
|
|
287
|
+
pre.className = "prompt-restart-readonly";
|
|
288
|
+
pre.setAttribute("data-testid", preTestId);
|
|
289
|
+
if (preTestId) pre.id = preTestId;
|
|
290
|
+
card.appendChild(pre);
|
|
291
|
+
const sync = (candidate) => {
|
|
292
|
+
const next = candidate ?? readCandidate?.();
|
|
293
|
+
titleEl.textContent =
|
|
294
|
+
next?.key === "frozen"
|
|
295
|
+
? "冻结原始提示词"
|
|
296
|
+
: (next?.label ?? title);
|
|
297
|
+
pre.textContent = next?.prompt ?? "";
|
|
298
|
+
metaEl.textContent = formatPromptRestartMeta(next?.prompt ?? "");
|
|
299
|
+
if (next?.truncated) metaEl.textContent += " · 已截断";
|
|
300
|
+
};
|
|
301
|
+
sync(readCandidate?.());
|
|
302
|
+
syncFold();
|
|
303
|
+
return { card, pre, titleEl, metaEl, sync };
|
|
304
|
+
}
|
|
305
|
+
|
|
55
306
|
function renderDagInspectorForCurrent() {
|
|
56
307
|
if (!inspectorSelection.dagRunId || !inspectorSelection.node) return;
|
|
57
308
|
renderDagInspector(
|
|
@@ -983,6 +1234,121 @@ async function renderDagNodeInput(content, dagRunId, node) {
|
|
|
983
1234
|
|
|
984
1235
|
renderDagNodeInputDetail(content, data);
|
|
985
1236
|
finishInputRender();
|
|
1237
|
+
// Frontend prewrite 门禁 / source fidelity ledger 展示(AC-009):只读安全摘要,
|
|
1238
|
+
// 从 run 产物(frontend-prewrite-result.json)与 /api/tasks/:id/ledger 拉取;
|
|
1239
|
+
// 接口失败进入安全降级,不泄漏敏感内容。
|
|
1240
|
+
if (/prewrite/.test(nodeId)) {
|
|
1241
|
+
void renderFrontendGateLedger(content, dagRunId);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Frontend prewrite 门禁 + ledger 摘要行(AC-BLOCK-005):纯函数,测试直接断言。
|
|
1247
|
+
* prewriteResult = {ok:true, body}|{ok:false, status}|null(网络失败);ledgerBody 为
|
|
1248
|
+
* /api/tasks/:id/ledger 响应或 null。绝不把 envelope/预览原文塞入 <pre>(删除
|
|
1249
|
+
* 旧 lines.push(previewText) 路径),缺失/拒绝显示明确 fail-closed 文案。
|
|
1250
|
+
*/
|
|
1251
|
+
export function frontendGateLedgerLines(input) {
|
|
1252
|
+
const { prewriteResult, ledgerBody, taskId } = input;
|
|
1253
|
+
const lines = [];
|
|
1254
|
+
if (prewriteResult?.ok && prewriteResult.body) {
|
|
1255
|
+
const parsed = prewriteResult.body;
|
|
1256
|
+
if (parsed?.classification) {
|
|
1257
|
+
lines.push(`classification: ${String(parsed.classification)}`);
|
|
1258
|
+
if (parsed.failureCode) {
|
|
1259
|
+
lines.push(`failureCode: ${String(parsed.failureCode)}`);
|
|
1260
|
+
}
|
|
1261
|
+
if (parsed.failureReason) {
|
|
1262
|
+
lines.push(`failureReason: ${String(parsed.failureReason)}`);
|
|
1263
|
+
}
|
|
1264
|
+
} else {
|
|
1265
|
+
lines.push("frontend-prewrite-result.json 内容无法解析为结构化结果。");
|
|
1266
|
+
}
|
|
1267
|
+
} else if (prewriteResult && prewriteResult.status === 404) {
|
|
1268
|
+
lines.push("frontend-prewrite-result.json 尚未生成(门禁未执行或缺失)。");
|
|
1269
|
+
} else if (prewriteResult) {
|
|
1270
|
+
lines.push(
|
|
1271
|
+
"frontend-prewrite-result.json 读取失败(只读接口拒绝/服务异常,安全降级)。",
|
|
1272
|
+
);
|
|
1273
|
+
} else {
|
|
1274
|
+
lines.push("frontend-prewrite-result.json 尚未生成(门禁未执行或缺失)。");
|
|
1275
|
+
}
|
|
1276
|
+
if (ledgerBody && ledgerBody.present === true) {
|
|
1277
|
+
lines.push(formatLedgerSummary(ledgerBody));
|
|
1278
|
+
} else if (ledgerBody) {
|
|
1279
|
+
lines.push("ledger: 无(任务无引用或 ledger 缺失,不伪造计数)");
|
|
1280
|
+
} else if (taskId) {
|
|
1281
|
+
lines.push("ledger: 暂不可查(只读接口失败,安全降级)");
|
|
1282
|
+
}
|
|
1283
|
+
return lines;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/** DAG run 专属 prewrite-result 只读拉取(内联 fetch,不新增 api.js 依赖)。 */
|
|
1287
|
+
async function fetchPrewriteResultJson(dagRunId) {
|
|
1288
|
+
try {
|
|
1289
|
+
const res = await fetch(
|
|
1290
|
+
`/api/dag-runs/${encodeURIComponent(dagRunId)}/prewrite-result`,
|
|
1291
|
+
{ credentials: "same-origin" },
|
|
1292
|
+
);
|
|
1293
|
+
if (!res.ok) return { ok: false, status: res.status };
|
|
1294
|
+
return { ok: true, body: await res.json() };
|
|
1295
|
+
} catch {
|
|
1296
|
+
return null; // 网络失败
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Resolve the DAG-owned task identity, prewrite result, and safe ledger summary.
|
|
1302
|
+
* Exported so the real browser request sequence can be regression-tested without
|
|
1303
|
+
* relying on source-string assertions.
|
|
1304
|
+
*/
|
|
1305
|
+
export async function fetchFrontendGateLedgerData(dagRunId) {
|
|
1306
|
+
const run = await fetchJson(
|
|
1307
|
+
`/api/dag-runs/${encodeURIComponent(dagRunId)}`,
|
|
1308
|
+
);
|
|
1309
|
+
const taskId = typeof run?.taskId === "string" ? run.taskId : "";
|
|
1310
|
+
const prewriteResult = await fetchPrewriteResultJson(dagRunId);
|
|
1311
|
+
let ledgerBody = null;
|
|
1312
|
+
if (taskId) {
|
|
1313
|
+
ledgerBody = await fetchJson(
|
|
1314
|
+
`/api/tasks/${encodeURIComponent(taskId)}/ledger`,
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
return { taskId, prewriteResult, ledgerBody };
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Inspect 节点面板:frontend prewrite 门禁分类/稳定 failure code + ledger 安全摘要。
|
|
1322
|
+
* 数据全部来自只读接口与确定性产物(AC-BLOCK-005):task identity 从 DAG run
|
|
1323
|
+
* GET /api/dag-runs/:dagRunId 读取,prewrite 门禁调 DAG run 专属
|
|
1324
|
+
* GET /api/dag-runs/:dagRunId/prewrite-result;任何失败仅显示安全降级文案,
|
|
1325
|
+
* 绝不 push envelope。
|
|
1326
|
+
*/
|
|
1327
|
+
async function renderFrontendGateLedger(content, dagRunId) {
|
|
1328
|
+
const section = el("section", "node-input-section");
|
|
1329
|
+
section.appendChild(
|
|
1330
|
+
el("h4", null, "Frontend prewrite 门禁 / source fidelity ledger"),
|
|
1331
|
+
);
|
|
1332
|
+
const status = el("p", "muted", "加载中…");
|
|
1333
|
+
status.setAttribute("role", "status");
|
|
1334
|
+
section.appendChild(status);
|
|
1335
|
+
content.appendChild(section);
|
|
1336
|
+
try {
|
|
1337
|
+
const { taskId, prewriteResult, ledgerBody } =
|
|
1338
|
+
await fetchFrontendGateLedgerData(dagRunId);
|
|
1339
|
+
const lines = frontendGateLedgerLines({
|
|
1340
|
+
prewriteResult,
|
|
1341
|
+
ledgerBody,
|
|
1342
|
+
taskId,
|
|
1343
|
+
});
|
|
1344
|
+
const pre = el("pre", "cmd", lines.join("\n"));
|
|
1345
|
+
pre.dataset.testid = "frontend-prewrite-ledger-summary";
|
|
1346
|
+
status.replaceWith(pre);
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
status.textContent = `加载失败:${
|
|
1349
|
+
error instanceof Error ? error.message : String(error)
|
|
1350
|
+
}(安全降级,不展示敏感内容)`;
|
|
1351
|
+
}
|
|
986
1352
|
}
|
|
987
1353
|
|
|
988
1354
|
/**
|
|
@@ -1017,29 +1383,62 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1017
1383
|
content.appendChild(error);
|
|
1018
1384
|
return;
|
|
1019
1385
|
}
|
|
1020
|
-
const
|
|
1386
|
+
const frozenBasePrompt =
|
|
1021
1387
|
typeof input.body.prompt?.text === "string" ? input.body.prompt.text : "";
|
|
1022
|
-
if (!
|
|
1388
|
+
if (!frozenBasePrompt) {
|
|
1023
1389
|
const error = el("p", "node-input-error", "该节点没有冻结提示词。");
|
|
1024
1390
|
error.setAttribute("role", "alert");
|
|
1025
1391
|
content.appendChild(error);
|
|
1026
1392
|
return;
|
|
1027
1393
|
}
|
|
1394
|
+
const promptCandidateOptions = buildPromptRestartCandidateOptions(
|
|
1395
|
+
frozenBasePrompt,
|
|
1396
|
+
input.body.promptCandidates,
|
|
1397
|
+
);
|
|
1398
|
+
let selectedCandidate = promptCandidateOptions[0];
|
|
1399
|
+
const historicalCandidatesMissing = Array.isArray(input.body.warnings)
|
|
1400
|
+
? input.body.warnings.some((warning) =>
|
|
1401
|
+
String(warning).startsWith("Failed prompt candidate missing for attempt "),
|
|
1402
|
+
)
|
|
1403
|
+
: false;
|
|
1028
1404
|
|
|
1029
1405
|
if (node.status !== "ERROR") {
|
|
1030
1406
|
// Non-failed nodes still show their frozen prompt — read-only.
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1407
|
+
const panel = el("div", "prompt-restart-panel");
|
|
1408
|
+
panel.appendChild(
|
|
1409
|
+
createPromptRestartBanner({
|
|
1410
|
+
tone: "readonly",
|
|
1411
|
+
kicker: "只读",
|
|
1412
|
+
title: `当前节点 ${node.status ?? "unknown"}`,
|
|
1413
|
+
body: "仅失败(ERROR)节点支持编辑提示词并重启。以下可只读查看冻结提示词与历史失败候选。",
|
|
1414
|
+
}),
|
|
1035
1415
|
);
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1416
|
+
const promptCard = createPromptRestartPromptCard({
|
|
1417
|
+
title: "冻结原始提示词",
|
|
1418
|
+
badge: "只读",
|
|
1419
|
+
readCandidate: () => selectedCandidate,
|
|
1420
|
+
preTestId: "prompt-restart-readonly",
|
|
1421
|
+
});
|
|
1422
|
+
const picker = createPromptRestartCandidatePicker({
|
|
1423
|
+
options: promptCandidateOptions,
|
|
1424
|
+
value: selectedCandidate.key,
|
|
1425
|
+
label: "提示词版本(只读)",
|
|
1426
|
+
hint:
|
|
1427
|
+
promptCandidateOptions.length === 1
|
|
1428
|
+
? historicalCandidatesMissing
|
|
1429
|
+
? "该历史运行未保存失败 attempt 的实际提示词;新运行会在此列出。"
|
|
1430
|
+
: "该节点没有已保存的失败 attempt。"
|
|
1431
|
+
: "可切换查看冻结底稿与历史失败候选。",
|
|
1432
|
+
onChange: (nextKey) => {
|
|
1433
|
+
selectedCandidate =
|
|
1434
|
+
promptCandidateOptions.find((option) => option.key === nextKey) ??
|
|
1435
|
+
promptCandidateOptions[0];
|
|
1436
|
+
promptCard.sync(selectedCandidate);
|
|
1437
|
+
},
|
|
1438
|
+
});
|
|
1439
|
+
panel.appendChild(picker.field);
|
|
1440
|
+
panel.appendChild(promptCard.card);
|
|
1441
|
+
content.appendChild(panel);
|
|
1043
1442
|
return;
|
|
1044
1443
|
}
|
|
1045
1444
|
|
|
@@ -1054,103 +1453,129 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1054
1453
|
return;
|
|
1055
1454
|
}
|
|
1056
1455
|
|
|
1057
|
-
const box = el("div", "prompt-restart-box");
|
|
1456
|
+
const box = el("div", "prompt-restart-box prompt-restart-panel is-edit");
|
|
1058
1457
|
box.appendChild(
|
|
1059
|
-
|
|
1060
|
-
"
|
|
1061
|
-
"
|
|
1062
|
-
|
|
1063
|
-
|
|
1458
|
+
createPromptRestartBanner({
|
|
1459
|
+
tone: "edit",
|
|
1460
|
+
kicker: "可编辑",
|
|
1461
|
+
title: "从失败节点编辑提示词并重启",
|
|
1462
|
+
body: "父 run spec 保持冻结,将创建带修改提示词的续跑 run。",
|
|
1463
|
+
}),
|
|
1064
1464
|
);
|
|
1465
|
+
const promptCard = createPromptRestartPromptCard({
|
|
1466
|
+
title: `冻结提示词(只读底稿 · ${nodeId})`,
|
|
1467
|
+
badge: "底稿",
|
|
1468
|
+
readCandidate: () => selectedCandidate,
|
|
1469
|
+
preTestId: "prompt-restart-base",
|
|
1470
|
+
collapsible: true,
|
|
1471
|
+
startCollapsed: true,
|
|
1472
|
+
});
|
|
1473
|
+
const picker = createPromptRestartCandidatePicker({
|
|
1474
|
+
options: promptCandidateOptions,
|
|
1475
|
+
value: selectedCandidate.key,
|
|
1476
|
+
label: "提示词版本",
|
|
1477
|
+
hint:
|
|
1478
|
+
promptCandidateOptions.length > 1
|
|
1479
|
+
? "选择失败候选会载入全文替换;冻结原始提示词仍可使用追加模式。"
|
|
1480
|
+
: historicalCandidatesMissing
|
|
1481
|
+
? "该历史运行未保存失败 attempt 的实际提示词;新运行会在此列出。"
|
|
1482
|
+
: "该节点没有已保存的失败 attempt;当前仅有冻结原始提示词。",
|
|
1483
|
+
onChange: (nextKey) => {
|
|
1484
|
+
selectedCandidate =
|
|
1485
|
+
promptCandidateOptions.find((option) => option.key === nextKey) ??
|
|
1486
|
+
promptCandidateOptions[0];
|
|
1487
|
+
promptCard.sync(selectedCandidate);
|
|
1488
|
+
replaceArea.value = selectedCandidate.prompt;
|
|
1489
|
+
if (selectedCandidate.key !== "frozen") {
|
|
1490
|
+
modeSelect.value = "replace";
|
|
1491
|
+
}
|
|
1492
|
+
picker.hintEl.hidden = false;
|
|
1493
|
+
picker.hintEl.textContent = selectedCandidate.truncated
|
|
1494
|
+
? "该候选已截断,仅供查看,不能直接提交重启。"
|
|
1495
|
+
: selectedCandidate.key === "frozen"
|
|
1496
|
+
? "冻结原始提示词可使用追加或全文替换。"
|
|
1497
|
+
: "已载入该失败候选;可在全文替换编辑器中修订后重启。";
|
|
1498
|
+
applyModeVisibility();
|
|
1499
|
+
},
|
|
1500
|
+
});
|
|
1501
|
+
const modeSwitch = createPromptRestartModeSwitch({
|
|
1502
|
+
value: "append",
|
|
1503
|
+
onChange: () => applyModeVisibility(),
|
|
1504
|
+
});
|
|
1505
|
+
const modeSelect = modeSwitch.group;
|
|
1506
|
+
const toolbar = el("div", "prompt-restart-toolbar");
|
|
1507
|
+
toolbar.appendChild(picker.field);
|
|
1508
|
+
toolbar.appendChild(modeSwitch.field);
|
|
1509
|
+
box.appendChild(toolbar);
|
|
1065
1510
|
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
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})`),
|
|
1511
|
+
const addendumLabel = el("label", "prompt-restart-field prompt-restart-work");
|
|
1512
|
+
addendumLabel.appendChild(
|
|
1513
|
+
el("span", "prompt-restart-label", "追加指令"),
|
|
1087
1514
|
);
|
|
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
1515
|
addendumLabel.appendChild(
|
|
1097
1516
|
el(
|
|
1098
|
-
"
|
|
1099
|
-
"
|
|
1100
|
-
"
|
|
1517
|
+
"p",
|
|
1518
|
+
"prompt-restart-hint",
|
|
1519
|
+
"将包裹为 <operator_addendum> 块附加到原提示词末尾",
|
|
1101
1520
|
),
|
|
1102
1521
|
);
|
|
1103
1522
|
const addendumArea = document.createElement("textarea");
|
|
1104
|
-
addendumArea.className = "prompt-restart-textarea";
|
|
1105
|
-
addendumArea.rows =
|
|
1523
|
+
addendumArea.className = "prompt-restart-textarea prompt-restart-textarea-work";
|
|
1524
|
+
addendumArea.rows = 8;
|
|
1106
1525
|
addendumArea.placeholder = "例如:重点检查 X 边界;输出前先自检 Y 约束…";
|
|
1107
1526
|
addendumArea.setAttribute("data-testid", "prompt-restart-addendum");
|
|
1108
1527
|
addendumLabel.appendChild(addendumArea);
|
|
1109
1528
|
box.appendChild(addendumLabel);
|
|
1110
1529
|
|
|
1111
|
-
const replaceLabel = el("label", "prompt-restart-field");
|
|
1112
|
-
replaceLabel.appendChild(
|
|
1530
|
+
const replaceLabel = el("label", "prompt-restart-field prompt-restart-work");
|
|
1531
|
+
replaceLabel.appendChild(
|
|
1532
|
+
el("span", "prompt-restart-label", `完整提示词(${nodeId})`),
|
|
1533
|
+
);
|
|
1113
1534
|
const replaceArea = document.createElement("textarea");
|
|
1114
|
-
replaceArea.className = "prompt-restart-textarea";
|
|
1115
|
-
replaceArea.rows =
|
|
1116
|
-
replaceArea.value =
|
|
1535
|
+
replaceArea.className = "prompt-restart-textarea prompt-restart-textarea-work";
|
|
1536
|
+
replaceArea.rows = 12;
|
|
1537
|
+
replaceArea.value = selectedCandidate.prompt;
|
|
1117
1538
|
replaceArea.setAttribute("data-testid", "prompt-restart-textarea");
|
|
1118
1539
|
replaceLabel.appendChild(replaceArea);
|
|
1119
1540
|
box.appendChild(replaceLabel);
|
|
1120
1541
|
|
|
1542
|
+
const baseLabel = promptCard.card;
|
|
1543
|
+
box.appendChild(baseLabel);
|
|
1544
|
+
|
|
1121
1545
|
const applyModeVisibility = () => {
|
|
1122
1546
|
const append = modeSelect.value === "append";
|
|
1123
|
-
baseLabel.
|
|
1124
|
-
addendumLabel.
|
|
1125
|
-
replaceLabel.
|
|
1547
|
+
baseLabel.classList.toggle("is-hidden", !append);
|
|
1548
|
+
addendumLabel.classList.toggle("is-hidden", !append);
|
|
1549
|
+
replaceLabel.classList.toggle("is-hidden", append);
|
|
1126
1550
|
};
|
|
1127
|
-
modeSelect.addEventListener("change", applyModeVisibility);
|
|
1128
1551
|
applyModeVisibility();
|
|
1129
1552
|
|
|
1130
1553
|
const reasonLabel = el("label", "prompt-restart-field");
|
|
1131
|
-
reasonLabel.appendChild(el("span", "
|
|
1554
|
+
reasonLabel.appendChild(el("span", "prompt-restart-label", "重启原因"));
|
|
1555
|
+
reasonLabel.appendChild(
|
|
1556
|
+
el("p", "prompt-restart-hint", "结构化说明,会写入续跑 provenance"),
|
|
1557
|
+
);
|
|
1132
1558
|
const reasonArea = document.createElement("textarea");
|
|
1133
|
-
reasonArea.className = "prompt-restart-textarea";
|
|
1559
|
+
reasonArea.className = "prompt-restart-textarea prompt-restart-textarea-compact";
|
|
1134
1560
|
reasonArea.rows = 3;
|
|
1135
1561
|
reasonArea.value = `operator-prompt-override / run=${dagRunId} / node=${nodeId} / 手工编辑失败节点提示词后重启`;
|
|
1136
1562
|
reasonArea.setAttribute("data-testid", "prompt-restart-reason");
|
|
1137
1563
|
reasonLabel.appendChild(reasonArea);
|
|
1138
1564
|
box.appendChild(reasonLabel);
|
|
1139
1565
|
|
|
1140
|
-
const row = el("div", "prompt-restart-actions");
|
|
1566
|
+
const row = el("div", "prompt-restart-actions prompt-restart-footer");
|
|
1567
|
+
const status = el("p", "prompt-restart-status", "");
|
|
1568
|
+
status.setAttribute("role", "status");
|
|
1569
|
+
status.setAttribute("aria-live", "polite");
|
|
1141
1570
|
const submit = document.createElement("button");
|
|
1142
1571
|
submit.type = "button";
|
|
1143
1572
|
submit.className = "prompt-restart-submit";
|
|
1144
1573
|
submit.textContent = "资格预检并重启";
|
|
1145
1574
|
submit.setAttribute("data-testid", "prompt-restart-submit");
|
|
1575
|
+
row.appendChild(status);
|
|
1146
1576
|
row.appendChild(submit);
|
|
1147
1577
|
box.appendChild(row);
|
|
1148
1578
|
|
|
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
1579
|
content.appendChild(box);
|
|
1155
1580
|
|
|
1156
1581
|
const setStatus = (text, isError = false) => {
|
|
@@ -1160,6 +1585,10 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1160
1585
|
|
|
1161
1586
|
submit.addEventListener("click", () => {
|
|
1162
1587
|
void (async () => {
|
|
1588
|
+
if (selectedCandidate.truncated) {
|
|
1589
|
+
setStatus("所选失败提示词已截断,不能作为完整提示词重启。", true);
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1163
1592
|
const mode = modeSelect.value === "replace" ? "replace" : "append";
|
|
1164
1593
|
const prompt =
|
|
1165
1594
|
mode === "append" ? addendumArea.value : replaceArea.value;
|
|
@@ -1171,7 +1600,11 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1171
1600
|
);
|
|
1172
1601
|
return;
|
|
1173
1602
|
}
|
|
1174
|
-
if (
|
|
1603
|
+
if (
|
|
1604
|
+
mode === "replace" &&
|
|
1605
|
+
selectedCandidate.key === "frozen" &&
|
|
1606
|
+
prompt === frozenBasePrompt
|
|
1607
|
+
) {
|
|
1175
1608
|
setStatus("提示词未修改;如需按原提示词重跑请使用恢复工作台。", true);
|
|
1176
1609
|
return;
|
|
1177
1610
|
}
|
|
@@ -1234,7 +1667,7 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1234
1667
|
setStatus(`重启完成,新 run:${newRunId}`);
|
|
1235
1668
|
const open = document.createElement("button");
|
|
1236
1669
|
open.type = "button";
|
|
1237
|
-
open.className = "prompt-restart-submit";
|
|
1670
|
+
open.className = "prompt-restart-submit is-secondary";
|
|
1238
1671
|
open.textContent = "打开新 run";
|
|
1239
1672
|
open.addEventListener("click", () => {
|
|
1240
1673
|
window.location.hash = `#/dag/${encodeURIComponent(String(newRunId))}`;
|
|
@@ -2077,6 +2510,16 @@ function fillDagInspectorContent(content, dagRunId, node) {
|
|
|
2077
2510
|
}
|
|
2078
2511
|
}
|
|
2079
2512
|
|
|
2513
|
+
function inspectorNodePurpose(node) {
|
|
2514
|
+
return describeDagNodePurpose(node?.nodeId, node);
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function appendInspectorPurpose(title, node) {
|
|
2518
|
+
const purpose = inspectorNodePurpose(node);
|
|
2519
|
+
if (!purpose) return;
|
|
2520
|
+
title.appendChild(el("p", "dag-inspector-purpose", purpose));
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2080
2523
|
function buildDagInspectorHeader(dagRunId, node) {
|
|
2081
2524
|
const header = el("div", "dag-inspector-header");
|
|
2082
2525
|
header.dataset.dagRunId = String(dagRunId ?? "");
|
|
@@ -2084,6 +2527,7 @@ function buildDagInspectorHeader(dagRunId, node) {
|
|
|
2084
2527
|
const title = el("div", "dag-inspector-title");
|
|
2085
2528
|
title.appendChild(el("h3", null, "节点检查器"));
|
|
2086
2529
|
title.appendChild(el("div", "dag-inspector-node", node.nodeId));
|
|
2530
|
+
appendInspectorPurpose(title, node);
|
|
2087
2531
|
header.appendChild(title);
|
|
2088
2532
|
const controls = el("div", "dag-inspector-controls");
|
|
2089
2533
|
const status = badge(node.status ?? "unknown");
|
|
@@ -2106,11 +2550,10 @@ function buildDagInspectorHeader(dagRunId, node) {
|
|
|
2106
2550
|
["timeline", "执行过程", "ri-route-line"],
|
|
2107
2551
|
["spec-evidence", "规范证据", "ri-book-open-line"],
|
|
2108
2552
|
];
|
|
2109
|
-
//
|
|
2110
|
-
//
|
|
2111
|
-
//
|
|
2112
|
-
//
|
|
2113
|
-
// strictly read-only.
|
|
2553
|
+
// Prompt-override rerun: the server injects the SCOPED inspect mutation
|
|
2554
|
+
// token into /inspect HTML by default (it only authorizes
|
|
2555
|
+
// dagRerunPlan/dagRerun). The emergency kill switch suppresses the token;
|
|
2556
|
+
// no token → no tab → the inspector stays strictly read-only.
|
|
2114
2557
|
if (globalThis.__INSPECT_MUTATION_TOKEN__) {
|
|
2115
2558
|
tabDefs.push(["prompt-restart", "提示词重启", "ri-restart-line"]);
|
|
2116
2559
|
}
|
|
@@ -2164,6 +2607,16 @@ function syncDagInspectorHeader(panel, dagRunId, node) {
|
|
|
2164
2607
|
header.dataset.dagNodeId = String(node.nodeId ?? "");
|
|
2165
2608
|
const nodeLabel = header.querySelector(".dag-inspector-node");
|
|
2166
2609
|
if (nodeLabel) nodeLabel.textContent = node.nodeId;
|
|
2610
|
+
const title = header.querySelector(".dag-inspector-title");
|
|
2611
|
+
const purposeLine = header.querySelector(".dag-inspector-purpose");
|
|
2612
|
+
const purpose = inspectorNodePurpose(node);
|
|
2613
|
+
if (purposeLine && purpose) {
|
|
2614
|
+
purposeLine.textContent = purpose;
|
|
2615
|
+
} else if (purposeLine && !purpose) {
|
|
2616
|
+
purposeLine.remove();
|
|
2617
|
+
} else if (!purposeLine && purpose && title) {
|
|
2618
|
+
title.appendChild(el("p", "dag-inspector-purpose", purpose));
|
|
2619
|
+
}
|
|
2167
2620
|
const status = header.querySelector(".dag-inspector-status");
|
|
2168
2621
|
if (status) {
|
|
2169
2622
|
const next = badge(node.status ?? "unknown");
|