@tea-agent/loop-agent 0.38.3 → 0.38.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +153 -96
- package/README.md +9 -5
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/executors/dag-pi-executor.js +18 -9
- package/dist/executors/pi-playwright-cli-tool.js +51 -12
- package/dist/executors/shell-executor.js +209 -13
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +32 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/operator-actions.js +116 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +36 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-pm3c2Rsl.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
- package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
- package/dist/worker/console/static/assets/{index-Ya5FE7cD.css → index-C8rOIAJr.css} +1 -1
- package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-DEbyrEfW.js} +71 -71
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-CAyXjQjN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-CcWKn6tS.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/observe/node-input.js +2 -0
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +11 -0
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +561 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +60 -4
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
- package/dist/workflows/dag/failure-category.js +8 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +5 -1
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +151 -11
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +125 -24
- package/dist/workflows/dag/node-execution.js +20 -2
- package/dist/workflows/dag/rerun-plan.js +112 -0
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +91 -0
- package/dist/workflows/dag/types.js +9 -1
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +2 -2
- package/package.json +5 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/playwright-cli/SKILL.md +22 -0
- package/skills/playwright-cli-case-generator/SKILL.md +20 -1
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
3
|
+
import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
|
|
4
|
+
import { parseExecutionOutputKey, } from "./dag-node-execution-output.js";
|
|
5
|
+
import { resolveDagRunArtifact, resolveDagRunJson, } from "./dag-run-artifacts.js";
|
|
6
|
+
import { namedShellHandlers } from "./shell-handler-keys.js";
|
|
7
|
+
export const NODE_PROCESS_SCHEMA_VERSION = 1;
|
|
8
|
+
export const NODE_PROCESS_FIELD_MAX_BYTES = 16 * 1024;
|
|
9
|
+
function boundField(value) {
|
|
10
|
+
if (value == null || value === "")
|
|
11
|
+
return { text: null, truncated: false };
|
|
12
|
+
const redacted = redactSecrets(value);
|
|
13
|
+
const truncated = truncateUtf8Preview(redacted, NODE_PROCESS_FIELD_MAX_BYTES);
|
|
14
|
+
return {
|
|
15
|
+
text: truncated,
|
|
16
|
+
truncated: truncated !== redacted,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function asRecord(value) {
|
|
20
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21
|
+
return null;
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function asString(value) {
|
|
25
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
26
|
+
}
|
|
27
|
+
function asNumber(value) {
|
|
28
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
29
|
+
}
|
|
30
|
+
function asBoolean(value) {
|
|
31
|
+
return value === true;
|
|
32
|
+
}
|
|
33
|
+
async function readJsonArtifact(repoRoot, dagRunId, segments) {
|
|
34
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, segments);
|
|
35
|
+
if (!resolved.ok)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
const raw = await readFile(resolved.result.absolutePath, "utf8");
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
return asRecord(parsed);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function readTextArtifact(repoRoot, dagRunId, segments) {
|
|
47
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, segments);
|
|
48
|
+
if (!resolved.ok)
|
|
49
|
+
return { text: null, truncated: false };
|
|
50
|
+
try {
|
|
51
|
+
const raw = await readFile(resolved.result.absolutePath, "utf8");
|
|
52
|
+
return boundField(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { text: null, truncated: false };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function emptyBody(dagRunId, nodeId, kind, emptyReason, extras = {}) {
|
|
59
|
+
return {
|
|
60
|
+
schemaVersion: NODE_PROCESS_SCHEMA_VERSION,
|
|
61
|
+
dagRunId,
|
|
62
|
+
nodeId,
|
|
63
|
+
kind,
|
|
64
|
+
executor: extras.executor ?? null,
|
|
65
|
+
handler: extras.handler ?? null,
|
|
66
|
+
emptyReason,
|
|
67
|
+
key: extras.key ?? null,
|
|
68
|
+
commands: extras.commands ?? [],
|
|
69
|
+
steps: extras.steps ?? [],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function classifyKind(task) {
|
|
73
|
+
const executor = asString(task.executor);
|
|
74
|
+
const shell = asRecord(task.shell);
|
|
75
|
+
const handlers = namedShellHandlers(shell);
|
|
76
|
+
const handler = handlers[0] ?? null;
|
|
77
|
+
const commands = shell?.commands;
|
|
78
|
+
const commandsDeclared = Array.isArray(commands) &&
|
|
79
|
+
commands.some((item) => typeof item === "string" && item.trim());
|
|
80
|
+
if (executor === "static" || task.static) {
|
|
81
|
+
return { kind: "static", executor: executor ?? "static", handler, commandsDeclared };
|
|
82
|
+
}
|
|
83
|
+
if (executor === "shell" || shell) {
|
|
84
|
+
if (commandsDeclared) {
|
|
85
|
+
return { kind: "shell-commands", executor: executor ?? "shell", handler, commandsDeclared };
|
|
86
|
+
}
|
|
87
|
+
if (handler) {
|
|
88
|
+
return { kind: "shell-preset", executor: executor ?? "shell", handler, commandsDeclared };
|
|
89
|
+
}
|
|
90
|
+
return { kind: "shell-commands", executor: executor ?? "shell", handler, commandsDeclared };
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
kind: "pi-session",
|
|
94
|
+
executor: executor ?? "pi",
|
|
95
|
+
handler,
|
|
96
|
+
commandsDeclared,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function receiptAttemptSegments(nodeId, parsed) {
|
|
100
|
+
if (parsed?.kind === "attempt") {
|
|
101
|
+
return [nodeId, `attempt-${parsed.index}`, "shell-command-receipt.json"];
|
|
102
|
+
}
|
|
103
|
+
return [nodeId, "shell-command-receipt.json"];
|
|
104
|
+
}
|
|
105
|
+
async function loadShellCommands(repoRoot, dagRunId, nodeId, parsed) {
|
|
106
|
+
const preferred = await readJsonArtifact(repoRoot, dagRunId, receiptAttemptSegments(nodeId, parsed));
|
|
107
|
+
const receipt = preferred ??
|
|
108
|
+
(parsed
|
|
109
|
+
? await readJsonArtifact(repoRoot, dagRunId, [
|
|
110
|
+
nodeId,
|
|
111
|
+
"shell-command-receipt.json",
|
|
112
|
+
])
|
|
113
|
+
: null);
|
|
114
|
+
if (!receipt)
|
|
115
|
+
return null;
|
|
116
|
+
const rawCommands = Array.isArray(receipt.commands) ? receipt.commands : [];
|
|
117
|
+
const commands = [];
|
|
118
|
+
for (let i = 0; i < rawCommands.length; i += 1) {
|
|
119
|
+
const entry = asRecord(rawCommands[i]) ?? {};
|
|
120
|
+
const stdout = await readTextArtifact(repoRoot, dagRunId, [
|
|
121
|
+
nodeId,
|
|
122
|
+
"commands",
|
|
123
|
+
`${i + 1}.stdout.txt`,
|
|
124
|
+
]);
|
|
125
|
+
const stderr = await readTextArtifact(repoRoot, dagRunId, [
|
|
126
|
+
nodeId,
|
|
127
|
+
"commands",
|
|
128
|
+
`${i + 1}.stderr.txt`,
|
|
129
|
+
]);
|
|
130
|
+
const commandBound = boundField(asString(entry.command) ?? "");
|
|
131
|
+
commands.push({
|
|
132
|
+
index: i + 1,
|
|
133
|
+
command: commandBound.text ?? "(command)",
|
|
134
|
+
ok: asBoolean(entry.ok),
|
|
135
|
+
exitCode: asNumber(entry.exitCode),
|
|
136
|
+
failureCategory: asString(entry.failureCategory),
|
|
137
|
+
startedAt: asString(entry.startedAt),
|
|
138
|
+
finishedAt: asString(entry.finishedAt),
|
|
139
|
+
durationMs: asNumber(entry.wallDurationMs) ??
|
|
140
|
+
asNumber(entry.executionDurationMs) ??
|
|
141
|
+
null,
|
|
142
|
+
reused: asBoolean(entry.reused),
|
|
143
|
+
stdout: stdout.text,
|
|
144
|
+
stderr: stderr.text,
|
|
145
|
+
truncated: commandBound.truncated || stdout.truncated || stderr.truncated,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return commands;
|
|
149
|
+
}
|
|
150
|
+
function handlerLabel(handler) {
|
|
151
|
+
if (handler === "frontendBrowserToolPreflight")
|
|
152
|
+
return "浏览器工具能力探测";
|
|
153
|
+
if (handler === "frontendTestStandardScenarios")
|
|
154
|
+
return "标准场景物化";
|
|
155
|
+
if (handler === "frontendTestEnvironmentProbe")
|
|
156
|
+
return "前端环境探测";
|
|
157
|
+
if (handler === "frontendTestCaseChecklist")
|
|
158
|
+
return "用例清单校验";
|
|
159
|
+
if (handler === "frontendTestCaseManifest")
|
|
160
|
+
return "用例清单物化";
|
|
161
|
+
if (handler === "frontendTestEvidenceValidation")
|
|
162
|
+
return "证据校验";
|
|
163
|
+
if (handler === "frontendTestResultFinalize")
|
|
164
|
+
return "结果定稿";
|
|
165
|
+
if (handler === "frontendTestReports" || handler === "frontendTestL5Report" || handler === "frontendTestHtmlReport") {
|
|
166
|
+
return "测试报告";
|
|
167
|
+
}
|
|
168
|
+
if (handler === "frontendPrewriteGate")
|
|
169
|
+
return "前端预写门禁";
|
|
170
|
+
if (handler === "finalWriteSetApprovalGate")
|
|
171
|
+
return "writeSet 终态批准";
|
|
172
|
+
return handler;
|
|
173
|
+
}
|
|
174
|
+
async function loadPresetSteps(repoRoot, dagRunId, nodeId, handler, parsed) {
|
|
175
|
+
const written = await readJsonArtifact(repoRoot, dagRunId, [
|
|
176
|
+
nodeId,
|
|
177
|
+
"process-receipt.json",
|
|
178
|
+
]);
|
|
179
|
+
if (written && Array.isArray(written.steps)) {
|
|
180
|
+
return written.steps.flatMap((item, index) => {
|
|
181
|
+
const step = asRecord(item);
|
|
182
|
+
if (!step)
|
|
183
|
+
return [];
|
|
184
|
+
const detail = boundField(asString(step.detail));
|
|
185
|
+
return [
|
|
186
|
+
{
|
|
187
|
+
id: asString(step.id) ?? `step-${index + 1}`,
|
|
188
|
+
label: asString(step.label) ?? handlerLabel(handler ?? "preset"),
|
|
189
|
+
ok: asBoolean(step.ok),
|
|
190
|
+
detail: detail.text,
|
|
191
|
+
durationMs: asNumber(step.durationMs),
|
|
192
|
+
truncated: detail.truncated,
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const capability = await readJsonArtifact(repoRoot, dagRunId, [
|
|
198
|
+
nodeId,
|
|
199
|
+
"frontend-browser-capability.json",
|
|
200
|
+
]);
|
|
201
|
+
if (capability && handler === "frontendBrowserToolPreflight") {
|
|
202
|
+
const detail = boundField(JSON.stringify(capability, null, 2));
|
|
203
|
+
return [
|
|
204
|
+
{
|
|
205
|
+
id: "capability",
|
|
206
|
+
label: handlerLabel(handler),
|
|
207
|
+
ok: asString(capability.status) !== "failed",
|
|
208
|
+
detail: detail.text,
|
|
209
|
+
durationMs: null,
|
|
210
|
+
truncated: detail.truncated,
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
const attemptIndex = parsed?.kind === "attempt" ? parsed.index : 1;
|
|
215
|
+
const attempt = await readJsonArtifact(repoRoot, dagRunId, [
|
|
216
|
+
nodeId,
|
|
217
|
+
`attempt-${attemptIndex}.json`,
|
|
218
|
+
]);
|
|
219
|
+
if (attempt) {
|
|
220
|
+
const ok = asBoolean(attempt.ok);
|
|
221
|
+
const detail = boundField(ok
|
|
222
|
+
? (asString(attempt.stdout) ?? asString(attempt.assistantText))
|
|
223
|
+
: (asString(attempt.stderr) ?? asString(attempt.stdout)));
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
id: "run",
|
|
227
|
+
label: handlerLabel(handler ?? "preset"),
|
|
228
|
+
ok,
|
|
229
|
+
detail: detail.text,
|
|
230
|
+
durationMs: asNumber(attempt.durationMs),
|
|
231
|
+
truncated: detail.truncated,
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
export async function loadDagNodeProcess(repoRoot, dagRunId, nodeId, rawKey) {
|
|
238
|
+
if (!isSafeObservabilityIdentifier(dagRunId) ||
|
|
239
|
+
!isSafeObservabilityIdentifier(nodeId)) {
|
|
240
|
+
return {
|
|
241
|
+
status: 400,
|
|
242
|
+
body: { error: "Invalid dag run or node identifier", reason: "invalid-id" },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
let parsedKey = null;
|
|
246
|
+
if (rawKey != null && String(rawKey).trim() !== "") {
|
|
247
|
+
const parsed = parseExecutionOutputKey(rawKey);
|
|
248
|
+
if (!parsed.ok) {
|
|
249
|
+
return {
|
|
250
|
+
status: 400,
|
|
251
|
+
body: {
|
|
252
|
+
error: "Invalid execution key (expected attempt-N or pass-N)",
|
|
253
|
+
reason: "invalid-key",
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
parsedKey = parsed;
|
|
258
|
+
}
|
|
259
|
+
const runResolved = resolveDagRunJson(repoRoot, dagRunId);
|
|
260
|
+
if (!runResolved.ok) {
|
|
261
|
+
if (runResolved.reason === "invalid-id") {
|
|
262
|
+
return {
|
|
263
|
+
status: 400,
|
|
264
|
+
body: { error: "Invalid dag run or node identifier", reason: "invalid-id" },
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (runResolved.reason === "ambiguous") {
|
|
268
|
+
return {
|
|
269
|
+
status: 409,
|
|
270
|
+
body: { error: "Ambiguous dag run lifecycle", reason: "ambiguous" },
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
status: 404,
|
|
275
|
+
body: { error: "DAG run not found", reason: "not-found" },
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
let spec = {};
|
|
279
|
+
try {
|
|
280
|
+
const raw = JSON.parse(await readFile(runResolved.result.absolutePath, "utf8"));
|
|
281
|
+
spec = asRecord(raw) ?? {};
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return {
|
|
285
|
+
status: 404,
|
|
286
|
+
body: { error: "run.json is unreadable", reason: "not-found" },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const tasks = Array.isArray(spec.tasks) ? spec.tasks : [];
|
|
290
|
+
const task = tasks
|
|
291
|
+
.map((item) => asRecord(item))
|
|
292
|
+
.find((item) => item && asString(item.id) === nodeId);
|
|
293
|
+
if (!task) {
|
|
294
|
+
return {
|
|
295
|
+
status: 200,
|
|
296
|
+
body: emptyBody(dagRunId, nodeId, "none", "not-executed"),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const classified = classifyKind(task);
|
|
300
|
+
const key = parsedKey?.key ?? null;
|
|
301
|
+
if (classified.kind === "pi-session") {
|
|
302
|
+
return {
|
|
303
|
+
status: 200,
|
|
304
|
+
body: emptyBody(dagRunId, nodeId, "pi-session", "pi-session", {
|
|
305
|
+
executor: classified.executor,
|
|
306
|
+
key,
|
|
307
|
+
}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (classified.kind === "static") {
|
|
311
|
+
return {
|
|
312
|
+
status: 200,
|
|
313
|
+
body: emptyBody(dagRunId, nodeId, "static", "static-no-process", {
|
|
314
|
+
executor: classified.executor,
|
|
315
|
+
key,
|
|
316
|
+
}),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (classified.kind === "shell-commands" || classified.kind === "shell-preset") {
|
|
320
|
+
const commands = await loadShellCommands(repoRoot, dagRunId, nodeId, parsedKey);
|
|
321
|
+
if (commands && commands.length > 0) {
|
|
322
|
+
return {
|
|
323
|
+
status: 200,
|
|
324
|
+
body: {
|
|
325
|
+
schemaVersion: NODE_PROCESS_SCHEMA_VERSION,
|
|
326
|
+
dagRunId,
|
|
327
|
+
nodeId,
|
|
328
|
+
kind: "shell-commands",
|
|
329
|
+
executor: classified.executor,
|
|
330
|
+
handler: classified.handler,
|
|
331
|
+
emptyReason: null,
|
|
332
|
+
key,
|
|
333
|
+
commands,
|
|
334
|
+
steps: [],
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (classified.handler || classified.kind === "shell-preset") {
|
|
339
|
+
const steps = await loadPresetSteps(repoRoot, dagRunId, nodeId, classified.handler, parsedKey);
|
|
340
|
+
if (steps.length > 0) {
|
|
341
|
+
return {
|
|
342
|
+
status: 200,
|
|
343
|
+
body: {
|
|
344
|
+
schemaVersion: NODE_PROCESS_SCHEMA_VERSION,
|
|
345
|
+
dagRunId,
|
|
346
|
+
nodeId,
|
|
347
|
+
kind: "shell-preset",
|
|
348
|
+
executor: classified.executor,
|
|
349
|
+
handler: classified.handler,
|
|
350
|
+
emptyReason: null,
|
|
351
|
+
key,
|
|
352
|
+
commands: [],
|
|
353
|
+
steps,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
status: 200,
|
|
360
|
+
body: emptyBody(dagRunId, nodeId, classified.kind, classified.kind === "shell-preset"
|
|
361
|
+
? "shell-preset-pending"
|
|
362
|
+
: "shell-receipt-missing", {
|
|
363
|
+
executor: classified.executor,
|
|
364
|
+
handler: classified.handler,
|
|
365
|
+
key,
|
|
366
|
+
}),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
status: 200,
|
|
371
|
+
body: emptyBody(dagRunId, nodeId, "none", "not-executed", {
|
|
372
|
+
executor: classified.executor,
|
|
373
|
+
handler: classified.handler,
|
|
374
|
+
key,
|
|
375
|
+
}),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
@@ -16,6 +16,7 @@ import { isAllowedArtifactTextPath, resolveArtifactPath, resolveRepoFilePreview,
|
|
|
16
16
|
import { extractSpecEvidence, extractSpecReadContent, } from "./spec-evidence.js";
|
|
17
17
|
import { buildDagNodeInput } from "./node-input.js";
|
|
18
18
|
import { loadDagNodeExecutionOutput } from "./dag-node-execution-output.js";
|
|
19
|
+
import { loadDagNodeProcess } from "./node-process.js";
|
|
19
20
|
import { buildObserveHealthV1 } from "./health.js";
|
|
20
21
|
import { buildNightJobDetail, buildNightJobsDoctor, buildNightJobsList, buildNightJobsMorning, } from "./night-jobs.js";
|
|
21
22
|
const ARTIFACT_PREVIEW_MAX_BYTES = 64 * 1024;
|
|
@@ -100,6 +101,11 @@ export const ROUTES = [
|
|
|
100
101
|
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/execution-output$/,
|
|
101
102
|
handler: handleDagNodeExecutionOutput,
|
|
102
103
|
},
|
|
104
|
+
{
|
|
105
|
+
method: "GET",
|
|
106
|
+
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/process$/,
|
|
107
|
+
handler: handleDagNodeProcess,
|
|
108
|
+
},
|
|
103
109
|
{
|
|
104
110
|
method: "GET",
|
|
105
111
|
pattern: /^\/api\/dag-runs\/([^/]+)\/nodes\/([^/]+)\/spec-evidence$/,
|
|
@@ -221,14 +227,23 @@ export async function serveObserveStatic(req, res, staticDir, options = {}) {
|
|
|
221
227
|
sendJson(res, 404, { error: "Not found" });
|
|
222
228
|
return;
|
|
223
229
|
}
|
|
224
|
-
|
|
230
|
+
let content = await readFile(resolvedFile);
|
|
225
231
|
const contentType = contentTypeFor(pathname);
|
|
226
|
-
|
|
232
|
+
const headers = {
|
|
227
233
|
"Content-Type": contentType,
|
|
228
234
|
"Content-Length": content.byteLength,
|
|
229
235
|
"X-Content-Type-Options": "nosniff",
|
|
230
236
|
"X-Frame-Options": "SAMEORIGIN",
|
|
231
|
-
}
|
|
237
|
+
};
|
|
238
|
+
if (contentType.startsWith("text/html") && options.htmlEnhancer) {
|
|
239
|
+
const enhanced = options.htmlEnhancer(Buffer.from(content));
|
|
240
|
+
content = enhanced.content;
|
|
241
|
+
headers["Content-Length"] = content.byteLength;
|
|
242
|
+
if (enhanced.setCookie) {
|
|
243
|
+
headers["Set-Cookie"] = enhanced.setCookie;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
res.writeHead(200, headers);
|
|
232
247
|
res.end(content);
|
|
233
248
|
}
|
|
234
249
|
async function handleHealth(_req, res, _match, ctx) {
|
|
@@ -570,6 +585,24 @@ async function handleDagNodeExecutionOutput(_req, res, match, ctx) {
|
|
|
570
585
|
const result = await loadDagNodeExecutionOutput(ctx.repoRoot, dagRunId, nodeId, key);
|
|
571
586
|
sendJson(res, result.status, result.body);
|
|
572
587
|
}
|
|
588
|
+
async function handleDagNodeProcess(_req, res, match, ctx) {
|
|
589
|
+
const dagRunId = match.params.id;
|
|
590
|
+
const nodeId = match.params.sub;
|
|
591
|
+
if (!isSafeObservabilityIdentifier(dagRunId) ||
|
|
592
|
+
!isSafeObservabilityIdentifier(nodeId)) {
|
|
593
|
+
sendJson(res, 400, { error: "Invalid dag run or node identifier" });
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (match.query.has("path") || match.query.has("artifactPath")) {
|
|
597
|
+
sendJson(res, 400, {
|
|
598
|
+
error: "Artifact path is not accepted; use optional key=attempt-N|pass-N",
|
|
599
|
+
reason: "path-not-allowed",
|
|
600
|
+
});
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const result = await loadDagNodeProcess(ctx.repoRoot, dagRunId, nodeId, match.query.get("key"));
|
|
604
|
+
sendJson(res, result.status, result.body);
|
|
605
|
+
}
|
|
573
606
|
async function handleDagNodeSessionEvents(_req, res, match, ctx) {
|
|
574
607
|
const dagRunId = match.params.id;
|
|
575
608
|
const nodeId = match.params.sub;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowlist of in-process shell handlers (preset/gate flags on task.shell).
|
|
3
|
+
* Observe projects these names; the executor writes process receipts for them.
|
|
4
|
+
*/
|
|
5
|
+
export const SHELL_NAMED_HANDLER_KEYS = [
|
|
6
|
+
"verdictGate",
|
|
7
|
+
"projectGovernanceGate",
|
|
8
|
+
"requirementCoverageGate",
|
|
9
|
+
"jsonArtifactGate",
|
|
10
|
+
"frontendPrewriteGate",
|
|
11
|
+
"frontendLintBaseline",
|
|
12
|
+
"frontendVerificationBundle",
|
|
13
|
+
"frontendReviewContext",
|
|
14
|
+
"frontendTestCaseChecklist",
|
|
15
|
+
"frontendBrowserToolPreflight",
|
|
16
|
+
"frontendTestStandardScenarios",
|
|
17
|
+
"frontendTestEnvironmentProbe",
|
|
18
|
+
"frontendTestEvidenceValidation",
|
|
19
|
+
"finalWriteSetApprovalGate",
|
|
20
|
+
"frontendTestL5Report",
|
|
21
|
+
"frontendTestHtmlReport",
|
|
22
|
+
"frontendTestCaseManifest",
|
|
23
|
+
"frontendTestResultFinalize",
|
|
24
|
+
"frontendTestReports",
|
|
25
|
+
"backendTestPipeline",
|
|
26
|
+
"jacocoCoverage",
|
|
27
|
+
"verifyEvidence",
|
|
28
|
+
"repairArtifactGate",
|
|
29
|
+
];
|
|
30
|
+
export function namedShellHandlers(shell) {
|
|
31
|
+
if (!shell)
|
|
32
|
+
return [];
|
|
33
|
+
return SHELL_NAMED_HANDLER_KEYS.filter((key) => shell[key] != null);
|
|
34
|
+
}
|
|
@@ -77,6 +77,14 @@ export async function fetchDagNodeInput(runId, nodeId) {
|
|
|
77
77
|
return fetchJsonResult(url);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
export async function fetchDagNodeProcess(runId, nodeId, key) {
|
|
81
|
+
const params = new URLSearchParams();
|
|
82
|
+
if (key) params.set("key", String(key));
|
|
83
|
+
const qs = params.toString();
|
|
84
|
+
const url = `/api/dag-runs/${encodeURIComponent(runId)}/nodes/${encodeURIComponent(nodeId)}/process${qs ? `?${qs}` : ""}`;
|
|
85
|
+
return fetchJsonResult(url);
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
/**
|
|
81
89
|
* Fetch a spec-evidence file preview (binding source or successful read).
|
|
82
90
|
* Resolves to the parsed JSON body, or throws an Error carrying the HTTP
|
|
@@ -104,6 +112,64 @@ export function artifactUrl(artifactPath) {
|
|
|
104
112
|
return `/api/artifacts?path=${encodeURIComponent(artifactPath)}&tail=200`;
|
|
105
113
|
}
|
|
106
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Experimental (prompt-override rerun only): POST one allowlisted operator
|
|
117
|
+
* action through the Console mutation gate using the SCOPED inspect token
|
|
118
|
+
* (window.__INSPECT_MUTATION_TOKEN__). The server injects it into /inspect
|
|
119
|
+
* HTML only when HARNESS_DAG_RERUN_PROMPT_OVERRIDE is enabled, and it only
|
|
120
|
+
* authorizes dagRerunPlan / dagRerun — never general Console mutations.
|
|
121
|
+
* Absent token → the call is not attempted.
|
|
122
|
+
*/
|
|
123
|
+
export async function postOperatorAction(action, actionParams = {}) {
|
|
124
|
+
const token = globalThis.__INSPECT_MUTATION_TOKEN__;
|
|
125
|
+
if (!token) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
status: 0,
|
|
129
|
+
body: { error: { code: "PROMPT_OVERRIDE_DISABLED", message: "提示词重启未启用(服务端未注入确认 token)" } },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const res = await fetch("/api/operator/v1/operations", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
credentials: "same-origin",
|
|
136
|
+
headers: {
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
"x-loop-console-confirmation": token,
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify({ action, actionParams }),
|
|
141
|
+
});
|
|
142
|
+
let body = null;
|
|
143
|
+
try {
|
|
144
|
+
body = await res.json();
|
|
145
|
+
} catch {
|
|
146
|
+
body = null;
|
|
147
|
+
}
|
|
148
|
+
return { ok: res.ok, status: res.status, body };
|
|
149
|
+
} catch {
|
|
150
|
+
return { ok: false, status: 0, body: null };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Poll a Console operation snapshot (read-only GET). */
|
|
155
|
+
export async function fetchOperatorOperation(operationId) {
|
|
156
|
+
try {
|
|
157
|
+
const res = await fetch(
|
|
158
|
+
`/api/operator/v1/operations/${encodeURIComponent(operationId)}`,
|
|
159
|
+
{ credentials: "same-origin" },
|
|
160
|
+
);
|
|
161
|
+
let body = null;
|
|
162
|
+
try {
|
|
163
|
+
body = await res.json();
|
|
164
|
+
} catch {
|
|
165
|
+
body = null;
|
|
166
|
+
}
|
|
167
|
+
return { ok: res.ok, status: res.status, body };
|
|
168
|
+
} catch {
|
|
169
|
+
return { ok: false, status: 0, body: null };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
107
173
|
export function parseArtifactPreviewResponse(text) {
|
|
108
174
|
const parsed = JSON.parse(text);
|
|
109
175
|
if (!parsed || typeof parsed.content !== "string") {
|
|
@@ -53,7 +53,14 @@ export const UI_TEXT = {
|
|
|
53
53
|
inputError: "加载节点输入失败。",
|
|
54
54
|
inputFrozenLabel: "冻结 DAG 节点定义",
|
|
55
55
|
inputFingerprintNote: "仅保存 assembled prompt 指纹,全文未持久化",
|
|
56
|
-
noSessionEvents: "
|
|
56
|
+
noSessionEvents: "该 Pi 节点暂无过程日志(可能尚未执行、走 CLI 路径,或为历史 run)",
|
|
57
|
+
noShellProcess: "该 shell 节点尚未留下命令回执。",
|
|
58
|
+
noPresetProcess: "该内部处理器尚未留下过程回执。",
|
|
59
|
+
staticNoProcess: "静态节点没有逐步执行过程,请查看节点输出。",
|
|
60
|
+
processLoading: "正在加载执行过程…",
|
|
61
|
+
processError: "加载执行过程失败。",
|
|
62
|
+
shellCommandsHeading: "Shell 命令",
|
|
63
|
+
shellPresetHeading: "内部处理器",
|
|
57
64
|
noArtifacts: "暂无产物记录。",
|
|
58
65
|
noActiveCommand: "当前没有正在执行的命令。",
|
|
59
66
|
noFailures: "当前没有异常 Task。",
|
|
@@ -345,3 +345,26 @@ export function formatRepoPathDisplay(repoRoot) {
|
|
|
345
345
|
.replace(/^\/Users\/[^/]+/, "~")
|
|
346
346
|
.replace(/^\/home\/[^/]+/, "~");
|
|
347
347
|
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* If `text` is a complete JSON object or array, return pretty-printed JSON.
|
|
351
|
+
* Primitives, JSONL, truncated/invalid JSON, and markdown return null so
|
|
352
|
+
* callers can fall back to Markdown rendering.
|
|
353
|
+
*/
|
|
354
|
+
export function prettyJsonBlock(text) {
|
|
355
|
+
if (typeof text !== "string") return null;
|
|
356
|
+
const trimmed = text.trim();
|
|
357
|
+
if (trimmed.length < 2) return null;
|
|
358
|
+
const start = trimmed[0];
|
|
359
|
+
const end = trimmed[trimmed.length - 1];
|
|
360
|
+
if (!((start === "{" && end === "}") || (start === "[" && end === "]"))) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
const value = JSON.parse(trimmed);
|
|
365
|
+
if (value === null || typeof value !== "object") return null;
|
|
366
|
+
return JSON.stringify(value, null, 2);
|
|
367
|
+
} catch {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/** Shared safe Markdown → DOM rendering for Observe UI. */
|
|
2
2
|
import { el } from "./dom.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
parseMarkdownBlocks,
|
|
5
|
+
markdownLinkHref,
|
|
6
|
+
prettyJsonBlock,
|
|
7
|
+
} from "./format.js";
|
|
4
8
|
|
|
5
9
|
export function appendInlineMarkdown(target, text) {
|
|
6
10
|
const tokenPattern = /(`[^`]*`|\*\*[^*]+\*\*|\*[^*]+\*|\[[^\]]+\]\([^)]+\))/g;
|
|
@@ -49,6 +53,21 @@ export function appendParagraphMarkdown(target, text) {
|
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
export function renderInspectorDocument(content) {
|
|
57
|
+
const pretty = prettyJsonBlock(content);
|
|
58
|
+
if (pretty != null) {
|
|
59
|
+
const article = el("article", "markdown-output json-output");
|
|
60
|
+
const pre = document.createElement("pre");
|
|
61
|
+
const code = document.createElement("code");
|
|
62
|
+
code.dataset.language = "json";
|
|
63
|
+
code.textContent = pretty;
|
|
64
|
+
pre.appendChild(code);
|
|
65
|
+
article.appendChild(pre);
|
|
66
|
+
return article;
|
|
67
|
+
}
|
|
68
|
+
return renderMarkdown(content);
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
export function renderMarkdown(content) {
|
|
53
72
|
const article = el("article", "markdown-output");
|
|
54
73
|
for (const block of parseMarkdownBlocks(content)) {
|
|
@@ -436,6 +436,10 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
436
436
|
const a = el("a", `och-mode-btn${btn.active ? " och-on" : ""}`);
|
|
437
437
|
a.href = btn.href;
|
|
438
438
|
a.dataset.mode = btn.id;
|
|
439
|
+
// 2026-08-22: inline-flex 布局下裸文本节点不参与 name-from-content,
|
|
440
|
+
// a11y snapshot 序列化为无名 link;显式 aria-label 保证「操作/观测」
|
|
441
|
+
// 对 find/读屏稳定可断言(frontend-test passed 权威依赖可见 find)。
|
|
442
|
+
a.setAttribute("aria-label", btn.label);
|
|
439
443
|
if (btn.active) a.setAttribute("aria-current", "page");
|
|
440
444
|
a.appendChild(makeIcon(btn.id === "operate" ? "zap" : "eye"));
|
|
441
445
|
a.appendChild(document.createTextNode(btn.label));
|
|
@@ -61,6 +61,12 @@ export const uiState = {
|
|
|
61
61
|
dagNodeInputData: null,
|
|
62
62
|
dagNodeInputError: null,
|
|
63
63
|
dagNodeInputLoading: false,
|
|
64
|
+
/** Node-process request identity ("<dagRunId>:<nodeId>:<key>#<seq>"). */
|
|
65
|
+
dagNodeProcessIdentity: null,
|
|
66
|
+
dagNodeProcessRequestSeq: 0,
|
|
67
|
+
dagNodeProcessData: null,
|
|
68
|
+
dagNodeProcessError: null,
|
|
69
|
+
dagNodeProcessLoading: false,
|
|
64
70
|
/**
|
|
65
71
|
* Execution-output request identity ("<dagRunId>:<nodeId>:<key>#<seq>").
|
|
66
72
|
* Stale async responses are rejected when this no longer matches.
|
|
@@ -222,6 +228,11 @@ export function makeDagNodeInputIdentity(dagRunId, nodeId) {
|
|
|
222
228
|
return `${dagRunId}:${nodeId}#${uiState.dagNodeInputRequestSeq}`;
|
|
223
229
|
}
|
|
224
230
|
|
|
231
|
+
export function makeDagNodeProcessIdentity(dagRunId, nodeId, key) {
|
|
232
|
+
uiState.dagNodeProcessRequestSeq += 1;
|
|
233
|
+
return `${dagRunId}:${nodeId}:${key ?? "latest"}#${uiState.dagNodeProcessRequestSeq}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
225
236
|
/** Build a fresh execution-output identity token (includes key). */
|
|
226
237
|
export function makeDagNodeExecutionOutputIdentity(dagRunId, nodeId, key) {
|
|
227
238
|
uiState.dagNodeExecutionOutputRequestSeq += 1;
|