@tea-agent/loop-agent 0.40.0-next.4 → 0.40.0-next.6
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 +20 -0
- package/dist/build-stamp.json +3 -3
- package/dist/executors/shell-executor.js +4 -1
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/worker/console/chat/chat-event-store.js +62 -4
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +264 -16
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +292 -29
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +14 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/routes.js +309 -1
- package/dist/worker/console/security.js +4 -4
- package/dist/worker/console/server.js +529 -186
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-DGt5TRsk.js} +1 -1
- package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-DKBSJsXm.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-BnhooRBs.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-Bm9qkrJP.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-B2cFh4bO.js} +1 -1
- package/dist/worker/console/static/assets/channel-DSgFX07x.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DhvWRI03.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-chzTP_2w.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-SdWIR12m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-C2-8Eo_A.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-DvbE1yQW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-CEenyPTz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-CcaZrq0k.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-FGngOTto.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-jT_yX3sf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-CjE9LQOM.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-unJObRls.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-unJObRls.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-GQWns1tb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-CGRpxWNJ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-Cd3uFGE4.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-DmDHuUW0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-kAr--hF3.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-woahL2Oh.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-7syNVaNf.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-BELPXiaY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-CrPa4aXi.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-B48u2VVe.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-DhBTfwTF.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-CAeZ5nDU.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-CkIFeFLN.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-t0HGYDel.js} +1 -1
- package/dist/worker/console/static/assets/index-B4l5ag79.js +407 -0
- package/dist/worker/console/static/assets/index-CwVHWB71.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-WM0HZhe4.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-CV5byHe0.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-lIyWkgnw.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-BIH4YNRY.js} +1 -1
- package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-lQ0ZGrWc.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-BYdXtke8.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-k93vkIa4.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-Bm6AqvSy.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-DnJW6Xpf.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-CMzm4ObL.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-fwFF5dFQ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-tzl9VDUc.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-B26V09DD.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-D2npyCeP.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-CTJtkRU2.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-B8lk4Lzu.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BmOcMTJE.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-C3yP53hH.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Beah-dq0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-Bck7xjhe.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CCntr1w7.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-DsCqCBZz.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-Br2l90OM.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +29 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +36 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +2 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +78 -17
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
- package/dist/worker/console/workspace-context.js +234 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +26 -6
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +172 -0
- package/dist/worker/observe/static/views/dag-inspector.js +3 -2
- package/dist/worker/observe/static/views/dag.js +2 -1
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/task.js +1 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +11 -10
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/docs/README.md +1 -0
- package/docs/operations/local-development-environment.md +21 -0
- package/harness.json +3 -3
- package/package.json +3 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
|
@@ -12,18 +12,17 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
export const OPERATE_TABS = Object.freeze([
|
|
15
|
-
{ id: "
|
|
16
|
-
{ id: "
|
|
17
|
-
{ id: "
|
|
18
|
-
{ id: "
|
|
19
|
-
{ id: "
|
|
20
|
-
{ id: "settings", label: "设置", href: "#/settings" },
|
|
15
|
+
{ id: "tasks", label: "任务", href: "/#/operate/tasks" },
|
|
16
|
+
{ id: "recovery", label: "恢复", href: "/#/operate/recovery" },
|
|
17
|
+
{ id: "night", label: "夜间", href: "/#/operate/night" },
|
|
18
|
+
{ id: "models", label: "模型与认证", href: "/#/operate/models" },
|
|
19
|
+
{ id: "settings", label: "设置", href: "/#/operate/settings" },
|
|
21
20
|
]);
|
|
22
21
|
|
|
23
22
|
export const INSPECT_TABS = Object.freeze([
|
|
24
|
-
{ id: "dashboard", label: "总览", href: "
|
|
25
|
-
{ id: "pool", label: "资源池", href: "
|
|
26
|
-
{ id: "failures", label: "异常 Task", href: "
|
|
23
|
+
{ id: "dashboard", label: "总览", href: "/#/inspect" },
|
|
24
|
+
{ id: "pool", label: "资源池", href: "/#/inspect/pool" },
|
|
25
|
+
{ id: "failures", label: "异常 Task", href: "/#/inspect/failures" },
|
|
27
26
|
]);
|
|
28
27
|
|
|
29
28
|
export const READINESS_LABELS = Object.freeze({
|
|
@@ -45,14 +44,39 @@ const CONTEXT_KIND_LABELS = Object.freeze({
|
|
|
45
44
|
/** 支持返回操作侧的对象视图 → 返回落地工作区。v1 仅接通 recovery 闭环。 */
|
|
46
45
|
const RETURNABLE_VIEWS = new Set(["dag", "run"]);
|
|
47
46
|
|
|
47
|
+
export function parseInspectWorkspaceId(hashValue) {
|
|
48
|
+
const { query } = splitHash(hashValue);
|
|
49
|
+
if (!query) return null;
|
|
50
|
+
const id = query.get("workspaceId")?.trim();
|
|
51
|
+
return id || null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function withWorkspaceQuery(href, workspaceId) {
|
|
55
|
+
const raw = String(href ?? "");
|
|
56
|
+
if (!workspaceId) return raw;
|
|
57
|
+
const qIndex = raw.indexOf("?");
|
|
58
|
+
if (qIndex < 0) return `${raw}?workspaceId=${encodeURIComponent(workspaceId)}`;
|
|
59
|
+
const params = new URLSearchParams(raw.slice(qIndex + 1));
|
|
60
|
+
if (params.get("workspaceId") === workspaceId) return raw;
|
|
61
|
+
params.set("workspaceId", workspaceId);
|
|
62
|
+
return `${raw.slice(0, qIndex)}?${params.toString()}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
48
65
|
function splitHash(hashValue) {
|
|
49
66
|
const raw = String(hashValue ?? "").replace(/^#/, "");
|
|
50
67
|
const qIndex = raw.indexOf("?");
|
|
51
|
-
const
|
|
68
|
+
const pathPart = (qIndex >= 0 ? raw.slice(0, qIndex) : raw) || "/";
|
|
69
|
+
const path = pathPart.startsWith("/") ? pathPart : `/${pathPart}`;
|
|
52
70
|
const query = qIndex >= 0 ? new URLSearchParams(raw.slice(qIndex + 1)) : null;
|
|
53
71
|
return { path, query };
|
|
54
72
|
}
|
|
55
73
|
|
|
74
|
+
function inspectInternalPath(path) {
|
|
75
|
+
if (path === "/inspect" || path === "/inspect/") return "/";
|
|
76
|
+
if (path.startsWith("/inspect/")) return path.slice("/inspect".length) || "/";
|
|
77
|
+
return path;
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
function decode(value) {
|
|
57
81
|
try {
|
|
58
82
|
return decodeURIComponent(value);
|
|
@@ -64,7 +88,8 @@ function decode(value) {
|
|
|
64
88
|
/** 与 router.js 同形的最小对象视图解析(只读 chrome 用,不替代路由)。 */
|
|
65
89
|
export function parseInspectObjectView(hashValue) {
|
|
66
90
|
const { path } = splitHash(hashValue);
|
|
67
|
-
const
|
|
91
|
+
const internal = inspectInternalPath(path);
|
|
92
|
+
const match = /^\/(dag|run|task|feature|batch|night)\/([^/]+)$/.exec(internal);
|
|
68
93
|
if (!match) return null;
|
|
69
94
|
const id = decode(match[2]);
|
|
70
95
|
if (!id) return null;
|
|
@@ -79,10 +104,15 @@ export function parseInspectObjectView(hashValue) {
|
|
|
79
104
|
*/
|
|
80
105
|
export function activeInspectTabHref(hashValue) {
|
|
81
106
|
const { path } = splitHash(hashValue);
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
const internal = inspectInternalPath(path);
|
|
108
|
+
if (internal === "/" || internal === "" || internal === "/inspect") {
|
|
109
|
+
return "/#/inspect";
|
|
110
|
+
}
|
|
111
|
+
if (internal === "/pool" || internal.startsWith("/task/")) {
|
|
112
|
+
return "/#/inspect/pool";
|
|
113
|
+
}
|
|
114
|
+
if (internal === "/failures") return "/#/inspect/failures";
|
|
115
|
+
if (internal.startsWith("/feature/")) return "/#/inspect";
|
|
86
116
|
return null;
|
|
87
117
|
}
|
|
88
118
|
|
|
@@ -104,7 +134,7 @@ export function parseInspectContext(hashValue) {
|
|
|
104
134
|
id: objectView.id,
|
|
105
135
|
from,
|
|
106
136
|
kindLabel: CONTEXT_KIND_LABELS[objectView.view] ?? objectView.view,
|
|
107
|
-
returnHref: `/#/recovery${suffix ? `?${suffix}` : ""}`,
|
|
137
|
+
returnHref: `/#/operate/recovery${suffix ? `?${suffix}` : ""}`,
|
|
108
138
|
returnLabel: "返回 恢复",
|
|
109
139
|
};
|
|
110
140
|
}
|
|
@@ -113,20 +143,19 @@ export function parseInspectContext(hashValue) {
|
|
|
113
143
|
export function resolveOperateHrefFromInspectHash(hashValue) {
|
|
114
144
|
const objectView = parseInspectObjectView(hashValue);
|
|
115
145
|
if (objectView?.view === "dag") {
|
|
116
|
-
return `/#/recovery?dagRunId=${encodeURIComponent(objectView.id)}`;
|
|
146
|
+
return `/#/operate/recovery?dagRunId=${encodeURIComponent(objectView.id)}`;
|
|
117
147
|
}
|
|
118
|
-
|
|
119
|
-
return "/#/chat";
|
|
148
|
+
return "/#/operate/tasks";
|
|
120
149
|
}
|
|
121
150
|
|
|
122
151
|
/** Inspect DAG 详情 → Operate Recovery 协作中止深链(只改导航,不发 mutation)。 */
|
|
123
152
|
export function buildOperateInterruptHref(dagRunId) {
|
|
124
|
-
return `/#/recovery?dagRunId=${encodeURIComponent(dagRunId)}&intent=interrupt`;
|
|
153
|
+
return `/#/operate/recovery?dagRunId=${encodeURIComponent(dagRunId)}&intent=interrupt`;
|
|
125
154
|
}
|
|
126
155
|
|
|
127
156
|
/** 操作侧 → Inspect 的 canonical 深链(可携带 from 以激活上下文 chip)。 */
|
|
128
157
|
export function buildInspectDagHref(dagRunId, options = {}) {
|
|
129
|
-
const base =
|
|
158
|
+
const base = `/#/inspect/dag/${encodeURIComponent(dagRunId)}`;
|
|
130
159
|
return options.from === "recovery" ? `${base}?from=recovery` : base;
|
|
131
160
|
}
|
|
132
161
|
|
|
@@ -174,45 +203,76 @@ export function chromeStateFromHealth(body) {
|
|
|
174
203
|
* 共用同一份模型结构,保证 DOM 契约一致。
|
|
175
204
|
*/
|
|
176
205
|
export function buildOperatorChromeModel(config) {
|
|
177
|
-
const mode =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
206
|
+
const mode =
|
|
207
|
+
config.mode === "inspect"
|
|
208
|
+
? "inspect"
|
|
209
|
+
: config.mode === "chat"
|
|
210
|
+
? "chat"
|
|
211
|
+
: "operate";
|
|
212
|
+
const tabs = (
|
|
213
|
+
mode === "inspect"
|
|
214
|
+
? INSPECT_TABS
|
|
215
|
+
: mode === "operate"
|
|
216
|
+
? OPERATE_TABS
|
|
217
|
+
: []
|
|
218
|
+
).map((tab) => ({
|
|
219
|
+
...tab,
|
|
220
|
+
active:
|
|
221
|
+
tab.href ===
|
|
222
|
+
(String(config.activeTabHref ?? "").split("?")[0] || null),
|
|
223
|
+
}));
|
|
184
224
|
const state = config.readinessState ?? "unknown";
|
|
225
|
+
const operateHref = config.operateHref ?? "/#/operate/tasks";
|
|
226
|
+
const inspectHref = config.inspectHref ?? "/#/inspect";
|
|
227
|
+
const activeWorkspaceId = config.workspaceSwitcher?.activeId ?? null;
|
|
185
228
|
return {
|
|
186
229
|
mode,
|
|
187
|
-
homeHref:
|
|
230
|
+
homeHref: withWorkspaceQuery(
|
|
231
|
+
mode === "inspect" ? "/#/inspect" : "/#/chat",
|
|
232
|
+
activeWorkspaceId,
|
|
233
|
+
),
|
|
188
234
|
brand: "Loop Agent",
|
|
189
235
|
repo: config.repo ?? null,
|
|
236
|
+
workspaceSwitcher: config.workspaceSwitcher ?? null,
|
|
190
237
|
modeButtons: [
|
|
238
|
+
{
|
|
239
|
+
id: "chat",
|
|
240
|
+
label: "对话",
|
|
241
|
+
href: config.chatHref ?? "/#/chat",
|
|
242
|
+
active: mode === "chat",
|
|
243
|
+
},
|
|
191
244
|
{
|
|
192
245
|
id: "operate",
|
|
193
246
|
label: "操作",
|
|
194
|
-
|
|
195
|
-
href: config.operateHref ?? "/#/chat",
|
|
247
|
+
href: operateHref,
|
|
196
248
|
active: mode === "operate",
|
|
197
249
|
},
|
|
198
250
|
{
|
|
199
251
|
id: "inspect",
|
|
200
252
|
label: "观测",
|
|
201
|
-
href:
|
|
253
|
+
href: inspectHref,
|
|
202
254
|
active: mode === "inspect",
|
|
203
255
|
},
|
|
204
256
|
],
|
|
205
257
|
context: config.context ?? null,
|
|
206
258
|
freshness: mode === "inspect" && config.freshness !== false,
|
|
259
|
+
statusVisible: config.statusVisible !== false,
|
|
207
260
|
pi: {
|
|
208
261
|
state,
|
|
209
262
|
label: READINESS_LABELS[state] ?? state,
|
|
210
263
|
href:
|
|
211
|
-
config.readinessHref ??
|
|
264
|
+
config.readinessHref ??
|
|
265
|
+
(mode === "inspect" ? "/#/operate/models" : "/#/operate/models"),
|
|
212
266
|
title: config.readinessTitle ?? "",
|
|
213
267
|
},
|
|
268
|
+
workspaceTabsVisible: tabs.length > 0,
|
|
214
269
|
tabs,
|
|
215
|
-
tabsAriaLabel:
|
|
270
|
+
tabsAriaLabel:
|
|
271
|
+
mode === "inspect"
|
|
272
|
+
? "观测看板"
|
|
273
|
+
: mode === "operate"
|
|
274
|
+
? "操作工作区"
|
|
275
|
+
: "对话工作区",
|
|
216
276
|
};
|
|
217
277
|
}
|
|
218
278
|
|
|
@@ -254,6 +314,17 @@ const ICONS = Object.freeze({
|
|
|
254
314
|
["path", { d: "M6 8.4v7.2M8.2 6.9 15.8 8" }],
|
|
255
315
|
],
|
|
256
316
|
},
|
|
317
|
+
folder: {
|
|
318
|
+
size: 14,
|
|
319
|
+
parts: [
|
|
320
|
+
["path", { d: "M3 6.5h6l2-2h4.5c1.4 0 2.5 1.1 2.5 2.5v9.5c0 1.4-1.1 2.5-2.5 2.5h-11C3.1 19 2 17.9 2 16.5V8c0-.8.4-1.5 1-1.5z" }],
|
|
321
|
+
],
|
|
322
|
+
},
|
|
323
|
+
check: {
|
|
324
|
+
size: 12,
|
|
325
|
+
strokeWidth: 2.4,
|
|
326
|
+
parts: [["path", { d: "m5 12 4 4L19 6" }]],
|
|
327
|
+
},
|
|
257
328
|
chevron: {
|
|
258
329
|
size: 10,
|
|
259
330
|
strokeWidth: 2.4,
|
|
@@ -366,10 +437,21 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
366
437
|
function build(model) {
|
|
367
438
|
freshnessText =
|
|
368
439
|
root.querySelector("#header-refresh")?.textContent ?? freshnessText;
|
|
369
|
-
|
|
440
|
+
/* Keep the product-mode control connected while the surrounding chrome is
|
|
441
|
+
* rebuilt. In particular, the shared thumb must retain its DOM identity and
|
|
442
|
+
* previous transform so the browser can interpolate to the next mode instead
|
|
443
|
+
* of flashing a freshly-created indicator at the destination. */
|
|
444
|
+
const persistentModeNav = root.querySelector(".och-mode");
|
|
445
|
+
if (persistentModeNav) root.appendChild(persistentModeNav);
|
|
446
|
+
for (const child of [...root.children]) {
|
|
447
|
+
if (child !== persistentModeNav) child.remove();
|
|
448
|
+
}
|
|
370
449
|
const chrome = el("div", "och");
|
|
371
450
|
const inner = el("div", "och-inner");
|
|
372
451
|
chrome.appendChild(inner);
|
|
452
|
+
/* Attach the new shell before measuring the persistent mode buttons. Moving
|
|
453
|
+
* the nav within the same connected root preserves its painted transform. */
|
|
454
|
+
root.appendChild(chrome);
|
|
373
455
|
|
|
374
456
|
/* Band 1 */
|
|
375
457
|
const global = el("div", "och-global");
|
|
@@ -386,7 +468,89 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
386
468
|
brand.appendChild(el("span", null, model.brand));
|
|
387
469
|
global.appendChild(brand);
|
|
388
470
|
|
|
389
|
-
if (model.
|
|
471
|
+
if (model.workspaceSwitcher) {
|
|
472
|
+
const picker = el("div", "och-workspace-picker");
|
|
473
|
+
const items = Array.isArray(model.workspaceSwitcher.items)
|
|
474
|
+
? model.workspaceSwitcher.items
|
|
475
|
+
: [];
|
|
476
|
+
const active = items.find(
|
|
477
|
+
(item) => item.workspaceId === model.workspaceSwitcher.activeId,
|
|
478
|
+
);
|
|
479
|
+
const repoBtn = el("button", "och-repo och-workspace-trigger");
|
|
480
|
+
repoBtn.type = "button";
|
|
481
|
+
repoBtn.setAttribute("aria-haspopup", "menu");
|
|
482
|
+
repoBtn.setAttribute("aria-controls", popoverId);
|
|
483
|
+
repoBtn.setAttribute("aria-expanded", String(popoverOpen));
|
|
484
|
+
repoBtn.setAttribute(
|
|
485
|
+
"aria-label",
|
|
486
|
+
active ? `切换工作区,当前 ${active.name}` : "切换工作区",
|
|
487
|
+
);
|
|
488
|
+
icon(repoBtn, "folder");
|
|
489
|
+
repoBtn.appendChild(
|
|
490
|
+
el(
|
|
491
|
+
"span",
|
|
492
|
+
"och-repo-name",
|
|
493
|
+
active?.name ?? (model.workspaceSwitcher.loading ? "加载中…" : "选择工作区"),
|
|
494
|
+
),
|
|
495
|
+
);
|
|
496
|
+
icon(repoBtn, "chevron");
|
|
497
|
+
picker.appendChild(repoBtn);
|
|
498
|
+
|
|
499
|
+
const pop = el("div", "och-pop och-workspace-menu");
|
|
500
|
+
pop.id = popoverId;
|
|
501
|
+
pop.hidden = !popoverOpen;
|
|
502
|
+
pop.setAttribute("role", "menu");
|
|
503
|
+
pop.setAttribute("aria-label", "切换工作区");
|
|
504
|
+
pop.append(el("p", "och-pop-title", "工作区"));
|
|
505
|
+
if (items.length === 0) {
|
|
506
|
+
pop.append(
|
|
507
|
+
el(
|
|
508
|
+
"p",
|
|
509
|
+
"och-workspace-empty",
|
|
510
|
+
model.workspaceSwitcher.loading ? "正在加载工作区…" : "暂无工作区",
|
|
511
|
+
),
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
for (const item of items) {
|
|
515
|
+
const option = el("button", "och-workspace-option");
|
|
516
|
+
option.type = "button";
|
|
517
|
+
option.setAttribute("role", "menuitemradio");
|
|
518
|
+
option.setAttribute(
|
|
519
|
+
"aria-checked",
|
|
520
|
+
String(item.workspaceId === model.workspaceSwitcher.activeId),
|
|
521
|
+
);
|
|
522
|
+
const copy = el("span", "och-workspace-option-copy");
|
|
523
|
+
copy.append(
|
|
524
|
+
el("strong", null, item.name),
|
|
525
|
+
el("small", null, item.displayPath || ""),
|
|
526
|
+
);
|
|
527
|
+
option.appendChild(copy);
|
|
528
|
+
if (item.workspaceId === model.workspaceSwitcher.activeId) {
|
|
529
|
+
icon(option, "check");
|
|
530
|
+
}
|
|
531
|
+
option.addEventListener("click", (event) => {
|
|
532
|
+
event.stopPropagation();
|
|
533
|
+
popoverOpen = false;
|
|
534
|
+
if (
|
|
535
|
+
item.workspaceId !== model.workspaceSwitcher.activeId &&
|
|
536
|
+
typeof currentConfig.onSelectWorkspace === "function"
|
|
537
|
+
) {
|
|
538
|
+
currentConfig.onSelectWorkspace(item.workspaceId);
|
|
539
|
+
} else {
|
|
540
|
+
render();
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
pop.appendChild(option);
|
|
544
|
+
}
|
|
545
|
+
picker.appendChild(pop);
|
|
546
|
+
global.appendChild(picker);
|
|
547
|
+
repoBtn.addEventListener("click", (event) => {
|
|
548
|
+
event.stopPropagation();
|
|
549
|
+
popoverOpen = !popoverOpen;
|
|
550
|
+
repoBtn.setAttribute("aria-expanded", String(popoverOpen));
|
|
551
|
+
pop.hidden = !popoverOpen;
|
|
552
|
+
});
|
|
553
|
+
} else if (model.repo) {
|
|
390
554
|
const repoBtn = el("button", "och-repo");
|
|
391
555
|
repoBtn.type = "button";
|
|
392
556
|
repoBtn.setAttribute("aria-haspopup", "dialog");
|
|
@@ -430,22 +594,61 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
430
594
|
});
|
|
431
595
|
}
|
|
432
596
|
|
|
433
|
-
const modeNav = el("nav", "och-mode");
|
|
597
|
+
const modeNav = persistentModeNav ?? el("nav", "och-mode");
|
|
434
598
|
modeNav.setAttribute("aria-label", "产品模式");
|
|
435
|
-
|
|
436
|
-
|
|
599
|
+
const modeIcons = { chat: "radar", operate: "zap", inspect: "eye" };
|
|
600
|
+
let buttons = [...modeNav.querySelectorAll(".och-mode-btn")];
|
|
601
|
+
if (buttons.length !== model.modeButtons.length) {
|
|
602
|
+
modeNav.replaceChildren();
|
|
603
|
+
buttons = model.modeButtons.map((btn) => {
|
|
604
|
+
const a = el("a", "och-mode-btn");
|
|
605
|
+
a.dataset.mode = btn.id;
|
|
606
|
+
// 2026-08-22: inline-flex 布局下裸文本节点不参与 name-from-content,
|
|
607
|
+
// a11y snapshot 序列化为无名 link;显式 aria-label 保证「操作/观测」
|
|
608
|
+
// 对 find/读屏稳定可断言(frontend-test passed 权威依赖可见 find)。
|
|
609
|
+
a.setAttribute("aria-label", btn.label);
|
|
610
|
+
a.appendChild(makeIcon(modeIcons[btn.id] ?? "eye"));
|
|
611
|
+
a.appendChild(document.createTextNode(btn.label));
|
|
612
|
+
modeNav.appendChild(a);
|
|
613
|
+
return a;
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
for (const [index, btn] of model.modeButtons.entries()) {
|
|
617
|
+
const a = buttons[index];
|
|
437
618
|
a.href = btn.href;
|
|
438
619
|
a.dataset.mode = btn.id;
|
|
439
|
-
|
|
440
|
-
// a11y snapshot 序列化为无名 link;显式 aria-label 保证「操作/观测」
|
|
441
|
-
// 对 find/读屏稳定可断言(frontend-test passed 权威依赖可见 find)。
|
|
442
|
-
a.setAttribute("aria-label", btn.label);
|
|
620
|
+
a.classList.toggle("och-on", btn.active);
|
|
443
621
|
if (btn.active) a.setAttribute("aria-current", "page");
|
|
444
|
-
a.
|
|
445
|
-
a.appendChild(document.createTextNode(btn.label));
|
|
446
|
-
modeNav.appendChild(a);
|
|
622
|
+
else a.removeAttribute("aria-current");
|
|
447
623
|
}
|
|
624
|
+
/* DSH 2026-08-23:椭圆选中态 + 共享滑块(160-220ms),同一实现覆盖
|
|
625
|
+
* React(Operate/Chat 壳)与 vanilla(Inspect)。reduced-motion 下无动画。 */
|
|
626
|
+
let thumb = modeNav.querySelector(".och-mode-thumb");
|
|
627
|
+
const firstMount = !thumb;
|
|
628
|
+
if (!thumb) {
|
|
629
|
+
thumb = el("span", "och-mode-thumb");
|
|
630
|
+
thumb.setAttribute("aria-hidden", "true");
|
|
631
|
+
modeNav.appendChild(thumb);
|
|
632
|
+
}
|
|
633
|
+
const positionThumb = (mode) => {
|
|
634
|
+
const target = buttons.find((button) => button.dataset.mode === mode);
|
|
635
|
+
if (!target) {
|
|
636
|
+
thumb.style.opacity = "0";
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
thumb.style.opacity = "";
|
|
640
|
+
thumb.style.width = `${target.offsetWidth}px`;
|
|
641
|
+
thumb.style.height = `${target.offsetHeight}px`;
|
|
642
|
+
thumb.style.transform = `translate3d(${target.offsetLeft}px, ${target.offsetTop}px, 0)`;
|
|
643
|
+
};
|
|
448
644
|
global.appendChild(modeNav);
|
|
645
|
+
positionThumb(model.mode);
|
|
646
|
+
if (firstMount) {
|
|
647
|
+
/* The initial placement must not animate in from the origin. Every later
|
|
648
|
+
* update reuses this ready thumb and therefore transitions immediately from
|
|
649
|
+
* its last painted transform. */
|
|
650
|
+
requestAnimationFrame(() => thumb.classList.add("is-ready"));
|
|
651
|
+
}
|
|
449
652
|
|
|
450
653
|
if (model.context) {
|
|
451
654
|
const chip = el("div", "och-context");
|
|
@@ -477,7 +680,7 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
477
680
|
|
|
478
681
|
global.appendChild(el("span", "och-spacer"));
|
|
479
682
|
|
|
480
|
-
if (model.freshness) {
|
|
683
|
+
if (model.statusVisible && model.freshness) {
|
|
481
684
|
const fresh = el("span", "och-fresh");
|
|
482
685
|
fresh.setAttribute("aria-label", "数据新鲜度");
|
|
483
686
|
fresh.appendChild(el("span", "och-fresh-dot"));
|
|
@@ -489,6 +692,10 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
489
692
|
global.appendChild(fresh);
|
|
490
693
|
}
|
|
491
694
|
|
|
695
|
+
/* r4 AC-002:Chat 模式右侧保持干净 — 不渲染 repo 名与 Pi readiness 胶囊
|
|
696
|
+
* (多 Workspace 下 repo 身份归左栏行,readiness 归侧栏与横幅)。 */
|
|
697
|
+
const piHidden = !model.statusVisible || model.mode === "chat";
|
|
698
|
+
if (!piHidden) {
|
|
492
699
|
const pi = el("a", "och-pi");
|
|
493
700
|
pi.dataset.state = model.pi.state;
|
|
494
701
|
pi.href = model.pi.href;
|
|
@@ -499,65 +706,69 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
499
706
|
piState.textContent = model.pi.label;
|
|
500
707
|
pi.appendChild(piState);
|
|
501
708
|
global.appendChild(pi);
|
|
709
|
+
}
|
|
502
710
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
711
|
+
if (model.workspaceTabsVisible) {
|
|
712
|
+
/* 标签带折叠切换(收展两态同一按钮,位于全局带右端)。 */
|
|
713
|
+
const fold = el("button", "och-workspace-fold");
|
|
714
|
+
fold.type = "button";
|
|
715
|
+
fold.setAttribute("aria-expanded", String(!workspaceCollapsed));
|
|
716
|
+
fold.setAttribute("aria-controls", "och-workspace");
|
|
717
|
+
const foldLabel = workspaceCollapsed ? "展开标签栏" : "收起标签栏";
|
|
718
|
+
fold.title = foldLabel;
|
|
719
|
+
fold.setAttribute("aria-label", foldLabel);
|
|
720
|
+
if (workspaceCollapsed) fold.classList.add("is-folded");
|
|
721
|
+
fold.appendChild(makeIcon("chevronUp"));
|
|
722
|
+
fold.addEventListener("click", () => {
|
|
723
|
+
workspaceCollapsed = !workspaceCollapsed;
|
|
724
|
+
try {
|
|
725
|
+
window.localStorage.setItem(
|
|
726
|
+
OCH_WORKSPACE_COLLAPSED_KEY,
|
|
727
|
+
workspaceCollapsed ? "1" : "0",
|
|
728
|
+
);
|
|
729
|
+
} catch {
|
|
730
|
+
/* localStorage 不可用时仅保持内存态。 */
|
|
731
|
+
}
|
|
732
|
+
const ws = root.querySelector(".och-workspace");
|
|
733
|
+
const btn = root.querySelector(".och-workspace-fold");
|
|
734
|
+
if (ws) {
|
|
735
|
+
ws.classList.toggle(
|
|
736
|
+
"och-workspace-collapsed",
|
|
737
|
+
workspaceCollapsed,
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
if (btn) {
|
|
741
|
+
btn.classList.toggle("is-folded", workspaceCollapsed);
|
|
742
|
+
btn.setAttribute("aria-expanded", String(!workspaceCollapsed));
|
|
743
|
+
const label = workspaceCollapsed ? "展开标签栏" : "收起标签栏";
|
|
744
|
+
btn.title = label;
|
|
745
|
+
btn.setAttribute("aria-label", label);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
global.appendChild(fold);
|
|
749
|
+
|
|
750
|
+
/* Band 2 */
|
|
751
|
+
const workspace = el("div", "och-workspace");
|
|
752
|
+
workspace.id = "och-workspace";
|
|
753
|
+
if (workspaceCollapsed) {
|
|
754
|
+
workspace.classList.add("och-workspace-collapsed");
|
|
522
755
|
}
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
756
|
+
inner.appendChild(workspace);
|
|
757
|
+
const tabs = el("nav", "och-tabs");
|
|
758
|
+
tabs.setAttribute("aria-label", model.tabsAriaLabel);
|
|
759
|
+
for (const tab of model.tabs) {
|
|
760
|
+
const a = el(
|
|
761
|
+
"a",
|
|
762
|
+
`och-tab${tab.active ? " och-tab-active" : ""}`,
|
|
763
|
+
tab.label,
|
|
529
764
|
);
|
|
765
|
+
a.href = tab.href;
|
|
766
|
+
if (tab.active) a.setAttribute("aria-current", "page");
|
|
767
|
+
tabs.appendChild(a);
|
|
530
768
|
}
|
|
531
|
-
|
|
532
|
-
btn.classList.toggle("is-folded", workspaceCollapsed);
|
|
533
|
-
btn.setAttribute("aria-expanded", String(!workspaceCollapsed));
|
|
534
|
-
const label = workspaceCollapsed ? "展开标签栏" : "收起标签栏";
|
|
535
|
-
btn.title = label;
|
|
536
|
-
btn.setAttribute("aria-label", label);
|
|
537
|
-
}
|
|
538
|
-
});
|
|
539
|
-
global.appendChild(fold);
|
|
540
|
-
|
|
541
|
-
/* Band 2 */
|
|
542
|
-
const workspace = el("div", "och-workspace");
|
|
543
|
-
workspace.id = "och-workspace";
|
|
544
|
-
if (workspaceCollapsed) workspace.classList.add("och-workspace-collapsed");
|
|
545
|
-
inner.appendChild(workspace);
|
|
546
|
-
const tabs = el("nav", "och-tabs");
|
|
547
|
-
tabs.setAttribute("aria-label", model.tabsAriaLabel);
|
|
548
|
-
for (const tab of model.tabs) {
|
|
549
|
-
const a = el(
|
|
550
|
-
"a",
|
|
551
|
-
`och-tab${tab.active ? " och-tab-active" : ""}`,
|
|
552
|
-
tab.label,
|
|
553
|
-
);
|
|
554
|
-
a.href = tab.href;
|
|
555
|
-
if (tab.active) a.setAttribute("aria-current", "page");
|
|
556
|
-
tabs.appendChild(a);
|
|
769
|
+
workspace.appendChild(tabs);
|
|
557
770
|
}
|
|
558
|
-
workspace.appendChild(tabs);
|
|
559
771
|
|
|
560
|
-
root.appendChild(chrome);
|
|
561
772
|
}
|
|
562
773
|
|
|
563
774
|
function render() {
|
|
@@ -581,6 +792,25 @@ export function mountOperatorChrome(root, config = {}) {
|
|
|
581
792
|
}
|
|
582
793
|
|
|
583
794
|
function onDocumentClick(event) {
|
|
795
|
+
if (event.target instanceof Element) {
|
|
796
|
+
const link = event.target.closest("a");
|
|
797
|
+
if (
|
|
798
|
+
link &&
|
|
799
|
+
root.contains(link) &&
|
|
800
|
+
typeof currentConfig.onNavigate === "function" &&
|
|
801
|
+
event.button === 0 &&
|
|
802
|
+
!event.metaKey &&
|
|
803
|
+
!event.ctrlKey &&
|
|
804
|
+
!event.shiftKey &&
|
|
805
|
+
!event.altKey
|
|
806
|
+
) {
|
|
807
|
+
const href = link.getAttribute("href") || "";
|
|
808
|
+
if (href.startsWith("#") || href.startsWith("/#") || href.startsWith("/inspect")) {
|
|
809
|
+
event.preventDefault();
|
|
810
|
+
currentConfig.onNavigate(href);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
584
814
|
if (!popoverOpen || !(event.target instanceof Node)) return;
|
|
585
815
|
const repoBtn = root.querySelector(".och-repo");
|
|
586
816
|
const pop = root.querySelector(".och-pop");
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** 与 router.js 对应的类型声明(供 vitest/TS 侧导入契约测试)。 */
|
|
2
|
+
|
|
3
|
+
export type InspectHashRoute = {
|
|
4
|
+
view: string;
|
|
5
|
+
batchRunId?: string;
|
|
6
|
+
workerRunId?: string;
|
|
7
|
+
dagRunId?: string;
|
|
8
|
+
featureId?: string;
|
|
9
|
+
taskId?: string;
|
|
10
|
+
scheduleId?: string;
|
|
11
|
+
page?: number;
|
|
12
|
+
pageSize?: number;
|
|
13
|
+
filters?: { status?: string; q?: string };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function parseHashRoute(hashValue: string): InspectHashRoute;
|
|
17
|
+
export function parseRoute(): InspectHashRoute;
|
|
18
|
+
export function setNavigateHandler(
|
|
19
|
+
handler: ((href: string) => void) | null,
|
|
20
|
+
): void;
|
|
21
|
+
export function inspectPathToCanonicalHref(path: string): string;
|
|
22
|
+
export function navigate(path: string): void;
|
|
23
|
+
|
|
24
|
+
export const DAG_HISTORY_DEFAULT_PAGE: number;
|
|
25
|
+
export const DAG_HISTORY_DEFAULT_PAGE_SIZE: number;
|
|
26
|
+
export const DAG_HISTORY_ALLOWED_PAGE_SIZES: readonly number[];
|
|
27
|
+
|
|
28
|
+
export function clampDagHistoryPageSize(value: unknown): number;
|
|
29
|
+
export function clampDagHistoryPage(
|
|
30
|
+
value: unknown,
|
|
31
|
+
totalPages?: number,
|
|
32
|
+
): number;
|
|
33
|
+
export function parseDagHistoryQuery(hashValue: string): {
|
|
34
|
+
page: number;
|
|
35
|
+
pageSize: number;
|
|
36
|
+
};
|
|
37
|
+
export function buildDagHistoryHash(page: unknown, pageSize: unknown): string;
|
|
38
|
+
export function parseHashFilters(hashValue: string): {
|
|
39
|
+
status?: string;
|
|
40
|
+
q?: string;
|
|
41
|
+
};
|
|
42
|
+
export function parseHashRouteWithFilters(hashValue: string): InspectHashRoute;
|
|
43
|
+
export function buildHashPath(
|
|
44
|
+
path: string,
|
|
45
|
+
filters?: Record<string, unknown>,
|
|
46
|
+
): string;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/** Hash routing for Observe UI (R5). */
|
|
2
2
|
|
|
3
3
|
export function parseHashRoute(hashValue) {
|
|
4
|
-
|
|
4
|
+
let hash = (hashValue.replace(/^#/, "") || "/").split("?")[0] || "/";
|
|
5
|
+
if (!hash.startsWith("/")) hash = `/${hash}`;
|
|
6
|
+
if (hash === "/inspect") hash = "/";
|
|
7
|
+
else if (hash.startsWith("/inspect/")) hash = hash.slice("/inspect".length) || "/";
|
|
5
8
|
const batchMatch = /^\/batch\/([^/]+)$/.exec(hash);
|
|
6
9
|
if (batchMatch) {
|
|
7
10
|
return { view: "batch", batchRunId: decodeURIComponent(batchMatch[1]) };
|
|
@@ -61,7 +64,28 @@ export function parseRoute() {
|
|
|
61
64
|
return parseHashRouteWithFilters(location.hash);
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
let navigateHandler = null;
|
|
68
|
+
|
|
69
|
+
export function setNavigateHandler(handler) {
|
|
70
|
+
navigateHandler = typeof handler === "function" ? handler : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function inspectPathToCanonicalHref(path) {
|
|
74
|
+
const raw = String(path ?? "");
|
|
75
|
+
if (raw.startsWith("/#/inspect")) return raw;
|
|
76
|
+
if (raw.startsWith("#/inspect")) return `/${raw}`;
|
|
77
|
+
const stripped = raw.replace(/^#/, "");
|
|
78
|
+
const withSlash = stripped.startsWith("/") ? stripped : `/${stripped}`;
|
|
79
|
+
if (withSlash === "/inspect" || withSlash === "/") return "/#/inspect";
|
|
80
|
+
if (withSlash.startsWith("/inspect/")) return `/#${withSlash}`;
|
|
81
|
+
return `/#/inspect${withSlash}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
export function navigate(path) {
|
|
85
|
+
if (navigateHandler) {
|
|
86
|
+
navigateHandler(inspectPathToCanonicalHref(path));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
65
89
|
location.hash = path.startsWith("#") ? path.slice(1) : path;
|
|
66
90
|
}
|
|
67
91
|
|