@tea-agent/loop-agent 0.32.1 → 0.33.1
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/CHANGELOG.md +78 -0
- package/dist/executors/model-routing.js +14 -4
- package/dist/governance/manifest-types.js +34 -7
- package/dist/worker/console/chat/model-resolver.js +114 -34
- package/dist/worker/console/chat/workspace-landing.js +58 -22
- package/dist/worker/console/doctor.js +1 -0
- package/dist/worker/console/night-aux-ticker.js +5 -0
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/pi-readiness.js +26 -17
- package/dist/worker/console/server.js +2 -0
- package/dist/worker/console/static/assets/index-CnUXAqxG.css +1 -0
- package/dist/worker/console/static/assets/index-PzYzcuFG.js +29 -0
- package/dist/worker/console/static/index.html +3 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +3 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +0 -31
- package/dist/worker/observability/read-model.js +106 -0
- package/dist/worker/observe/routes.js +22 -9
- package/dist/worker/observe/static/api.js +42 -3
- package/dist/worker/observe/static/app.js +15 -0
- package/dist/worker/observe/static/constants.js +10 -0
- package/dist/worker/observe/static/custom-select.js +567 -0
- package/dist/worker/observe/static/index.html +56 -47
- package/dist/worker/observe/static/kpi.js +2 -24
- package/dist/worker/observe/static/operator-chrome.css +480 -0
- package/dist/worker/observe/static/operator-chrome.d.ts +82 -0
- package/dist/worker/observe/static/operator-chrome.js +554 -0
- package/dist/worker/observe/static/router.js +54 -1
- package/dist/worker/observe/static/shell-chrome.js +1 -11
- package/dist/worker/observe/static/state.js +20 -0
- package/dist/worker/observe/static/styles.css +680 -299
- package/dist/worker/observe/static/views/dag-inspector.js +20 -17
- package/dist/worker/observe/static/views/dag.js +136 -59
- package/dist/worker/observe/static/views/dags.js +877 -0
- package/dist/worker/observe/static/views/dashboard.js +67 -8
- package/dist/workflows/dag/init-hybrid.js +58 -29
- package/docs/templates/harness.schema.json +5 -0
- package/harness.json +4 -4
- package/package.json +1 -1
- package/dist/worker/console/static/assets/index-Bpa2qrc-.js +0 -29
- package/dist/worker/console/static/assets/index-BqfFDdnG.css +0 -1
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
|
+
<link rel="stylesheet" href="/inspect/operator-chrome.css" />
|
|
7
8
|
<title>Loop 操作台 · Operator Console</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-PzYzcuFG.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CnUXAqxG.css">
|
|
10
11
|
</head>
|
|
11
12
|
<body>
|
|
12
13
|
<div id="root"></div>
|
|
@@ -30,7 +30,8 @@ export function useRecoveryConsole(params) {
|
|
|
30
30
|
const [inspectSplitOpen, setInspectSplitOpen] = useState(false);
|
|
31
31
|
const inspectPath = useCallback(() => {
|
|
32
32
|
if (dagRunId.trim()) {
|
|
33
|
-
|
|
33
|
+
// from=recovery 激活统一壳的对象上下文 chip 与显式返回路径
|
|
34
|
+
return `/inspect/#/dag/${encodeURIComponent(dagRunId.trim())}?from=recovery`;
|
|
34
35
|
}
|
|
35
36
|
return "/inspect/#/";
|
|
36
37
|
}, [dagRunId]);
|
|
@@ -91,7 +92,7 @@ export function useRecoveryConsole(params) {
|
|
|
91
92
|
const resolved = await resolveDagRunForInspect();
|
|
92
93
|
if (!resolved)
|
|
93
94
|
return;
|
|
94
|
-
window.location.assign(`/inspect/#/dag/${encodeURIComponent(resolved)}`);
|
|
95
|
+
window.location.assign(`/inspect/#/dag/${encodeURIComponent(resolved)}?from=recovery`);
|
|
95
96
|
}, [resolveDagRunForInspect]);
|
|
96
97
|
const loadRecoveryRuns = useCallback(async () => {
|
|
97
98
|
setRecoveryRunsLoading(true);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from "react";
|
|
2
|
-
import { confirmationToken, } from "./night-types.js";
|
|
3
2
|
/** Night board data plane: schedules list + doctor report + task pool +
|
|
4
3
|
* foreground aux-ticker projection, loaded together on mount/refresh. */
|
|
5
4
|
export function useNightBoard(params) {
|
|
@@ -12,7 +11,6 @@ export function useNightBoard(params) {
|
|
|
12
11
|
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
13
12
|
const [morningPreview, setMorningPreview] = useState(null);
|
|
14
13
|
const [auxTicker, setAuxTicker] = useState(null);
|
|
15
|
-
const [auxTickerBusy, setAuxTickerBusy] = useState(false);
|
|
16
14
|
const refreshBoard = useCallback(async () => {
|
|
17
15
|
setLoadingBoard(true);
|
|
18
16
|
setLoadError(null);
|
|
@@ -59,33 +57,6 @@ export function useNightBoard(params) {
|
|
|
59
57
|
useEffect(() => {
|
|
60
58
|
void refreshBoard();
|
|
61
59
|
}, [refreshBoard]);
|
|
62
|
-
const onToggleAuxTicker = useCallback(async (enabled) => {
|
|
63
|
-
setAuxTickerBusy(true);
|
|
64
|
-
try {
|
|
65
|
-
const res = await fetch("/api/operator/v1/night-aux-ticker", {
|
|
66
|
-
method: "POST",
|
|
67
|
-
credentials: "same-origin",
|
|
68
|
-
headers: {
|
|
69
|
-
"Content-Type": "application/json",
|
|
70
|
-
"X-Loop-Console-Confirmation": confirmationToken(),
|
|
71
|
-
},
|
|
72
|
-
body: JSON.stringify({ enabled }),
|
|
73
|
-
});
|
|
74
|
-
const body = (await res.json());
|
|
75
|
-
if (!res.ok || body.ok === false || !body.ticker) {
|
|
76
|
-
setOpError(body.error?.message ??
|
|
77
|
-
`切换前台辅助 ticker 失败(HTTP ${res.status})`);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
setAuxTicker(body.ticker);
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
setOpError(error instanceof Error ? error.message : String(error));
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
setAuxTickerBusy(false);
|
|
87
|
-
}
|
|
88
|
-
}, [setOpError]);
|
|
89
60
|
const onMorningReport = useCallback(async () => {
|
|
90
61
|
try {
|
|
91
62
|
const res = await fetch("/api/night-jobs/morning", {
|
|
@@ -115,9 +86,7 @@ export function useNightBoard(params) {
|
|
|
115
86
|
setAdvancedOpen,
|
|
116
87
|
morningPreview,
|
|
117
88
|
auxTicker,
|
|
118
|
-
auxTickerBusy,
|
|
119
89
|
refreshBoard,
|
|
120
|
-
onToggleAuxTicker,
|
|
121
90
|
onMorningReport,
|
|
122
91
|
doctorIssues,
|
|
123
92
|
clockHint,
|
|
@@ -43,6 +43,11 @@ export function resolveLegacyTask(tasks, taskId) {
|
|
|
43
43
|
}
|
|
44
44
|
export const TASK_RUN_HISTORY_DEFAULT_LIMIT = 20;
|
|
45
45
|
export const TASK_RUN_HISTORY_MAX_LIMIT = 100;
|
|
46
|
+
/** Default / allowed page sizes for GET /api/dag-runs history list. */
|
|
47
|
+
export const DAG_RUN_HISTORY_DEFAULT_PAGE = 1;
|
|
48
|
+
export const DAG_RUN_HISTORY_DEFAULT_PAGE_SIZE = 20;
|
|
49
|
+
export const DAG_RUN_HISTORY_MAX_PAGE_SIZE = 100;
|
|
50
|
+
export const DAG_RUN_HISTORY_ALLOWED_PAGE_SIZES = [20, 50, 100];
|
|
46
51
|
const ACTIVE_TASK_STATUSES = new Set(["running", "pending"]);
|
|
47
52
|
const ACTIVE_BATCH_STATUSES = new Set(["running", "pending"]);
|
|
48
53
|
export async function buildGlobalSnapshot(options) {
|
|
@@ -227,6 +232,107 @@ export function clampTaskRunHistoryLimit(limit) {
|
|
|
227
232
|
}
|
|
228
233
|
return Math.min(Math.floor(limit), TASK_RUN_HISTORY_MAX_LIMIT);
|
|
229
234
|
}
|
|
235
|
+
export function clampDagRunHistoryPageSize(pageSize) {
|
|
236
|
+
if (pageSize === undefined || !Number.isFinite(pageSize) || pageSize <= 0) {
|
|
237
|
+
return DAG_RUN_HISTORY_DEFAULT_PAGE_SIZE;
|
|
238
|
+
}
|
|
239
|
+
const floored = Math.floor(pageSize);
|
|
240
|
+
if (DAG_RUN_HISTORY_ALLOWED_PAGE_SIZES.includes(floored)) {
|
|
241
|
+
return floored;
|
|
242
|
+
}
|
|
243
|
+
// Nearest allowed size, prefer smaller when equidistant, then clamp to max.
|
|
244
|
+
let best = DAG_RUN_HISTORY_DEFAULT_PAGE_SIZE;
|
|
245
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
246
|
+
for (const allowed of DAG_RUN_HISTORY_ALLOWED_PAGE_SIZES) {
|
|
247
|
+
const dist = Math.abs(allowed - floored);
|
|
248
|
+
if (dist < bestDist || (dist === bestDist && allowed < best)) {
|
|
249
|
+
best = allowed;
|
|
250
|
+
bestDist = dist;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return Math.min(best, DAG_RUN_HISTORY_MAX_PAGE_SIZE);
|
|
254
|
+
}
|
|
255
|
+
export function clampDagRunHistoryPage(page, totalPages) {
|
|
256
|
+
const safeTotal = Math.max(1, Math.floor(totalPages) || 1);
|
|
257
|
+
if (page === undefined || !Number.isFinite(page) || page <= 0) {
|
|
258
|
+
return DAG_RUN_HISTORY_DEFAULT_PAGE;
|
|
259
|
+
}
|
|
260
|
+
return Math.min(Math.floor(page), safeTotal);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Formal DAG history membership (AC-004):
|
|
264
|
+
* exclude executionMode or lifecycle dry-run/init-only;
|
|
265
|
+
* keep missing executionMode when lifecycle is not a non-execution mode.
|
|
266
|
+
*/
|
|
267
|
+
export function isFormalDagRunHistoryCandidate(dag) {
|
|
268
|
+
const executionMode = (dag.executionMode ?? "").toLowerCase();
|
|
269
|
+
if (NON_EXECUTION_MODES.has(executionMode))
|
|
270
|
+
return false;
|
|
271
|
+
const lifecycle = (dag.lifecycle ?? "").toLowerCase();
|
|
272
|
+
if (NON_EXECUTION_MODES.has(lifecycle))
|
|
273
|
+
return false;
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
function dagRunHistorySortKey(dag) {
|
|
277
|
+
return `${dag.startedAt ?? dag.finishedAt ?? ""}\0${dag.dagRunId}`;
|
|
278
|
+
}
|
|
279
|
+
function projectDagRunHistoryProgress(dag) {
|
|
280
|
+
const nodes = dag.nodes ?? [];
|
|
281
|
+
let finished = 0;
|
|
282
|
+
for (const node of nodes) {
|
|
283
|
+
const status = (node.status ?? "").toLowerCase();
|
|
284
|
+
if (status === "finished" ||
|
|
285
|
+
status === "completed" ||
|
|
286
|
+
status === "succeeded" ||
|
|
287
|
+
status === "done" ||
|
|
288
|
+
status === "error" ||
|
|
289
|
+
status === "failed" ||
|
|
290
|
+
status === "skipped" ||
|
|
291
|
+
status === "partial_failed") {
|
|
292
|
+
finished += 1;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return { finished, total: nodes.length };
|
|
296
|
+
}
|
|
297
|
+
function toDagRunHistoryItem(dag) {
|
|
298
|
+
return {
|
|
299
|
+
dagRunId: dag.dagRunId,
|
|
300
|
+
...(dag.title ? { title: dag.title } : {}),
|
|
301
|
+
...(dag.status ? { status: dag.status } : {}),
|
|
302
|
+
...(dag.effectiveStatus ? { effectiveStatus: dag.effectiveStatus } : {}),
|
|
303
|
+
...(dag.lifecycle ? { lifecycle: dag.lifecycle } : {}),
|
|
304
|
+
...(dag.executionMode ? { executionMode: dag.executionMode } : {}),
|
|
305
|
+
...(dag.startedAt ? { startedAt: dag.startedAt } : {}),
|
|
306
|
+
...(dag.finishedAt ? { finishedAt: dag.finishedAt } : {}),
|
|
307
|
+
...(dag.durationMs !== undefined ? { durationMs: dag.durationMs } : {}),
|
|
308
|
+
progress: projectDagRunHistoryProgress(dag),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Paginated lightweight formal DAG run history from an in-memory GlobalSnapshot.
|
|
313
|
+
* Filters dry-run/init-only; sorts newest first; never embeds nodes/edges/previews.
|
|
314
|
+
*/
|
|
315
|
+
export function listDagRunHistoryFromSnapshot(snapshot, options = {}) {
|
|
316
|
+
const pageSize = clampDagRunHistoryPageSize(options.pageSize);
|
|
317
|
+
const formal = (snapshot.dagRuns ?? [])
|
|
318
|
+
.filter(isFormalDagRunHistoryCandidate)
|
|
319
|
+
.slice()
|
|
320
|
+
.sort((a, b) => dagRunHistorySortKey(b).localeCompare(dagRunHistorySortKey(a)));
|
|
321
|
+
const total = formal.length;
|
|
322
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1);
|
|
323
|
+
const page = clampDagRunHistoryPage(options.page, totalPages);
|
|
324
|
+
const start = (page - 1) * pageSize;
|
|
325
|
+
const runs = formal.slice(start, start + pageSize).map(toDagRunHistoryItem);
|
|
326
|
+
return {
|
|
327
|
+
schemaVersion: 1,
|
|
328
|
+
generatedAt: snapshot.generatedAt,
|
|
329
|
+
page,
|
|
330
|
+
pageSize,
|
|
331
|
+
total,
|
|
332
|
+
totalPages,
|
|
333
|
+
runs,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
230
336
|
function emptySnapshot(repoRoot, now) {
|
|
231
337
|
let taskPoolPresent = false;
|
|
232
338
|
try {
|
|
@@ -7,7 +7,7 @@ import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
|
7
7
|
import { parseWorkerEventLine } from "../observability/events.js";
|
|
8
8
|
import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
|
|
9
9
|
import { clampEventHistoryLimit, listBatchEventHistory, listPoolEventHistory, } from "../observability/event-history.js";
|
|
10
|
-
import { buildGlobalSnapshot, clampTaskRunHistoryLimit, listTaskRunHistory, resolveLegacyTask, } from "../observability/read-model.js";
|
|
10
|
+
import { buildGlobalSnapshot, clampDagRunHistoryPageSize, clampTaskRunHistoryLimit, listDagRunHistoryFromSnapshot, listTaskRunHistory, resolveLegacyTask, } from "../observability/read-model.js";
|
|
11
11
|
import { loadDagRunExecutionTrajectory } from "../observability/dag-execution-trajectory.js";
|
|
12
12
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
13
13
|
import { dagSourceBindingSchema } from "../../workflows/dag/types.js";
|
|
@@ -115,6 +115,13 @@ export const ROUTES = [
|
|
|
115
115
|
pattern: /^\/api\/dag-runs\/([^/]+)$/,
|
|
116
116
|
handler: handleDagRunById,
|
|
117
117
|
},
|
|
118
|
+
// List route must be registered after /api/dag-runs/:id so detail keeps priority;
|
|
119
|
+
// matchObserveGetRoute scans in order and this path has no capture group.
|
|
120
|
+
{
|
|
121
|
+
method: "GET",
|
|
122
|
+
pattern: /^\/api\/dag-runs$/,
|
|
123
|
+
handler: handleDagRuns,
|
|
124
|
+
},
|
|
118
125
|
{ method: "GET", pattern: /^\/api\/artifacts$/, handler: handleArtifactRead },
|
|
119
126
|
{
|
|
120
127
|
method: "GET",
|
|
@@ -194,9 +201,9 @@ export async function serveObserveStatic(req, res, staticDir, options = {}) {
|
|
|
194
201
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
195
202
|
let pathname = decodeURIComponent(url.pathname);
|
|
196
203
|
const urlPrefix = options.urlPrefix?.replace(/\/$/, "");
|
|
197
|
-
const
|
|
204
|
+
const withinPrefix = Boolean(urlPrefix) &&
|
|
198
205
|
(pathname === urlPrefix || pathname.startsWith(`${urlPrefix}/`));
|
|
199
|
-
if (urlPrefix &&
|
|
206
|
+
if (urlPrefix && withinPrefix) {
|
|
200
207
|
pathname = pathname.slice(urlPrefix.length) || "/";
|
|
201
208
|
}
|
|
202
209
|
if (pathname === "/") {
|
|
@@ -214,13 +221,8 @@ export async function serveObserveStatic(req, res, staticDir, options = {}) {
|
|
|
214
221
|
sendJson(res, 404, { error: "Not found" });
|
|
215
222
|
return;
|
|
216
223
|
}
|
|
217
|
-
|
|
224
|
+
const content = await readFile(resolvedFile);
|
|
218
225
|
const contentType = contentTypeFor(pathname);
|
|
219
|
-
if (embeddedShell && contentType.startsWith("text/html")) {
|
|
220
|
-
content = Buffer.from(content
|
|
221
|
-
.toString("utf8")
|
|
222
|
-
.replace(/<body\b/i, '<body data-embedded-shell="true"'));
|
|
223
|
-
}
|
|
224
226
|
res.writeHead(200, {
|
|
225
227
|
"Content-Type": contentType,
|
|
226
228
|
"Content-Length": content.byteLength,
|
|
@@ -507,6 +509,17 @@ async function handleRunArtifacts(_req, res, match, ctx) {
|
|
|
507
509
|
}
|
|
508
510
|
sendJson(res, 200, { artifacts });
|
|
509
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* GET /api/dag-runs?page=&pageSize=
|
|
514
|
+
* Lightweight formal DAG history page (schemaVersion 1).
|
|
515
|
+
*/
|
|
516
|
+
async function handleDagRuns(_req, res, match, ctx) {
|
|
517
|
+
const snapshot = await getSnapshot(ctx);
|
|
518
|
+
const page = parsePositiveInt(match.query.get("page"), 1);
|
|
519
|
+
const pageSize = clampDagRunHistoryPageSize(parsePositiveInt(match.query.get("pageSize"), 0) || undefined);
|
|
520
|
+
const body = listDagRunHistoryFromSnapshot(snapshot, { page, pageSize });
|
|
521
|
+
sendJson(res, 200, body);
|
|
522
|
+
}
|
|
510
523
|
export async function handleDagRunById(_req, res, match, ctx) {
|
|
511
524
|
const snapshot = await getSnapshot(ctx);
|
|
512
525
|
const dagRun = snapshot.dagRuns.find((d) => d.dagRunId === match.params.id);
|
|
@@ -13,10 +13,12 @@ export async function fetchJson(url) {
|
|
|
13
13
|
/**
|
|
14
14
|
* Read-only JSON fetch that preserves HTTP status so callers can distinguish
|
|
15
15
|
* 404 (missing) from 409 (ambiguous task identity). Body is parsed best-effort.
|
|
16
|
+
* Optional `signal` enables AbortController cancellation for formal navigation.
|
|
16
17
|
*/
|
|
17
|
-
export async function fetchJsonResult(url) {
|
|
18
|
+
export async function fetchJsonResult(url, options = {}) {
|
|
19
|
+
const signal = options?.signal;
|
|
18
20
|
try {
|
|
19
|
-
const res = await fetch(url);
|
|
21
|
+
const res = await fetch(url, signal ? { signal } : undefined);
|
|
20
22
|
let body = null;
|
|
21
23
|
try {
|
|
22
24
|
body = await res.json();
|
|
@@ -24,11 +26,48 @@ export async function fetchJsonResult(url) {
|
|
|
24
26
|
body = null;
|
|
25
27
|
}
|
|
26
28
|
return { ok: res.ok, status: res.status, body };
|
|
27
|
-
} catch {
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (
|
|
31
|
+
(err && (err.name === "AbortError" || err.code === 20)) ||
|
|
32
|
+
(signal && signal.aborted)
|
|
33
|
+
) {
|
|
34
|
+
return { ok: false, status: 0, body: null, aborted: true };
|
|
35
|
+
}
|
|
28
36
|
return { ok: false, status: 0, body: null };
|
|
29
37
|
}
|
|
30
38
|
}
|
|
31
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Formal DAG history page (real request path). page/pageSize are query params.
|
|
42
|
+
* Callers should discard late responses via request generation tokens and may
|
|
43
|
+
* pass AbortSignal to cancel superseded formal navigation requests.
|
|
44
|
+
*/
|
|
45
|
+
export async function fetchDagRunHistory(page, pageSize, options = {}) {
|
|
46
|
+
const params = new URLSearchParams();
|
|
47
|
+
if (page != null) params.set("page", String(page));
|
|
48
|
+
if (pageSize != null) params.set("pageSize", String(pageSize));
|
|
49
|
+
const qs = params.toString();
|
|
50
|
+
const url = qs ? `/api/dag-runs?${qs}` : "/api/dag-runs";
|
|
51
|
+
return fetchJsonResult(url, options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Safe user-facing error summary for DAG history (AC-015).
|
|
56
|
+
* Only local trusted fixed copy — never renders server error/message/detail
|
|
57
|
+
* or absolute local paths (Windows or POSIX).
|
|
58
|
+
*/
|
|
59
|
+
export function summarizeFetchError(result) {
|
|
60
|
+
if (!result) return "加载失败。";
|
|
61
|
+
if (result.status === 0) return "网络错误,请检查 Observe 服务是否可用。";
|
|
62
|
+
if (typeof result.status === "number" && result.status >= 500) {
|
|
63
|
+
return "服务暂时不可用,请稍后重试。";
|
|
64
|
+
}
|
|
65
|
+
if (typeof result.status === "number" && result.status >= 400) {
|
|
66
|
+
return "请求失败,请稍后重试。";
|
|
67
|
+
}
|
|
68
|
+
return "加载失败。";
|
|
69
|
+
}
|
|
70
|
+
|
|
32
71
|
/**
|
|
33
72
|
* Fetch a dag node input projection. Preserves HTTP status so callers can
|
|
34
73
|
* distinguish missing runs, unavailable nodes, and transport errors.
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
import { renderFailures } from "./views/failures.js";
|
|
50
50
|
import { startPoolView } from "./views/pool.js";
|
|
51
51
|
import { startDashboardPolling } from "./views/dashboard.js";
|
|
52
|
+
import { startDagsView } from "./views/dags.js";
|
|
52
53
|
import { renderFeatureDetail } from "./views/feature.js";
|
|
53
54
|
import { renderBatch } from "./views/batch.js";
|
|
54
55
|
import { startTaskPolling } from "./views/task.js";
|
|
@@ -66,6 +67,7 @@ import {
|
|
|
66
67
|
mergeSessionEvents,
|
|
67
68
|
renderSessionTimeline,
|
|
68
69
|
} from "./views/session-timeline.js";
|
|
70
|
+
import { mountOperatorChrome } from "./operator-chrome.js";
|
|
69
71
|
|
|
70
72
|
// Compatibility re-exports for poll-stable UI helpers (owned by state.js / views):
|
|
71
73
|
// reuse-dag-detail-shell, reuse-dag-graph-wrap, existingGraphWrap?.parentNode === ranksEl,
|
|
@@ -119,6 +121,8 @@ function route() {
|
|
|
119
121
|
const r = parseRoute();
|
|
120
122
|
if (r.view === "dashboard") {
|
|
121
123
|
startDashboardPolling(r.scrollTo);
|
|
124
|
+
} else if (r.view === "dags") {
|
|
125
|
+
void startDagsView();
|
|
122
126
|
} else if (r.view === "batch") {
|
|
123
127
|
void renderBatch(r.batchRunId);
|
|
124
128
|
} else if (r.view === "run") {
|
|
@@ -139,6 +143,16 @@ function route() {
|
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
if (typeof window !== "undefined") {
|
|
146
|
+
// 先同步挂载共享壳,再启动路由;首个 snapshot 的新鲜度不会因异步挂载丢失。
|
|
147
|
+
const chromeHost = document.getElementById("operator-chrome");
|
|
148
|
+
if (chromeHost) {
|
|
149
|
+
mountOperatorChrome(chromeHost, {
|
|
150
|
+
mode: "inspect",
|
|
151
|
+
liveLocation: true,
|
|
152
|
+
healthUrl: "/api/health",
|
|
153
|
+
readinessTitle: "Pi 状态(只读展示)。模型与认证管理在操作侧,点击进入。",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
142
156
|
window.addEventListener("hashchange", route);
|
|
143
157
|
window.addEventListener("load", route);
|
|
144
158
|
document.addEventListener("pointerdown", closeDagInspectorOnOutsideClick);
|
|
@@ -146,6 +160,7 @@ if (typeof window !== "undefined") {
|
|
|
146
160
|
if (!isPageVisible()) return;
|
|
147
161
|
const r = parseRoute();
|
|
148
162
|
if (r.view === "dashboard") startDashboardPolling();
|
|
163
|
+
// History page is manual-refresh only (no 5s dashboard polling).
|
|
149
164
|
if (r.view === "run") startRunPolling(r.workerRunId, false);
|
|
150
165
|
if (r.view === "dag") startDagPolling(r.dagRunId, false);
|
|
151
166
|
if (r.view === "pool" && uiState.poolAutoRefresh) startPoolView();
|
|
@@ -21,6 +21,8 @@ export const OUTPUT_FOLLOW_LATEST_PX = 48;
|
|
|
21
21
|
export const UI_TEXT = {
|
|
22
22
|
dashboard: "总览",
|
|
23
23
|
dagRuns: "DAG 运行",
|
|
24
|
+
dagHistory: "DAG 运行历史",
|
|
25
|
+
viewAllDags: "查看全部 →",
|
|
24
26
|
failures: "异常 Task",
|
|
25
27
|
nightJobs: "夜间任务",
|
|
26
28
|
batchDetail: "批次详情",
|
|
@@ -31,6 +33,14 @@ export const UI_TEXT = {
|
|
|
31
33
|
batches: "Worker 批次",
|
|
32
34
|
noActiveDags: "当前没有进行中的 DAG,启动后将出现在这里。",
|
|
33
35
|
noRecentDags: "暂无 DAG 运行记录。",
|
|
36
|
+
noFormalDagHistory: "暂无正式 DAG 运行记录",
|
|
37
|
+
noFormalDagHistoryHint:
|
|
38
|
+
"dry-run / init-only 预检记录不会出现在此列表中。",
|
|
39
|
+
dagHistoryRefreshFailed: "刷新失败,当前展示上一次结果",
|
|
40
|
+
dagHistoryCopied: "已复制",
|
|
41
|
+
dagHistoryCopiedAnnounce: "已复制运行 ID",
|
|
42
|
+
dagHistoryCopyFailed: "复制失败,请手动选择运行 ID",
|
|
43
|
+
dagHistoryCopyTooltip: "复制运行 ID",
|
|
34
44
|
noBatches: "暂无 Worker 批次,运行后将出现在这里。",
|
|
35
45
|
noNodes: "暂无节点。",
|
|
36
46
|
noOutput: "暂无节点输出。",
|