@volter/live 0.5.212 → 0.5.213
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/BUNDLED_NOTICES +4 -4
- package/README.md +4 -1
- package/dist/asset-library.d.ts +98 -0
- package/dist/asset-library.js +7 -0
- package/dist/asset-library.js.map +7 -0
- package/dist/chunk-B2HUWFTT.js +25 -0
- package/dist/chunk-B2HUWFTT.js.map +7 -0
- package/dist/{chunk-7SZ3C4CC.js → chunk-IZJOOON2.js} +45 -8
- package/dist/chunk-IZJOOON2.js.map +7 -0
- package/dist/{chunk-Y5W77HYT.js → chunk-JDOIPALX.js} +38 -3
- package/dist/chunk-JDOIPALX.js.map +7 -0
- package/dist/{chunk-YPAOTPIF.js → chunk-JFAHP5CC.js} +19 -3
- package/dist/chunk-JFAHP5CC.js.map +7 -0
- package/dist/{chunk-SDIWRX5X.js → chunk-PC56TDHV.js} +5 -5
- package/dist/chunk-PC56TDHV.js.map +7 -0
- package/dist/{chunk-USXOIBVX.js → chunk-WIAH4XZE.js} +5 -4
- package/dist/chunk-WIAH4XZE.js.map +7 -0
- package/dist/client.d.ts +10 -4
- package/dist/client.js +2 -1
- package/dist/discovery.js +2 -2
- package/dist/editor.d.ts +11 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +18 -51
- package/dist/index.js.map +3 -3
- package/dist/product-locator.d.ts +2 -2
- package/dist/product-locator.js +2 -2
- package/dist/registry-format.d.ts +31 -3
- package/dist/registry-format.js +13 -3
- package/dist/session.d.ts +4 -15
- package/dist/volter-manifest.js +1 -1
- package/dist/workbench-locator.js +2 -2
- package/package.json +7 -3
- package/src/asset-library.ts +104 -0
- package/src/client.ts +48 -7
- package/src/discovery.ts +2 -2
- package/src/editor.ts +11 -3
- package/src/index.ts +2 -1
- package/src/product-locator.ts +2 -2
- package/src/registry-format.ts +68 -5
- package/src/session.ts +6 -86
- package/dist/chunk-7SZ3C4CC.js.map +0 -7
- package/dist/chunk-SDIWRX5X.js.map +0 -7
- package/dist/chunk-USXOIBVX.js.map +0 -7
- package/dist/chunk-Y5W77HYT.js.map +0 -7
- package/dist/chunk-YPAOTPIF.js.map +0 -7
package/BUNDLED_NOTICES
CHANGED
|
@@ -3,9 +3,9 @@ Bundled license notices
|
|
|
3
3
|
These notices accompany code incorporated into this package. Separate npm dependencies retain their own licenses.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
@volter/project@0.5.
|
|
6
|
+
@volter/project@0.5.213
|
|
7
7
|
Declared license: Apache-2.0
|
|
8
|
-
Source: the unmodified npm package @volter/project@0.5.
|
|
8
|
+
Source: the unmodified npm package @volter/project@0.5.213, from https://github.com/volter-ai/editor.git
|
|
9
9
|
|
|
10
10
|
--- packages/project/LICENSE ---
|
|
11
11
|
|
|
@@ -223,9 +223,9 @@ repository. Original source revision:
|
|
|
223
223
|
cf9cb8e22dd7ae5709ed0dd8a46dd6f16d1e4945. LICENSE contains the Apache-2.0 text.
|
|
224
224
|
|
|
225
225
|
|
|
226
|
-
@volter/sdk@0.5.
|
|
226
|
+
@volter/sdk@0.5.213
|
|
227
227
|
Declared license: Apache-2.0
|
|
228
|
-
Source: the unmodified npm package @volter/sdk@0.5.
|
|
228
|
+
Source: the unmodified npm package @volter/sdk@0.5.213, from https://github.com/volter-ai/editor.git
|
|
229
229
|
|
|
230
230
|
--- packages/sdk/LICENSE ---
|
|
231
231
|
|
package/README.md
CHANGED
|
@@ -11,6 +11,9 @@ await editor.contributedVerb('<package>-status');
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Connection selection matches the project's canonical path and refuses a missing
|
|
14
|
-
or mismatched session.
|
|
14
|
+
or mismatched session. A session answers only requests that carry its connection
|
|
15
|
+
token, which this package reads from the session's entry in the owner's session
|
|
16
|
+
registry (`~/.volter/editor-sessions.json`); a script's own request to a session
|
|
17
|
+
goes through `sessionFetch` from `@volter/live/registry-format`, which adds it. Runtime JavaScript is bundled for Node; source and
|
|
15
18
|
declarations ship beside it. The running document's registered commands remain
|
|
16
19
|
the authority for the operations an integration exposes.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ASSET LIBRARY, as the session's `/__editor/asset-library/*` routes answer it (editor-core's
|
|
3
|
+
* `server/asset-library-routes.ts`, which takes these shapes from here): search a source, list an asset's files, and
|
|
4
|
+
* download one into the project. `editor.assetLibrary` reaches it through `EditorClient`, so a request carries the
|
|
5
|
+
* session's connection token as every other one does.
|
|
6
|
+
*/
|
|
7
|
+
import type { EditorClient } from './client.js';
|
|
8
|
+
/** An asset as a search answers it. */
|
|
9
|
+
export interface OnlineAsset {
|
|
10
|
+
id: string;
|
|
11
|
+
source: 'polyhaven' | 'ambientcg' | 'local';
|
|
12
|
+
name: string;
|
|
13
|
+
type: 'model' | 'animation' | 'source' | 'hdri' | 'texture' | 'material';
|
|
14
|
+
thumbnailUrl: string;
|
|
15
|
+
categories: string[];
|
|
16
|
+
tags: string[];
|
|
17
|
+
downloadCount?: number;
|
|
18
|
+
license?: string;
|
|
19
|
+
author?: string;
|
|
20
|
+
sourceUrl?: string;
|
|
21
|
+
cloudHosted?: boolean;
|
|
22
|
+
variantId?: string;
|
|
23
|
+
variantCount?: number;
|
|
24
|
+
sizeBytes?: number;
|
|
25
|
+
animationCount?: number;
|
|
26
|
+
deliveryKinds?: ('local-ssd' | 'cloud' | 'remote')[];
|
|
27
|
+
addedAt?: string;
|
|
28
|
+
capabilities?: {
|
|
29
|
+
previewable: boolean;
|
|
30
|
+
importable: boolean;
|
|
31
|
+
runtimeReady: boolean;
|
|
32
|
+
animated: boolean;
|
|
33
|
+
deliveryAvailable: boolean;
|
|
34
|
+
health: 'healthy' | 'source-only' | 'unsupported';
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** One file of an asset that can be downloaded (`files`); `download` takes it with the asset. */
|
|
38
|
+
export interface AssetFileOption {
|
|
39
|
+
label: string;
|
|
40
|
+
format: string;
|
|
41
|
+
url: string;
|
|
42
|
+
sizeBytes?: number;
|
|
43
|
+
/** Additional files that must be downloaded alongside the main URL (GLTF includes). */
|
|
44
|
+
includes?: {
|
|
45
|
+
relativePath: string;
|
|
46
|
+
url: string;
|
|
47
|
+
size: number;
|
|
48
|
+
}[];
|
|
49
|
+
}
|
|
50
|
+
/** `GET /search`'s query. `type` is the kind the source names (`model`, `hdri`, …; `all` in the local catalog). */
|
|
51
|
+
export interface AssetSearchQuery {
|
|
52
|
+
source: OnlineAsset['source'];
|
|
53
|
+
type: string;
|
|
54
|
+
q?: string;
|
|
55
|
+
category?: string;
|
|
56
|
+
offset?: number;
|
|
57
|
+
limit?: number;
|
|
58
|
+
}
|
|
59
|
+
/** `POST /download`'s body: the asset and the file `files` offered for it. `license` is the search result's, which the
|
|
60
|
+
* route records for a remote source; the local catalog's own license is recorded whatever is passed. */
|
|
61
|
+
export interface AssetDownloadRequest {
|
|
62
|
+
source: OnlineAsset['source'];
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
url: string;
|
|
66
|
+
format: string;
|
|
67
|
+
includes?: AssetFileOption['includes'];
|
|
68
|
+
license?: string;
|
|
69
|
+
}
|
|
70
|
+
/** What a download answers: the file's path under the project's `public/`, the provenance operation that wrote it, and
|
|
71
|
+
* `ledgerError` when the asset and its license could not be recorded in `.volter/assets.json`. */
|
|
72
|
+
export interface AssetDownloadResult {
|
|
73
|
+
ok: true;
|
|
74
|
+
path: string;
|
|
75
|
+
provenanceOperationId?: string;
|
|
76
|
+
ledgerError?: string;
|
|
77
|
+
alreadyExists?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/** `editor.assetLibrary`: the routes, one call each. */
|
|
80
|
+
export declare class LiveAssetLibrary {
|
|
81
|
+
#private;
|
|
82
|
+
constructor(client: EditorClient);
|
|
83
|
+
/** Search one source: `{ assets, total }`, each asset with its tags, license, author and source address. */
|
|
84
|
+
search(query: AssetSearchQuery): Promise<{
|
|
85
|
+
assets: OnlineAsset[];
|
|
86
|
+
total: number;
|
|
87
|
+
}>;
|
|
88
|
+
/** The files an asset can be downloaded as. */
|
|
89
|
+
files(asset: {
|
|
90
|
+
source: OnlineAsset['source'];
|
|
91
|
+
id: string;
|
|
92
|
+
}): Promise<{
|
|
93
|
+
files: AssetFileOption[];
|
|
94
|
+
}>;
|
|
95
|
+
/** Download one file of an asset into the project's `public/asset-library/`, recording the asset and its license in
|
|
96
|
+
* the project's `.volter/assets.json`. */
|
|
97
|
+
download(request: AssetDownloadRequest): Promise<AssetDownloadResult>;
|
|
98
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/asset-library.ts
|
|
2
|
+
var LiveAssetLibrary = class {
|
|
3
|
+
#client;
|
|
4
|
+
constructor(client) {
|
|
5
|
+
this.#client = client;
|
|
6
|
+
}
|
|
7
|
+
/** Search one source: `{ assets, total }`, each asset with its tags, license, author and source address. */
|
|
8
|
+
async search(query) {
|
|
9
|
+
return this.#client.assetLibraryRequest("GET", "search", query);
|
|
10
|
+
}
|
|
11
|
+
/** The files an asset can be downloaded as. */
|
|
12
|
+
async files(asset) {
|
|
13
|
+
return this.#client.assetLibraryRequest("GET", "files", asset);
|
|
14
|
+
}
|
|
15
|
+
/** Download one file of an asset into the project's `public/asset-library/`, recording the asset and its license in
|
|
16
|
+
* the project's `.volter/assets.json`. */
|
|
17
|
+
async download(request) {
|
|
18
|
+
return this.#client.assetLibraryRequest("POST", "download", request);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
LiveAssetLibrary
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=chunk-B2HUWFTT.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/asset-library.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * THE ASSET LIBRARY, as the session's `/__editor/asset-library/*` routes answer it (editor-core's\n * `server/asset-library-routes.ts`, which takes these shapes from here): search a source, list an asset's files, and\n * download one into the project. `editor.assetLibrary` reaches it through `EditorClient`, so a request carries the\n * session's connection token as every other one does.\n */\nimport type { EditorClient } from './client.js';\n\n/** An asset as a search answers it. */\nexport interface OnlineAsset {\n id: string;\n source: 'polyhaven' | 'ambientcg' | 'local';\n name: string;\n type: 'model' | 'animation' | 'source' | 'hdri' | 'texture' | 'material';\n thumbnailUrl: string;\n categories: string[];\n tags: string[];\n downloadCount?: number;\n license?: string;\n author?: string;\n sourceUrl?: string;\n cloudHosted?: boolean;\n variantId?: string;\n variantCount?: number;\n sizeBytes?: number;\n animationCount?: number;\n deliveryKinds?: ('local-ssd' | 'cloud' | 'remote')[];\n addedAt?: string;\n capabilities?: {\n previewable: boolean;\n importable: boolean;\n runtimeReady: boolean;\n animated: boolean;\n deliveryAvailable: boolean;\n health: 'healthy' | 'source-only' | 'unsupported';\n };\n}\n\n/** One file of an asset that can be downloaded (`files`); `download` takes it with the asset. */\nexport interface AssetFileOption {\n label: string;\n format: string;\n url: string;\n sizeBytes?: number;\n /** Additional files that must be downloaded alongside the main URL (GLTF includes). */\n includes?: { relativePath: string; url: string; size: number }[];\n}\n\n/** `GET /search`'s query. `type` is the kind the source names (`model`, `hdri`, \u2026; `all` in the local catalog). */\nexport interface AssetSearchQuery {\n source: OnlineAsset['source'];\n type: string;\n q?: string;\n category?: string;\n offset?: number;\n limit?: number;\n}\n\n/** `POST /download`'s body: the asset and the file `files` offered for it. `license` is the search result's, which the\n * route records for a remote source; the local catalog's own license is recorded whatever is passed. */\nexport interface AssetDownloadRequest {\n source: OnlineAsset['source'];\n id: string;\n name: string;\n url: string;\n format: string;\n includes?: AssetFileOption['includes'];\n license?: string;\n}\n\n/** What a download answers: the file's path under the project's `public/`, the provenance operation that wrote it, and\n * `ledgerError` when the asset and its license could not be recorded in `.volter/assets.json`. */\nexport interface AssetDownloadResult {\n ok: true;\n path: string;\n provenanceOperationId?: string;\n ledgerError?: string;\n alreadyExists?: boolean;\n}\n\n/** `editor.assetLibrary`: the routes, one call each. */\nexport class LiveAssetLibrary {\n readonly #client: EditorClient;\n\n constructor(client: EditorClient) {\n this.#client = client;\n }\n\n /** Search one source: `{ assets, total }`, each asset with its tags, license, author and source address. */\n async search(query: AssetSearchQuery): Promise<{ assets: OnlineAsset[]; total: number }> {\n return this.#client.assetLibraryRequest('GET', 'search', query);\n }\n\n /** The files an asset can be downloaded as. */\n async files(asset: { source: OnlineAsset['source']; id: string }): Promise<{ files: AssetFileOption[] }> {\n return this.#client.assetLibraryRequest('GET', 'files', asset);\n }\n\n /** Download one file of an asset into the project's `public/asset-library/`, recording the asset and its license in\n * the project's `.volter/assets.json`. */\n async download(request: AssetDownloadRequest): Promise<AssetDownloadResult> {\n return this.#client.assetLibraryRequest('POST', 'download', request);\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAiFO,IAAM,mBAAN,MAAuB;AAAA,EACnB;AAAA,EAET,YAAY,QAAsB;AAChC,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGA,MAAM,OAAO,OAA4E;AACvF,WAAO,KAAK,QAAQ,oBAAoB,OAAO,UAAU,KAAK;AAAA,EAChE;AAAA;AAAA,EAGA,MAAM,MAAM,OAA6F;AACvG,WAAO,KAAK,QAAQ,oBAAoB,OAAO,SAAS,KAAK;AAAA,EAC/D;AAAA;AAAA;AAAA,EAIA,MAAM,SAAS,SAA6D;AAC1E,WAAO,KAAK,QAAQ,oBAAoB,QAAQ,YAAY,OAAO;AAAA,EACrE;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
refuseWithoutRegisteredToken,
|
|
3
|
+
withSessionToken
|
|
4
|
+
} from "./chunk-JDOIPALX.js";
|
|
5
|
+
|
|
1
6
|
// src/http-transport.ts
|
|
2
7
|
import { Agent, fetch as nodeFetch } from "undici";
|
|
3
8
|
function createDispatcher(timeoutMs) {
|
|
@@ -115,7 +120,7 @@ var EditorClient = class {
|
|
|
115
120
|
signal: AbortSignal.timeout(deadlineMs)
|
|
116
121
|
};
|
|
117
122
|
commandDispatcher = deadlineMs > UNDICI_DEFAULT_HEADERS_TIMEOUT_MS ? createDispatcher(deadlineMs + 3e4) : void 0;
|
|
118
|
-
res = this.fetchOverride ? await this.fetchOverride(url, init) : await dispatchFetch(url, init, commandDispatcher);
|
|
123
|
+
res = this.fetchOverride ? await this.fetchOverride(url, init) : await dispatchFetch(url, withSessionToken(url, init), commandDispatcher);
|
|
119
124
|
break;
|
|
120
125
|
} catch (error) {
|
|
121
126
|
await commandDispatcher?.destroy();
|
|
@@ -137,6 +142,14 @@ var EditorClient = class {
|
|
|
137
142
|
);
|
|
138
143
|
}
|
|
139
144
|
}
|
|
145
|
+
if (!this.fetchOverride) {
|
|
146
|
+
try {
|
|
147
|
+
refuseWithoutRegisteredToken(url, res.status);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
await commandDispatcher?.destroy();
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
140
153
|
let data;
|
|
141
154
|
try {
|
|
142
155
|
data = await this.readJson(res);
|
|
@@ -331,11 +344,11 @@ var EditorClient = class {
|
|
|
331
344
|
await this.command({ type: "toggle-console" });
|
|
332
345
|
}
|
|
333
346
|
/** Switch the editor's NAMED WORKSPACE — the task-named layout memory
|
|
334
|
-
* (`design`/`animate`/`look`, or one a package contributes).
|
|
335
|
-
*
|
|
336
|
-
*
|
|
347
|
+
* (`design`/`animate`/`look`, or one a package contributes). Answers the workspace now active and whether its dock
|
|
348
|
+
* had rebuilt within the command's wait (`applied`): when it had, a following capture photographs the arrangement
|
|
349
|
+
* that was asked for. */
|
|
337
350
|
async setWorkspace(workspace) {
|
|
338
|
-
|
|
351
|
+
return this.command({ type: "set-workspace", workspace });
|
|
339
352
|
}
|
|
340
353
|
/** Choose a STYLE (`volter.style`) — its material, icon set and type, and the colour theme it
|
|
341
354
|
* was drawn with (`classic`/`glass`/`maya`/`substance`, or one a package the project or
|
|
@@ -623,6 +636,27 @@ var EditorClient = class {
|
|
|
623
636
|
if (!res.ok) throw new Error(data.error ?? `Failed to get project: ${res.status}`);
|
|
624
637
|
return data.project;
|
|
625
638
|
}
|
|
639
|
+
/** One `/__editor/asset-library/<route>` request, for `editor.assetLibrary` (`asset-library.ts`): a GET's fields as
|
|
640
|
+
* its query, a POST's as its JSON body. A refusal throws the route's own error. */
|
|
641
|
+
async assetLibraryRequest(method, route, fields) {
|
|
642
|
+
const url = new URL(`${this.baseUrl}/__editor/asset-library/${route}`);
|
|
643
|
+
if (method === "GET") {
|
|
644
|
+
for (const [name, value] of Object.entries(fields)) if (value !== void 0) url.searchParams.set(name, String(value));
|
|
645
|
+
}
|
|
646
|
+
const dispatcher = route === "download" ? createDispatcher(0) : void 0;
|
|
647
|
+
try {
|
|
648
|
+
const res = await this.httpFetch(
|
|
649
|
+
url.toString(),
|
|
650
|
+
method === "GET" ? void 0 : { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(fields) },
|
|
651
|
+
dispatcher
|
|
652
|
+
);
|
|
653
|
+
const data = await this.readJson(res);
|
|
654
|
+
if (!res.ok) throw new Error(data.error ?? `${method} /__editor/asset-library/${route} failed: ${res.status}`);
|
|
655
|
+
return data;
|
|
656
|
+
} finally {
|
|
657
|
+
await dispatcher?.destroy();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
626
660
|
async listRecentProjects() {
|
|
627
661
|
const res = await this.httpFetch(`${this.baseUrl}/__editor/recent-projects`);
|
|
628
662
|
const data = await this.readJson(res);
|
|
@@ -719,9 +753,10 @@ var EditorClient = class {
|
|
|
719
753
|
* conditions, so repeating one is the caller's decision. The relayed
|
|
720
754
|
* `command` path above has its own opt-in retry for the read-only captures.
|
|
721
755
|
*/
|
|
722
|
-
async httpFetch(url, init) {
|
|
756
|
+
async httpFetch(url, init, dispatcher) {
|
|
757
|
+
let res;
|
|
723
758
|
try {
|
|
724
|
-
|
|
759
|
+
res = this.fetchOverride ? await this.fetchOverride(url, init) : await dispatchFetch(url, withSessionToken(url, init), dispatcher);
|
|
725
760
|
} catch (error) {
|
|
726
761
|
if (error?.name === "TimeoutError") throw error;
|
|
727
762
|
const { code, detail } = describeFetchFailure(error);
|
|
@@ -730,6 +765,8 @@ var EditorClient = class {
|
|
|
730
765
|
code
|
|
731
766
|
);
|
|
732
767
|
}
|
|
768
|
+
if (!this.fetchOverride) refuseWithoutRegisteredToken(url, res.status);
|
|
769
|
+
return res;
|
|
733
770
|
}
|
|
734
771
|
/** Require a JSON response from the editor server. */
|
|
735
772
|
async readJson(res) {
|
|
@@ -765,4 +802,4 @@ export {
|
|
|
765
802
|
EditorCommandError,
|
|
766
803
|
EditorClient
|
|
767
804
|
};
|
|
768
|
-
//# sourceMappingURL=chunk-
|
|
805
|
+
//# sourceMappingURL=chunk-IZJOOON2.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/http-transport.ts", "../../sdk/src/commands.ts", "../src/client.ts"],
|
|
4
|
+
"sourcesContent": ["/** Node session clients need explicit header deadlines for long modeling calls.\n * Some runtimes supply an undici-compatible Agent without destroy(); callers\n * close explicitly when that lifecycle method exists. */\nimport { Agent, fetch as nodeFetch, type Dispatcher } from 'undici';\n\nexport function createDispatcher(timeoutMs: number): Dispatcher {\n return new Agent({ headersTimeout: timeoutMs, bodyTimeout: timeoutMs });\n}\n\nexport async function dispatchFetch(\n url: string,\n init?: RequestInit,\n dispatcher?: Dispatcher,\n): Promise<Response> {\n if (!dispatcher) return fetch(url, init);\n return await nodeFetch(url, {\n ...(init as object), dispatcher,\n }) as unknown as Response;\n}\n", "/**\n * A PACKAGE'S VERBS ARE CODE-OSS COMMANDS (`docs/adr/plugin-apis.md`): the verbs the editor's\n * `eval` command, `volter <verb>` and every agent send through the editor's ONE relay\n * (`POST /__editor/command`). A type the host's own table has is answered by the host; any other\n * type `T` runs as the command `volter.T`, which a package's page half registers in its\n * `activate` and its `package.json` declares:\n *\n * `contributes.commands`\n * { \"command\": \"volter.list-instances\", \"title\": \"\u2026\",\n * \"volter\": { \"timeoutMs\": 60000, \"derivedRefresh\": \"if-content-changed\" } }\n *\n * page entry\n * export function activate(context: PluginContext) {\n * registerVerbCommands(context, { 'list-instances': (command) => \u2026 });\n * }\n *\n * The declaration is the verb's budget and its status derivation, read where the relay waits\n * (the session's server reads the manifests) and where the page derives (the page's packages):\n * `timeoutMs` is how long the relay waits for the WORK (5 s when omitted), and `derivedRefresh`\n * whether a completed command owes a tree-scale status derivation (`'always'` when omitted).\n * A view's verbs are commands too, `volter.<view>.<verb>` (`registerViewCommands`).\n */\nimport type { PluginContext } from './plugins';\n\nexport interface EditorCommandMessage {\n readonly type: string;\n readonly _requestId?: string;\n readonly [key: string]: unknown;\n}\n\n/** The relay's answer shape (`command-table.ts`'s `CommandResult`): `ok`, an\n * `error` sentence on failure, and `data` for verbs that answer with a\n * payload \u2014 structured markers (`{ code: \u2026 }`) the SDK matches on, never\n * message prose. */\nexport interface EditorCommandResult {\n readonly ok: boolean;\n readonly error?: string;\n readonly data?: Record<string, unknown>;\n}\n\n/** Whether a completed command owes a status derivation: none, only when\n * the tree's content changed, or always. */\nexport type CommandDerivedRefresh = 'none' | 'if-content-changed' | 'always';\n\n/** A verb's handler: the relay's message in, the answer the host's own verbs give out. */\nexport type VerbHandler = (command: EditorCommandMessage) => EditorCommandResult | Promise<EditorCommandResult>;\n\n/** The Code-OSS command a relay type runs as: `<namespace>-start` \u2192 `volter.<namespace>-start`. */\nexport function verbCommandId(type: string): string {\n return `volter.${type}`;\n}\n\n/** A verb's refusal, carrying its answer's `data` (structured markers such as `{ code }`) to the relay. */\nexport class VerbError extends Error {\n override readonly name = 'VerbError';\n constructor(message: string, readonly data?: Record<string, unknown>) {\n super(message);\n }\n}\n\n/**\n * Register a table of verbs as Code-OSS commands, each released with the page half. A verb's\n * command answers its `data`, and throws its refusal as a {@link VerbError}, as a command does.\n */\nexport function registerVerbCommands(\n context: Pick<PluginContext, 'commands' | 'subscriptions'>,\n verbs: Readonly<Record<string, VerbHandler>>,\n): void {\n for (const [type, handle] of Object.entries(verbs)) {\n context.subscriptions.push(context.commands.registerCommand(verbCommandId(type), async (args) => {\n const answer = await handle({ ...(args as Record<string, unknown> | undefined), type });\n if (!answer.ok) throw new VerbError(answer.error ?? `${type} failed`, answer.data);\n return answer.data ?? {};\n }));\n }\n}\n\nexport interface ViewVerb {\n /** The verb, bare: `state`, `view-all`, `zoom`; its command is `volter.<view>.<verb>`. */\n readonly id: string;\n /** Run it. `args` is the command's own argument object, unvalidated: a view validates its own\n * arguments and REFUSES BY NAME, because what an argument means is the view's. */\n readonly run: (args?: Record<string, unknown>) => unknown;\n}\n\n/** Register a view's verbs as the commands `volter.<view>.<verb>`. */\nexport function registerViewCommands(\n context: Pick<PluginContext, 'commands' | 'subscriptions'>,\n view: string,\n verbs: readonly ViewVerb[],\n): void {\n for (const verb of verbs) {\n context.subscriptions.push(context.commands.registerCommand(`volter.${view}.${verb.id}`, (args) =>\n verb.run(args as Record<string, unknown> | undefined)));\n }\n}\n\n/** A job's state as its status verb answers it: running, done, or failed with why. */\nexport interface CommandJob {\n readonly state: 'rendering' | 'done' | 'failed';\n readonly error?: string;\n}\n\n/** How long a status verb waits for a running job to end: inside the relay's default deadline for a command. */\nconst JOB_STATUS_WAIT_MS = 3_000;\n\n/**\n * THE JOBS A START VERB BEGINS AND A STATUS VERB FOLLOWS, keyed (a movie render: started, answering at once, then read\n * until it ends). Work longer than any command's deadline runs this way, so each command answers inside the relay's\n * default deadline whatever the work's length, and a page's own control reads the same state.\n */\nexport function commandJobs<Job extends CommandJob>(): {\n /** Begin `work` under `key`, answering `started` at once. `work` reports each new state; a throw ends it failed. */\n start(key: string, started: Job, work: (report: (job: Job) => void) => Promise<void>): Job;\n /** The job under `key`, once it has ended or a status wait has passed. */\n status(key: string): Promise<Job>;\n /** The job under `key` as it stands, if one has started. */\n get(key: string): Job | null;\n /** Whether any job is running. */\n running(): boolean;\n subscribe(listener: () => void): () => void;\n version(): number;\n} {\n const jobs = new Map<string, Job>();\n const ends = new Map<string, Promise<void>>();\n const listeners = new Set<() => void>();\n let version = 0;\n const set = (key: string, job: Job) => {\n jobs.set(key, job);\n version++;\n for (const listener of listeners) listener();\n };\n return {\n start(key, started, work) {\n set(key, started);\n let last = started;\n const report = (job: Job) => { last = job; set(key, job); };\n ends.set(key, work(report).catch((thrown: unknown) => {\n report({ ...last, state: 'failed', error: thrown instanceof Error ? thrown.message : String(thrown) });\n }).finally(() => ends.delete(key)));\n return started;\n },\n async status(key) {\n const job = jobs.get(key);\n if (!job) throw new Error(`Nothing has started under ${key}.`);\n if (job.state === 'rendering') await Promise.race([ends.get(key), new Promise((done) => setTimeout(done, JOB_STATUS_WAIT_MS))]);\n return jobs.get(key)!;\n },\n get: (key) => jobs.get(key) ?? null,\n running: () => [...jobs.values()].some((job) => job.state === 'rendering'),\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n version: () => version,\n };\n}\n", "import type { Dispatcher } from 'undici';\nimport { createDispatcher, dispatchFetch } from './http-transport.js';\nimport { refuseWithoutRegisteredToken, withSessionToken } from './registry-format.js';\nimport { verbCommandId } from '@volter/sdk/commands';\nimport type { DocumentProbeResult, DocumentProbeStep } from '@volter/sdk/document-probe';\nimport type {\n ActiveDocumentCapture,\n AssetCompareCapture,\n AssetCompareOptions,\n AssetKind,\n AssetPreviewCapture,\n AssetPreviewOptions,\n AssetPreviewShotSetDefinition,\n AssetPreviewSource,\n CaptureDimensions,\n DocumentCameraPose,\n DocumentLookOutcome,\n StageFrameCostReading,\n DocumentTableProjection,\n EditorChromeCapture,\n EditorChromeCaptureOptions,\n EditorState,\n EditorView,\n EditorWorkspaceName,\n HelperVisibility,\n InspectedFieldWrite,\n InspectedHierarchy,\n InspectedInspection,\n LabeledShotSetCapture,\n PresentedEditorView,\n ProjectInfo,\n RecentProject,\n ShadingMode,\n StructureOp,\n StructureOpOptions,\n StructureOpResult,\n TransformMode,\n TransformSpace,\n Vec3Value,\n ViewPreset,\n ViewportCapture,\n} from '@volter/sdk';\n\n// The editor's default origin, spelled out because this package deliberately\n// does not depend on `@volter/project`. `DEFAULT_EDITOR_PORT` in\n// `packages/project/src/manifest/editor-port.ts` is the owner of the number and\n// of the never-`localhost` rule; keep this in step with it.\nconst DEFAULT_URL = 'http://127.0.0.1:20173';\n\n/**\n * A refused editor command, carrying the relay's own STRUCTURED failure code\n * (`/__editor/command`'s `{ ok: false, error, code }`) alongside the prose, so\n * a caller that reacts to a specific refusal matches the code, never the\n * sentence.\n */\nexport class EditorCommandError extends Error {\n readonly code: string | undefined;\n constructor(message: string, code?: string | undefined) {\n super(message);\n this.name = 'EditorCommandError';\n this.code = code;\n }\n}\n\n/**\n * The client's own ceiling on ONE relayed command.\n *\n * A backstop for a LOST server, not a per-command budget: the server already\n * owns per-type budgets (`server/server-utils.ts`'s `relayCommandTimeoutMs`)\n * and its timer must always be the one that fires, because its message names\n * the tab and the remedy while this one can only say \"no answer\". So this is\n * deliberately ONE number, comfortably above the longest server budget\n * (120s, `play`/`capture-story-variants`) rather than a mirror of that table \u2014\n * a second copy of it would drift silently, and the drift would show up as\n * this timer winning a race it must always lose.\n *\n * Without it a `fetch` with no `AbortSignal` waits on the OS: a dev server\n * that stops answering mid-command holds the CLI open indefinitely, with no\n * output and nothing to read.\n */\nconst COMMAND_DEADLINE_MS = 150_000;\n/** undici's default `headersTimeout`; a command deadline beyond it needs its own dispatcher (see `command`). */\nconst UNDICI_DEFAULT_HEADERS_TIMEOUT_MS = 300_000;\n\n/** Deadline for console-ledger reads and acknowledgements. */\nconst CONSOLE_DRAIN_TIMEOUT_MS = 1_500;\n\n/**\n * Node's `fetch` collapses EVERY network-layer failure into one two-word\n * `TypeError: fetch failed`. The real reason \u2014 `ECONNREFUSED`, `ECONNRESET`,\n * `EPIPE`, a DNS miss \u2014 lives only on `error.cause` (sometimes two links down,\n * or inside an `AggregateError`), and nothing prints it unless something walks\n * the chain, so a bare \"fetch failed\" cannot tell a dead editor from a\n * momentary one. This walks the chain so the message can say which.\n */\nfunction describeFetchFailure(error: unknown): { code: string; detail: string } {\n const messages: string[] = [];\n let current: unknown = error;\n for (let depth = 0; depth < 8; depth++) {\n if (!(current instanceof Error)) break;\n if (current.message) messages.push(current.message);\n const code = (current as { code?: unknown }).code;\n if (typeof code === 'string' && code !== '') {\n return { code, detail: messages.join(' <- ') };\n }\n const aggregate = (current as { errors?: unknown }).errors;\n if (Array.isArray(aggregate) && aggregate.length > 0) {\n const inner = describeFetchFailure(aggregate[0]);\n return { code: inner.code, detail: [...messages, inner.detail].join(' <- ') };\n }\n current = (current as { cause?: unknown }).cause;\n }\n return { code: 'UNKNOWN', detail: messages.join(' <- ') || String(error) };\n}\n\n/**\n * Connection failures eligible for one retry when a command opts in. An editor\n * response is not a transport failure and does not use this retry path.\n */\nconst RETRYABLE_TRANSPORT_CODES = new Set([\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'EPIPE',\n 'ETIMEDOUT',\n 'EHOSTUNREACH',\n 'UND_ERR_SOCKET',\n 'UND_ERR_CONNECT_TIMEOUT',\n]);\n\n/** Gap before the one automatic retry \u2014 long enough for a dev-server restart's\n * listen socket to come back, short enough to stay invisible. */\nconst TRANSPORT_RETRY_DELAY_MS = 400;\n\n/** What one undo/redo step reports back \u2014 `moved` is false when there was\n * nothing left in that direction, which is an answer, not an error. */\nexport interface HistoryStep {\n readonly moved: boolean;\n readonly canUndo: boolean;\n readonly canRedo: boolean;\n readonly undoLabel: string | null;\n readonly redoLabel: string | null;\n}\n\n/** What `open()` acknowledges: the workspace document the scene-table entry\n * resolved to, and the title its tab now carries \u2014 the project's own word for\n * that composition, not a filename. */\nexport interface OpenedDocument {\n readonly documentId: string;\n readonly title: string;\n}\n\nexport class EditorClient {\n private readonly baseUrl: string;\n private readonly fetchOverride: typeof fetch | undefined;\n private readonly transport:\n | ((body: Record<string, unknown>) => Promise<Record<string, unknown>>)\n | null;\n\n constructor(opts?: {\n url?: string;\n /** An authenticated transport for the same HTTP routes (e.g. a hosted tab). */\n fetch?: typeof fetch;\n /**\n * An IN-PAGE command channel, for a client that lives inside the editor\n * page itself and has no editor server to reach. Given, every command goes through it instead of\n * `POST /__editor/command`, and answers in the route's own body shape\n * (`{ ok: true, ...data }` / `{ ok: false, error, code? }`).\n */\n transport?: (body: Record<string, unknown>) => Promise<Record<string, unknown>>;\n }) {\n if (opts !== undefined && (typeof opts !== 'object' || opts === null || Array.isArray(opts))) {\n throw new TypeError(\n 'EditorClient options must be an object. Use new EditorClient({ url: \"http://127.0.0.1:20173\" }), not new EditorClient(\"...\").',\n );\n }\n const unknownOptions = Object.keys(opts ?? {}).filter(\n (key) => key !== 'url' && key !== 'transport' && key !== 'fetch',\n );\n if (unknownOptions.length > 0) {\n throw new Error(\n `EditorClient: unknown option${unknownOptions.length === 1 ? '' : 's'} ${unknownOptions.map((key) => `\"${key}\"`).join(', ')}. Use { url: \"http://127.0.0.1:<port>\" } to target an editor.`,\n );\n }\n this.baseUrl = (opts?.url ?? DEFAULT_URL).replace(/\\/$/, '');\n this.fetchOverride = opts?.fetch;\n this.transport = opts?.transport ?? null;\n }\n\n /**\n * `retryTransport` opts a command into ONE automatic retry after a transport\n * failure (see {@link RETRYABLE_TRANSPORT_CODES}). It is deliberately\n * OPT-IN and off by default: a socket that died after the request was written\n * cannot prove the editor did not already run the command, so a blanket retry\n * would risk playing/stopping/writing twice. Read-only relays \u2014 the captures \u2014\n * have no such hazard and turn it on.\n */\n private async command<T extends object = Record<string, never>>(\n body: Record<string, unknown>,\n options?: { retryTransport?: boolean; deadlineMs?: number },\n ): Promise<T> {\n const type = String(body['type'] ?? 'command');\n const deadlineMs = options?.deadlineMs ?? COMMAND_DEADLINE_MS;\n if (this.transport) {\n const answered = (await this.transport(body)) as {\n ok: boolean;\n error?: string;\n code?: string;\n } & T;\n if (!answered.ok) {\n throw new EditorCommandError(\n answered.error ?? `Editor command \"${type}\" failed`,\n answered.code,\n );\n }\n return answered;\n }\n const url = `${this.baseUrl}/__editor/command`;\n const request = JSON.stringify(body);\n let res: Response | undefined;\n let retried = false;\n let commandDispatcher: Dispatcher | undefined;\n for (;;) {\n try {\n const init: RequestInit = {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: request,\n signal: AbortSignal.timeout(deadlineMs),\n };\n // The route answers when the command completes. Undici's default header\n // timeout is 300s, independent of the abort signal; longer command budgets\n // use an explicit dispatcher through undici's fetch to extend that timeout.\n commandDispatcher = deadlineMs > UNDICI_DEFAULT_HEADERS_TIMEOUT_MS\n ? createDispatcher(deadlineMs + 30_000)\n : undefined;\n res = this.fetchOverride ? await this.fetchOverride(url, init) : await dispatchFetch(url, withSessionToken(url, init), commandDispatcher);\n break;\n } catch (error) {\n await commandDispatcher?.destroy();\n commandDispatcher = undefined;\n // Only the deadline is reshaped into a \"no answer\" verdict; everything\n // else is a TRANSPORT failure, and Node hides its reason behind a bare\n // `fetch failed` (see `describeFetchFailure`).\n if ((error as { name?: string } | null)?.name === 'TimeoutError') {\n throw new EditorCommandError(\n `The editor at ${this.baseUrl} never answered \"${type}\" ` +\n `within ${Math.round(deadlineMs / 1000)}s \u2014 past every server-side budget, so ` +\n 'the server itself is not answering. Check the terminal that started this editor session.',\n );\n }\n const { code, detail } = describeFetchFailure(error);\n if (options?.retryTransport === true && !retried && RETRYABLE_TRANSPORT_CODES.has(code)) {\n retried = true;\n await new Promise((resolve) => setTimeout(resolve, TRANSPORT_RETRY_DELAY_MS));\n continue;\n }\n throw new EditorCommandError(\n `POST ${url} (\"${type}\") never reached the editor: ${code}${detail ? ` (${detail})` : ''}.` +\n (retried\n ? ` Retried once after ${TRANSPORT_RETRY_DELAY_MS}ms; it failed the same way.`\n : '') +\n (options?.retryTransport === true\n ? ''\n : ' Not retried automatically: this command can change editor state, and a socket that' +\n ' died after the request was written cannot prove the editor did not already run it.') +\n ' A transport failure means the port stopped answering, not that the editor refused \u2014' +\n ' the dev server restarts on any watched source edit, and it shuts itself down after an' +\n ' idle window. Check the terminal that started this editor session and confirm the port' +\n ' this client resolved.',\n code,\n );\n }\n }\n if (!this.fetchOverride) {\n try {\n refuseWithoutRegisteredToken(url, res.status);\n } catch (error) {\n await commandDispatcher?.destroy();\n throw error;\n }\n }\n let data: { ok: boolean; error?: string; code?: string } & T;\n try {\n data = (await this.readJson(res)) as typeof data;\n } finally {\n await commandDispatcher?.destroy();\n }\n if (!data.ok) {\n throw new EditorCommandError(\n data.error ?? `Editor command failed: ${res.status}`,\n data.code,\n );\n }\n return data;\n }\n\n /** Refuse shutdown if a document cannot be saved. Does not stop the server. */\n async prepareClose(): Promise<void> {\n await this.command({ type: 'session-prepare-close' });\n }\n\n // --- Selection ---\n\n async select(id: string | null): Promise<void> {\n await this.command({ type: 'select', id });\n }\n\n async selectMultiple(ids: string[]): Promise<void> {\n await this.command({ type: 'select-multiple', ids });\n }\n\n async selectAll(): Promise<void> {\n await this.command({ type: 'select-all' });\n }\n\n // --- Viewport ---\n\n async focusEntity(id: string): Promise<void> {\n await this.command({ type: 'focus-entity', id });\n }\n\n async focusSelection(): Promise<void> {\n await this.command({ type: 'focus-selection' });\n }\n\n /**\n * Frame the EDIT viewport camera on one entity \u2014 the strict sibling of\n * {@link focusEntity}. Same framing; an id the scene does not know is a\n * refusal naming the id (`EditorCommandError`, code `ENTITY_NOT_FOUND`)\n * rather than `focusEntity`'s silent no-op, so a caller that frames an\n * entity before capturing it cannot photograph the wrong thing.\n */\n async frameEntity(id: string): Promise<void> {\n await this.command({ type: 'frame-entity', id });\n }\n\n async viewPreset(preset: ViewPreset): Promise<void> {\n await this.command({ type: 'view-preset', preset });\n }\n\n /**\n * LOOK AROUND THE OPEN MODEL, visibly. Swings the active asset\n * document's camera \u2014 the one on the human's screen \u2014 by `azimuth`/\n * `elevation` radians, animated over `duration` seconds, and resolves when\n * the move ends. A human drag during the move cancels it where it stands\n * (`cancelledBy: 'human'`); the promise still resolves.\n */\n async orbitDocument(options: {\n azimuth?: number;\n elevation?: number;\n duration?: number;\n }): Promise<DocumentLookOutcome> {\n return this.command<DocumentLookOutcome>({ type: 'document-orbit', ...options });\n }\n\n /** What a frame of a 3D document's stage costs, uncapped (`document-frame-cost`). */\n async frameCostDocument(options?: { readonly frames?: number; readonly stage?: string; readonly quality?: 'full' | 'navigation' }): Promise<StageFrameCostReading> {\n return this.command<StageFrameCostReading>({ type: 'document-frame-cost', ...options });\n }\n\n /** A slow full revolution around the open document's subject, at a constant rate. */\n async turntableDocument(options?: {\n seconds?: number;\n revolutions?: number;\n }): Promise<DocumentLookOutcome> {\n return this.command<DocumentLookOutcome>({ type: 'document-turntable', ...options });\n }\n\n /**\n * Frame the open document's subject (its selection if it has one). `fit`\n * scales the fitted distance: 1 is the toolbar Frame button's tight fit.\n */\n async frameDocument(fit?: number): Promise<DocumentCameraPose> {\n return this.command<DocumentCameraPose>({\n type: 'document-frame',\n ...(fit === undefined ? {} : { fit }),\n });\n }\n\n async setCamera(position: Vec3Value, target: Vec3Value, fov?: number): Promise<void> {\n await this.command({\n type: 'set-camera',\n position,\n target,\n ...(fov === undefined ? {} : { fov }),\n });\n }\n\n async captureViewport(size?: number): Promise<ViewportCapture> {\n const data = await this.command<ViewportCapture>({\n type: 'capture-viewport',\n ...(size === undefined ? {} : { size }),\n });\n return { base64: data.base64, mimeType: data.mimeType };\n }\n\n /**\n * Capture an isolated, deterministic four-view preview through the editor's\n * native Asset Lab. The SDK delegates rendering to the editor; it never\n * loads, clones, or interprets 3D assets itself.\n */\n async captureAssetPreview(\n source: AssetPreviewSource,\n options: AssetPreviewOptions = {},\n ): Promise<AssetPreviewCapture> {\n const data = await this.command<AssetPreviewCapture>(\n {\n type: 'capture-asset-preview',\n ...source,\n ...options,\n },\n // Photographing changes nothing, so a momentary transport failure is\n // retried rather than costing the caller its capture.\n { retryTransport: true },\n );\n return {\n width: data.width,\n height: data.height,\n orientation: data.orientation,\n views: data.views,\n contactSheet: data.contactSheet,\n };\n }\n\n /**\n * A caller-defined labeled shot set (`live`'s `assetPreviewShots`): the\n * DEFINITION travels with the command (see `AssetPreviewShotSetDefinition`),\n * and the editor's generic\n * capture engine renders it \u2014 see the 3D integration's\n * `captureShotSetAssetPreview`. Throws (via `command`'s `{ok:false}`\n * unwrap) with a clear message naming the missing joint(s) when the asset\n * lacks a bone the definition requires.\n */\n async captureShotSetPreview(\n source: AssetPreviewSource,\n definition: AssetPreviewShotSetDefinition,\n options: AssetPreviewOptions = {},\n ): Promise<LabeledShotSetCapture> {\n const data = await this.command<LabeledShotSetCapture>(\n {\n type: 'capture-asset-preview',\n ...source,\n ...options,\n shotSet: definition,\n },\n { retryTransport: true },\n );\n return {\n width: data.width,\n height: data.height,\n shots: data.shots,\n warnings: data.warnings,\n contactSheet: data.contactSheet,\n };\n }\n\n /**\n * Score the asset against a reference GLB (the editor's `screenshot\n * <model.glb> --compare <ref.glb>` command): matched orthographic front + side silhouettes\n * (equal-height bounding-box framing, both yaw-normalized to face the\n * camera), per-view IoU numbers, and overlay evidence images. The\n * reference GLB's raw bytes travel base64 in the command; the editor\n * renders and scores \u2014 the SDK never interprets 3D assets itself.\n */\n async captureAssetComparePreview(\n source: AssetPreviewSource,\n refGlbBase64: string,\n options: AssetCompareOptions = {},\n ): Promise<AssetCompareCapture> {\n const { refForward, ...dimensions } = options;\n const data = await this.command<AssetCompareCapture>({\n type: 'capture-asset-preview',\n ...source,\n ...dimensions,\n compare: { glbBase64: refGlbBase64, ...(refForward ? { forward: refForward } : {}) },\n });\n return { width: data.width, height: data.height, views: data.views };\n }\n\n // --- Panels ---\n\n /** Focus a static workspace panel by the key the editor's panel registry\n * holds; an unknown key refuses naming the keys it does hold. */\n async showPanel(panel: string): Promise<void> {\n await this.command({ type: 'show-panel', panel });\n }\n\n /** Show several instances of the running project split-screen \u2014 multiplayer\n * authoring. Pass a total `count` (default \"Player N\" labels) or an array of\n * `names` (its length is the count; index 0 is the primary). Requires a live\n * play session. */\n async setInstanceCount(countOrNames: number | string[]): Promise<void> {\n await this.command(\n Array.isArray(countOrNames)\n ? { type: 'set-instance-count', names: countOrNames }\n : { type: 'set-instance-count', count: countOrNames },\n );\n }\n\n async openAsset(path: string, kind: AssetKind): Promise<void> {\n await this.command({ type: 'open-asset-tab', path, kind });\n }\n\n /** SELECT a project asset \u2014 the other half of the browser's\n * selection-vs-open contract (single click selects and fills the\n * Inspector; double click opens a document). */\n async selectAsset(path: string): Promise<void> {\n await this.command({ type: 'select-asset', path });\n }\n\n async closeAsset(key: string): Promise<void> {\n await this.command({ type: 'close-asset-tab', key });\n }\n\n async toggleCommandPalette(): Promise<void> {\n await this.command({ type: 'toggle-command-palette' });\n }\n\n async toggleConsole(): Promise<void> {\n await this.command({ type: 'toggle-console' });\n }\n\n /** Switch the editor's NAMED WORKSPACE \u2014 the task-named layout memory\n * (`design`/`animate`/`look`, or one a package contributes). Answers the workspace now active and whether its dock\n * had rebuilt within the command's wait (`applied`): when it had, a following capture photographs the arrangement\n * that was asked for. */\n async setWorkspace(workspace: EditorWorkspaceName): Promise<{ readonly workspace: EditorWorkspaceName; readonly applied: boolean }> {\n return this.command<{ workspace: EditorWorkspaceName; applied: boolean }>({ type: 'set-workspace', workspace });\n }\n\n /** Choose a STYLE (`volter.style`) \u2014 its material, icon set and type, and the colour theme it\n * was drawn with (`classic`/`glass`/`maya`/`substance`, or one a package the project or\n * product composes carries). */\n async setStyle(style: string): Promise<void> {\n await this.command({ type: 'set-style', style });\n }\n\n /** A view's presentation, answered resolved. With a LAYER the person's choice is recorded\n * first. */\n async viewportPresentation(\n documentId: string,\n layer?: import('@volter/sdk/viewport-presentation').PresentationLayer,\n ): Promise<{\n presentation: import('@volter/sdk/viewport-presentation').ViewportPresentation;\n binding: { stageKind: string; documentLayer: import('@volter/sdk/viewport-presentation').PresentationLayer | null } | null;\n bound: { viewId: string; stageKind: string }[];\n lastDraw: import('@volter/sdk/viewport-presentation').ViewDrawReport | null;\n presets: string[];\n /** The environment images registered (`./environment-images`), for `lighting.preview.environment.image`. */\n environmentImages: string[];\n }> {\n return this.command({\n type: 'viewport-presentation',\n documentId,\n ...(layer ? { layer } : {}),\n });\n }\n\n async showBuild(): Promise<void> {\n await this.command({ type: 'show-build' });\n }\n\n /** Atomically present a durable editor view and return its shareable URL. */\n async present(view: EditorView): Promise<PresentedEditorView> {\n const presented = await this.command<PresentedEditorView>({ type: 'present-view', view });\n return { view: presented.view, url: presented.url, warnings: presented.warnings };\n }\n\n\n async inspect(): Promise<InspectedInspection> {\n const data = await this.command<{ subject: InspectedInspection }>({ type: 'inspect' });\n return data.subject;\n }\n\n /** Run one verb exposed by the active Inspector subject, by its id. */\n async runInspectionAction(actionId: string): Promise<InspectedInspection> {\n const data = await this.command<{ subject: InspectedInspection }>({\n type: 'run-inspection-action',\n actionId,\n });\n return data.subject;\n }\n\n /**\n * Run ONE command by id \u2014 the door to everything the command palette lists.\n *\n * Under the Code-OSS frame this is the workbench's own `ICommandService`, so\n * any command id works: a view's `volter.<view>.<verb>`, an editor action's\n * `volter.action.<id>`, or one of VS Code's own. A call made before the\n * command service lands waits for it.\n *\n * The result is whatever the command answered \u2014 a view verb's state, or\n * `null` for a command that returns nothing.\n */\n async runCommand(commandId: string, args?: unknown): Promise<unknown> {\n const data = await this.command<{ result: unknown }>({\n type: 'run-command',\n commandId,\n ...(args === undefined ? {} : { args }),\n });\n return data.result;\n }\n\n /**\n * One STRUCTURE op on the authored tree \u2014 the hierarchy context menu's own\n * verbs, on the same helpers, for a caller with no pointer to right-click\n * with. `id`/`ids` default to the current selection.\n */\n async structureOp(op: StructureOp, options: StructureOpOptions = {}): Promise<StructureOpResult> {\n return this.command<StructureOpResult>({ type: 'structure-op', op, ...options });\n }\n\n /** \"Extract Component\u2026\" \u2014 the hierarchy row's action, as a command. Answers\n * the action's own sentence, which NAMES the files it created. */\n async extractComponent(options: { id?: string; name?: string } = {}): Promise<{ hint: string }> {\n return this.command<{ hint: string }>({ type: 'extract-component', ...options });\n }\n\n /** \"Fork Component\u2026\" \u2014 extract's twin: one new file, one callsite retargeted. */\n async forkComponent(options: { id?: string } = {}): Promise<{ hint: string }> {\n return this.command<{ hint: string }>({ type: 'fork-component', ...options });\n }\n\n /**\n * The HIERARCHY PANEL's actual rendered row tree, as data.\n *\n * The panel projects component marks, document promotion, child caps,\n * collapse state, search and selection scope onto the adapter tree.\n *\n * Deliberately NOT `status().entities`, which walks the raw adapter tree and\n * therefore answers a different question: a panel defect is invisible in it.\n *\n * Each row reports the projected child count and whether the panel draws a disclosure control.\n *\n * Rejects, naming the panel, when no hierarchy panel is mounted: an empty\n * tree would be a fabricated answer about a surface nobody is being shown.\n */\n async hierarchy(): Promise<InspectedHierarchy> {\n const data = await this.command<{ hierarchy: InspectedHierarchy }>({ type: 'hierarchy' });\n return data.hierarchy;\n }\n\n /** Run the Hierarchy panel's own Expand All action. */\n async expandHierarchyAll(): Promise<void> {\n await this.command<Record<string, never>>({ type: 'expand-hierarchy-all' });\n }\n\n /** Run the Hierarchy panel's own Collapse All action \u2014 Expand All's other\n * half, and the only way back to the tree's rest state through the product\n * (see `HierarchyPanelSnapshot.collapseAll`). */\n async collapseHierarchyAll(): Promise<void> {\n await this.command<Record<string, never>>({ type: 'collapse-hierarchy-all' });\n }\n\n /**\n * Write one editable path through the active Inspector's own IO.\n *\n * The answer carries `write` as well as the subject, because an ack alone\n * cannot be believed: a write with no persistence route open succeeds and\n * changes no byte, and `write.persisted` is how the caller tells the two\n * apart without diffing the tree (`InspectedWriteDestination` in `types.ts`).\n */\n async setInspectionField(path: string, value: unknown): Promise<InspectedFieldWrite> {\n const data = await this.command<InspectedFieldWrite>({\n type: 'set-inspection-field',\n path,\n value,\n });\n return { subject: data.subject, write: data.write };\n }\n\n /**\n * REMOVE one editable path's authored override \u2014 the other half of the write\n * door, and the only one that can express byte-ABSENCE.\n *\n * {@link setInspectionField} writes a VALUE, so reverting a property an\n * authoring gesture ADDED puts the default back EXPLICITLY and leaves the\n * source one attribute heavier than it started. This drops the property, so\n * whatever governs it in its absence takes over \u2014 the same `io.remove` the\n * Inspector's revert arrow calls, the same persistence pipe, the same awaited\n * `{ destination, persisted }` ack.\n *\n * Rejects with `code: 'REMOVAL_UNAVAILABLE'` when the field does not declare\n * itself removable or the lane implements no removal door. That refusal is a\n * MISSING SEAM, not a failed removal, and it is coded rather than phrased\n * precisely so a caller can grade the two differently.\n */\n async removeInspectionField(path: string): Promise<InspectedFieldWrite> {\n const data = await this.command<InspectedFieldWrite>({\n type: 'remove-inspection-field',\n path,\n });\n return { subject: data.subject, write: data.write };\n }\n\n /**\n * OPEN one piece of the adapter's SCENE TABLE by id \u2014 a scene, a prefab, or\n * a story state, because the table makes them siblings (they differ only in\n * instance site). The ids are exactly what `getState().adapter.scenes.entries`\n * reports, so the table is both the menu and the address space. It opens\n * the entry's document in Play as in Edit.\n *\n * Rejects with a coded reason rather than prose: `SCENE_NOT_FOUND` (and it\n * names the ids that DO exist), `SCENE_NOT_OPENABLE` carrying the reason an\n * entry owns no document (the adapter's own, when it declared one),\n * `SCENE_TABLE_UNAVAILABLE` before the adapter has loaded, and\n * `SCENE_DOCUMENT_NOT_MOUNTED` when the host has no document for a piece the\n * table says is openable \u2014 a host gap, not a table statement.\n */\n async open(id: string): Promise<OpenedDocument> {\n return this.command<OpenedDocument>({ type: 'open', id });\n }\n\n /**\n * Undo / redo one project transaction \u2014 the same queue the keyboard shortcut\n * drives. `moved` is false when there was nothing left in that direction.\n */\n async undo(): Promise<HistoryStep> {\n return this.command<HistoryStep>({ type: 'undo' });\n }\n\n async redo(): Promise<HistoryStep> {\n return this.command<HistoryStep>({ type: 'redo' });\n }\n\n /** Read the editor's actual current durable projection. */\n async currentView(): Promise<EditorView> {\n const data = await this.command<{ view: EditorView }>({ type: 'current-view' });\n return data.view;\n }\n\n /** Photograph the editor PAGE itself \u2014 every panel as the person sees it, at\n * `scale` output pixels per CSS pixel (default `devicePixelRatio`), which is\n * what a 1 px border or a glyph edge is judged through. */\n async captureEditorChrome(options?: EditorChromeCaptureOptions): Promise<EditorChromeCapture> {\n return this.command<EditorChromeCapture>({\n type: 'capture-editor-chrome',\n ...(options?.scale === undefined ? {} : { scale: options.scale }),\n ...(options?.region === undefined ? {} : { region: options.region }),\n ...(options?.name === undefined ? {} : { name: options.name }),\n });\n }\n\n /** Capture the active document's render, excluding editor chrome. A number\n * requests a square; `{width, height}` requests that shape, bounded by\n * {@link CaptureDimensions}. With a view, present and capture it in one request\n * so discovery cannot retarget the capture between client calls. */\n async captureActiveDocument(\n size?: CaptureDimensions,\n view?: EditorView,\n ): Promise<ActiveDocumentCapture> {\n return this.command<ActiveDocumentCapture>({\n type: 'capture-active-document',\n ...(view ? { view } : {}),\n ...(typeof size === 'number' ? { size } : {}),\n ...(typeof size === 'object' && size !== null\n ? { width: size.width, height: size.height }\n : {}),\n });\n }\n\n /**\n * Read or drive the ACTIVE center document's own DOM \u2014 the scoped\n * editor-chrome door, and the read/gesture half of the same subject\n * {@link captureActiveDocument} photographs. NOT play-mode gated, and NOT\n * page automation: a target outside the active document's container is\n * refused by name. Design and scope contract:\n * `packages/sdk/src/editor-document-probe.ts`.\n */\n async documentProbe(step: DocumentProbeStep): Promise<DocumentProbeResult> {\n return this.command<DocumentProbeResult>({ type: 'document-probe', step });\n }\n\n /**\n * A verb a package contributes: the Code-OSS command `volter.<type>` its page half registers,\n * waited on as long as its declaration says (`contributes.commands`' `volter.timeoutMs`, which\n * the relay reads from the same manifest), so a package's long verb (a modeling chunk, minutes\n * under Wasm) has its budget in one place. A verb no package declares is refused by name\n * before anything is sent.\n */\n async contributedVerb<T extends object = Record<string, unknown>>(\n type: string,\n fields: Record<string, unknown> = {},\n ): Promise<T> {\n const id = verbCommandId(type);\n const res = await this.httpFetch(`${this.baseUrl}/__editor/commands/${encodeURIComponent(id)}`);\n const declared = (await this.readJson(res)) as { timeoutMs: number; error?: string };\n if (!res.ok) throw new Error(declared.error ?? `No package declares the command \"${id}\" (${res.status}).`);\n // Past the relay's own budget, so the relay's answer (its result or its timeout) is what arrives.\n return this.command<T>({ type, ...fields }, { deadlineMs: declared.timeoutMs + 30_000 });\n }\n\n /**\n * Run a wire-carried step against the ACTIVE document's published context\n * (`packages/editor-core/src/document-context-registry.ts`) \u2014 the REPL door over\n * an open document. `src` is JavaScript source evaluated in the page; references to the caller's\n * closures are unavailable.\n */\n async documentScript<T = unknown>(src: string): Promise<T> {\n const outcome = await this.command<{ result: T }>({ type: 'document-script', src });\n return outcome.result;\n }\n\n // --- Display (set semantics) ---\n\n async setGrid(enabled: boolean): Promise<void> {\n await this.command({ type: 'set-grid', enabled });\n }\n\n async setHelpers(enabled: boolean): Promise<void> {\n await this.command({ type: 'set-helpers', enabled });\n }\n\n async setStats(enabled: boolean): Promise<void> {\n await this.command({ type: 'set-stats', enabled });\n }\n\n async setShadingMode(mode: ShadingMode): Promise<void> {\n await this.command({ type: 'set-shading-mode', mode });\n }\n\n async setHelperType(helperType: keyof HelperVisibility, enabled: boolean): Promise<void> {\n await this.command({ type: 'set-helper-type', helperType, enabled });\n }\n\n // --- Transform tools (set semantics) ---\n\n async setTransformMode(mode: TransformMode): Promise<void> {\n await this.command({ type: 'set-transform-mode', mode });\n }\n\n async setTransformSpace(space: TransformSpace): Promise<void> {\n await this.command({ type: 'set-transform-space', space });\n }\n\n async setSnap(enabled: boolean): Promise<void> {\n await this.command({ type: 'set-snap', enabled });\n }\n\n // --- Project management ---\n\n /** A new project from one of the running product's templates or examples (its `/__editor/templates`), or its default\n * where neither is named. */\n async createProject(\n name: string,\n location: string,\n from: { readonly template?: string; readonly example?: string } = {},\n ): Promise<ProjectInfo> {\n const res = await this.httpFetch(`${this.baseUrl}/__editor/create-project`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, location, ...from }),\n });\n const data = (await this.readJson(res)) as {\n ok?: boolean;\n error?: string;\n path?: string;\n config?: ProjectInfo['config'];\n };\n if (!res.ok) {\n throw new Error(data.error ?? `Create project failed: ${res.status}`);\n }\n return { path: data.path as string, config: data.config as ProjectInfo['config'] };\n }\n\n async openProject(path: string): Promise<void> {\n const res = await this.httpFetch(`${this.baseUrl}/__editor/open-project`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ path }),\n });\n const body = (await this.readJson(res)) as { error?: string };\n if (!res.ok) {\n throw new Error(body.error ?? `Open project failed: ${res.status}`);\n }\n }\n\n async getProject(): Promise<ProjectInfo | null> {\n const res = await this.httpFetch(`${this.baseUrl}/__editor/project`);\n const data = (await this.readJson(res)) as { project: ProjectInfo | null; error?: string };\n if (!res.ok) throw new Error(data.error ?? `Failed to get project: ${res.status}`);\n return data.project;\n }\n\n /** One `/__editor/asset-library/<route>` request, for `editor.assetLibrary` (`asset-library.ts`): a GET's fields as\n * its query, a POST's as its JSON body. A refusal throws the route's own error. */\n async assetLibraryRequest<T>(method: 'GET' | 'POST', route: string, fields: object): Promise<T> {\n const url = new URL(`${this.baseUrl}/__editor/asset-library/${route}`);\n if (method === 'GET') {\n for (const [name, value] of Object.entries(fields)) if (value !== undefined) url.searchParams.set(name, String(value));\n }\n // A DOWNLOAD WAITS AS LONG AS THE ROUTE DOES. It answers once the whole file is in the project, and it cancels the\n // import when its caller goes (`res.once('close')` in the route), so undici's default 300 s headers deadline would\n // cancel a large asset on a slow link. Like a command whose budget passes that default (`command`), it gets its\n // own dispatcher, here with no deadline at all (undici sets no timer for 0, `client-h1.js` `setTimeout`): the\n // route sets none. A search or a file listing is one upstream lookup whose route cancels nothing, and keeps the\n // default as its ceiling.\n const dispatcher = route === 'download' ? createDispatcher(0) : undefined;\n try {\n const res = await this.httpFetch(\n url.toString(),\n method === 'GET' ? undefined : { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(fields) },\n dispatcher,\n );\n const data = (await this.readJson(res)) as T & { error?: string };\n if (!res.ok) throw new Error(data.error ?? `${method} /__editor/asset-library/${route} failed: ${res.status}`);\n return data;\n } finally {\n await dispatcher?.destroy();\n }\n }\n\n async listRecentProjects(): Promise<RecentProject[]> {\n const res = await this.httpFetch(`${this.baseUrl}/__editor/recent-projects`);\n const data = (await this.readJson(res)) as { projects: RecentProject[]; error?: string };\n if (!res.ok) throw new Error(data.error ?? `Failed to list projects: ${res.status}`);\n return data.projects;\n }\n\n // --- State ---\n\n /**\n * The document table the host resolved \u2014 every scene, prefab, page, model,\n * shot, take \u2026 the project's finders produced (`getState().adapter.scenes`\n * is the same projection). A command, so it answers wherever the control\n * channel reaches, not only where `/__editor/state` is served.\n */\n async documentTable(): Promise<DocumentTableProjection> {\n return this.command<DocumentTableProjection>({ type: 'document-table' });\n }\n\n\n async reloadPage(): Promise<void> {\n const timeoutMs = 60_000;\n const epochs = async () => {\n const { tabs } = await this.getState().catch(() => ({ tabs: [] }));\n return new Map(tabs.map((tab) => [tab.tabId8, tab] as const));\n };\n const pause = () => new Promise((settle) => setTimeout(settle, 250));\n const before = await epochs();\n const deadline = Date.now() + timeoutMs;\n await this.command({ type: 'page-reload' });\n let reloaded: string[] = [];\n while (reloaded.length === 0) {\n if (Date.now() > deadline) throw new Error('The reload was ordered and no tab of this session reported a new page load.');\n await pause();\n const now = await epochs();\n reloaded = [...now].filter(([tabId, tab]) => tab.epochCount > (before.get(tabId)?.epochCount ?? 0)).map(([tabId]) => tabId);\n }\n for (;;) {\n if (Date.now() > deadline) throw new Error('A new page loaded, but its command listener did not become ready.');\n const now = await epochs();\n if (reloaded.some((tabId) => {\n const listener = now.get(tabId)?.commandListener;\n return typeof listener !== 'string' || listener === 'ready';\n })) break;\n await pause();\n }\n for (;;) {\n try {\n await this.command({ type: 'current-view' });\n return;\n } catch (error) {\n if (Date.now() > deadline) throw error;\n await pause();\n }\n }\n }\n\n async getState(): Promise<EditorState> {\n const res = await this.httpFetch(`${this.baseUrl}/__editor/state`);\n const state = (await this.readJson(res)) as EditorState;\n if (!res.ok) throw new Error(`Failed to get editor state: ${res.status} ${res.statusText}`);\n return state;\n }\n\n /**\n * The complete unresolved console set the session is holding right now.\n *\n * The console endpoint returns named conditions; command envelopes carry\n * only their counts. The request has a bounded header deadline.\n */\n async getUnresolvedConsole(opts?: { all?: boolean }): Promise<unknown> {\n const res = await this.httpFetch(\n `${this.baseUrl}/__editor/console${opts?.all === true ? '?all=1' : ''}`,\n { signal: AbortSignal.timeout(CONSOLE_DRAIN_TIMEOUT_MS) },\n );\n // Status first: a 404's empty body would otherwise die inside readJson\n // with a parse error that hides the one fact the caller classifies on\n // (does this server SERVE the console route at all?).\n if (!res.ok) {\n throw new Error(`Failed to read unresolved console: ${res.status}`);\n }\n return await this.readJson(res);\n }\n\n /** Acknowledge one named console condition. */\n async acknowledgeConsole(input: {\n readonly id: string;\n readonly reason: string;\n readonly by: string;\n }): Promise<unknown> {\n const res = await this.httpFetch(`${this.baseUrl}/__editor/console/ack`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(input),\n signal: AbortSignal.timeout(CONSOLE_DRAIN_TIMEOUT_MS * 4),\n });\n const body = await this.readJson(res);\n return body;\n }\n\n /**\n * `fetch` for this client's plain routes, with the ONE thing Node's `fetch`\n * will not do: name why it failed.\n *\n * `readJson` below already owns \"the server answered the wrong thing\"; this\n * owns \"nothing answered at all\", naming the real code Node buries on\n * `.cause`. No retry\n * here \u2014 these routes create projects, run tools and acknowledge console\n * conditions, so repeating one is the caller's decision. The relayed\n * `command` path above has its own opt-in retry for the read-only captures.\n */\n private async httpFetch(\n url: string,\n init?: RequestInit,\n dispatcher?: Dispatcher,\n ): Promise<Response> {\n let res: Response;\n try {\n res = this.fetchOverride ? await this.fetchOverride(url, init) : await dispatchFetch(url, withSessionToken(url, init), dispatcher);\n } catch (error) {\n if ((error as { name?: string } | null)?.name === 'TimeoutError') throw error;\n const { code, detail } = describeFetchFailure(error);\n throw new EditorCommandError(\n `${init?.method ?? 'GET'} ${url} never reached the editor: ${code}` +\n `${detail ? ` (${detail})` : ''}. Nothing answered on that port \u2014 check the terminal ` +\n 'that started this editor session and confirm the port this client resolved.',\n code,\n );\n }\n if (!this.fetchOverride) refuseWithoutRegisteredToken(url, res.status);\n return res;\n }\n\n /** Require a JSON response from the editor server. */\n private async readJson(res: Response): Promise<unknown> {\n // The client's JSON response routes funnel through here, so this is\n // where \"did the editor server answer?\" is asked \u2014 the same question, and\n // the same JSON-content-type rule, that\n // `packages/sdk/src/editor-server-response.ts` owns on the browser side.\n // It is asked again rather than imported because THIS package is published\n // and depends on neither `@volter/project` nor the editor bundle (see\n // `DEFAULT_URL` above for that policy).\n //\n // Not theoretical here: `baseUrl` is whatever `--url`/`VOLTER_EDITOR_URL`\n // says, so the CLI can be pointed at a static host, which answers\n // `200 text/html` for a route nothing serves, and `res.json()` would die as\n // `Unexpected token '<'`, naming neither the URL nor the cause.\n const contentType = res.headers.get('content-type') ?? '';\n if (!contentType.includes('application/json')) {\n throw new Error(\n `The editor at ${this.baseUrl} answered with its page fallback ` +\n `(${contentType || 'no content-type'}) rather than JSON, so no editor server handled ` +\n 'the request. Check that this URL is a running editor session.',\n );\n }\n return await res.json();\n }\n\n /**\n * Whether an editor browser tab is connected to the server *right now*.\n * Unlike {@link getState}, this reflects live SSE connections, not cached\n * state \u2014 use it to check whether commands will actually reach an editor.\n */\n async isConnected(): Promise<boolean> {\n const state = await this.getState();\n return state.connected === true;\n }\n\n async waitForState(\n predicate: (s: EditorState) => boolean,\n timeoutMs = 10_000,\n ): Promise<EditorState> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const state = await this.getState();\n if (predicate(state)) return state;\n await new Promise((r) => setTimeout(r, 100));\n }\n throw new Error(`waitForState timed out after ${timeoutMs}ms`);\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;AAGA,SAAS,OAAO,SAAS,iBAAkC;AAEpD,SAAS,iBAAiB,WAA+B;AAC9D,SAAO,IAAI,MAAM,EAAE,gBAAgB,WAAW,aAAa,UAAU,CAAC;AACxE;AAEA,eAAsB,cACpB,KACA,MACA,YACmB;AACnB,MAAI,CAAC,WAAY,QAAO,MAAM,KAAK,IAAI;AACvC,SAAO,MAAM,UAAU,KAAK;AAAA,IAC1B,GAAI;AAAA,IAAiB;AAAA,EACvB,CAAC;AACH;;;AC8BO,SAAS,cAAc,MAAsB;AAClD,SAAO,UAAU,IAAI;AACvB;;;ACHA,IAAM,cAAc;AAQb,IAAM,qBAAN,cAAiC,MAAM;AAAA,EACnC;AAAA,EACT,YAAY,SAAiB,MAA2B;AACtD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAkBA,IAAM,sBAAsB;AAE5B,IAAM,oCAAoC;AAG1C,IAAM,2BAA2B;AAUjC,SAAS,qBAAqB,OAAkD;AAC9E,QAAM,WAAqB,CAAC;AAC5B,MAAI,UAAmB;AACvB,WAAS,QAAQ,GAAG,QAAQ,GAAG,SAAS;AACtC,QAAI,EAAE,mBAAmB,OAAQ;AACjC,QAAI,QAAQ,QAAS,UAAS,KAAK,QAAQ,OAAO;AAClD,UAAM,OAAQ,QAA+B;AAC7C,QAAI,OAAO,SAAS,YAAY,SAAS,IAAI;AAC3C,aAAO,EAAE,MAAM,QAAQ,SAAS,KAAK,MAAM,EAAE;AAAA,IAC/C;AACA,UAAM,YAAa,QAAiC;AACpD,QAAI,MAAM,QAAQ,SAAS,KAAK,UAAU,SAAS,GAAG;AACpD,YAAM,QAAQ,qBAAqB,UAAU,CAAC,CAAC;AAC/C,aAAO,EAAE,MAAM,MAAM,MAAM,QAAQ,CAAC,GAAG,UAAU,MAAM,MAAM,EAAE,KAAK,MAAM,EAAE;AAAA,IAC9E;AACA,cAAW,QAAgC;AAAA,EAC7C;AACA,SAAO,EAAE,MAAM,WAAW,QAAQ,SAAS,KAAK,MAAM,KAAK,OAAO,KAAK,EAAE;AAC3E;AAMA,IAAM,4BAA4B,oBAAI,IAAI;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAID,IAAM,2BAA2B;AAoB1B,IAAM,eAAN,MAAmB;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EAIjB,YAAY,MAWT;AACD,QAAI,SAAS,WAAc,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,IAAI;AAC5F,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,iBAAiB,OAAO,KAAK,QAAQ,CAAC,CAAC,EAAE;AAAA,MAC7C,CAAC,QAAQ,QAAQ,SAAS,QAAQ,eAAe,QAAQ;AAAA,IAC3D;AACA,QAAI,eAAe,SAAS,GAAG;AAC7B,YAAM,IAAI;AAAA,QACR,+BAA+B,eAAe,WAAW,IAAI,KAAK,GAAG,IAAI,eAAe,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,MAC7H;AAAA,IACF;AACA,SAAK,WAAW,MAAM,OAAO,aAAa,QAAQ,OAAO,EAAE;AAC3D,SAAK,gBAAgB,MAAM;AAC3B,SAAK,YAAY,MAAM,aAAa;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,QACZ,MACA,SACY;AACZ,UAAM,OAAO,OAAO,KAAK,MAAM,KAAK,SAAS;AAC7C,UAAM,aAAa,SAAS,cAAc;AAC1C,QAAI,KAAK,WAAW;AAClB,YAAM,WAAY,MAAM,KAAK,UAAU,IAAI;AAK3C,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,SAAS,SAAS,mBAAmB,IAAI;AAAA,UACzC,SAAS;AAAA,QACX;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,UAAM,MAAM,GAAG,KAAK,OAAO;AAC3B,UAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAI;AACJ,QAAI,UAAU;AACd,QAAI;AACJ,eAAS;AACP,UAAI;AACF,cAAM,OAAoB;AAAA,UACxB,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM;AAAA,UACN,QAAQ,YAAY,QAAQ,UAAU;AAAA,QACxC;AAIA,4BAAoB,aAAa,oCAC7B,iBAAiB,aAAa,GAAM,IACpC;AACJ,cAAM,KAAK,gBAAgB,MAAM,KAAK,cAAc,KAAK,IAAI,IAAI,MAAM,cAAc,KAAK,iBAAiB,KAAK,IAAI,GAAG,iBAAiB;AACxI;AAAA,MACF,SAAS,OAAO;AACd,cAAM,mBAAmB,QAAQ;AACjC,4BAAoB;AAIpB,YAAK,OAAoC,SAAS,gBAAgB;AAChE,gBAAM,IAAI;AAAA,YACR,iBAAiB,KAAK,OAAO,oBAAoB,IAAI,YACzC,KAAK,MAAM,aAAa,GAAI,CAAC;AAAA,UAE3C;AAAA,QACF;AACA,cAAM,EAAE,MAAM,OAAO,IAAI,qBAAqB,KAAK;AACnD,YAAI,SAAS,mBAAmB,QAAQ,CAAC,WAAW,0BAA0B,IAAI,IAAI,GAAG;AACvF,oBAAU;AACV,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,wBAAwB,CAAC;AAC5E;AAAA,QACF;AACA,cAAM,IAAI;AAAA,UACR,QAAQ,GAAG,MAAM,IAAI,gCAAgC,IAAI,GAAG,SAAS,KAAK,MAAM,MAAM,EAAE,OACrF,UACG,uBAAuB,wBAAwB,gCAC/C,OACH,SAAS,mBAAmB,OACzB,KACA,4KAEJ;AAAA,UAIF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,KAAK,eAAe;AACvB,UAAI;AACF,qCAA6B,KAAK,IAAI,MAAM;AAAA,MAC9C,SAAS,OAAO;AACd,cAAM,mBAAmB,QAAQ;AACjC,cAAM;AAAA,MACR;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,aAAQ,MAAM,KAAK,SAAS,GAAG;AAAA,IACjC,UAAE;AACA,YAAM,mBAAmB,QAAQ;AAAA,IACnC;AACA,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI;AAAA,QACR,KAAK,SAAS,0BAA0B,IAAI,MAAM;AAAA,QAClD,KAAK;AAAA,MACP;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,eAA8B;AAClC,UAAM,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAAA,EACtD;AAAA;AAAA,EAIA,MAAM,OAAO,IAAkC;AAC7C,UAAM,KAAK,QAAQ,EAAE,MAAM,UAAU,GAAG,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAM,eAAe,KAA8B;AACjD,UAAM,KAAK,QAAQ,EAAE,MAAM,mBAAmB,IAAI,CAAC;AAAA,EACrD;AAAA,EAEA,MAAM,YAA2B;AAC/B,UAAM,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAAA,EAC3C;AAAA;AAAA,EAIA,MAAM,YAAY,IAA2B;AAC3C,UAAM,KAAK,QAAQ,EAAE,MAAM,gBAAgB,GAAG,CAAC;AAAA,EACjD;AAAA,EAEA,MAAM,iBAAgC;AACpC,UAAM,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YAAY,IAA2B;AAC3C,UAAM,KAAK,QAAQ,EAAE,MAAM,gBAAgB,GAAG,CAAC;AAAA,EACjD;AAAA,EAEA,MAAM,WAAW,QAAmC;AAClD,UAAM,KAAK,QAAQ,EAAE,MAAM,eAAe,OAAO,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,cAAc,SAIa;AAC/B,WAAO,KAAK,QAA6B,EAAE,MAAM,kBAAkB,GAAG,QAAQ,CAAC;AAAA,EACjF;AAAA;AAAA,EAGA,MAAM,kBAAkB,SAA2I;AACjK,WAAO,KAAK,QAA+B,EAAE,MAAM,uBAAuB,GAAG,QAAQ,CAAC;AAAA,EACxF;AAAA;AAAA,EAGA,MAAM,kBAAkB,SAGS;AAC/B,WAAO,KAAK,QAA6B,EAAE,MAAM,sBAAsB,GAAG,QAAQ,CAAC;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,KAA2C;AAC7D,WAAO,KAAK,QAA4B;AAAA,MACtC,MAAM;AAAA,MACN,GAAI,QAAQ,SAAY,CAAC,IAAI,EAAE,IAAI;AAAA,IACrC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,UAAqB,QAAmB,KAA6B;AACnF,UAAM,KAAK,QAAQ;AAAA,MACjB,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,GAAI,QAAQ,SAAY,CAAC,IAAI,EAAE,IAAI;AAAA,IACrC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,gBAAgB,MAAyC;AAC7D,UAAM,OAAO,MAAM,KAAK,QAAyB;AAAA,MAC/C,MAAM;AAAA,MACN,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,KAAK;AAAA,IACvC,CAAC;AACD,WAAO,EAAE,QAAQ,KAAK,QAAQ,UAAU,KAAK,SAAS;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBACJ,QACA,UAA+B,CAAC,GACF;AAC9B,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,QACE,MAAM;AAAA,QACN,GAAG;AAAA,QACH,GAAG;AAAA,MACL;AAAA;AAAA;AAAA,MAGA,EAAE,gBAAgB,KAAK;AAAA,IACzB;AACA,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,aAAa,KAAK;AAAA,MAClB,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,sBACJ,QACA,YACA,UAA+B,CAAC,GACA;AAChC,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,QACE,MAAM;AAAA,QACN,GAAG;AAAA,QACH,GAAG;AAAA,QACH,SAAS;AAAA,MACX;AAAA,MACA,EAAE,gBAAgB,KAAK;AAAA,IACzB;AACA,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,cAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,2BACJ,QACA,cACA,UAA+B,CAAC,GACF;AAC9B,UAAM,EAAE,YAAY,GAAG,WAAW,IAAI;AACtC,UAAM,OAAO,MAAM,KAAK,QAA6B;AAAA,MACnD,MAAM;AAAA,MACN,GAAG;AAAA,MACH,GAAG;AAAA,MACH,SAAS,EAAE,WAAW,cAAc,GAAI,aAAa,EAAE,SAAS,WAAW,IAAI,CAAC,EAAG;AAAA,IACrF,CAAC;AACD,WAAO,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,OAAO,KAAK,MAAM;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAU,OAA8B;AAC5C,UAAM,KAAK,QAAQ,EAAE,MAAM,cAAc,MAAM,CAAC;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAiB,cAAgD;AACrE,UAAM,KAAK;AAAA,MACT,MAAM,QAAQ,YAAY,IACtB,EAAE,MAAM,sBAAsB,OAAO,aAAa,IAClD,EAAE,MAAM,sBAAsB,OAAO,aAAa;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,MAAc,MAAgC;AAC5D,UAAM,KAAK,QAAQ,EAAE,MAAM,kBAAkB,MAAM,KAAK,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,MAA6B;AAC7C,UAAM,KAAK,QAAQ,EAAE,MAAM,gBAAgB,KAAK,CAAC;AAAA,EACnD;AAAA,EAEA,MAAM,WAAW,KAA4B;AAC3C,UAAM,KAAK,QAAQ,EAAE,MAAM,mBAAmB,IAAI,CAAC;AAAA,EACrD;AAAA,EAEA,MAAM,uBAAsC;AAC1C,UAAM,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,gBAA+B;AACnC,UAAM,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAAa,WAAiH;AAClI,WAAO,KAAK,QAA8D,EAAE,MAAM,iBAAiB,UAAU,CAAC;AAAA,EAChH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,OAA8B;AAC3C,UAAM,KAAK,QAAQ,EAAE,MAAM,aAAa,MAAM,CAAC;AAAA,EACjD;AAAA;AAAA;AAAA,EAIA,MAAM,qBACJ,YACA,OASC;AACD,WAAO,KAAK,QAAQ;AAAA,MAClB,MAAM;AAAA,MACN;AAAA,MACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YAA2B;AAC/B,UAAM,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAAA,EAC3C;AAAA;AAAA,EAGA,MAAM,QAAQ,MAAgD;AAC5D,UAAM,YAAY,MAAM,KAAK,QAA6B,EAAE,MAAM,gBAAgB,KAAK,CAAC;AACxF,WAAO,EAAE,MAAM,UAAU,MAAM,KAAK,UAAU,KAAK,UAAU,UAAU,SAAS;AAAA,EAClF;AAAA,EAGA,MAAM,UAAwC;AAC5C,UAAM,OAAO,MAAM,KAAK,QAA0C,EAAE,MAAM,UAAU,CAAC;AACrF,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,oBAAoB,UAAgD;AACxE,UAAM,OAAO,MAAM,KAAK,QAA0C;AAAA,MAChE,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,WAAW,WAAmB,MAAkC;AACpE,UAAM,OAAO,MAAM,KAAK,QAA6B;AAAA,MACnD,MAAM;AAAA,MACN;AAAA,MACA,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,KAAK;AAAA,IACvC,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,YAAY,IAAiB,UAA8B,CAAC,GAA+B;AAC/F,WAAO,KAAK,QAA2B,EAAE,MAAM,gBAAgB,IAAI,GAAG,QAAQ,CAAC;AAAA,EACjF;AAAA;AAAA;AAAA,EAIA,MAAM,iBAAiB,UAA0C,CAAC,GAA8B;AAC9F,WAAO,KAAK,QAA0B,EAAE,MAAM,qBAAqB,GAAG,QAAQ,CAAC;AAAA,EACjF;AAAA;AAAA,EAGA,MAAM,cAAc,UAA2B,CAAC,GAA8B;AAC5E,WAAO,KAAK,QAA0B,EAAE,MAAM,kBAAkB,GAAG,QAAQ,CAAC;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,YAAyC;AAC7C,UAAM,OAAO,MAAM,KAAK,QAA2C,EAAE,MAAM,YAAY,CAAC;AACxF,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,qBAAoC;AACxC,UAAM,KAAK,QAA+B,EAAE,MAAM,uBAAuB,CAAC;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,uBAAsC;AAC1C,UAAM,KAAK,QAA+B,EAAE,MAAM,yBAAyB,CAAC;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,mBAAmB,MAAc,OAA8C;AACnF,UAAM,OAAO,MAAM,KAAK,QAA6B;AAAA,MACnD,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO,EAAE,SAAS,KAAK,SAAS,OAAO,KAAK,MAAM;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,sBAAsB,MAA4C;AACtE,UAAM,OAAO,MAAM,KAAK,QAA6B;AAAA,MACnD,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AACD,WAAO,EAAE,SAAS,KAAK,SAAS,OAAO,KAAK,MAAM;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,KAAK,IAAqC;AAC9C,WAAO,KAAK,QAAwB,EAAE,MAAM,QAAQ,GAAG,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAA6B;AACjC,WAAO,KAAK,QAAqB,EAAE,MAAM,OAAO,CAAC;AAAA,EACnD;AAAA,EAEA,MAAM,OAA6B;AACjC,WAAO,KAAK,QAAqB,EAAE,MAAM,OAAO,CAAC;AAAA,EACnD;AAAA;AAAA,EAGA,MAAM,cAAmC;AACvC,UAAM,OAAO,MAAM,KAAK,QAA8B,EAAE,MAAM,eAAe,CAAC;AAC9E,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBAAoB,SAAoE;AAC5F,WAAO,KAAK,QAA6B;AAAA,MACvC,MAAM;AAAA,MACN,GAAI,SAAS,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,QAAQ,MAAM;AAAA,MAC/D,GAAI,SAAS,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,QAAQ,OAAO;AAAA,MAClE,GAAI,SAAS,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,QAAQ,KAAK;AAAA,IAC9D,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,sBACJ,MACA,MACgC;AAChC,WAAO,KAAK,QAA+B;AAAA,MACzC,MAAM;AAAA,MACN,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACvB,GAAI,OAAO,SAAS,WAAW,EAAE,KAAK,IAAI,CAAC;AAAA,MAC3C,GAAI,OAAO,SAAS,YAAY,SAAS,OACrC,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO,IACzC,CAAC;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,cAAc,MAAuD;AACzE,WAAO,KAAK,QAA6B,EAAE,MAAM,kBAAkB,KAAK,CAAC;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,gBACJ,MACA,SAAkC,CAAC,GACvB;AACZ,UAAM,KAAK,cAAc,IAAI;AAC7B,UAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,sBAAsB,mBAAmB,EAAE,CAAC,EAAE;AAC9F,UAAM,WAAY,MAAM,KAAK,SAAS,GAAG;AACzC,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,SAAS,SAAS,oCAAoC,EAAE,MAAM,IAAI,MAAM,IAAI;AAEzG,WAAO,KAAK,QAAW,EAAE,MAAM,GAAG,OAAO,GAAG,EAAE,YAAY,SAAS,YAAY,IAAO,CAAC;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAA4B,KAAyB;AACzD,UAAM,UAAU,MAAM,KAAK,QAAuB,EAAE,MAAM,mBAAmB,IAAI,CAAC;AAClF,WAAO,QAAQ;AAAA,EACjB;AAAA;AAAA,EAIA,MAAM,QAAQ,SAAiC;AAC7C,UAAM,KAAK,QAAQ,EAAE,MAAM,YAAY,QAAQ,CAAC;AAAA,EAClD;AAAA,EAEA,MAAM,WAAW,SAAiC;AAChD,UAAM,KAAK,QAAQ,EAAE,MAAM,eAAe,QAAQ,CAAC;AAAA,EACrD;AAAA,EAEA,MAAM,SAAS,SAAiC;AAC9C,UAAM,KAAK,QAAQ,EAAE,MAAM,aAAa,QAAQ,CAAC;AAAA,EACnD;AAAA,EAEA,MAAM,eAAe,MAAkC;AACrD,UAAM,KAAK,QAAQ,EAAE,MAAM,oBAAoB,KAAK,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,cAAc,YAAoC,SAAiC;AACvF,UAAM,KAAK,QAAQ,EAAE,MAAM,mBAAmB,YAAY,QAAQ,CAAC;AAAA,EACrE;AAAA;AAAA,EAIA,MAAM,iBAAiB,MAAoC;AACzD,UAAM,KAAK,QAAQ,EAAE,MAAM,sBAAsB,KAAK,CAAC;AAAA,EACzD;AAAA,EAEA,MAAM,kBAAkB,OAAsC;AAC5D,UAAM,KAAK,QAAQ,EAAE,MAAM,uBAAuB,MAAM,CAAC;AAAA,EAC3D;AAAA,EAEA,MAAM,QAAQ,SAAiC;AAC7C,UAAM,KAAK,QAAQ,EAAE,MAAM,YAAY,QAAQ,CAAC;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cACJ,MACA,UACA,OAAkE,CAAC,GAC7C;AACtB,UAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,4BAA4B;AAAA,MAC1E,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,MAAM,UAAU,GAAG,KAAK,CAAC;AAAA,IAClD,CAAC;AACD,UAAM,OAAQ,MAAM,KAAK,SAAS,GAAG;AAMrC,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,KAAK,SAAS,0BAA0B,IAAI,MAAM,EAAE;AAAA,IACtE;AACA,WAAO,EAAE,MAAM,KAAK,MAAgB,QAAQ,KAAK,OAAgC;AAAA,EACnF;AAAA,EAEA,MAAM,YAAY,MAA6B;AAC7C,UAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,0BAA0B;AAAA,MACxE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,IAC/B,CAAC;AACD,UAAM,OAAQ,MAAM,KAAK,SAAS,GAAG;AACrC,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,KAAK,SAAS,wBAAwB,IAAI,MAAM,EAAE;AAAA,IACpE;AAAA,EACF;AAAA,EAEA,MAAM,aAA0C;AAC9C,UAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,mBAAmB;AACnE,UAAM,OAAQ,MAAM,KAAK,SAAS,GAAG;AACrC,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,KAAK,SAAS,0BAA0B,IAAI,MAAM,EAAE;AACjF,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA,EAIA,MAAM,oBAAuB,QAAwB,OAAe,QAA4B;AAC9F,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,2BAA2B,KAAK,EAAE;AACrE,QAAI,WAAW,OAAO;AACpB,iBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,EAAG,KAAI,UAAU,OAAW,KAAI,aAAa,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,IACvH;AAOA,UAAM,aAAa,UAAU,aAAa,iBAAiB,CAAC,IAAI;AAChE,QAAI;AACF,YAAM,MAAM,MAAM,KAAK;AAAA,QACrB,IAAI,SAAS;AAAA,QACb,WAAW,QAAQ,SAAY,EAAE,QAAQ,SAAS,EAAE,gBAAgB,mBAAmB,GAAG,MAAM,KAAK,UAAU,MAAM,EAAE;AAAA,QACvH;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,KAAK,SAAS,GAAG;AACrC,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,KAAK,SAAS,GAAG,MAAM,4BAA4B,KAAK,YAAY,IAAI,MAAM,EAAE;AAC7G,aAAO;AAAA,IACT,UAAE;AACA,YAAM,YAAY,QAAQ;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,qBAA+C;AACnD,UAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,2BAA2B;AAC3E,UAAM,OAAQ,MAAM,KAAK,SAAS,GAAG;AACrC,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,KAAK,SAAS,4BAA4B,IAAI,MAAM,EAAE;AACnF,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,gBAAkD;AACtD,WAAO,KAAK,QAAiC,EAAE,MAAM,iBAAiB,CAAC;AAAA,EACzE;AAAA,EAGA,MAAM,aAA4B;AAChC,UAAM,YAAY;AAClB,UAAM,SAAS,YAAY;AACzB,YAAM,EAAE,KAAK,IAAI,MAAM,KAAK,SAAS,EAAE,MAAM,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE;AACjE,aAAO,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAU,CAAC;AAAA,IAC9D;AACA,UAAM,QAAQ,MAAM,IAAI,QAAQ,CAAC,WAAW,WAAW,QAAQ,GAAG,CAAC;AACnE,UAAM,SAAS,MAAM,OAAO;AAC5B,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,UAAM,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC1C,QAAI,WAAqB,CAAC;AAC1B,WAAO,SAAS,WAAW,GAAG;AAC5B,UAAI,KAAK,IAAI,IAAI,SAAU,OAAM,IAAI,MAAM,6EAA6E;AACxH,YAAM,MAAM;AACZ,YAAM,MAAM,MAAM,OAAO;AACzB,iBAAW,CAAC,GAAG,GAAG,EAAE,OAAO,CAAC,CAAC,OAAO,GAAG,MAAM,IAAI,cAAc,OAAO,IAAI,KAAK,GAAG,cAAc,EAAE,EAAE,IAAI,CAAC,CAAC,KAAK,MAAM,KAAK;AAAA,IAC5H;AACA,eAAS;AACP,UAAI,KAAK,IAAI,IAAI,SAAU,OAAM,IAAI,MAAM,mEAAmE;AAC9G,YAAM,MAAM,MAAM,OAAO;AACzB,UAAI,SAAS,KAAK,CAAC,UAAU;AAC3B,cAAM,WAAW,IAAI,IAAI,KAAK,GAAG;AACjC,eAAO,OAAO,aAAa,YAAY,aAAa;AAAA,MACtD,CAAC,EAAG;AACJ,YAAM,MAAM;AAAA,IACd;AACA,eAAS;AACP,UAAI;AACF,cAAM,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC3C;AAAA,MACF,SAAS,OAAO;AACd,YAAI,KAAK,IAAI,IAAI,SAAU,OAAM;AACjC,cAAM,MAAM;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAAiC;AACrC,UAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,iBAAiB;AACjE,UAAM,QAAS,MAAM,KAAK,SAAS,GAAG;AACtC,QAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,+BAA+B,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAC1F,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,qBAAqB,MAA4C;AACrE,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB,GAAG,KAAK,OAAO,oBAAoB,MAAM,QAAQ,OAAO,WAAW,EAAE;AAAA,MACrE,EAAE,QAAQ,YAAY,QAAQ,wBAAwB,EAAE;AAAA,IAC1D;AAIA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,sCAAsC,IAAI,MAAM,EAAE;AAAA,IACpE;AACA,WAAO,MAAM,KAAK,SAAS,GAAG;AAAA,EAChC;AAAA;AAAA,EAGA,MAAM,mBAAmB,OAIJ;AACnB,UAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,yBAAyB;AAAA,MACvE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,KAAK;AAAA,MAC1B,QAAQ,YAAY,QAAQ,2BAA2B,CAAC;AAAA,IAC1D,CAAC;AACD,UAAM,OAAO,MAAM,KAAK,SAAS,GAAG;AACpC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,UACZ,KACA,MACA,YACmB;AACnB,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,gBAAgB,MAAM,KAAK,cAAc,KAAK,IAAI,IAAI,MAAM,cAAc,KAAK,iBAAiB,KAAK,IAAI,GAAG,UAAU;AAAA,IACnI,SAAS,OAAO;AACd,UAAK,OAAoC,SAAS,eAAgB,OAAM;AACxE,YAAM,EAAE,MAAM,OAAO,IAAI,qBAAqB,KAAK;AACnD,YAAM,IAAI;AAAA,QACR,GAAG,MAAM,UAAU,KAAK,IAAI,GAAG,8BAA8B,IAAI,GAC5D,SAAS,KAAK,MAAM,MAAM,EAAE;AAAA,QAEjC;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,KAAK,cAAe,8BAA6B,KAAK,IAAI,MAAM;AACrE,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAc,SAAS,KAAiC;AAatD,UAAM,cAAc,IAAI,QAAQ,IAAI,cAAc,KAAK;AACvD,QAAI,CAAC,YAAY,SAAS,kBAAkB,GAAG;AAC7C,YAAM,IAAI;AAAA,QACR,iBAAiB,KAAK,OAAO,qCACvB,eAAe,iBAAiB;AAAA,MAExC;AAAA,IACF;AACA,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAAgC;AACpC,UAAM,QAAQ,MAAM,KAAK,SAAS;AAClC,WAAO,MAAM,cAAc;AAAA,EAC7B;AAAA,EAEA,MAAM,aACJ,WACA,YAAY,KACU;AACtB,UAAM,QAAQ,KAAK,IAAI;AACvB,WAAO,KAAK,IAAI,IAAI,QAAQ,WAAW;AACrC,YAAM,QAAQ,MAAM,KAAK,SAAS;AAClC,UAAI,UAAU,KAAK,EAAG,QAAO;AAC7B,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,IAC7C;AACA,UAAM,IAAI,MAAM,gCAAgC,SAAS,IAAI;AAAA,EAC/D;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -15,13 +15,13 @@ function isEditorSessionEntry(v) {
|
|
|
15
15
|
if (typeof v !== "object" || v === null) return false;
|
|
16
16
|
const s = v;
|
|
17
17
|
const optionalIdentity = (value) => value === void 0 || value === null || typeof value === "string";
|
|
18
|
-
return (typeof s["project"] === "string" || s["project"] === null) && typeof s["port"] === "number" && typeof s["pid"] === "number" && typeof s["startedAt"] === "string" && optionalIdentity(s["sessionId"]) && optionalIdentity(s["
|
|
18
|
+
return (typeof s["project"] === "string" || s["project"] === null) && typeof s["port"] === "number" && typeof s["pid"] === "number" && typeof s["startedAt"] === "string" && optionalIdentity(s["sessionId"]) && optionalIdentity(s["connectionToken"]) && optionalIdentity(s["repositoryId"]) && optionalIdentity(s["worktreeId"]) && optionalIdentity(s["worktreeRoot"]) && optionalIdentity(s["projectRelativePath"]) && optionalIdentity(s["branch"]) && optionalIdentity(s["headCommit"]) && optionalIdentity(s["baseCommit"]);
|
|
19
19
|
}
|
|
20
20
|
function normalizeEditorSessionEntry(session) {
|
|
21
21
|
return {
|
|
22
22
|
...session,
|
|
23
23
|
sessionId: session.sessionId ?? null,
|
|
24
|
-
|
|
24
|
+
connectionToken: session.connectionToken ?? null,
|
|
25
25
|
repositoryId: session.repositoryId ?? null,
|
|
26
26
|
worktreeId: session.worktreeId ?? null,
|
|
27
27
|
worktreeRoot: session.worktreeRoot ?? null,
|
|
@@ -47,6 +47,36 @@ function readLiveRegisteredSessions() {
|
|
|
47
47
|
return [];
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
var CONNECTION_TOKEN_COOKIE = "vscode-tkn";
|
|
51
|
+
function registeredConnectionToken(port) {
|
|
52
|
+
return readLiveRegisteredSessions().find((session) => session.port === port)?.connectionToken ?? null;
|
|
53
|
+
}
|
|
54
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
55
|
+
function loopbackSessionPort(url) {
|
|
56
|
+
const target = new URL(url);
|
|
57
|
+
return target.protocol === "http:" && LOOPBACK_HOSTS.has(target.hostname) && target.port !== "" ? Number(target.port) : null;
|
|
58
|
+
}
|
|
59
|
+
function withSessionToken(url, init = {}) {
|
|
60
|
+
const port = loopbackSessionPort(url);
|
|
61
|
+
const token = port === null ? null : registeredConnectionToken(port);
|
|
62
|
+
if (token === null) return init;
|
|
63
|
+
const headers = new Headers(init.headers);
|
|
64
|
+
headers.set("cookie", `${CONNECTION_TOKEN_COOKIE}=${token}`);
|
|
65
|
+
return { ...init, headers };
|
|
66
|
+
}
|
|
67
|
+
function refuseWithoutRegisteredToken(url, status) {
|
|
68
|
+
if (status !== 403) return;
|
|
69
|
+
const port = loopbackSessionPort(url);
|
|
70
|
+
if (port === null || registeredConnectionToken(port) !== null) return;
|
|
71
|
+
throw new Error(
|
|
72
|
+
`The editor session on port ${port} refused this request: its connection token could not be read, because the session registry ${EDITOR_SESSIONS_REGISTRY_FILE} has no live entry for port ${port} carrying one. The session says why its entry could not be written in its terminal and on its console, and writes it again every minute.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
async function sessionFetch(url, init) {
|
|
76
|
+
const response = await fetch(url, withSessionToken(url, init));
|
|
77
|
+
refuseWithoutRegisteredToken(url, response.status);
|
|
78
|
+
return response;
|
|
79
|
+
}
|
|
50
80
|
function announceEditorLaunch(project) {
|
|
51
81
|
const directory = join(project, ".volter", "editor-launches");
|
|
52
82
|
mkdirSync(directory, { recursive: true });
|
|
@@ -116,8 +146,13 @@ export {
|
|
|
116
146
|
normalizeEditorSessionEntry,
|
|
117
147
|
pidAlive,
|
|
118
148
|
readLiveRegisteredSessions,
|
|
149
|
+
CONNECTION_TOKEN_COOKIE,
|
|
150
|
+
registeredConnectionToken,
|
|
151
|
+
withSessionToken,
|
|
152
|
+
refuseWithoutRegisteredToken,
|
|
153
|
+
sessionFetch,
|
|
119
154
|
announceEditorLaunch,
|
|
120
155
|
waitForPendingEditorLaunch,
|
|
121
156
|
servedProjectAnswer
|
|
122
157
|
};
|
|
123
|
-
//# sourceMappingURL=chunk-
|
|
158
|
+
//# sourceMappingURL=chunk-JDOIPALX.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/registry-format.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Shared editor-session registry format: entry shape, guards, path,\n * liveness-filtered reads, served-project parsing and pending launches.\n * The server\u2019s session-registry module owns writes to this format.\n *\n * The registry is also the CREDENTIAL FILE for the owner's own processes: each entry carries its session's\n * connection token, and `sessionFetch` is how a process on this machine reaches a session with it, as a Code-OSS\n * client reads its server's `--connection-token-file` (`src/vs/server/node/serverConnectionToken.ts:62-79` in the\n * `volter-ai/code-oss` fork). The file is the user's alone (0600, written by the session).\n */\n\nimport {\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\n\n/** One registry entry, exactly as the server's write half records it. */\nexport interface EditorSessionEntry {\n /** Canonical project root currently open, or null when none. */\n project: string | null;\n port: number;\n /** PID of the dev-server process. */\n pid: number;\n /** ISO timestamp of server start. */\n startedAt: string;\n sessionId: string | null;\n /** The session's connection token, which every request to it carries (`sessionFetch`). Never returned by a\n * session route; it is in this user-private file and in the page's cookie. */\n connectionToken?: string | null;\n repositoryId: string | null;\n worktreeId: string | null;\n worktreeRoot: string | null;\n projectRelativePath: string | null;\n branch: string | null;\n headCommit: string | null;\n baseCommit: string | null;\n}\n\nexport const EDITOR_SESSIONS_REGISTRY_FILE = join(homedir(), '.volter', 'editor-sessions.json');\n\nexport function isEditorSessionEntry(v: unknown): v is EditorSessionEntry {\n if (typeof v !== 'object' || v === null) return false;\n const s = v as Record<string, unknown>;\n const optionalIdentity = (value: unknown) =>\n value === undefined || value === null || typeof value === 'string';\n return (\n (typeof s['project'] === 'string' || s['project'] === null) &&\n typeof s['port'] === 'number' &&\n typeof s['pid'] === 'number' &&\n typeof s['startedAt'] === 'string' &&\n optionalIdentity(s['sessionId']) &&\n optionalIdentity(s['connectionToken']) &&\n optionalIdentity(s['repositoryId']) &&\n optionalIdentity(s['worktreeId']) &&\n optionalIdentity(s['worktreeRoot']) &&\n optionalIdentity(s['projectRelativePath']) &&\n optionalIdentity(s['branch']) &&\n optionalIdentity(s['headCommit']) &&\n optionalIdentity(s['baseCommit'])\n );\n}\n\nexport function normalizeEditorSessionEntry(session: EditorSessionEntry): EditorSessionEntry {\n return {\n ...session,\n sessionId: session.sessionId ?? null,\n connectionToken: session.connectionToken ?? null,\n repositoryId: session.repositoryId ?? null,\n worktreeId: session.worktreeId ?? null,\n worktreeRoot: session.worktreeRoot ?? null,\n projectRelativePath: session.projectRelativePath ?? null,\n branch: session.branch ?? null,\n headCommit: session.headCommit ?? null,\n baseCommit: session.baseCommit ?? null,\n };\n}\n\n/** Whether pid exists. Signal 0's EPERM means it exists but this caller cannot signal it. */\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/** The registry's live entries \u2014 shape-validated and PID-liveness-filtered. */\nexport function readLiveRegisteredSessions(): EditorSessionEntry[] {\n try {\n const raw: unknown = JSON.parse(readFileSync(EDITOR_SESSIONS_REGISTRY_FILE, 'utf8'));\n return Array.isArray(raw)\n ? raw\n .filter(isEditorSessionEntry)\n .map(normalizeEditorSessionEntry)\n .filter((s) => pidAlive(s.pid))\n : [];\n } catch {\n return [];\n }\n}\n\n/**\n * The cookie the session reads its connection token from: Code-OSS's own (`connectionTokenCookieName`,\n * `src/vs/base/common/network.ts:187`). The page's Code-OSS server sets it on the page's host from the address's\n * `?tkn=` (`src/vs/server/node/webClientServer.ts:313-336`), and the session's routes share that host through its\n * proxy and the token through the project's token file, so one cookie carries both.\n */\nexport const CONNECTION_TOKEN_COOKIE = 'vscode-tkn';\n\n/** The connection token of the live session listening on `port` of this machine, from its entry; null when no live\n * session there registered one. */\nexport function registeredConnectionToken(port: number): string | null {\n return readLiveRegisteredSessions().find((session) => session.port === port)?.connectionToken ?? null;\n}\n\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]']);\n\n/** The port of a session `url` names on this machine, or null for any other address. */\nfunction loopbackSessionPort(url: string | URL): number | null {\n const target = new URL(url);\n return target.protocol === 'http:' && LOOPBACK_HOSTS.has(target.hostname) && target.port !== '' ? Number(target.port) : null;\n}\n\n/** `init` with the connection token of the session `url` names, as the cookie the session reads. Only a loopback\n * address gets one, and only a registered session's: the token never leaves this machine. */\nexport function withSessionToken(url: string | URL, init: RequestInit = {}): RequestInit {\n const port = loopbackSessionPort(url);\n const token = port === null ? null : registeredConnectionToken(port);\n if (token === null) return init;\n const headers = new Headers(init.headers);\n headers.set('cookie', `${CONNECTION_TOKEN_COOKIE}=${token}`);\n return { ...init, headers };\n}\n\n/**\n * Say why a session on this machine refused (403) a request this process could send it no token with: its registry\n * entry, the one place the owner's processes read the token from, is missing or carries none. Throws that; returns\n * when the refusal is something else (a token was sent, or the address is not a local session's).\n */\nexport function refuseWithoutRegisteredToken(url: string | URL, status: number): void {\n if (status !== 403) return;\n const port = loopbackSessionPort(url);\n if (port === null || registeredConnectionToken(port) !== null) return;\n throw new Error(\n `The editor session on port ${port} refused this request: its connection token could not be read, because the ` +\n `session registry ${EDITOR_SESSIONS_REGISTRY_FILE} has no live entry for port ${port} carrying one. The session ` +\n \"says why its entry could not be written in its terminal and on its console, and writes it again every minute.\",\n );\n}\n\n/** `fetch` to a session on this machine, carrying its connection token (`withSessionToken`). Every request a process\n * of the owner's makes to a session goes through here or through `EditorClient`, which does the same, and a refusal\n * for want of the token says so (`refuseWithoutRegisteredToken`). */\nexport async function sessionFetch(url: string | URL, init?: RequestInit): Promise<Response> {\n const response = await fetch(url, withSessionToken(url, init));\n refuseWithoutRegisteredToken(url, response.status);\n return response;\n}\n\n/** Announce a launch before the server can register. Each launcher owns its file. */\nexport function announceEditorLaunch(project: string): () => void {\n const directory = join(project, '.volter', 'editor-launches');\n mkdirSync(directory, { recursive: true });\n const file = join(directory, `${process.pid}.json`);\n writeFileSync(file, JSON.stringify({ pid: process.pid }));\n const clear = () => {\n process.removeListener('exit', clear);\n try {\n unlinkSync(file);\n } catch {\n // Already cleared when the server became ready.\n }\n };\n process.once('exit', clear);\n return clear;\n}\n\nfunction hasPendingEditorLaunch(project: string): boolean {\n const directory = join(project, '.volter', 'editor-launches');\n try {\n return readdirSync(directory).some((name) => {\n if (!/^\\d+\\.json$/.test(name)) return false;\n const pid = Number(name.slice(0, -5));\n return pid !== process.pid && pidAlive(pid);\n });\n } catch {\n return false;\n }\n}\n\n/** Wait only for an observed live launcher, never for a missing editor. */\nexport async function waitForPendingEditorLaunch(project: string): Promise<void> {\n const canon = canonicalizeProjectPath(project);\n const ready = () =>\n readLiveRegisteredSessions().some(\n (session) => session.project !== null && canonicalizeProjectPath(session.project) === canon,\n );\n if (ready() || !hasPendingEditorLaunch(canon)) return;\n const deadline = Date.now() + 180_000;\n while (!ready()) {\n if (!hasPendingEditorLaunch(canon)) {\n // The launcher clears its announcement only after registration.\n if (ready()) return;\n throw new Error(`The editor launch for ${canon} ended before registering a session.`);\n }\n if (Date.now() >= deadline) {\n throw new Error(\n `The editor launch for ${canon} is still running but has not registered within 180s.`,\n );\n }\n await delay(100);\n }\n}\n\n/** Read the project identity reported by /__editor/project. A serving path\n * identifies an open project even when its manifest cannot be parsed or\n * validated; project: null alone denotes no described project. */\nexport function servedProjectAnswer(body: unknown): {\n path: string | null;\n manifestError: string | null;\n} {\n const b = body as {\n project?: { path?: string } | null;\n serving?: { path?: string; error?: string } | null;\n };\n return {\n path: b.project?.path ?? b.serving?.path ?? null,\n manifestError: b.project ? null : (b.serving?.error ?? null),\n };\n}\n\n/** Canonical (realpathed when possible) absolute form of a project path. */\nfunction canonicalizeProjectPath(p: string): string {\n const absolute = resolve(p);\n try {\n return realpathSync(absolute);\n } catch {\n return absolute;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|