@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
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo-scoped Workspace Context (2026-08-22 multi-workspace foundation, W1).
|
|
3
|
+
*
|
|
4
|
+
* One lazily-created ConsoleContext per workspaceId. Each context INDEPENDENTLY
|
|
5
|
+
* owns the full operator runtime for its repo root: fingerprint-isolated
|
|
6
|
+
* appData (via openConsoleAppData), operation/event stores, draft /
|
|
7
|
+
* confirmation / receipt / interview / assessment stores, Chat session store +
|
|
8
|
+
* events + deferred Turn registry + ConsolePiRuntime(cwd=repoRoot),
|
|
9
|
+
* OperatorActionContext, Observe route context, and readiness.
|
|
10
|
+
*
|
|
11
|
+
* Invariants:
|
|
12
|
+
* - Selecting (viewing) another workspace NEVER disposes a context; running
|
|
13
|
+
* Turns / Operations / DAGs continue in the background.
|
|
14
|
+
* - dispose() is invoked exactly once per context by the owning server's
|
|
15
|
+
* close().
|
|
16
|
+
* - bootToken is shared per Console boot: it binds the startup fingerprint
|
|
17
|
+
* and bind host, and its confirmationToken signs human-gate tokens for all
|
|
18
|
+
* workspaces in this process (single Console security domain).
|
|
19
|
+
*/
|
|
20
|
+
import { randomBytes } from "node:crypto";
|
|
21
|
+
import { existsSync } from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { LoopAgentClient } from "../loop-agent/loop-agent-client.js";
|
|
24
|
+
import { openConsoleAppData } from "./app-data.js";
|
|
25
|
+
import { DagConfirmationStore } from "./dag-confirmation.js";
|
|
26
|
+
import { DagExecutionReceiptStore } from "./dag-execution-receipt.js";
|
|
27
|
+
import { DraftStore } from "./draft-store.js";
|
|
28
|
+
import { AssessmentStore } from "./interview/assessment.js";
|
|
29
|
+
import { InterviewSessionStore } from "./interview/session.js";
|
|
30
|
+
import { ChatSessionStore } from "./chat/session-store.js";
|
|
31
|
+
import { createChatEventStore, createChatOperationLinker, } from "./chat/chat-event-store.js";
|
|
32
|
+
import { createChatTurnExecutionRegistry } from "./chat/turn-execution-registry.js";
|
|
33
|
+
import { ConsolePiRuntime } from "./chat/pi-runtime.js";
|
|
34
|
+
import { UserQuestionRegistry } from "./chat/user-questions.js";
|
|
35
|
+
import { TodoListRegistry } from "./chat/todos.js";
|
|
36
|
+
import { createOperationEventStore, } from "./operation-sse.js";
|
|
37
|
+
import { OperationStore } from "./operation-store.js";
|
|
38
|
+
import { probePiReadiness, } from "./pi-readiness.js";
|
|
39
|
+
import { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
|
|
40
|
+
import { NightAuxTicker } from "./night-aux-ticker.js";
|
|
41
|
+
import { createObserveRouteContext, } from "../observe/routes.js";
|
|
42
|
+
import { WorkspaceRegistry } from "./workspace-registry.js";
|
|
43
|
+
import { resolveConsoleDefaultTimeoutMs } from "./server.js";
|
|
44
|
+
/**
|
|
45
|
+
* Lazily create the full repo-scoped Console context for a registered
|
|
46
|
+
* workspace record. Pure factory — no global registry; the caller owns
|
|
47
|
+
* lifecycle (single creation per workspaceId, dispose on server close).
|
|
48
|
+
*/
|
|
49
|
+
export async function createWorkspaceContext(record, deps) {
|
|
50
|
+
const repoRoot = record.repoRoot;
|
|
51
|
+
const appData = openConsoleAppData({
|
|
52
|
+
repoRoot,
|
|
53
|
+
...(deps.appDataRoot ? { appDataRoot: deps.appDataRoot } : {}),
|
|
54
|
+
});
|
|
55
|
+
const registry = new WorkspaceRegistry(appData);
|
|
56
|
+
const readiness = await probePiReadiness({
|
|
57
|
+
override: deps.piReadinessProbe,
|
|
58
|
+
repoRoot,
|
|
59
|
+
});
|
|
60
|
+
let readinessCache = readiness;
|
|
61
|
+
const artifactRoot = path.join(appData.root, "client-artifacts", appData.fingerprint);
|
|
62
|
+
const client = deps.createClient?.(artifactRoot) ??
|
|
63
|
+
new LoopAgentClient({
|
|
64
|
+
loopAgentBin: resolveSiblingLoopAgentBin(),
|
|
65
|
+
artifactRoot,
|
|
66
|
+
resolveIdentity: true,
|
|
67
|
+
defaultTimeoutMs: resolveConsoleDefaultTimeoutMs(deps.defaultTimeoutMs),
|
|
68
|
+
});
|
|
69
|
+
const operations = new OperationStore(appData);
|
|
70
|
+
const events = createOperationEventStore({
|
|
71
|
+
persistenceDir: path.join(appData.operations, "events"),
|
|
72
|
+
});
|
|
73
|
+
const drafts = new DraftStore(appData);
|
|
74
|
+
const confirmations = new DagConfirmationStore(appData, deps.bootToken.confirmationToken);
|
|
75
|
+
const dagExecutionReceipts = new DagExecutionReceiptStore(appData);
|
|
76
|
+
const interviews = new InterviewSessionStore(appData);
|
|
77
|
+
const assessments = new AssessmentStore(appData);
|
|
78
|
+
if (!deps.skipReconcile) {
|
|
79
|
+
await operations.reconcileOnBoot();
|
|
80
|
+
}
|
|
81
|
+
const getReadiness = () => readinessCache;
|
|
82
|
+
const refreshReadiness = async () => {
|
|
83
|
+
readinessCache = await probePiReadiness({
|
|
84
|
+
override: deps.piReadinessProbe,
|
|
85
|
+
repoRoot,
|
|
86
|
+
});
|
|
87
|
+
actionContext.readiness = readinessCache;
|
|
88
|
+
return readinessCache;
|
|
89
|
+
};
|
|
90
|
+
const actionContext = {
|
|
91
|
+
repoRoot,
|
|
92
|
+
appData,
|
|
93
|
+
operatorSessionId: deps.operatorSessionId,
|
|
94
|
+
client,
|
|
95
|
+
operations,
|
|
96
|
+
events,
|
|
97
|
+
drafts,
|
|
98
|
+
confirmations,
|
|
99
|
+
dagExecutionReceipts,
|
|
100
|
+
humanGateSecret: deps.bootToken.confirmationToken,
|
|
101
|
+
interviews,
|
|
102
|
+
assessments,
|
|
103
|
+
readiness: readinessCache,
|
|
104
|
+
getReadiness,
|
|
105
|
+
refreshReadiness,
|
|
106
|
+
observeBaseUrl: deps.observeBaseUrl,
|
|
107
|
+
fetchImpl: deps.fetchImpl,
|
|
108
|
+
getConsoleOrigin: deps.getConsoleOrigin,
|
|
109
|
+
};
|
|
110
|
+
const chatStore = new ChatSessionStore(appData);
|
|
111
|
+
const chatEventEpoch = randomBytes(12).toString("hex");
|
|
112
|
+
const chatEvents = createChatEventStore({
|
|
113
|
+
appData,
|
|
114
|
+
epoch: chatEventEpoch,
|
|
115
|
+
});
|
|
116
|
+
const chatTurnExecutions = createChatTurnExecutionRegistry();
|
|
117
|
+
const questionRegistry = new UserQuestionRegistry(path.join(appData.chats, "user-questions"));
|
|
118
|
+
const todoRegistry = new TodoListRegistry();
|
|
119
|
+
questionRegistry.subscribe((change) => {
|
|
120
|
+
const question = change.question;
|
|
121
|
+
chatEvents.append(question.sessionId, question.turnId ?? `${question.sessionId}:question`, {
|
|
122
|
+
kind: `question_${change.type}`,
|
|
123
|
+
data: { question },
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
todoRegistry.subscribe((change) => {
|
|
127
|
+
chatEvents.append(change.sessionId, change.turnId ?? `${change.sessionId}:todo`, {
|
|
128
|
+
kind: change.type === "updated" ? "todo_updated" : "todo_cleared",
|
|
129
|
+
data: {
|
|
130
|
+
todos: change.todos,
|
|
131
|
+
...(change.toolCallId ? { toolCallId: change.toolCallId } : {}),
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
const chatOperationLinker = createChatOperationLinker({
|
|
136
|
+
chatEvents,
|
|
137
|
+
operations,
|
|
138
|
+
operationEvents: events,
|
|
139
|
+
});
|
|
140
|
+
const chatRuntime = new ConsolePiRuntime({
|
|
141
|
+
cwd: repoRoot,
|
|
142
|
+
sessionDir: appData.chatSessions,
|
|
143
|
+
resourcePreferencesPath: appData.resourcePreferences,
|
|
144
|
+
actionContext,
|
|
145
|
+
questionRegistry,
|
|
146
|
+
todoRegistry,
|
|
147
|
+
});
|
|
148
|
+
const chatRouteDeps = {
|
|
149
|
+
runtime: chatRuntime,
|
|
150
|
+
store: chatStore,
|
|
151
|
+
events: chatEvents,
|
|
152
|
+
turnExecutions: chatTurnExecutions,
|
|
153
|
+
questionRegistry,
|
|
154
|
+
todoRegistry,
|
|
155
|
+
operationLinker: chatOperationLinker,
|
|
156
|
+
appData,
|
|
157
|
+
bootToken: deps.bootToken,
|
|
158
|
+
getConsoleOrigin: deps.getConsoleOrigin,
|
|
159
|
+
allowNonLoopbackAccess: deps.allowNonLoopbackAccess,
|
|
160
|
+
};
|
|
161
|
+
const { recoverDeferredTurns } = await import("./chat/routes.js");
|
|
162
|
+
const nightAuxTicker = new NightAuxTicker({ repoRoot, client });
|
|
163
|
+
const observeRouteContext = createObserveRouteContext(repoRoot, {
|
|
164
|
+
staticDir: deps.inspectStaticDir,
|
|
165
|
+
});
|
|
166
|
+
let disposed = false;
|
|
167
|
+
const context = {
|
|
168
|
+
workspaceId: record.workspaceId,
|
|
169
|
+
record,
|
|
170
|
+
repoRoot,
|
|
171
|
+
appData,
|
|
172
|
+
registry,
|
|
173
|
+
client,
|
|
174
|
+
operations,
|
|
175
|
+
events,
|
|
176
|
+
drafts,
|
|
177
|
+
confirmations,
|
|
178
|
+
dagExecutionReceipts,
|
|
179
|
+
interviews,
|
|
180
|
+
assessments,
|
|
181
|
+
chatRouteDeps,
|
|
182
|
+
chatOperationLinker,
|
|
183
|
+
chatRuntime,
|
|
184
|
+
nightAuxTicker,
|
|
185
|
+
actionContext,
|
|
186
|
+
observeRouteContext,
|
|
187
|
+
getReadiness,
|
|
188
|
+
refreshReadiness,
|
|
189
|
+
recoverDeferredTurns: () => recoverDeferredTurns(chatRouteDeps),
|
|
190
|
+
dispose: () => {
|
|
191
|
+
if (disposed)
|
|
192
|
+
return;
|
|
193
|
+
disposed = true;
|
|
194
|
+
try {
|
|
195
|
+
chatOperationLinker.dispose();
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// best-effort
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
chatRuntime.disposeAll();
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// best-effort
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
nightAuxTicker.dispose();
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// best-effort
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
return context;
|
|
215
|
+
}
|
|
216
|
+
/** Inspect-facing readiness/capability summary for a registry record. */
|
|
217
|
+
export async function summarizeWorkspaceRecord(record, deps) {
|
|
218
|
+
let state;
|
|
219
|
+
try {
|
|
220
|
+
const report = await probePiReadiness({
|
|
221
|
+
override: deps.piReadinessProbe,
|
|
222
|
+
repoRoot: record.repoRoot,
|
|
223
|
+
});
|
|
224
|
+
state = report.state;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
state = "setup-required";
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
state,
|
|
231
|
+
summary: `pi=${state}; inspect=${deps.inspectReady ? "ready" : "unavailable"}`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
void existsSync;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-level Workspace Registry (2026-08-22 multi-workspace foundation, W1).
|
|
3
|
+
*
|
|
4
|
+
* Persists the set of registered Console workspaces at
|
|
5
|
+
* `<appDataRoot>/workspaces.json` (user level, shared across Console boots),
|
|
6
|
+
* independent of per-repo fingerprint namespaces.
|
|
7
|
+
*
|
|
8
|
+
* Invariants:
|
|
9
|
+
* - workspaceId is stable per normalized realpath: it is derived from
|
|
10
|
+
* sha256("loop-console-workspace-v1\0" + realpath) and never minted twice.
|
|
11
|
+
* - REMOVE is soft (hidden=true); on-disk directories, AppData namespaces,
|
|
12
|
+
* Sessions, Operations and DAGs are never deleted.
|
|
13
|
+
* - Re-adding a previously removed workspace RESTORES the original record
|
|
14
|
+
* (same workspaceId + repoFingerprint; only lastAccessedAt advances).
|
|
15
|
+
* - Duplicate visible realpath is rejected; add failures leave the registry
|
|
16
|
+
* byte-identical (same ids and order).
|
|
17
|
+
*/
|
|
18
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
19
|
+
import { realpathSync, statSync } from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { readJsonIfExists, writeSecureJson, } from "./app-data.js";
|
|
22
|
+
import { normalizeWorktreeRealpath, repoFingerprintV1, } from "./repo-fingerprint.js";
|
|
23
|
+
export const WORKSPACE_REGISTRY_SCHEMA_VERSION = 1;
|
|
24
|
+
/** Stable workspaceId: sha256("loop-console-workspace-v1\0" + normalizedRealpath). */
|
|
25
|
+
export function workspaceIdForRealpath(normalizedRealpath) {
|
|
26
|
+
return createHash("sha256")
|
|
27
|
+
.update(`loop-console-workspace-v1\0${normalizedRealpath}`, "utf8")
|
|
28
|
+
.digest("hex");
|
|
29
|
+
}
|
|
30
|
+
export class WorkspaceRegistry {
|
|
31
|
+
appData;
|
|
32
|
+
options;
|
|
33
|
+
file;
|
|
34
|
+
cache;
|
|
35
|
+
constructor(appData, options = {}) {
|
|
36
|
+
this.appData = appData;
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.file = path.join(appData.root, "workspaces.json");
|
|
39
|
+
}
|
|
40
|
+
/** Auto-register the startup repoRoot (idempotent). */
|
|
41
|
+
async ensureStartupWorkspace(repoRoot) {
|
|
42
|
+
const record = await this.add(repoRoot);
|
|
43
|
+
if (record.ok)
|
|
44
|
+
return record.record;
|
|
45
|
+
// Startup root may already be visible (duplicate) or previously hidden —
|
|
46
|
+
// re-adding a hidden record restores it via add(); the only expected
|
|
47
|
+
// failure left is DUPLICATE (already visible), which returns the existing.
|
|
48
|
+
const existing = await this.findByRealpath(repoRoot);
|
|
49
|
+
if (existing)
|
|
50
|
+
return existing;
|
|
51
|
+
throw new Error(`workspace registry: startup root rejected: ${record.code}`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register (or restore) a workspace directory.
|
|
55
|
+
* Failures never mutate the persisted registry.
|
|
56
|
+
* `options.name` overrides the default directory-name label.
|
|
57
|
+
*/
|
|
58
|
+
async add(rawPath, options = {}) {
|
|
59
|
+
const trimmed = rawPath.trim();
|
|
60
|
+
if (!trimmed) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
code: "INVALID_PATH",
|
|
64
|
+
message: "path is required",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
let real;
|
|
68
|
+
try {
|
|
69
|
+
real = realpathSync(path.resolve(trimmed));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
code: "NOT_A_DIRECTORY",
|
|
75
|
+
message: `path does not exist: ${trimmed}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
let st;
|
|
79
|
+
try {
|
|
80
|
+
st = statSync(real);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
code: "UNREADABLE",
|
|
86
|
+
message: `path is not readable: ${trimmed}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (!st.isDirectory()) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
code: "NOT_A_DIRECTORY",
|
|
93
|
+
message: `path is not a directory: ${trimmed}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const normalized = normalizeWorktreeRealpath(real);
|
|
97
|
+
const existing = await this.findByRealpathNormalized(normalized);
|
|
98
|
+
if (existing && !existing.hidden) {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
code: "DUPLICATE",
|
|
102
|
+
message: `workspace already registered: ${existing.displayPath}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const nowIso = (this.options.now ?? (() => new Date()))().toISOString();
|
|
106
|
+
if (existing) {
|
|
107
|
+
// Restore the soft-removed record; identity (workspaceId,
|
|
108
|
+
// repoFingerprint, createdAt, order) is preserved.
|
|
109
|
+
existing.hidden = undefined;
|
|
110
|
+
existing.lastAccessedAt = nowIso;
|
|
111
|
+
if (options.name && options.name.trim()) {
|
|
112
|
+
existing.name = options.name.trim();
|
|
113
|
+
}
|
|
114
|
+
const registry = await this.load();
|
|
115
|
+
await this.persist(registry);
|
|
116
|
+
return { ok: true, record: existing, registryUnchanged: false };
|
|
117
|
+
}
|
|
118
|
+
const record = {
|
|
119
|
+
workspaceId: workspaceIdForRealpath(normalized),
|
|
120
|
+
name: options.name?.trim() || path.basename(normalized) || normalized,
|
|
121
|
+
displayPath: normalized,
|
|
122
|
+
repoRoot: real,
|
|
123
|
+
repoFingerprint: repoFingerprintV1(real),
|
|
124
|
+
createdAt: nowIso,
|
|
125
|
+
lastAccessedAt: nowIso,
|
|
126
|
+
};
|
|
127
|
+
const registry = await this.load();
|
|
128
|
+
registry.workspaces.push(record);
|
|
129
|
+
await this.persist(registry);
|
|
130
|
+
return { ok: true, record, registryUnchanged: false };
|
|
131
|
+
}
|
|
132
|
+
/** Visible list in canonical insertion order. */
|
|
133
|
+
async listVisible() {
|
|
134
|
+
const registry = await this.load();
|
|
135
|
+
return registry.workspaces.filter((workspace) => {
|
|
136
|
+
if (workspace.hidden)
|
|
137
|
+
return false;
|
|
138
|
+
try {
|
|
139
|
+
return statSync(workspace.repoRoot).isDirectory();
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Keep historical registry data, but do not project stale test roots
|
|
143
|
+
// into the Workspace browser after their directories disappear.
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async get(workspaceId) {
|
|
149
|
+
const registry = await this.load();
|
|
150
|
+
return registry.workspaces.find((w) => w.workspaceId === workspaceId);
|
|
151
|
+
}
|
|
152
|
+
/** Get a VISIBLE workspace by id (removed workspaces are not operable). */
|
|
153
|
+
async getVisible(workspaceId) {
|
|
154
|
+
const record = await this.get(workspaceId);
|
|
155
|
+
if (!record || record.hidden)
|
|
156
|
+
return undefined;
|
|
157
|
+
try {
|
|
158
|
+
return statSync(record.repoRoot).isDirectory() ? record : undefined;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Soft-remove: hides the record; never touches directories or AppData. */
|
|
165
|
+
async remove(workspaceId) {
|
|
166
|
+
const registry = await this.load();
|
|
167
|
+
const record = registry.workspaces.find((w) => w.workspaceId === workspaceId);
|
|
168
|
+
if (!record || record.hidden)
|
|
169
|
+
return false;
|
|
170
|
+
record.hidden = true;
|
|
171
|
+
await this.persist(registry);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
async findByRealpath(rawPath) {
|
|
175
|
+
let normalized;
|
|
176
|
+
try {
|
|
177
|
+
normalized = normalizeWorktreeRealpath(path.resolve(rawPath.trim()));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
return this.findByRealpathNormalized(normalized);
|
|
183
|
+
}
|
|
184
|
+
async findByRealpathNormalized(normalized) {
|
|
185
|
+
const registry = await this.load();
|
|
186
|
+
return registry.workspaces.find((w) => w.displayPath === normalized);
|
|
187
|
+
}
|
|
188
|
+
async load() {
|
|
189
|
+
if (this.cache)
|
|
190
|
+
return this.cache;
|
|
191
|
+
const raw = await readJsonIfExists(this.file).catch(() => undefined);
|
|
192
|
+
const registry = {
|
|
193
|
+
schemaVersion: 1,
|
|
194
|
+
workspaces: raw && Array.isArray(raw.workspaces)
|
|
195
|
+
? raw.workspaces.filter((w) => Boolean(w) &&
|
|
196
|
+
typeof w === "object" &&
|
|
197
|
+
typeof w.workspaceId === "string" &&
|
|
198
|
+
typeof w.displayPath === "string" &&
|
|
199
|
+
typeof w.repoRoot === "string")
|
|
200
|
+
: [],
|
|
201
|
+
};
|
|
202
|
+
this.cache = registry;
|
|
203
|
+
return registry;
|
|
204
|
+
}
|
|
205
|
+
async persist(registry) {
|
|
206
|
+
// writeSecureJson is atomic; a reader mid-write sees the previous bytes.
|
|
207
|
+
await writeSecureJson(this.file, registry);
|
|
208
|
+
this.cache = registry;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Deterministic-looking nonce reserved for tests requiring unique roots. */
|
|
212
|
+
export function mintWorkspaceNonce() {
|
|
213
|
+
return randomBytes(8).toString("hex");
|
|
214
|
+
}
|
|
@@ -83,6 +83,10 @@ export function createObserveServer(options) {
|
|
|
83
83
|
url: `http://${host}:${addr.port}`,
|
|
84
84
|
close: () => new Promise((closeResolve, closeReject) => {
|
|
85
85
|
server.close((err) => (err ? closeReject(err) : closeResolve()));
|
|
86
|
+
// Explicit shutdown must not wait for the 65s keep-alive window.
|
|
87
|
+
// This also closes active SSE clients so process/test teardown is
|
|
88
|
+
// deterministic instead of timing out behind pooled connections.
|
|
89
|
+
server.closeAllConnections();
|
|
86
90
|
}),
|
|
87
91
|
});
|
|
88
92
|
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/** Fetch and artifact helpers (Observe UI R5). */
|
|
2
|
+
import {
|
|
3
|
+
fetchWithWorkspaceScope,
|
|
4
|
+
workspaceScope,
|
|
5
|
+
} from "./state.js";
|
|
2
6
|
|
|
3
7
|
export async function fetchJson(url) {
|
|
4
8
|
try {
|
|
5
|
-
const res = await
|
|
9
|
+
const res = await fetchWithWorkspaceScope(url);
|
|
6
10
|
if (!res.ok) return null;
|
|
7
11
|
return await res.json();
|
|
8
12
|
} catch {
|
|
@@ -18,7 +22,10 @@ export async function fetchJson(url) {
|
|
|
18
22
|
export async function fetchJsonResult(url, options = {}) {
|
|
19
23
|
const signal = options?.signal;
|
|
20
24
|
try {
|
|
21
|
-
const res = await
|
|
25
|
+
const res = await fetchWithWorkspaceScope(
|
|
26
|
+
url,
|
|
27
|
+
signal ? { signal } : undefined,
|
|
28
|
+
);
|
|
22
29
|
let body = null;
|
|
23
30
|
try {
|
|
24
31
|
body = await res.json();
|
|
@@ -92,7 +99,7 @@ export async function fetchDagNodeProcess(runId, nodeId, key) {
|
|
|
92
99
|
*/
|
|
93
100
|
export async function fetchSpecEvidenceFile(runId, nodeId, source, relPath) {
|
|
94
101
|
const url = `/api/dag-runs/${encodeURIComponent(runId)}/nodes/${encodeURIComponent(nodeId)}/spec-evidence/file?source=${encodeURIComponent(source)}&path=${encodeURIComponent(relPath)}`;
|
|
95
|
-
const res = await
|
|
102
|
+
const res = await fetchWithWorkspaceScope(url);
|
|
96
103
|
let body = null;
|
|
97
104
|
try {
|
|
98
105
|
body = await res.json();
|
|
@@ -130,15 +137,28 @@ export async function postOperatorAction(action, actionParams = {}) {
|
|
|
130
137
|
body: { error: { code: "PROMPT_OVERRIDE_DISABLED", message: "提示词重启未启用(服务端未注入确认 token)" } },
|
|
131
138
|
};
|
|
132
139
|
}
|
|
140
|
+
const workspaceId = workspaceScope();
|
|
141
|
+
if (!workspaceId) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
status: 400,
|
|
145
|
+
body: {
|
|
146
|
+
error: {
|
|
147
|
+
code: "WORKSPACE_REQUIRED",
|
|
148
|
+
message: "workspaceId is required",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
133
153
|
try {
|
|
134
|
-
const res = await
|
|
154
|
+
const res = await fetchWithWorkspaceScope("/api/operator/v1/operations", {
|
|
135
155
|
method: "POST",
|
|
136
156
|
credentials: "same-origin",
|
|
137
157
|
headers: {
|
|
138
158
|
"Content-Type": "application/json",
|
|
139
159
|
"x-loop-console-confirmation": token,
|
|
140
160
|
},
|
|
141
|
-
body: JSON.stringify({ action, actionParams }),
|
|
161
|
+
body: JSON.stringify({ workspaceId, action, actionParams }),
|
|
142
162
|
});
|
|
143
163
|
let body = null;
|
|
144
164
|
try {
|
|
@@ -155,7 +175,7 @@ export async function postOperatorAction(action, actionParams = {}) {
|
|
|
155
175
|
/** Poll a Console operation snapshot (read-only GET). */
|
|
156
176
|
export async function fetchOperatorOperation(operationId) {
|
|
157
177
|
try {
|
|
158
|
-
const res = await
|
|
178
|
+
const res = await fetchWithWorkspaceScope(
|
|
159
179
|
`/api/operator/v1/operations/${encodeURIComponent(operationId)}`,
|
|
160
180
|
{ credentials: "same-origin" },
|
|
161
181
|
);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
/** Observe UI bootstrap:
|
|
1
|
+
/** Observe UI bootstrap: standalone compatibility entry + re-exports. */
|
|
2
2
|
import {
|
|
3
3
|
parseHashRoute,
|
|
4
4
|
parseHashRouteWithFilters,
|
|
5
5
|
buildHashPath,
|
|
6
|
-
parseRoute,
|
|
7
6
|
} from "./router.js";
|
|
8
7
|
import { parseArtifactPreviewResponse } from "./api.js";
|
|
9
8
|
import {
|
|
@@ -15,7 +14,6 @@ import {
|
|
|
15
14
|
} from "./relations.js";
|
|
16
15
|
import { copyText, renderRecommendedCommand } from "./copy.js";
|
|
17
16
|
import {
|
|
18
|
-
isPageVisible,
|
|
19
17
|
detailPollDelay,
|
|
20
18
|
updateDagGraphViewportState,
|
|
21
19
|
captureDagGraphViewportState,
|
|
@@ -25,11 +23,9 @@ import {
|
|
|
25
23
|
shouldReuseDagDetailShell,
|
|
26
24
|
shouldPreserveDiagnosticsOpen,
|
|
27
25
|
isInteractiveGestureActive,
|
|
28
|
-
stopTimers,
|
|
29
26
|
renderViewState,
|
|
30
27
|
mountViewState,
|
|
31
28
|
viewStateDescriptor,
|
|
32
|
-
uiState,
|
|
33
29
|
} from "./state.js";
|
|
34
30
|
import { sortPoolTasks, poolStatusCounts } from "./format-pool.js";
|
|
35
31
|
import {
|
|
@@ -45,34 +41,22 @@ import {
|
|
|
45
41
|
dashboardVisibleFeatures,
|
|
46
42
|
findDagForRun,
|
|
47
43
|
} from "./dag-model.js";
|
|
48
|
-
|
|
49
|
-
import { renderFailures } from "./views/failures.js";
|
|
50
|
-
import { startPoolView } from "./views/pool.js";
|
|
51
|
-
import { startDashboardPolling } from "./views/dashboard.js";
|
|
52
|
-
import { startDagsView } from "./views/dags.js";
|
|
53
|
-
import { renderFeatureDetail } from "./views/feature.js";
|
|
54
|
-
import { renderBatch } from "./views/batch.js";
|
|
55
|
-
import { startTaskPolling } from "./views/task.js";
|
|
56
|
-
import { startRunPolling } from "./views/run.js";
|
|
57
|
-
import { startDagPolling } from "./views/dag.js";
|
|
58
44
|
import { renderDagGraph } from "./views/dag-graph.js";
|
|
59
|
-
import { renderNightJobs } from "./views/night.js";
|
|
60
45
|
import {
|
|
61
46
|
selectDagNode,
|
|
62
47
|
closeDagInspector,
|
|
63
|
-
closeDagInspectorOnOutsideClick,
|
|
64
48
|
renderDagInspector,
|
|
65
49
|
} from "./views/dag-inspector.js";
|
|
66
50
|
import {
|
|
67
51
|
mergeSessionEvents,
|
|
68
52
|
renderSessionTimeline,
|
|
69
53
|
} from "./views/session-timeline.js";
|
|
70
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
route,
|
|
56
|
+
startStandaloneInspect,
|
|
57
|
+
tryCanonicalizeStandaloneInspect,
|
|
58
|
+
} from "./inspect-workspace.js";
|
|
71
59
|
|
|
72
|
-
// Compatibility re-exports for poll-stable UI helpers (owned by state.js / views):
|
|
73
|
-
// reuse-dag-detail-shell, reuse-dag-graph-wrap, existingGraphWrap?.parentNode === ranksEl,
|
|
74
|
-
// existingDetails?.parentNode === metaEl, let pre = existingPre, computeFollowLatest,
|
|
75
|
-
// updateDagOutputViewportState, isInteractiveGestureActive.
|
|
76
60
|
export {
|
|
77
61
|
detailPollDelay,
|
|
78
62
|
parseMarkdownBlocks,
|
|
@@ -114,56 +98,20 @@ export {
|
|
|
114
98
|
renderDagInspector,
|
|
115
99
|
renderSessionTimeline,
|
|
116
100
|
mergeSessionEvents,
|
|
101
|
+
route,
|
|
102
|
+
tryCanonicalizeStandaloneInspect,
|
|
117
103
|
};
|
|
118
104
|
|
|
119
|
-
function route() {
|
|
120
|
-
stopTimers();
|
|
121
|
-
const r = parseRoute();
|
|
122
|
-
if (r.view === "dashboard") {
|
|
123
|
-
startDashboardPolling(r.scrollTo);
|
|
124
|
-
} else if (r.view === "dags") {
|
|
125
|
-
void startDagsView();
|
|
126
|
-
} else if (r.view === "batch") {
|
|
127
|
-
void renderBatch(r.batchRunId);
|
|
128
|
-
} else if (r.view === "run") {
|
|
129
|
-
startRunPolling(r.workerRunId);
|
|
130
|
-
} else if (r.view === "dag") {
|
|
131
|
-
startDagPolling(r.dagRunId);
|
|
132
|
-
} else if (r.view === "failures") {
|
|
133
|
-
void renderFailures();
|
|
134
|
-
} else if (r.view === "pool") {
|
|
135
|
-
startPoolView();
|
|
136
|
-
} else if (r.view === "night") {
|
|
137
|
-
void renderNightJobs(r.scheduleId);
|
|
138
|
-
} else if (r.view === "task") {
|
|
139
|
-
startTaskPolling(r.featureId, r.taskId);
|
|
140
|
-
} else if (r.view === "feature") {
|
|
141
|
-
void renderFeatureDetail(r.featureId);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
105
|
if (typeof window !== "undefined") {
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
106
|
+
// Compatibility listeners remain owned by inspect-workspace.js:
|
|
107
|
+
// window.addEventListener("hashchange", route)
|
|
108
|
+
// window.addEventListener("load", route)
|
|
109
|
+
// document.addEventListener("pointerdown", closeDagInspectorOnOutsideClick)
|
|
110
|
+
// document.addEventListener("visibilitychange", ...)
|
|
111
|
+
// reuse-dag-detail-shell, reuse-dag-graph-wrap, existingGraphWrap?.parentNode === ranksEl,
|
|
112
|
+
// existingDetails?.parentNode === metaEl, let pre = existingPre, computeFollowLatest,
|
|
113
|
+
// updateDagOutputViewportState, isInteractiveGestureActive.
|
|
114
|
+
if (!tryCanonicalizeStandaloneInspect(window.location)) {
|
|
115
|
+
startStandaloneInspect();
|
|
155
116
|
}
|
|
156
|
-
window.addEventListener("hashchange", route);
|
|
157
|
-
window.addEventListener("load", route);
|
|
158
|
-
document.addEventListener("pointerdown", closeDagInspectorOnOutsideClick);
|
|
159
|
-
document.addEventListener("visibilitychange", () => {
|
|
160
|
-
if (!isPageVisible()) return;
|
|
161
|
-
const r = parseRoute();
|
|
162
|
-
if (r.view === "dashboard") startDashboardPolling();
|
|
163
|
-
// History page is manual-refresh only (no 5s dashboard polling).
|
|
164
|
-
if (r.view === "run") startRunPolling(r.workerRunId, false);
|
|
165
|
-
if (r.view === "dag") startDagPolling(r.dagRunId, false);
|
|
166
|
-
if (r.view === "pool" && uiState.poolAutoRefresh) startPoolView();
|
|
167
|
-
if (r.view === "task") startTaskPolling(r.featureId, r.taskId);
|
|
168
|
-
});
|
|
169
117
|
}
|