@tea-agent/loop-agent 0.40.0-next.8 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +21 -20
- package/README.md +2 -2
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +9 -5
- package/dist/cli/program.js +38 -3
- package/dist/commands/client-recovery.js +8 -36
- package/dist/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/executors/dag-pi-executor.js +3 -0
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +79 -6
- package/dist/executors/shell-executor.js +94 -20
- package/dist/governance/checks.js +1 -0
- package/dist/shared/operator/capabilities.js +9 -1
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/worker/console/chat/assistant-content.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +82 -11
- package/dist/worker/console/chat/goal-round-driver.js +72 -0
- package/dist/worker/console/chat/goals.js +300 -0
- package/dist/worker/console/chat/pi-runtime.js +285 -8
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/routes.js +495 -30
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +27 -18
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/turn-process.js +42 -5
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/operator-actions.js +20 -5
- package/dist/worker/console/operator-selection.js +6 -2
- package/dist/worker/console/pi-plugins.catalog.json +73658 -0
- package/dist/worker/console/pi-plugins.js +179 -0
- package/dist/worker/console/routes.js +246 -6
- package/dist/worker/console/server.js +173 -42
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BYBmDfPW.js → abnfDiagram-N423BO3Z-Dy71QJp3.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BKMmxpGF.js → arc-M25fNMTE.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DJpwxr1d.js → architectureDiagram-T3A2C74G-CUWlVl1a.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BaPj_ZlY.js → blockDiagram-VBNYF7ZC-Dd0Whpx2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8iGmHlm.js → c4Diagram-5PPSVZJV-BdfeCkI5.js} +1 -1
- package/dist/worker/console/static/assets/channel-Ba0AKiEZ.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DaxXQayc.js → chunk-2GRJ4B5K-D_1sUkyD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DqQKGA7q.js → chunk-2Q5K7J3B-Cxnxf36E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CgMKpJBB.js → chunk-5RXB4S5H-D9RG3G_x.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BpToUlaW.js → chunk-5VM5RSS4-Zz9jgjLX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BsEWOHxv.js → chunk-6Q2QTUOP-BCggDRCH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CKGSCe71.js → chunk-GF5L2VYU-CFg-5iAS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Bm1U5299.js → chunk-JWPE2WC7-DRQTE8x_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BI0ov870.js → chunk-KBJHAD2P-C_Dw7uZ8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BmnQo0iJ.js → chunk-RYQCIY6F-BjIiApsk.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BJBqa6Xc.js → chunk-XXDRQBXY-B5XQD4KG.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BU3IjVEe.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BuN9-7AR.js → cose-bilkent-JH36ORCC-huHqH6WQ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bw22jc2k.js → cynefin-VYW2F7L2-BSi6yZqP.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BHnSH8Dr.js → cynefinDiagram-MW4NZA55-Bs_Jp31y.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BoXXIegj.js → dagre-VZM6K2ZE-CYxavikT.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CU8qyPhT.js → diagram-7IWD3JNH-B6TErl71.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-lrjhF0S8.js → diagram-B4RE2ZJO-CvH4J2ii.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DL0QDEpo.js → diagram-LBJQPF4R-v1NBAqXS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DxVMJMBH.js → diagram-Q27KOJAE-CUNHVAn2.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CFg9FzvG.js → diagram-UB23O5K3-Dzz1gUu-.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CQgP9fTG.js → ebnfDiagram-BXEA7PRR-CCF8_LjK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-A5vQXx9U.js → erDiagram-JOGREHBK-DObdPVLa.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-ytXnLyXD.js → flowDiagram-UKHOOZJN-DlN8Pxrj.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CYuksfnS.js → ganttDiagram-PKOTCBZU-BJKIPTGX.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-f_jW3zgU.js → gitGraphDiagram-DS77QQ5N-XcSSnObi.js} +1 -1
- package/dist/worker/console/static/assets/index-CIYzS16i.css +1 -0
- package/dist/worker/console/static/assets/index-CS3QOTfl.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C3HLE-xV.js → infoDiagram-6WML65LV-BKrRKMww.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DwpgejS6.js → ishikawaDiagram-WSZJBQD7-CN5dk-yb.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-ll0YWX1y.js → journeyDiagram-NVQOT4AX-B4P4KZ-Y.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Hq3rchvU.js → kanban-definition-27J2QSJJ-BRlteP32.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BaGjJx8z.js → linear-CG8D9fuL.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CKGQHWX_.js → mermaid.core-BuB6P2rX.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DM1FA6Dr.js → mindmap-definition-FAOFIHXS-DYXGl_DR.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Xa9fHkE2.js → pegDiagram-VL7TDLO6-BM48yEVG.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-pCd4PDqJ.js → pieDiagram-7S7Q4E2Y-mT8NL-Ow.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-KqjpEBLk.js → quadrantDiagram-CIZ2JOQS-C4pZ6DH_.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DgvR7LZD.js → railroadDiagram-AXF67PYL-BKAcr70m.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Bys1UCWA.js → requirementDiagram-LRYGKXZP-CRxcKpoO.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Ms9npnZt.js → sankeyDiagram-W5VNT64P-Ds4LxQDI.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DEIWrtQZ.js → sequenceDiagram-SI44F4Z6-CPLwPf_p.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DELJ0AhZ.js → sizeCapture-X5ZJPWSS-CL65uij0.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BpHt9wKh.js → stateDiagram-OKZ733FA-CO0Mgd1W.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BUewAVYS.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-GKRsBluT.js → swimlanes-SLNWSIFB-Cxv3TSNw.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bhe67Wjl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cwsq1DxL.js → timeline-definition-Z64GVDOM-Dw85f9L2.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CfaqtKx0.js → vennDiagram-T6HMQDX7-B4QLCqCy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BlEUtK_g.js → wardleyDiagram-T6FBY63Y-B_yKCWwn.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CHSRXPvO.js → xychartDiagram-ELKLHX3M-nniBEGv2.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
- package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
- package/dist/worker/console/workspace-context.js +28 -3
- package/dist/worker/console/workspace-registry.js +333 -60
- package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
- package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
- package/dist/worker/observe/static/inspect-workspace.js +54 -10
- package/dist/worker/observe/static/operator-chrome.css +23 -4
- package/dist/worker/observe/static/operator-chrome.js +7 -2
- package/dist/worker/observe/static/styles.css +87 -25
- package/dist/worker/observe/static/views/dag-inspector.js +42 -1
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/worker/observe/static/views/session-timeline.js +135 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +5 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +18 -12
- package/dist/workflows/dag/init-hybrid.js +32 -9
- package/dist/workflows/dag/types.js +13 -0
- package/docs/architecture/runtime-boundaries.md +2 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +27 -9
- package/docs/templates/init-managed-agents.md +1 -1
- package/package.json +3 -2
- package/skills/loop-agent/references/command-reference.md +15 -1
- package/dist/worker/console/static/assets/channel-CrzAa3FA.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-B7QwCCb4.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-B7QwCCb4.js +0 -1
- package/dist/worker/console/static/assets/index-BB6KxwT_.css +0 -1
- package/dist/worker/console/static/assets/index-D_wxbadT.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BEeSswlv.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B2m_wca8.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
* byte-identical (same ids and order).
|
|
17
17
|
*/
|
|
18
18
|
import { createHash, randomBytes } from "node:crypto";
|
|
19
|
-
import { realpathSync, statSync } from "node:fs";
|
|
19
|
+
import { realpathSync, statSync, unwatchFile, watchFile } from "node:fs";
|
|
20
|
+
import { mkdir, open, readFile, rm, stat } from "node:fs/promises";
|
|
20
21
|
import path from "node:path";
|
|
21
22
|
import { readJsonIfExists, writeSecureJson, } from "./app-data.js";
|
|
22
23
|
import { normalizeWorktreeRealpath, repoFingerprintV1, } from "./repo-fingerprint.js";
|
|
23
|
-
export const WORKSPACE_REGISTRY_SCHEMA_VERSION =
|
|
24
|
+
export const WORKSPACE_REGISTRY_SCHEMA_VERSION = 3;
|
|
24
25
|
/** Stable workspaceId: sha256("loop-console-workspace-v1\0" + normalizedRealpath). */
|
|
25
26
|
export function workspaceIdForRealpath(normalizedRealpath) {
|
|
26
27
|
return createHash("sha256")
|
|
@@ -31,11 +32,19 @@ export class WorkspaceRegistry {
|
|
|
31
32
|
appData;
|
|
32
33
|
options;
|
|
33
34
|
file;
|
|
34
|
-
|
|
35
|
+
lockFile;
|
|
36
|
+
mutationTail = Promise.resolve();
|
|
37
|
+
listeners = new Set();
|
|
38
|
+
watching = false;
|
|
39
|
+
lastObservedRevision = 0;
|
|
40
|
+
onRegistryFileChanged = () => {
|
|
41
|
+
void this.load().then((registry) => this.emitRevision(registry.revision));
|
|
42
|
+
};
|
|
35
43
|
constructor(appData, options = {}) {
|
|
36
44
|
this.appData = appData;
|
|
37
45
|
this.options = options;
|
|
38
46
|
this.file = path.join(appData.root, "workspaces.json");
|
|
47
|
+
this.lockFile = path.join(appData.root, "workspaces.lock");
|
|
39
48
|
}
|
|
40
49
|
/** Auto-register the startup repoRoot (idempotent). */
|
|
41
50
|
async ensureStartupWorkspace(repoRoot) {
|
|
@@ -94,56 +103,72 @@ export class WorkspaceRegistry {
|
|
|
94
103
|
};
|
|
95
104
|
}
|
|
96
105
|
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
106
|
const nowIso = (this.options.now ?? (() => new Date()))().toISOString();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
return this.mutate((registry) => {
|
|
108
|
+
const existing = registry.workspaces.find((workspace) => workspace.displayPath === normalized);
|
|
109
|
+
if (existing && !existing.hidden) {
|
|
110
|
+
return {
|
|
111
|
+
changed: false,
|
|
112
|
+
value: {
|
|
113
|
+
ok: false,
|
|
114
|
+
code: "DUPLICATE",
|
|
115
|
+
message: `workspace already registered: ${existing.displayPath}`,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
113
118
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
if (existing) {
|
|
120
|
+
// Restore the soft-removed record; identity and order are preserved.
|
|
121
|
+
existing.hidden = undefined;
|
|
122
|
+
existing.lastAccessedAt = nowIso;
|
|
123
|
+
if (options.name?.trim())
|
|
124
|
+
existing.name = options.name.trim();
|
|
125
|
+
return {
|
|
126
|
+
changed: true,
|
|
127
|
+
value: { ok: true, record: existing, registryUnchanged: false },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const record = {
|
|
131
|
+
workspaceId: workspaceIdForRealpath(normalized),
|
|
132
|
+
name: options.name?.trim() || path.basename(normalized) || normalized,
|
|
133
|
+
displayPath: normalized,
|
|
134
|
+
repoRoot: real,
|
|
135
|
+
repoFingerprint: repoFingerprintV1(real),
|
|
136
|
+
createdAt: nowIso,
|
|
137
|
+
lastAccessedAt: nowIso,
|
|
138
|
+
sessionIds: [],
|
|
139
|
+
};
|
|
140
|
+
registry.workspaces.push(record);
|
|
141
|
+
return {
|
|
142
|
+
changed: true,
|
|
143
|
+
value: { ok: true, record, registryUnchanged: false },
|
|
144
|
+
};
|
|
145
|
+
});
|
|
131
146
|
}
|
|
132
|
-
/** Visible list in canonical
|
|
147
|
+
/** Visible list in canonical persisted order. */
|
|
133
148
|
async listVisible() {
|
|
149
|
+
return (await this.snapshotVisible()).workspaces;
|
|
150
|
+
}
|
|
151
|
+
/** Complete durable registry order, including soft-removed Workspaces. */
|
|
152
|
+
async listAll() {
|
|
153
|
+
return (await this.load()).workspaces;
|
|
154
|
+
}
|
|
155
|
+
async snapshotVisible() {
|
|
134
156
|
const registry = await this.load();
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
return {
|
|
158
|
+
revision: registry.revision,
|
|
159
|
+
workspaces: registry.workspaces.filter((workspace) => {
|
|
160
|
+
if (workspace.hidden)
|
|
161
|
+
return false;
|
|
162
|
+
try {
|
|
163
|
+
return statSync(workspace.repoRoot).isDirectory();
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Keep historical registry data, but do not project stale test roots
|
|
167
|
+
// into the Workspace browser after their directories disappear.
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}),
|
|
171
|
+
};
|
|
147
172
|
}
|
|
148
173
|
async get(workspaceId) {
|
|
149
174
|
const registry = await this.load();
|
|
@@ -163,13 +188,159 @@ export class WorkspaceRegistry {
|
|
|
163
188
|
}
|
|
164
189
|
/** Soft-remove: hides the record; never touches directories or AppData. */
|
|
165
190
|
async remove(workspaceId) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
191
|
+
return this.mutate((registry) => {
|
|
192
|
+
const record = registry.workspaces.find((workspace) => workspace.workspaceId === workspaceId);
|
|
193
|
+
if (!record || record.hidden)
|
|
194
|
+
return { changed: false, value: false };
|
|
195
|
+
record.hidden = true;
|
|
196
|
+
return { changed: true, value: true };
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/** Move one visible workspace before another; a null anchor moves it last. */
|
|
200
|
+
async insertBefore(workspaceId, beforeWorkspaceId) {
|
|
201
|
+
return this.mutate((registry) => {
|
|
202
|
+
const source = registry.workspaces.find((workspace) => workspace.workspaceId === workspaceId && !workspace.hidden);
|
|
203
|
+
if (!source) {
|
|
204
|
+
return {
|
|
205
|
+
changed: false,
|
|
206
|
+
value: { ok: false, code: "WORKSPACE_NOT_FOUND" },
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (beforeWorkspaceId === workspaceId) {
|
|
210
|
+
return {
|
|
211
|
+
changed: false,
|
|
212
|
+
value: { ok: true, changed: false },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const anchor = beforeWorkspaceId
|
|
216
|
+
? registry.workspaces.find((workspace) => workspace.workspaceId === beforeWorkspaceId && !workspace.hidden)
|
|
217
|
+
: undefined;
|
|
218
|
+
if (beforeWorkspaceId && !anchor) {
|
|
219
|
+
return {
|
|
220
|
+
changed: false,
|
|
221
|
+
value: { ok: false, code: "ANCHOR_NOT_FOUND" },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const visibleBefore = registry.workspaces
|
|
225
|
+
.filter((workspace) => !workspace.hidden)
|
|
226
|
+
.map((workspace) => workspace.workspaceId);
|
|
227
|
+
const visibleAfter = visibleBefore.filter((id) => id !== workspaceId);
|
|
228
|
+
const insertAt = beforeWorkspaceId
|
|
229
|
+
? visibleAfter.indexOf(beforeWorkspaceId)
|
|
230
|
+
: visibleAfter.length;
|
|
231
|
+
visibleAfter.splice(insertAt, 0, workspaceId);
|
|
232
|
+
if (visibleAfter.every((id, index) => id === visibleBefore[index])) {
|
|
233
|
+
return {
|
|
234
|
+
changed: false,
|
|
235
|
+
value: { ok: true, changed: false },
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
registry.workspaces.splice(registry.workspaces.indexOf(source), 1);
|
|
239
|
+
const rawAnchorIndex = anchor
|
|
240
|
+
? registry.workspaces.indexOf(anchor)
|
|
241
|
+
: registry.workspaces.length;
|
|
242
|
+
registry.workspaces.splice(rawAnchorIndex, 0, source);
|
|
243
|
+
return {
|
|
244
|
+
changed: true,
|
|
245
|
+
value: { ok: true, changed: true },
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/** Attach one Session to exactly one Workspace and prepend it to manual order. */
|
|
250
|
+
async attachSession(workspaceId, sessionId) {
|
|
251
|
+
const id = sessionId.trim();
|
|
252
|
+
if (!id)
|
|
253
|
+
return false;
|
|
254
|
+
return this.mutate((registry) => {
|
|
255
|
+
const target = registry.workspaces.find((workspace) => workspace.workspaceId === workspaceId && !workspace.hidden);
|
|
256
|
+
if (!target)
|
|
257
|
+
return { changed: false, value: false };
|
|
258
|
+
const alreadyAttached = target.sessionIds.includes(id);
|
|
259
|
+
let changed = false;
|
|
260
|
+
for (const workspace of registry.workspaces) {
|
|
261
|
+
if (workspace === target)
|
|
262
|
+
continue;
|
|
263
|
+
const without = workspace.sessionIds.filter((candidate) => candidate !== id);
|
|
264
|
+
if (without.length !== workspace.sessionIds.length) {
|
|
265
|
+
workspace.sessionIds = without;
|
|
266
|
+
changed = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!alreadyAttached) {
|
|
270
|
+
target.sessionIds.unshift(id);
|
|
271
|
+
changed = true;
|
|
272
|
+
}
|
|
273
|
+
return { changed, value: true };
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/** Remove one Session membership without touching the Session record itself. */
|
|
277
|
+
async detachSession(workspaceId, sessionId) {
|
|
278
|
+
const id = sessionId.trim();
|
|
279
|
+
if (!id)
|
|
169
280
|
return false;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
281
|
+
return this.mutate((registry) => {
|
|
282
|
+
const target = registry.workspaces.find((workspace) => workspace.workspaceId === workspaceId);
|
|
283
|
+
if (!target)
|
|
284
|
+
return { changed: false, value: false };
|
|
285
|
+
const next = target.sessionIds.filter((candidate) => candidate !== id);
|
|
286
|
+
if (next.length === target.sessionIds.length) {
|
|
287
|
+
return { changed: false, value: false };
|
|
288
|
+
}
|
|
289
|
+
target.sessionIds = next;
|
|
290
|
+
return { changed: true, value: true };
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Reconcile a Workspace with its authoritative per-repo Session store.
|
|
295
|
+
* Existing manual order is retained; newly discovered legacy Sessions are
|
|
296
|
+
* prepended in discovery order, and missing records are removed from the
|
|
297
|
+
* membership projection only (their files are never touched).
|
|
298
|
+
*/
|
|
299
|
+
async reconcileSessions(workspaceId, discoveredSessionIds) {
|
|
300
|
+
const discovered = [...new Set(discoveredSessionIds.map((id) => id.trim()).filter(Boolean))];
|
|
301
|
+
return this.mutate((registry) => {
|
|
302
|
+
const target = registry.workspaces.find((workspace) => workspace.workspaceId === workspaceId && !workspace.hidden);
|
|
303
|
+
if (!target)
|
|
304
|
+
return { changed: false, value: false };
|
|
305
|
+
const discoveredSet = new Set(discovered);
|
|
306
|
+
let changed = false;
|
|
307
|
+
for (const workspace of registry.workspaces) {
|
|
308
|
+
if (workspace === target)
|
|
309
|
+
continue;
|
|
310
|
+
const next = workspace.sessionIds.filter((id) => !discoveredSet.has(id));
|
|
311
|
+
if (next.length !== workspace.sessionIds.length) {
|
|
312
|
+
workspace.sessionIds = next;
|
|
313
|
+
changed = true;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const retained = target.sessionIds.filter((id) => discoveredSet.has(id));
|
|
317
|
+
const retainedSet = new Set(retained);
|
|
318
|
+
const additions = discovered.filter((id) => !retainedSet.has(id));
|
|
319
|
+
const next = [...additions, ...retained];
|
|
320
|
+
if (next.length !== target.sessionIds.length ||
|
|
321
|
+
next.some((id, index) => id !== target.sessionIds[index])) {
|
|
322
|
+
target.sessionIds = next;
|
|
323
|
+
changed = true;
|
|
324
|
+
}
|
|
325
|
+
return { changed, value: changed };
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/** Notify same-Host clients and watch for revisions written by sibling Hosts. */
|
|
329
|
+
subscribe(listener) {
|
|
330
|
+
this.listeners.add(listener);
|
|
331
|
+
if (!this.watching) {
|
|
332
|
+
this.watching = true;
|
|
333
|
+
watchFile(this.file, { interval: 500, persistent: false }, this.onRegistryFileChanged);
|
|
334
|
+
}
|
|
335
|
+
return () => {
|
|
336
|
+
this.listeners.delete(listener);
|
|
337
|
+
if (this.listeners.size === 0)
|
|
338
|
+
this.stopWatching();
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
close() {
|
|
342
|
+
this.listeners.clear();
|
|
343
|
+
this.stopWatching();
|
|
173
344
|
}
|
|
174
345
|
async findByRealpath(rawPath) {
|
|
175
346
|
let normalized;
|
|
@@ -186,26 +357,128 @@ export class WorkspaceRegistry {
|
|
|
186
357
|
return registry.workspaces.find((w) => w.displayPath === normalized);
|
|
187
358
|
}
|
|
188
359
|
async load() {
|
|
189
|
-
if (this.cache)
|
|
190
|
-
return this.cache;
|
|
191
360
|
const raw = await readJsonIfExists(this.file).catch(() => undefined);
|
|
192
361
|
const registry = {
|
|
193
|
-
schemaVersion:
|
|
362
|
+
schemaVersion: WORKSPACE_REGISTRY_SCHEMA_VERSION,
|
|
363
|
+
revision: raw && Number.isSafeInteger(raw.revision) && (raw.revision ?? -1) >= 0
|
|
364
|
+
? raw.revision
|
|
365
|
+
: 0,
|
|
194
366
|
workspaces: raw && Array.isArray(raw.workspaces)
|
|
195
367
|
? raw.workspaces.filter((w) => Boolean(w) &&
|
|
196
368
|
typeof w === "object" &&
|
|
197
369
|
typeof w.workspaceId === "string" &&
|
|
198
370
|
typeof w.displayPath === "string" &&
|
|
199
|
-
typeof w.repoRoot === "string")
|
|
371
|
+
typeof w.repoRoot === "string").map((workspace) => ({
|
|
372
|
+
...workspace,
|
|
373
|
+
sessionIds: Array.isArray(workspace.sessionIds)
|
|
374
|
+
? workspace.sessionIds.filter((id) => typeof id === "string" && Boolean(id.trim()))
|
|
375
|
+
: [],
|
|
376
|
+
}))
|
|
200
377
|
: [],
|
|
201
378
|
};
|
|
202
|
-
this.cache = registry;
|
|
203
379
|
return registry;
|
|
204
380
|
}
|
|
205
381
|
async persist(registry) {
|
|
206
382
|
// writeSecureJson is atomic; a reader mid-write sees the previous bytes.
|
|
207
383
|
await writeSecureJson(this.file, registry);
|
|
208
|
-
this.
|
|
384
|
+
this.emitRevision(registry.revision);
|
|
385
|
+
}
|
|
386
|
+
mutate(op) {
|
|
387
|
+
return this.serializeMutation(() => this.withFileLock(async () => {
|
|
388
|
+
// Always reload while holding the cross-process lock. This is the
|
|
389
|
+
// read-modify-write boundary that prevents stale-cache lost updates.
|
|
390
|
+
const registry = await this.load();
|
|
391
|
+
const result = op(registry);
|
|
392
|
+
if (result.changed) {
|
|
393
|
+
registry.revision += 1;
|
|
394
|
+
await this.persist(registry);
|
|
395
|
+
}
|
|
396
|
+
return result.value;
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
serializeMutation(op) {
|
|
400
|
+
const next = this.mutationTail.then(op, op);
|
|
401
|
+
this.mutationTail = next.then(() => undefined, () => undefined);
|
|
402
|
+
return next;
|
|
403
|
+
}
|
|
404
|
+
async withFileLock(op) {
|
|
405
|
+
await mkdir(path.dirname(this.lockFile), { recursive: true, mode: 0o700 });
|
|
406
|
+
const deadline = Date.now() + 15_000;
|
|
407
|
+
for (let attempt = 0;; attempt += 1) {
|
|
408
|
+
try {
|
|
409
|
+
const handle = await open(this.lockFile, "wx", 0o600);
|
|
410
|
+
try {
|
|
411
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }), "utf8");
|
|
412
|
+
return await op();
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
await handle.close().catch(() => undefined);
|
|
416
|
+
await rm(this.lockFile, { force: true }).catch(() => undefined);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
if (error.code !== "EEXIST")
|
|
421
|
+
throw error;
|
|
422
|
+
if (await this.removeAbandonedLock())
|
|
423
|
+
continue;
|
|
424
|
+
if (Date.now() >= deadline) {
|
|
425
|
+
throw new Error("workspace registry is busy in another Console process");
|
|
426
|
+
}
|
|
427
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(25 * (attempt + 1), 250)));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async removeAbandonedLock() {
|
|
432
|
+
try {
|
|
433
|
+
const [body, info] = await Promise.all([
|
|
434
|
+
readFile(this.lockFile, "utf8"),
|
|
435
|
+
stat(this.lockFile),
|
|
436
|
+
]);
|
|
437
|
+
const ageMs = Date.now() - info.mtimeMs;
|
|
438
|
+
if (ageMs >= 60_000) {
|
|
439
|
+
await rm(this.lockFile, { force: true });
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
const pid = Number(JSON.parse(body).pid);
|
|
443
|
+
if (Number.isSafeInteger(pid) && pid > 0) {
|
|
444
|
+
try {
|
|
445
|
+
process.kill(pid, 0);
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
const code = error.code;
|
|
450
|
+
if (code !== "ESRCH")
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (ageMs < 10_000 && !Number.isSafeInteger(pid)) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
await rm(this.lockFile, { force: true });
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
emitRevision(revision) {
|
|
465
|
+
if (revision <= this.lastObservedRevision)
|
|
466
|
+
return;
|
|
467
|
+
this.lastObservedRevision = revision;
|
|
468
|
+
for (const listener of this.listeners) {
|
|
469
|
+
try {
|
|
470
|
+
listener(revision);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
/* Projection listeners must never invalidate a durable write. */
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
stopWatching() {
|
|
478
|
+
if (!this.watching)
|
|
479
|
+
return;
|
|
480
|
+
unwatchFile(this.file, this.onRegistryFileChanged);
|
|
481
|
+
this.watching = false;
|
|
209
482
|
}
|
|
210
483
|
}
|
|
211
484
|
/** Deterministic-looking nonce reserved for tests requiring unique roots. */
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function extractInspectMutationToken(
|
|
2
|
+
html: string | null | undefined,
|
|
3
|
+
): string | null;
|
|
4
|
+
export function applyInspectMutationToken(
|
|
5
|
+
token: string | null | undefined,
|
|
6
|
+
): boolean;
|
|
7
|
+
export function inspectAssetHref(href: string | null | undefined): string | null;
|
|
8
|
+
export function isInspectShellHref(href: string | null | undefined): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for hosted Inspect bootstrap. Safe to import from Node tests.
|
|
3
|
+
* No DOM, no view modules.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const INSPECT_MUTATION_TOKEN_RE =
|
|
7
|
+
/window\.__INSPECT_MUTATION_TOKEN__\s*=\s*("(?:\\.|[^"\\])*")/;
|
|
8
|
+
|
|
9
|
+
/** Parse the scoped inspect token injected into `/inspect/` HTML. */
|
|
10
|
+
export function extractInspectMutationToken(html) {
|
|
11
|
+
const match = String(html ?? "").match(INSPECT_MUTATION_TOKEN_RE);
|
|
12
|
+
if (!match) return null;
|
|
13
|
+
try {
|
|
14
|
+
const value = JSON.parse(match[1]);
|
|
15
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function applyInspectMutationToken(token) {
|
|
22
|
+
if (typeof globalThis === "undefined") return false;
|
|
23
|
+
if (typeof token === "string" && token.length > 0) {
|
|
24
|
+
globalThis.__INSPECT_MUTATION_TOKEN__ = token;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
delete globalThis.__INSPECT_MUTATION_TOKEN__;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Resolve inspect index.html stylesheet hrefs against `/inspect/` on the Console origin. */
|
|
32
|
+
export function inspectAssetHref(href) {
|
|
33
|
+
const raw = String(href ?? "").trim();
|
|
34
|
+
if (!raw) return null;
|
|
35
|
+
if (
|
|
36
|
+
/^[a-z][a-z0-9+.-]*:/i.test(raw) ||
|
|
37
|
+
raw.startsWith("//") ||
|
|
38
|
+
raw.startsWith("/")
|
|
39
|
+
) {
|
|
40
|
+
return raw;
|
|
41
|
+
}
|
|
42
|
+
return `/inspect/${raw.replace(/^\.\//, "")}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isInspectShellHref(href) {
|
|
46
|
+
const raw = String(href ?? "").trim();
|
|
47
|
+
if (!raw) return false;
|
|
48
|
+
if (raw.startsWith("#/") || raw.startsWith("/#/")) return true;
|
|
49
|
+
if (
|
|
50
|
+
raw === "/inspect" ||
|
|
51
|
+
raw.startsWith("/inspect/") ||
|
|
52
|
+
raw.startsWith("/inspect#")
|
|
53
|
+
) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "./router.js";
|
|
14
14
|
import {
|
|
15
15
|
isPageVisible,
|
|
16
|
+
isWorkspaceActive,
|
|
16
17
|
pauseForegroundTimers,
|
|
17
18
|
setWorkspaceActive,
|
|
18
19
|
setWorkspaceScope,
|
|
@@ -33,6 +34,12 @@ import { startDagPolling } from "./views/dag.js";
|
|
|
33
34
|
import { renderNightJobs } from "./views/night.js";
|
|
34
35
|
import { closeDagInspectorOnOutsideClick } from "./views/dag-inspector.js";
|
|
35
36
|
import { mountOperatorChrome } from "./operator-chrome.js";
|
|
37
|
+
import {
|
|
38
|
+
applyInspectMutationToken,
|
|
39
|
+
extractInspectMutationToken,
|
|
40
|
+
inspectAssetHref,
|
|
41
|
+
isInspectShellHref,
|
|
42
|
+
} from "./inspect-bootstrap.js";
|
|
36
43
|
|
|
37
44
|
let instance = null;
|
|
38
45
|
let lastInspectHash = "";
|
|
@@ -73,6 +80,31 @@ export function navigateInspectPath(path) {
|
|
|
73
80
|
return href;
|
|
74
81
|
}
|
|
75
82
|
|
|
83
|
+
function onInspectRootClick(event) {
|
|
84
|
+
if (event.defaultPrevented || event.button !== 0) return;
|
|
85
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
86
|
+
const target = event.target;
|
|
87
|
+
if (!(target instanceof Element)) return;
|
|
88
|
+
const anchor = target.closest("a[href]");
|
|
89
|
+
if (!anchor) return;
|
|
90
|
+
const href = anchor.getAttribute("href");
|
|
91
|
+
if (!isInspectShellHref(href)) return;
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
navigateInspectPath(href);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resumeInspectPolling() {
|
|
97
|
+
if (!isPageVisible() || !isWorkspaceActive()) return;
|
|
98
|
+
const r = lastInspectHash
|
|
99
|
+
? parseHashRouteWithFilters(lastInspectHash)
|
|
100
|
+
: parseRoute();
|
|
101
|
+
if (r.view === "dashboard") startDashboardPolling();
|
|
102
|
+
if (r.view === "run") startRunPolling(r.workerRunId, false);
|
|
103
|
+
if (r.view === "dag") startDagPolling(r.dagRunId, false);
|
|
104
|
+
if (r.view === "pool" && uiState.poolAutoRefresh) startPoolView();
|
|
105
|
+
if (r.view === "task") startTaskPolling(r.featureId, r.taskId);
|
|
106
|
+
}
|
|
107
|
+
|
|
76
108
|
function startView(routeValue) {
|
|
77
109
|
if (routeValue.view === "dashboard") {
|
|
78
110
|
startDashboardPolling(routeValue.scrollTo);
|
|
@@ -131,7 +163,12 @@ export async function prepareInspectWorkspace(root) {
|
|
|
131
163
|
throw new Error(`Inspect markup HTTP ${res.status}`);
|
|
132
164
|
}
|
|
133
165
|
const html = await res.text();
|
|
166
|
+
applyInspectMutationToken(extractInspectMutationToken(html));
|
|
134
167
|
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
168
|
+
for (const link of doc.querySelectorAll('link[rel="stylesheet"]')) {
|
|
169
|
+
const resolved = inspectAssetHref(link.getAttribute("href"));
|
|
170
|
+
if (resolved) ensureStylesheet(resolved);
|
|
171
|
+
}
|
|
135
172
|
const app = doc.getElementById("app");
|
|
136
173
|
if (!app) throw new Error("Inspect markup missing #app");
|
|
137
174
|
root.appendChild(document.importNode(app, true));
|
|
@@ -143,15 +180,7 @@ function bindStandaloneListeners() {
|
|
|
143
180
|
window.addEventListener("hashchange", () => route());
|
|
144
181
|
window.addEventListener("load", () => route());
|
|
145
182
|
document.addEventListener("pointerdown", closeDagInspectorOnOutsideClick);
|
|
146
|
-
document.addEventListener("visibilitychange",
|
|
147
|
-
if (!isPageVisible()) return;
|
|
148
|
-
const r = parseRoute();
|
|
149
|
-
if (r.view === "dashboard") startDashboardPolling();
|
|
150
|
-
if (r.view === "run") startRunPolling(r.workerRunId, false);
|
|
151
|
-
if (r.view === "dag") startDagPolling(r.dagRunId, false);
|
|
152
|
-
if (r.view === "pool" && uiState.poolAutoRefresh) startPoolView();
|
|
153
|
-
if (r.view === "task") startTaskPolling(r.featureId, r.taskId);
|
|
154
|
-
});
|
|
183
|
+
document.addEventListener("visibilitychange", resumeInspectPolling);
|
|
155
184
|
}
|
|
156
185
|
|
|
157
186
|
export function startStandaloneInspect() {
|
|
@@ -204,7 +233,6 @@ export function mountInspectWorkspace(root, options = {}) {
|
|
|
204
233
|
// workspaceId via mount options/hash and postMessage updates; every observe
|
|
205
234
|
// GET is then scoped via scopedApiUrl.
|
|
206
235
|
setWorkspaceScope(options.workspaceId ?? workspaceIdFromHash(options.hash));
|
|
207
|
-
window.addEventListener("message", onWorkspaceScopeMessage);
|
|
208
236
|
|
|
209
237
|
const hosted = options.hosted === true || options.hosted !== false;
|
|
210
238
|
const onNavigate =
|
|
@@ -216,6 +244,20 @@ export function mountInspectWorkspace(root, options = {}) {
|
|
|
216
244
|
lastInspectHash = options.hash || (typeof location !== "undefined" ? location.hash : "");
|
|
217
245
|
setWorkspaceActive(options.active !== false);
|
|
218
246
|
|
|
247
|
+
const listenerAbort =
|
|
248
|
+
typeof AbortController !== "undefined" ? new AbortController() : null;
|
|
249
|
+
const listenerOpts = listenerAbort ? { signal: listenerAbort.signal } : undefined;
|
|
250
|
+
window.addEventListener("message", onWorkspaceScopeMessage, listenerOpts);
|
|
251
|
+
if (hosted && typeof document !== "undefined") {
|
|
252
|
+
document.addEventListener(
|
|
253
|
+
"pointerdown",
|
|
254
|
+
closeDagInspectorOnOutsideClick,
|
|
255
|
+
listenerOpts,
|
|
256
|
+
);
|
|
257
|
+
document.addEventListener("visibilitychange", resumeInspectPolling, listenerOpts);
|
|
258
|
+
root.addEventListener("click", onInspectRootClick, listenerOpts);
|
|
259
|
+
}
|
|
260
|
+
|
|
219
261
|
const host = {
|
|
220
262
|
root,
|
|
221
263
|
hosted,
|
|
@@ -250,6 +292,8 @@ export function mountInspectWorkspace(root, options = {}) {
|
|
|
250
292
|
host.destroyed = true;
|
|
251
293
|
pauseForegroundTimers();
|
|
252
294
|
setNavigateHandler(null);
|
|
295
|
+
listenerAbort?.abort();
|
|
296
|
+
if (hosted) applyInspectMutationToken(null);
|
|
253
297
|
if (instance === host) instance = null;
|
|
254
298
|
},
|
|
255
299
|
};
|