@volter/live 0.5.211 → 0.5.212
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 +13 -13
- package/README.md +7 -11
- package/dist/chunk-7SZ3C4CC.js +768 -0
- package/dist/chunk-7SZ3C4CC.js.map +7 -0
- package/dist/chunk-7WKVJGHZ.js +15 -0
- package/dist/chunk-7WKVJGHZ.js.map +7 -0
- package/dist/chunk-SDIWRX5X.js +189 -0
- package/dist/chunk-SDIWRX5X.js.map +7 -0
- package/dist/chunk-USXOIBVX.js +81 -0
- package/dist/chunk-USXOIBVX.js.map +7 -0
- package/dist/chunk-Y5W77HYT.js +123 -0
- package/dist/chunk-Y5W77HYT.js.map +7 -0
- package/dist/chunk-YPAOTPIF.js +202 -0
- package/dist/chunk-YPAOTPIF.js.map +7 -0
- package/dist/client.d.ts +370 -0
- package/dist/client.js +9 -0
- package/dist/client.js.map +7 -0
- package/dist/discovery.d.ts +32 -0
- package/dist/discovery.js +19 -0
- package/dist/discovery.js.map +7 -0
- package/dist/editor-document.d.ts +12 -47
- package/dist/editor.d.ts +34 -181
- package/dist/http-transport.d.ts +6 -0
- package/dist/index.d.ts +1 -5
- package/dist/index.js +76 -1722
- package/dist/index.js.map +4 -4
- package/dist/installed-package.d.ts +6 -0
- package/dist/installed-package.js +7 -0
- package/dist/installed-package.js.map +7 -0
- package/dist/lazy-proxy.d.ts +11 -11
- package/dist/member-hints.d.ts +6 -9
- package/dist/product-create.d.ts +29 -0
- package/dist/product-create.js +13 -0
- package/dist/product-create.js.map +7 -0
- package/dist/product-locator.d.ts +100 -0
- package/dist/product-locator.js +25 -0
- package/dist/product-locator.js.map +7 -0
- package/dist/registry-format.d.ts +44 -0
- package/dist/registry-format.js +21 -0
- package/dist/registry-format.js.map +7 -0
- package/dist/session.d.ts +10 -3
- package/dist/singleton.d.ts +1 -13
- package/dist/volter-manifest.d.ts +55 -0
- package/dist/volter-manifest.js +9 -0
- package/dist/volter-manifest.js.map +7 -0
- package/dist/workbench-locator.d.ts +84 -0
- package/dist/workbench-locator.js +416 -0
- package/dist/workbench-locator.js.map +7 -0
- package/package.json +35 -3
- package/src/client.ts +1052 -0
- package/src/discovery.ts +105 -0
- package/src/editor-document.ts +12 -49
- package/src/editor.ts +45 -212
- package/src/http-transport.ts +19 -0
- package/src/index.ts +3 -7
- package/src/installed-package.ts +15 -0
- package/src/lazy-proxy.ts +11 -11
- package/src/member-hints.ts +8 -12
- package/src/product-create.ts +38 -0
- package/src/product-locator.ts +327 -0
- package/src/registry-format.ts +185 -0
- package/src/session.ts +38 -66
- package/src/singleton.ts +1 -16
- package/src/volter-manifest.ts +97 -0
- package/src/workbench-locator.ts +625 -0
- package/dist/tools.d.ts +0 -23
- package/src/tools.ts +0 -45
|
@@ -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 { 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, 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 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). Resolves once the\n * dock has finished rebuilding, so a following capture photographs the\n * arrangement that was asked for. */\n async setWorkspace(workspace: EditorWorkspaceName): Promise<void> {\n await this.command({ 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 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 ): Promise<Response> {\n try {\n return this.fetchOverride ? await this.fetchOverride(url, init) : await dispatchFetch(url, init);\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 }\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": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/installed-package.ts
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
function installedPackageDir(fromDir, name) {
|
|
5
|
+
for (let dir = fromDir; ; dir = dirname(dir)) {
|
|
6
|
+
const candidate = join(dir, "node_modules", ...name.split("/"));
|
|
7
|
+
if (existsSync(join(candidate, "package.json"))) return realpathSync(candidate);
|
|
8
|
+
if (dirname(dir) === dir) return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
installedPackageDir
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=chunk-7WKVJGHZ.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/installed-package.ts"],
|
|
4
|
+
"sourcesContent": ["import { existsSync, realpathSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\n/**\n * The real directory of installed package `name` as Node finds it from `fromDir`: up the `node_modules` folders, or\n * null when none holds it. It is found by its folder rather than by resolving `<name>/package.json`, which fails for a\n * package whose `exports` does not list that file (`@volter/sdk`).\n */\nexport function installedPackageDir(fromDir: string, name: string): string | null {\n for (let dir = fromDir; ; dir = dirname(dir)) {\n const candidate = join(dir, 'node_modules', ...name.split('/'));\n if (existsSync(join(candidate, 'package.json'))) return realpathSync(candidate);\n if (dirname(dir) === dir) return null;\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,YAAY;AAOvB,SAAS,oBAAoB,SAAiB,MAA6B;AAChF,WAAS,MAAM,WAAW,MAAM,QAAQ,GAAG,GAAG;AAC5C,UAAM,YAAY,KAAK,KAAK,gBAAgB,GAAG,KAAK,MAAM,GAAG,CAAC;AAC9D,QAAI,WAAW,KAAK,WAAW,cAAc,CAAC,EAAG,QAAO,aAAa,SAAS;AAC9E,QAAI,QAAQ,GAAG,MAAM,IAAK,QAAO;AAAA,EACnC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
installedPackageDir
|
|
3
|
+
} from "./chunk-7WKVJGHZ.js";
|
|
4
|
+
import {
|
|
5
|
+
declaredContributions,
|
|
6
|
+
readVolterManifest
|
|
7
|
+
} from "./chunk-YPAOTPIF.js";
|
|
8
|
+
|
|
9
|
+
// src/product-locator.ts
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
var PRODUCT_DECLARATION_KEY = "contributes.volter.product";
|
|
13
|
+
var PRODUCT_DECLARATION_HINT = `a product package as a dev dependency: one whose package.json declares \`${PRODUCT_DECLARATION_KEY}\``;
|
|
14
|
+
var PRODUCT_COLOR_THEMES = ["dark", "light"];
|
|
15
|
+
function readProductManifest(packageDir) {
|
|
16
|
+
const manifestPath = join(packageDir, "package.json");
|
|
17
|
+
const manifest = readVolterManifest(manifestPath);
|
|
18
|
+
const declared = manifest?.product;
|
|
19
|
+
if (manifest === null || declared === void 0) return null;
|
|
20
|
+
const entry = declared.entry;
|
|
21
|
+
if (typeof entry !== "string" || !entry.startsWith("./"))
|
|
22
|
+
throw new Error(
|
|
23
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "entry" is ${JSON.stringify(entry)}. It must be a ./-relative module \u2014 the product's ONE source entry, which is also its frame entry (e.g. "./src/index.ts").`
|
|
24
|
+
);
|
|
25
|
+
const resolved = join(packageDir, entry);
|
|
26
|
+
if (!existsSync(resolved))
|
|
27
|
+
throw new Error(
|
|
28
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY}.entry ${JSON.stringify(entry)}, which does not exist (${resolved}).`
|
|
29
|
+
);
|
|
30
|
+
const colorTheme = declared.colorTheme;
|
|
31
|
+
if (typeof colorTheme !== "string" || !PRODUCT_COLOR_THEMES.includes(colorTheme))
|
|
32
|
+
throw new Error(
|
|
33
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "colorTheme" is ${JSON.stringify(colorTheme)}. It must be ${PRODUCT_COLOR_THEMES.map(
|
|
34
|
+
(one) => JSON.stringify(one)
|
|
35
|
+
).join(" or ")} \u2014 which way round this product paints, read before any of its code runs so the workbench's first frame is already the product's own background.`
|
|
36
|
+
);
|
|
37
|
+
const composes = productComposes(manifestPath, declared);
|
|
38
|
+
const bin = manifest.bin;
|
|
39
|
+
const commands = bin !== null && typeof bin === "object" ? Object.keys(bin) : [];
|
|
40
|
+
if (commands.length !== 1)
|
|
41
|
+
throw new Error(
|
|
42
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "bin" names ${commands.length === 0 ? "no command" : `${commands.length} commands`}. A product has exactly one command \u2014 the one every session message tells a person to run.`
|
|
43
|
+
);
|
|
44
|
+
const displayName = declared.displayName;
|
|
45
|
+
if (typeof displayName !== "string" || displayName.trim() === "")
|
|
46
|
+
throw new Error(
|
|
47
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "displayName" is ${JSON.stringify(displayName)}. It must be the name a person sees (e.g. "Volter Editor").`
|
|
48
|
+
);
|
|
49
|
+
const name = manifest.name ?? packageDir;
|
|
50
|
+
const install = declared.install;
|
|
51
|
+
if (install !== void 0 && (typeof install !== "string" || install.trim() === ""))
|
|
52
|
+
throw new Error(
|
|
53
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "install" is ${JSON.stringify(install)}. It is the one line that installs this product and starts a project (e.g. "npx ${name} create my-project"), or absent.`
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
name,
|
|
57
|
+
version: manifest.version ?? "unknown",
|
|
58
|
+
command: commands[0],
|
|
59
|
+
displayName,
|
|
60
|
+
upgrade: declared.upgrade === true,
|
|
61
|
+
adapterRequired: declared.adapterRequired === true,
|
|
62
|
+
dir: packageDir,
|
|
63
|
+
entry,
|
|
64
|
+
composes,
|
|
65
|
+
colorTheme,
|
|
66
|
+
// A private source product may use an explicitly selected matching checkout.
|
|
67
|
+
// Public packages must always carry a downloadable, checksummed workbench.
|
|
68
|
+
workbench: manifest.private && declared.workbench === void 0 ? null : readProductWorkbench(manifestPath, declared.workbench),
|
|
69
|
+
install: typeof install === "string" ? install.trim() : `npx ${name} create my-project`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function productComposes(manifestPath, declared) {
|
|
73
|
+
const composes = declared.composes;
|
|
74
|
+
if (!Array.isArray(composes) || composes.some((name) => typeof name !== "string" || name === ""))
|
|
75
|
+
throw new Error(
|
|
76
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "composes" is ${JSON.stringify(composes)}. It must list the packages this product mounts, by name (e.g. ["<package>"]); each is a dependency of the product package.`
|
|
77
|
+
);
|
|
78
|
+
return composes;
|
|
79
|
+
}
|
|
80
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
81
|
+
function readProductWorkbench(manifestPath, declared) {
|
|
82
|
+
const example = ' "contributes": { "volter": { "product": { "workbench": { "release": "<product>-<fork sha 12>-<platform>", "tarballSha256": "<64 hex>" } } } }\n`node scripts/workbench/build-release.mjs --publish --out <release dir>` cuts the release and prints exactly that block.';
|
|
83
|
+
if (typeof declared !== "object" || declared === null || Array.isArray(declared))
|
|
84
|
+
throw new Error(
|
|
85
|
+
`${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its "workbench" is ${JSON.stringify(declared)}. A product IS the Code-OSS workbench it was compiled into, so it names the published release those bytes are:
|
|
86
|
+
${example}`
|
|
87
|
+
);
|
|
88
|
+
const platforms = declared;
|
|
89
|
+
if (!("release" in platforms) && !("tarballSha256" in platforms)) {
|
|
90
|
+
const machine = `${process.platform}-${process.arch}`;
|
|
91
|
+
const declaredHere = platforms[machine];
|
|
92
|
+
if (declaredHere === void 0)
|
|
93
|
+
throw new Error(
|
|
94
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench declares releases for ${Object.keys(platforms).join(", ") || "no platform"} and this machine is ${machine}. A Code-OSS server package carries its own platform's node binary and native modules, so there is nothing this product can run in here until a ${machine} release is cut:
|
|
95
|
+
node scripts/workbench/build-release.mjs --product <id> --platform ${machine} --checkout <fork dir> --publish`
|
|
96
|
+
);
|
|
97
|
+
return readPlatformWorkbench(manifestPath, declaredHere, example);
|
|
98
|
+
}
|
|
99
|
+
return readPlatformWorkbench(manifestPath, declared, example);
|
|
100
|
+
}
|
|
101
|
+
function readPlatformWorkbench(manifestPath, declared, example) {
|
|
102
|
+
if (typeof declared !== "object" || declared === null || Array.isArray(declared))
|
|
103
|
+
throw new Error(
|
|
104
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench names ${JSON.stringify(declared)} for this platform, which is not a release:
|
|
105
|
+
${example}`
|
|
106
|
+
);
|
|
107
|
+
const record = declared;
|
|
108
|
+
const extra = Object.keys(record).filter((key) => key !== "release" && key !== "tarballSha256");
|
|
109
|
+
if (extra.length > 0)
|
|
110
|
+
throw new Error(
|
|
111
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench carries ${extra.map((key) => `"${key}"`).join(", ")}, which this declaration has no meaning for. It names the release and the pin, and nothing else:
|
|
112
|
+
${example}`
|
|
113
|
+
);
|
|
114
|
+
const release = record["release"];
|
|
115
|
+
if (typeof release !== "string" || release.trim() === "")
|
|
116
|
+
throw new Error(
|
|
117
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench names no "release" \u2014 the GitHub Release tag its workbench was published under:
|
|
118
|
+
${example}`
|
|
119
|
+
);
|
|
120
|
+
const tarballSha256 = record["tarballSha256"];
|
|
121
|
+
if (typeof tarballSha256 !== "string" || !SHA256_HEX.test(tarballSha256))
|
|
122
|
+
throw new Error(
|
|
123
|
+
`${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench has tarballSha256 ${JSON.stringify(tarballSha256)}, which is not 64 hex characters. It is the PIN \u2014 the hash the downloaded tarball is checked against before a byte of it is extracted, so it is never approximate:
|
|
124
|
+
${example}`
|
|
125
|
+
);
|
|
126
|
+
return { release, tarballSha256 };
|
|
127
|
+
}
|
|
128
|
+
function workbenchProductId(packageName) {
|
|
129
|
+
return packageName.split("/").pop() ?? packageName;
|
|
130
|
+
}
|
|
131
|
+
function productEntryPath(product, fromDir) {
|
|
132
|
+
const absolute = join(product.dir, product.entry);
|
|
133
|
+
const prefix = fromDir.endsWith("/") ? fromDir : `${fromDir}/`;
|
|
134
|
+
const relative = absolute.startsWith(prefix) ? absolute.slice(prefix.length) : absolute;
|
|
135
|
+
return relative.split("\\").join("/");
|
|
136
|
+
}
|
|
137
|
+
function declaredVolterPackages(projectRoot) {
|
|
138
|
+
const manifest = readVolterManifest(join(projectRoot, "package.json"));
|
|
139
|
+
return (manifest?.dependencyNames ?? []).filter((name) => name.startsWith("@volter/")).sort();
|
|
140
|
+
}
|
|
141
|
+
function productComposedPackages(product) {
|
|
142
|
+
return product.composes.filter((name) => {
|
|
143
|
+
const dir = installedPackageDir(product.dir, name);
|
|
144
|
+
if (dir === null) return false;
|
|
145
|
+
const manifest = readVolterManifest(join(dir, "package.json"));
|
|
146
|
+
return declaredContributions(manifest) !== null;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function declaredProducts(projectRoot) {
|
|
150
|
+
const found = [];
|
|
151
|
+
for (const name of declaredVolterPackages(projectRoot)) {
|
|
152
|
+
const dir = installedPackageDir(projectRoot, name);
|
|
153
|
+
if (dir === null) continue;
|
|
154
|
+
const product = readProductManifest(dir);
|
|
155
|
+
if (product !== null) found.push(product);
|
|
156
|
+
}
|
|
157
|
+
return found;
|
|
158
|
+
}
|
|
159
|
+
function resolveProductForProject(projectRoot) {
|
|
160
|
+
const products = declaredProducts(projectRoot);
|
|
161
|
+
if (products.length === 1) return products[0];
|
|
162
|
+
if (products.length === 0)
|
|
163
|
+
throw new Error(
|
|
164
|
+
`${projectRoot} declares no editor product, so there is no editor to open it in.
|
|
165
|
+
The installed product supplies the editor,
|
|
166
|
+
and which one runs is what the project installed, never a flag.
|
|
167
|
+
For THIS folder, install ${PRODUCT_DECLARATION_HINT},
|
|
168
|
+
then run that product's \`edit\` command again. For a NEW project, the product's own \`create\` verb makes it.`
|
|
169
|
+
);
|
|
170
|
+
throw new Error(
|
|
171
|
+
`${projectRoot} declares ${products.length} editor products \u2014 ${products.map((product) => product.name).join(" and ")}.
|
|
172
|
+
One page runs one product, and nothing here may choose between them. Remove the one you
|
|
173
|
+
are not building with:
|
|
174
|
+
` + products.map((product) => ` npm uninstall ${product.name}`).join("\n")
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export {
|
|
179
|
+
PRODUCT_DECLARATION_KEY,
|
|
180
|
+
PRODUCT_DECLARATION_HINT,
|
|
181
|
+
readProductManifest,
|
|
182
|
+
productComposes,
|
|
183
|
+
workbenchProductId,
|
|
184
|
+
productEntryPath,
|
|
185
|
+
productComposedPackages,
|
|
186
|
+
declaredProducts,
|
|
187
|
+
resolveProductForProject
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=chunk-SDIWRX5X.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/product-locator.ts"],
|
|
4
|
+
"sourcesContent": ["\n\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { installedPackageDir } from './installed-package';\nimport { declaredContributions, type ProductDeclaration, readVolterManifest } from './volter-manifest';\n\n\nexport const PRODUCT_DECLARATION_KEY = 'contributes.volter.product';\n\n/** Light or dark \u2014 the two the workbench's own `ColorScheme` has plain names\n * for, and the only two a product needs to state (a high-contrast scheme is a\n * person's accessibility choice, never a product's declaration). */\nexport type ProductColorTheme = 'dark' | 'light';\n\n\nexport interface ProductWorkbench {\n /** The GitHub Release tag on `volter-ai/code-oss`. */\n readonly release: string;\n /** The sha256 of that release's tarball asset, hex. */\n readonly tarballSha256: string;\n}\n\n/** A product package, as everything outside it needs to know it. */\nexport interface ProductIdentity {\n\n readonly name: string;\n /** Its version, reported beside the workbench by the editor's `status` command. */\n readonly version: string;\n\n readonly command: string;\n /** The name a person sees \u2014 `contributes.volter.product.displayName`. */\n readonly displayName: string;\n /**\n * `contributes.volter.product.upgrade: true` \u2014 its command has `upgrade [version]`\n * (the product CLI's own), so a pinned-engine refusal\n * may tell a person to run it. Optional: a product that says nothing has none.\n */\n readonly upgrade?: boolean;\n /**\n * `contributes.volter.product.adapterRequired: true` \u2014 a project this product opens must have its own\n * adapter module (`editor/volter.adapter.ts`), because the product's layout and document\n * finders are declared there and the native default shows none of them. Such a product refuses\n * a project without one. Optional: a product that says nothing opens it on the native default.\n */\n readonly adapterRequired?: boolean;\n /** Its package root, absolute. */\n readonly dir: string;\n /**\n * Its ONE source entry, package-relative and POSIX (`./src/index.ts`). That\n * entry IS the product's frame entry: it composes and re-exports `mountVolter`,\n * which is the name the fork's contribution reads off the served module.\n */\n readonly entry: string;\n /**\n * The packages this product mounts, in order \u2014 `contributes.volter.product.composes`. The ONE statement of\n * the composition: the build serves it to the entry as `volter:composition`, and the session\n * reads it here for the catalog and the serving doors.\n */\n readonly composes: readonly string[];\n\n readonly colorTheme: ProductColorTheme;\n /** WHICH WORKBENCH THIS PRODUCT IS \u2014 see {@link ProductWorkbench}. */\n readonly workbench: ProductWorkbench | null;\n /**\n * The ONE LINE that installs this product and starts a project in it \u2014\n * `contributes.volter.product.install` (`npx <name> create my-project`). A limited view quotes\n * it wherever it cannot do what the local editor does (its agent answers with it, and every\n * route it does not carry names it), so it is a declaration read without running the product.\n * An undeclared one is `npx <name> create my-project`, which is what every product's `create`\n * verb accepts.\n */\n readonly install: string;\n}\n\n/** What a project without a product is told to add: the declaration this module reads, naming no package. */\nexport const PRODUCT_DECLARATION_HINT =\n `a product package as a dev dependency: one whose package.json declares \\`${PRODUCT_DECLARATION_KEY}\\``;\n\nconst PRODUCT_COLOR_THEMES: readonly ProductColorTheme[] = ['dark', 'light'];\n\n/**\n * Read one product package's declaration, or `null` when the manifest is not a\n * product's. Throws when it declares `contributes.volter.product` and the declaration is\n * unusable \u2014 a half-written product must be named, never skipped.\n */\nexport function readProductManifest(packageDir: string): ProductIdentity | null {\n const manifestPath = join(packageDir, 'package.json');\n const manifest = readVolterManifest(manifestPath);\n const declared = manifest?.product;\n if (manifest === null || declared === undefined) return null;\n const entry = declared.entry;\n if (typeof entry !== 'string' || !entry.startsWith('./'))\n throw new Error(\n `${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its \"entry\" is ` +\n `${JSON.stringify(entry)}. It must be a ./-relative module \u2014 the product's ONE source ` +\n 'entry, which is also its frame entry (e.g. \"./src/index.ts\").',\n );\n const resolved = join(packageDir, entry);\n if (!existsSync(resolved))\n throw new Error(\n `${manifestPath} declares ${PRODUCT_DECLARATION_KEY}.entry ${JSON.stringify(entry)}, ` +\n `which does not exist (${resolved}).`,\n );\n const colorTheme = declared.colorTheme;\n // REQUIRED, like `entry`, and for the same reason: a product that does not\n // say which way round it paints gets the workbench's web default, which is\n // LIGHT, and its cover is white until its real theme loads. There is no\n // defensible default to invent here \u2014 a dark product and a light product are\n // both ordinary \u2014 so an unstated one is named rather than guessed.\n if (typeof colorTheme !== 'string' || !PRODUCT_COLOR_THEMES.includes(colorTheme as never))\n throw new Error(\n `${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its \"colorTheme\" is ` +\n `${JSON.stringify(colorTheme)}. It must be ${PRODUCT_COLOR_THEMES.map((one) =>\n JSON.stringify(one),\n ).join(' or ')} \u2014 which way round this product paints, read before any of its code ` +\n \"runs so the workbench's first frame is already the product's own background.\",\n );\n // REQUIRED, for the same reason as `entry`: the kit names verbs in every\n // message it writes, and there is no command to name but the product's own.\n const composes = productComposes(manifestPath, declared);\n const bin = manifest.bin;\n const commands = bin !== null && typeof bin === 'object' ? Object.keys(bin) : [];\n if (commands.length !== 1)\n throw new Error(\n `${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its \"bin\" names ` +\n `${commands.length === 0 ? 'no command' : `${commands.length} commands`}. A product has ` +\n 'exactly one command \u2014 the one every session message tells a person to run.',\n );\n const displayName = declared.displayName;\n if (typeof displayName !== 'string' || displayName.trim() === '')\n throw new Error(\n `${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its \"displayName\" is ` +\n `${JSON.stringify(displayName)}. It must be the name a person sees (e.g. \"Volter Editor\").`,\n );\n const name = manifest.name ?? packageDir;\n const install = declared.install;\n if (install !== undefined && (typeof install !== 'string' || install.trim() === ''))\n throw new Error(\n `${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its \"install\" is ` +\n `${JSON.stringify(install)}. It is the one line that installs this product and starts a ` +\n `project (e.g. \"npx ${name} create my-project\"), or absent.`,\n );\n return {\n name,\n version: manifest.version ?? 'unknown',\n command: commands[0]!,\n displayName,\n upgrade: declared.upgrade === true,\n adapterRequired: declared.adapterRequired === true,\n dir: packageDir,\n entry,\n composes,\n colorTheme: colorTheme as ProductColorTheme,\n // A private source product may use an explicitly selected matching checkout.\n // Public packages must always carry a downloadable, checksummed workbench.\n workbench: manifest.private && declared.workbench === undefined\n ? null : readProductWorkbench(manifestPath, declared.workbench),\n install: typeof install === 'string' ? install.trim() : `npx ${name} create my-project`,\n };\n}\n\n/** A product declaration's `composes`, checked: the packages it mounts, by name, in order. The\n * build's composition module and the session read it through this one check. */\nexport function productComposes(manifestPath: string, declared: ProductDeclaration): string[] {\n const composes = declared.composes;\n if (!Array.isArray(composes) || composes.some((name) => typeof name !== 'string' || name === ''))\n throw new Error(\n `${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its \"composes\" is ` +\n `${JSON.stringify(composes)}. It must list the packages this product mounts, by name ` +\n '(e.g. [\"<package>\"]); each is a dependency of the product package.',\n );\n return composes as string[];\n}\n\n/** The sha256 a release pins its tarball with, as `BUILD.json` writes it. */\nconst SHA256_HEX = /^[0-9a-f]{64}$/;\n\n/**\n * REQUIRED, like `entry` and `colorTheme`, and for the sharpest reason of all\n * three of them: a product that does not say which workbench it is cannot be INSTALLED\n * \u2014 `npx @volter/<product> create <name>` on a machine with nothing else on it\n * has no other way to find the bytes it runs in, and the person is left writing\n * a path to a directory they would have to build first. There is no default to\n * invent (the tag names a fork commit nobody can guess), so an unstated one is\n * named rather than guessed.\n */\nfunction readProductWorkbench(manifestPath: string, declared: unknown): ProductWorkbench {\n const example =\n ' \"contributes\": { \"volter\": { \"product\": { \"workbench\": { \"release\": \"<product>-<fork sha 12>-<platform>\", ' +\n '\"tarballSha256\": \"<64 hex>\" } } } }\\n' +\n '`node scripts/workbench/build-release.mjs --publish --out <release dir>` cuts the release ' +\n 'and prints exactly that block.';\n if (typeof declared !== 'object' || declared === null || Array.isArray(declared))\n throw new Error(\n `${manifestPath} declares ${PRODUCT_DECLARATION_KEY} but its \"workbench\" is ` +\n `${JSON.stringify(declared)}. A product IS the Code-OSS workbench it was compiled into, ` +\n `so it names the published release those bytes are:\\n${example}`,\n );\n const platforms = declared as Record<string, unknown>;\n if (!('release' in platforms) && !('tarballSha256' in platforms)) {\n const machine = `${process.platform}-${process.arch}`;\n const declaredHere = platforms[machine];\n if (declaredHere === undefined)\n throw new Error(\n `${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench declares releases for ` +\n `${Object.keys(platforms).join(', ') || 'no platform'} and this machine is ${machine}. A ` +\n \"Code-OSS server package carries its own platform's node binary and native modules, so \" +\n `there is nothing this product can run in here until a ${machine} release is cut:\\n` +\n ` node scripts/workbench/build-release.mjs --product <id> --platform ${machine} --checkout <fork dir> --publish`,\n );\n return readPlatformWorkbench(manifestPath, declaredHere, example);\n }\n return readPlatformWorkbench(manifestPath, declared, example);\n}\n\nfunction readPlatformWorkbench(manifestPath: string, declared: unknown, example: string): ProductWorkbench {\n if (typeof declared !== 'object' || declared === null || Array.isArray(declared))\n throw new Error(\n `${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench names ${JSON.stringify(declared)} ` +\n `for this platform, which is not a release:\\n${example}`,\n );\n const record = declared as Record<string, unknown>;\n const extra = Object.keys(record).filter((key) => key !== 'release' && key !== 'tarballSha256');\n if (extra.length > 0)\n throw new Error(\n `${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench carries ` +\n `${extra.map((key) => `\"${key}\"`).join(', ')}, which this declaration has no meaning for. ` +\n `It names the release and the pin, and nothing else:\\n${example}`,\n );\n const release = record['release'];\n if (typeof release !== 'string' || release.trim() === '')\n throw new Error(\n `${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench names no \"release\" \u2014 the GitHub ` +\n `Release tag its workbench was published under:\\n${example}`,\n );\n const tarballSha256 = record['tarballSha256'];\n if (typeof tarballSha256 !== 'string' || !SHA256_HEX.test(tarballSha256))\n throw new Error(\n `${manifestPath}'s ${PRODUCT_DECLARATION_KEY}.workbench has tarballSha256 ` +\n `${JSON.stringify(tarballSha256)}, which is not 64 hex characters. It is the PIN \u2014 the ` +\n 'hash the downloaded tarball is checked against before a byte of it is extracted, so it ' +\n `is never approximate:\\n${example}`,\n );\n return { release, tarballSha256 };\n}\n\n/**\n * A product's SHORT id \u2014 `@<scope>/<name>` \u2192 `<name>`.\n *\n * It is the product's own `product({ id })`, the directory its workbench half\n * lives in (`packages/<id>/workbench`), the `--product` flag the overlay and the\n * release build take, and what a release records in `BUILD.json`. One derivation\n * rather than four literals: the package name is the only place the product is\n * NAMED, and everything else is that name's last segment.\n */\nexport function workbenchProductId(packageName: string): string {\n return packageName.split('/').pop() ?? packageName;\n}\n\n/** A product's entry as a path relative to `fromDir`, POSIX \u2014 the spelling a\n * Vite manifest key and a dev-server URL both use. */\nexport function productEntryPath(product: ProductIdentity, fromDir: string): string {\n const absolute = join(product.dir, product.entry);\n const prefix = fromDir.endsWith('/') ? fromDir : `${fromDir}/`;\n const relative = absolute.startsWith(prefix) ? absolute.slice(prefix.length) : absolute;\n return relative.split('\\\\').join('/');\n}\n\n/** Every `@volter/*` this project declares, in either section, sorted. */\nfunction declaredVolterPackages(projectRoot: string): string[] {\n const manifest = readVolterManifest(join(projectRoot, 'package.json'));\n return (manifest?.dependencyNames ?? []).filter((name) => name.startsWith('@volter/')).sort();\n}\n\n/**\n * The packages `product` mounts that are installed and contribute: its `contributes.volter.product.composes`,\n * each resolved from the product's own directory and kept when its manifest declares\n * a `contributes.volter` object (its serving half, `contributes.volter.serving`, may be all a package contributes).\n */\nexport function productComposedPackages(product: ProductIdentity): string[] {\n return product.composes.filter((name) => {\n const dir = installedPackageDir(product.dir, name);\n if (dir === null) return false;\n const manifest = readVolterManifest(join(dir, 'package.json'));\n return declaredContributions(manifest) !== null;\n });\n}\n\n/** The products this project declares AND resolves, in declaration order. */\nexport function declaredProducts(projectRoot: string): ProductIdentity[] {\n const found: ProductIdentity[] = [];\n for (const name of declaredVolterPackages(projectRoot)) {\n const dir = installedPackageDir(projectRoot, name);\n if (dir === null) continue;\n const product = readProductManifest(dir);\n if (product !== null) found.push(product);\n }\n return found;\n}\n\n/**\n * THE product this project opens in, or a throw naming what is wrong.\n *\n * The refusals are the whole point: a project with none and a project with two\n * are both states a person can be in, and neither has an answer this code may\n * invent.\n */\nexport function resolveProductForProject(projectRoot: string): ProductIdentity {\n const products = declaredProducts(projectRoot);\n if (products.length === 1) return products[0] as ProductIdentity;\n if (products.length === 0)\n throw new Error(\n `${projectRoot} declares no editor product, so there is no editor to open it in.\\n` +\n 'The installed product supplies the editor,\\n' +\n 'and which one runs is what the project installed, never a flag.\\n' +\n `For THIS folder, install ${PRODUCT_DECLARATION_HINT},\\n` +\n \"then run that product's `edit` command again. For a NEW project, the product's own `create` verb makes it.\",\n );\n throw new Error(\n `${projectRoot} declares ${products.length} editor products \u2014 ` +\n `${products.map((product) => product.name).join(' and ')}.\\n` +\n 'One page runs one product, and nothing here may choose between them. Remove the one you\\n' +\n 'are not building with:\\n' +\n products.map((product) => ` npm uninstall ${product.name}`).join('\\n'),\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readLiveRegisteredSessions,
|
|
3
|
+
servedProjectAnswer
|
|
4
|
+
} from "./chunk-Y5W77HYT.js";
|
|
5
|
+
|
|
6
|
+
// src/discovery.ts
|
|
7
|
+
var EDITOR_SESSION_DISCOVERY_TIMEOUT_MS = 2e3;
|
|
8
|
+
var EDITOR_PROBE_TIMEOUT_MS = 1500;
|
|
9
|
+
var EditorTimeoutError = class extends Error {
|
|
10
|
+
constructor(label, ms) {
|
|
11
|
+
super(`${label} timed out after ${ms}ms`);
|
|
12
|
+
this.name = "EditorTimeoutError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function withTimeout(promise, ms, label) {
|
|
16
|
+
return new Promise((resolvePromise, reject) => {
|
|
17
|
+
const timer = setTimeout(() => reject(new EditorTimeoutError(label, ms)), ms);
|
|
18
|
+
promise.then(
|
|
19
|
+
(v) => {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
resolvePromise(v);
|
|
22
|
+
},
|
|
23
|
+
(err) => {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function fetchJson(url, timeoutMs) {
|
|
31
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
32
|
+
if (!res.ok) throw new Error(`GET /__editor/project returned HTTP ${res.status}`);
|
|
33
|
+
return await res.json();
|
|
34
|
+
}
|
|
35
|
+
function baseUrl(session) {
|
|
36
|
+
return session.url ?? `http://127.0.0.1:${session.port}`;
|
|
37
|
+
}
|
|
38
|
+
var HttpSessionDiscovery = class {
|
|
39
|
+
async listSessions(timeoutMs) {
|
|
40
|
+
const registered = readLiveRegisteredSessions();
|
|
41
|
+
const perProbeTimeout = Math.min(EDITOR_PROBE_TIMEOUT_MS, Math.max(200, timeoutMs));
|
|
42
|
+
const failures = [];
|
|
43
|
+
const probes = await Promise.all(
|
|
44
|
+
registered.map(async (s) => {
|
|
45
|
+
try {
|
|
46
|
+
const body = await fetchJson(
|
|
47
|
+
`${baseUrl({ port: s.port, project: null, pid: s.pid })}/__editor/project`,
|
|
48
|
+
perProbeTimeout
|
|
49
|
+
);
|
|
50
|
+
const served = servedProjectAnswer(body);
|
|
51
|
+
const info = {
|
|
52
|
+
port: s.port,
|
|
53
|
+
project: served.path,
|
|
54
|
+
pid: s.pid,
|
|
55
|
+
manifestError: served.manifestError
|
|
56
|
+
};
|
|
57
|
+
return info;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const timedOut = error?.name === "TimeoutError";
|
|
60
|
+
const detail = timedOut ? `GET /__editor/project sent no answer within ${perProbeTimeout} ms (pid ${s.pid})` : error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
61
|
+
failures.push(`port ${s.port}: ${detail}`);
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
const sessions = probes.filter((p) => p !== void 0);
|
|
67
|
+
if (registered.length > 0 && sessions.length === 0) {
|
|
68
|
+
throw new Error(`Registered editor sessions could not be inspected: ${failures.join("; ")}`);
|
|
69
|
+
}
|
|
70
|
+
return sessions;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
EDITOR_SESSION_DISCOVERY_TIMEOUT_MS,
|
|
76
|
+
EDITOR_PROBE_TIMEOUT_MS,
|
|
77
|
+
EditorTimeoutError,
|
|
78
|
+
withTimeout,
|
|
79
|
+
HttpSessionDiscovery
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=chunk-USXOIBVX.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/discovery.ts"],
|
|
4
|
+
"sourcesContent": ["/** Registered-session discovery; never selects another project's session. */\nimport { readLiveRegisteredSessions, servedProjectAnswer } from './registry-format.js';\nexport { servedProjectAnswer } from './registry-format.js';\n\nexport const EDITOR_SESSION_DISCOVERY_TIMEOUT_MS = 2000;\n\nexport const EDITOR_PROBE_TIMEOUT_MS = 1500;\n\nexport class EditorTimeoutError extends Error {\n constructor(label: string, ms: number) {\n super(`${label} timed out after ${ms}ms`);\n this.name = 'EditorTimeoutError';\n }\n}\n\nexport function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {\n return new Promise<T>((resolvePromise, reject) => {\n const timer = setTimeout(() => reject(new EditorTimeoutError(label, ms)), ms);\n promise.then(\n (v) => {\n clearTimeout(timer);\n resolvePromise(v);\n },\n (err) => {\n clearTimeout(timer);\n reject(err instanceof Error ? err : new Error(String(err)));\n },\n );\n });\n}\n\nexport interface EditorSessionInfo {\n /** The port this session's dev server is bound to. */\n port: number;\n /** Canonical project root path currently open, or null when none. */\n project: string | null;\n /** Dev-server process id, when known from the local session registry (null for an unregistered server the caller only probed by port). */\n pid: number | null;\n /** Exact explicitly targeted editor origin/base URL, including protocol and host. */\n url?: string;\n /**\n * Why this server cannot DESCRIBE the `project` it is serving \u2014 its\n * manifest's own parse/validation failure, naming the failing key. `null`\n * when the project reads fine, and `undefined` from a probe that did not ask\n * See {@link servedProjectAnswer}: a session in\n * this state is still THIS project's session, and callers must report the\n * reason rather than treat it as no session at all.\n */\n manifestError?: string | null;\n}\n\nasync function fetchJson(url: string, timeoutMs: number): Promise<unknown> {\n const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });\n if (!res.ok) throw new Error(`GET /__editor/project returned HTTP ${res.status}`);\n return await res.json();\n}\n\nfunction baseUrl(session: EditorSessionInfo): string {\n return session.url ?? `http://127.0.0.1:${session.port}`;\n}\n\nexport interface SessionListingTransport { listSessions(timeoutMs: number): Promise<EditorSessionInfo[]>; }\nexport class HttpSessionDiscovery implements SessionListingTransport {\n async listSessions(timeoutMs: number): Promise<EditorSessionInfo[]> {\n const registered = readLiveRegisteredSessions();\n const perProbeTimeout = Math.min(EDITOR_PROBE_TIMEOUT_MS, Math.max(200, timeoutMs));\n const failures: string[] = [];\n const probes = await Promise.all(\n registered.map(async (s) => {\n try {\n const body = await fetchJson(\n `${baseUrl({ port: s.port, project: null, pid: s.pid })}/__editor/project`,\n perProbeTimeout,\n );\n const served = servedProjectAnswer(body);\n const info: EditorSessionInfo = {\n port: s.port,\n project: served.path,\n pid: s.pid,\n manifestError: served.manifestError,\n };\n return info;\n } catch (error) {\n // Name the step that did not answer, not a guess at why: an aborted fetch here means\n // the server took the request to /__editor/project and sent nothing back in time.\n const timedOut = (error as { name?: string } | null)?.name === 'TimeoutError';\n const detail = timedOut\n ? `GET /__editor/project sent no answer within ${perProbeTimeout} ms (pid ${s.pid})`\n : error instanceof Error ? `${error.name}: ${error.message}` : String(error);\n failures.push(`port ${s.port}: ${detail}`);\n return undefined;\n }\n }),\n );\n const sessions = probes.filter((p): p is EditorSessionInfo => p !== undefined);\n // A readable registry with registered live processes is not an empty\n // registry just because every HTTP discovery request failed. Keep any\n // verified sessions usable, but refuse an entirely unanswered inventory\n // rather than telling the caller to launch a second editor.\n if (registered.length > 0 && sessions.length === 0) {\n throw new Error(`Registered editor sessions could not be inspected: ${failures.join('; ')}`);\n }\n return sessions;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;AAIO,IAAM,sCAAsC;AAE5C,IAAM,0BAA0B;AAEhC,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YAAY,OAAe,IAAY;AACrC,UAAM,GAAG,KAAK,oBAAoB,EAAE,IAAI;AACxC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,YAAe,SAAqB,IAAY,OAA2B;AACzF,SAAO,IAAI,QAAW,CAAC,gBAAgB,WAAW;AAChD,UAAM,QAAQ,WAAW,MAAM,OAAO,IAAI,mBAAmB,OAAO,EAAE,CAAC,GAAG,EAAE;AAC5E,YAAQ;AAAA,MACN,CAAC,MAAM;AACL,qBAAa,KAAK;AAClB,uBAAe,CAAC;AAAA,MAClB;AAAA,MACA,CAAC,QAAQ;AACP,qBAAa,KAAK;AAClB,eAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAsBA,eAAe,UAAU,KAAa,WAAqC;AACzE,QAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,SAAS,EAAE,CAAC;AACvE,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,uCAAuC,IAAI,MAAM,EAAE;AAChF,SAAO,MAAM,IAAI,KAAK;AACxB;AAEA,SAAS,QAAQ,SAAoC;AACnD,SAAO,QAAQ,OAAO,oBAAoB,QAAQ,IAAI;AACxD;AAGO,IAAM,uBAAN,MAA8D;AAAA,EACnE,MAAM,aAAa,WAAiD;AAClE,UAAM,aAAa,2BAA2B;AAC9C,UAAM,kBAAkB,KAAK,IAAI,yBAAyB,KAAK,IAAI,KAAK,SAAS,CAAC;AAClF,UAAM,WAAqB,CAAC;AAC5B,UAAM,SAAS,MAAM,QAAQ;AAAA,MAC3B,WAAW,IAAI,OAAO,MAAM;AAC1B,YAAI;AACF,gBAAM,OAAO,MAAM;AAAA,YACjB,GAAG,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAS,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC;AAAA,YACvD;AAAA,UACF;AACA,gBAAM,SAAS,oBAAoB,IAAI;AACvC,gBAAM,OAA0B;AAAA,YAC9B,MAAM,EAAE;AAAA,YACR,SAAS,OAAO;AAAA,YAChB,KAAK,EAAE;AAAA,YACP,eAAe,OAAO;AAAA,UACxB;AACA,iBAAO;AAAA,QACT,SAAS,OAAO;AAGd,gBAAM,WAAY,OAAoC,SAAS;AAC/D,gBAAM,SAAS,WACX,+CAA+C,eAAe,YAAY,EAAE,GAAG,MAC/E,iBAAiB,QAAQ,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO,KAAK,OAAO,KAAK;AAC7E,mBAAS,KAAK,QAAQ,EAAE,IAAI,KAAK,MAAM,EAAE;AACzC,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AACA,UAAM,WAAW,OAAO,OAAO,CAAC,MAA8B,MAAM,MAAS;AAK7E,QAAI,WAAW,SAAS,KAAK,SAAS,WAAW,GAAG;AAClD,YAAM,IAAI,MAAM,sDAAsD,SAAS,KAAK,IAAI,CAAC,EAAE;AAAA,IAC7F;AACA,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|