@devframes/hub 0.9.8 → 0.9.10
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/dist/assemble-Hj36CCzH.mjs +103 -0
- package/dist/client/index.d.mts +64 -50
- package/dist/client/index.mjs +136 -42
- package/dist/{constants-DJrPlLi0.d.mts → constants-Bz4yclHo.d.mts} +14 -5
- package/dist/{constants-D1zicoJ4.mjs → constants-Da23JxCZ.mjs} +12 -5
- package/dist/constants.d.mts +1 -1
- package/dist/constants.mjs +1 -1
- package/dist/{context-DYUkd3il.mjs → context-BWaW6N53.mjs} +259 -225
- package/dist/{context-BJFGlcf8.d.mts → context-Di-fhMKP.d.mts} +17 -17
- package/dist/{index-OpSX0dK5.d.mts → index-faD2d5iX.d.mts} +2 -2
- package/dist/index.d.mts +3 -3
- package/dist/node/build.d.mts +74 -0
- package/dist/node/build.mjs +167 -0
- package/dist/node/index.d.mts +28 -19
- package/dist/node/index.mjs +3 -3
- package/dist/node/initiate.d.mts +39 -32
- package/dist/node/initiate.mjs +60 -122
- package/dist/{remote-url-Bgc7gtsP.mjs → remote-url-UClKLQ5_.mjs} +34 -39
- package/dist/{settings-DwxCyeI6.d.mts → settings-CWHPzCWS.d.mts} +59 -38
- package/dist/types/index.d.mts +3 -3
- package/package.json +4 -3
- package/dist/{dist-R0T2g9Vw.mjs → dist-BP3CGpgZ.mjs} +1 -1
|
@@ -1,36 +1,55 @@
|
|
|
1
1
|
import { r as defineHubRpcFunction } from "./define-Ceekw2EO.mjs";
|
|
2
|
-
import { n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS } from "./constants-
|
|
3
|
-
import { i as isBareModuleSpecifier, t as buildRemoteConnectionUrl } from "./remote-url-
|
|
2
|
+
import { n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS } from "./constants-Da23JxCZ.mjs";
|
|
3
|
+
import { i as isBareModuleSpecifier, t as buildRemoteConnectionUrl } from "./remote-url-UClKLQ5_.mjs";
|
|
4
4
|
import { joinURL, withTrailingSlash } from "ufo";
|
|
5
5
|
import { createEventEmitter } from "devframe/utils/events";
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import { debounce } from "perfect-debounce";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import process from "node:process";
|
|
9
8
|
import { coerceAgentPositionalArgs, resolveClientAssets } from "devframe/internal";
|
|
10
|
-
import { defineDiagnostics } from "devframe/utils/nostics";
|
|
11
9
|
import { basename, dirname, isAbsolute, join, resolve } from "pathe";
|
|
10
|
+
import { defineDiagnostics } from "devframe/utils/nostics";
|
|
11
|
+
import { getInternalContext, resolveBasePath } from "devframe/node/hub-internals";
|
|
12
|
+
import { createHostContext, createStorage } from "devframe/node";
|
|
13
|
+
import { debounce } from "perfect-debounce";
|
|
12
14
|
import { nanoid } from "devframe/utils/nanoid";
|
|
13
|
-
import process from "node:process";
|
|
14
|
-
import { existsSync } from "node:fs";
|
|
15
15
|
//#region src/node/diagnostics.ts
|
|
16
|
+
/**
|
|
17
|
+
* Hub-side diagnostics for docks, terminals, messages, and commands.
|
|
18
|
+
* Shares the `DF` prefix with devframe core; the hub reserves the
|
|
19
|
+
* `DF8xxx` range so the unified surface stays collision-free.
|
|
20
|
+
* Sub-ranges:
|
|
21
|
+
* DF8000-DF8099: hub context / lifecycle
|
|
22
|
+
* DF8100-DF8199: docks
|
|
23
|
+
* DF8200-DF8299: terminals
|
|
24
|
+
* DF8300-DF8399: messages
|
|
25
|
+
* DF8400-DF8499: commands
|
|
26
|
+
*/
|
|
16
27
|
const diagnostics = defineDiagnostics({
|
|
17
28
|
docsBase: "https://devfra.me/errors",
|
|
18
29
|
codes: {
|
|
19
30
|
DF8000: {
|
|
20
|
-
why: (p) => `Devframe id "${p.id}" collides with a reserved hub path
|
|
31
|
+
why: (p) => `Devframe id "${p.id}" collides with a reserved hub path, so it cannot be mounted directly under the hub base.`,
|
|
21
32
|
fix: "The filenames directly under the hub base (`__connection.json`, `__ws`, `__index.json`, `__client-imports.js`, `__mcp`, `embedded.js`) are reserved for the hub protocol. Rename the devframe id, or override its mount with a non-colliding `basePath`."
|
|
22
33
|
},
|
|
23
34
|
DF8002: {
|
|
24
|
-
why: "initHub received both `devframes` and `context
|
|
25
|
-
fix: "Pass `devframes` to let the instance create the hub context and mount each frame itself, or pass a pre-built `context` (your host already mounted the frames)
|
|
35
|
+
why: "initHub received both `devframes` and `context`; the two assembly modes are mutually exclusive.",
|
|
36
|
+
fix: "Pass `devframes` to let the instance create the hub context and mount each frame itself, or pass a pre-built `context` (your host already mounted the frames), but never both."
|
|
26
37
|
},
|
|
27
38
|
DF8003: {
|
|
28
39
|
why: "connectionMeta() was called before initHub finished initializing.",
|
|
29
|
-
fix: "Await `instance.ready` (or any request through `instance.handler`) before reading `connectionMeta()
|
|
40
|
+
fix: "Await `instance.ready` (or any request through `instance.handler`) before reading `connectionMeta()`, since the WebSocket binding it describes is only known once initialization completes."
|
|
30
41
|
},
|
|
31
42
|
DF8004: {
|
|
32
|
-
why: (p) => `Devframe id "${p.id}" is not a mountable URL segment
|
|
33
|
-
fix: "Ids become route segments, so they may only contain letters, digits, `_`, `-`, and
|
|
43
|
+
why: (p) => `Devframe id "${p.id}" is not a mountable URL segment, and the hub mounts each frame at \`<base><id>/\`.`,
|
|
44
|
+
fix: "Ids become route segments, so they may only contain letters, digits, `_`, `-`, and `.`; `:` and `*` are route-pattern markers to the underlying router, and `/` would escape the segment. Set a route-safe `id` on the definition (e.g. `my_plugin` instead of `my:plugin`)."
|
|
45
|
+
},
|
|
46
|
+
DF8005: {
|
|
47
|
+
why: (p) => `Devframe "${p.id}" requests an MCP route, but the hub's aggregate MCP is off (\`mcp: false\`), so its tools are not exposed over MCP.`,
|
|
48
|
+
fix: "A hub exposes one aggregate MCP endpoint over every mounted devframe, so per-devframe `mcp` settings are ignored. Drop `mcp: false` from `initHub` (the `'auto'` default mounts the aggregate route once agent tools exist) to surface this devframe's tools, or drop `mcp` from the devframe to silence this warning."
|
|
49
|
+
},
|
|
50
|
+
DF8006: {
|
|
51
|
+
why: (p) => `A static hub build can only write mounts under its own base: "${p.urlBase}" escapes "${p.base}".`,
|
|
52
|
+
fix: "buildHub maps each mount base to a directory under its `outDir`, so every mount must live under the hub base. Drop the `basePath` override (or the `ctx.install` base) that points outside it, or move the hub `base` up so it contains the mount."
|
|
34
53
|
},
|
|
35
54
|
DF8100: {
|
|
36
55
|
why: (p) => `Dock with id "${p.id}" is already registered`,
|
|
@@ -57,12 +76,12 @@ const diagnostics = defineDiagnostics({
|
|
|
57
76
|
fix: "Each devframe is deduplicated by id. Set `duplicationStrategy: \"duplicate\"` on the definition to let instances coexist, `\"silent\"` to drop duplicates quietly, or `\"throw\"` to surface them as errors."
|
|
58
77
|
},
|
|
59
78
|
DF8106: {
|
|
60
|
-
why: (p) => `The host cannot serve the RPC connection meta for devframe "${p.name}" (id "${p.id}") at "${p.base}"
|
|
79
|
+
why: (p) => `The host cannot serve the RPC connection meta for devframe "${p.name}" (id "${p.id}") at "${p.base}"; its \`DevframeHost\` does not implement \`mountConnectionMeta\`.`,
|
|
61
80
|
fix: "Implement `mountConnectionMeta(base)` on your DevframeHost so it serves `__connection.json` at each mounted base. Without it, the devframe SPA connects only when it shares an origin with the hub UI (same-origin window inheritance); cross-origin, sandboxed, or directly-opened iframes stay disconnected. Static-snapshot hosts that bake the meta into the served files can implement it as a no-op to acknowledge this intentionally."
|
|
62
81
|
},
|
|
63
82
|
DF8107: {
|
|
64
83
|
why: (p) => `Dock activation requested for unknown dock id "${p.id}"`,
|
|
65
|
-
fix: "Pass a `dockId` that matches a registered dock entry. The activation is still broadcast, but no hub UI provider will switch to it. Ids are case-sensitive
|
|
84
|
+
fix: "Pass a `dockId` that matches a registered dock entry. The activation is still broadcast, but no hub UI provider will switch to it. Ids are case-sensitive, so check for typos, and ensure the target dock is registered before activating it."
|
|
66
85
|
},
|
|
67
86
|
DF8108: {
|
|
68
87
|
why: (p) => `A renderer module is already registered for dock type "${p.type}"`,
|
|
@@ -73,11 +92,11 @@ const diagnostics = defineDiagnostics({
|
|
|
73
92
|
fix: "Point the registration's `file` at the prebuilt browser ES module (build the renderer package first, or check the path). Registration helpers like `jsonRenderUiRenderer()` resolve the path for you."
|
|
74
93
|
},
|
|
75
94
|
DF8110: {
|
|
76
|
-
why: (p) => `Dock type "${p.type}" is not a servable renderer-module name
|
|
95
|
+
why: (p) => `Dock type "${p.type}" is not a servable renderer-module name; the hub serves each module at \`<base>__renderers/<type>.mjs\``,
|
|
77
96
|
fix: "Renderer types become URL segments, so they may only contain letters, digits, `_`, `-`, and `.`. Use a route-safe dock type (e.g. `json-render`)."
|
|
78
97
|
},
|
|
79
98
|
DF8111: {
|
|
80
|
-
why: (p) => `Dock "${p.id}" declares the bare-specifier client script "${p.specifier}", but this host advertises no client-module resolution
|
|
99
|
+
why: (p) => `Dock "${p.id}" declares the bare-specifier client script "${p.specifier}", but this host advertises no client-module resolution, and the browser cannot resolve a bare npm specifier natively, so the script will fail to load.`,
|
|
81
100
|
fix: "Run under a host framework that declares `initHub({ clientModuleResolution })` (e.g. Vite's `'/@id/{specifier}'`), ship the script as a self-contained bundle served by URL, or resolve it in the hub UI provider via `createDevframeClientRuntime({ resolveClientModule })` (then disregard this warning)."
|
|
82
101
|
},
|
|
83
102
|
DF8200: { why: (p) => `Terminal session with id "${p.id}" already registered` },
|
|
@@ -89,14 +108,14 @@ const diagnostics = defineDiagnostics({
|
|
|
89
108
|
DF8203: { why: (p) => `Failed to spawn PTY session for "${p.command}": ${p.reason}` },
|
|
90
109
|
DF8204: {
|
|
91
110
|
why: (p) => `Terminal session "${p.id}" cannot be controlled (no lifecycle handle)`,
|
|
92
|
-
fix: "Spawn it via ctx.terminals.startChildProcess() or startPtySession()
|
|
111
|
+
fix: "Spawn it via ctx.terminals.startChildProcess() or startPtySession(); sessions added with a bare register() expose no terminate/restart handle."
|
|
93
112
|
},
|
|
94
113
|
DF8205: {
|
|
95
114
|
why: (p) => `Terminal session "${p.id}" is not restartable`,
|
|
96
115
|
fix: "It was registered with `restartable: false`; restart it through its owner's controls, or spawn it with `restartable: true` (the default) to allow in-place restarts."
|
|
97
116
|
},
|
|
98
117
|
DF8206: {
|
|
99
|
-
why: (p) => `Terminal session "${p.id}" cannot be restarted
|
|
118
|
+
why: (p) => `Terminal session "${p.id}" cannot be restarted because its output stream is already closed`,
|
|
100
119
|
fix: "The session already exited (or was terminated) and its stream is spent. Drop it with `ctx.terminals.remove(session)`, then spawn a replacement via `ctx.terminals.startChildProcess()` or `ctx.terminals.startPtySession()` with a fresh id."
|
|
101
120
|
},
|
|
102
121
|
DF8400: { why: (p) => `Command "${p.id}" is already registered` },
|
|
@@ -113,6 +132,134 @@ const diagnostics = defineDiagnostics({
|
|
|
113
132
|
}
|
|
114
133
|
});
|
|
115
134
|
//#endregion
|
|
135
|
+
//#region src/node/install-devframe.ts
|
|
136
|
+
/**
|
|
137
|
+
* Find the next free dock id derived from `baseId`. Returns `baseId`
|
|
138
|
+
* when it is unused, otherwise appends `-2`, `-3`, … until a free slot
|
|
139
|
+
* is found. Used by the `'duplicate'` strategy so co-existing instances
|
|
140
|
+
* never collide in the dock registry.
|
|
141
|
+
*/
|
|
142
|
+
function nextAvailableDockId(views, baseId) {
|
|
143
|
+
if (!views.has(baseId)) return baseId;
|
|
144
|
+
let n = 2;
|
|
145
|
+
while (views.has(`${baseId}-${n}`)) n++;
|
|
146
|
+
return `${baseId}-${n}`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* When a dock's `clientScript.importFrom` is an absolute filesystem path, serve
|
|
150
|
+
* its directory under `<base>__page-script/` and rewrite `importFrom` to that
|
|
151
|
+
* URL. A URL or bare specifier (not existing on disk) passes through untouched.
|
|
152
|
+
*/
|
|
153
|
+
async function resolvePageScriptClientScript(ctx, clientScript, base) {
|
|
154
|
+
if (!clientScript?.importFrom) return clientScript;
|
|
155
|
+
const { importFrom } = clientScript;
|
|
156
|
+
if (!isAbsolute(importFrom) || !existsSync(importFrom)) return clientScript;
|
|
157
|
+
const scriptBase = withTrailingSlash(joinURL(base, "__page-script"));
|
|
158
|
+
await ctx.host.mountStatic(scriptBase, dirname(importFrom));
|
|
159
|
+
return {
|
|
160
|
+
...clientScript,
|
|
161
|
+
importFrom: joinURL(scriptBase, basename(importFrom))
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Framework-neutral primitive backing {@link DevframeHubContext.install} -
|
|
166
|
+
* installs a {@link DevframeDefinition} as a dock inside a hub-aware context:
|
|
167
|
+
* serves the devframe's SPA at the resolved base path, synthesizes an iframe
|
|
168
|
+
* dock entry from the definition's metadata, and runs the definition's
|
|
169
|
+
* `setup(ctx)`. Reach for it through `ctx.install(devframe)` rather than
|
|
170
|
+
* calling it directly.
|
|
171
|
+
*
|
|
172
|
+
* Framework kits wrap `ctx.install` with their own plugin/middleware
|
|
173
|
+
* machinery, e.g. `@vitejs/devtools-kit`'s `createPluginFromDevframe`
|
|
174
|
+
* returns a Vite `Plugin` whose `devtools.setup` ultimately delegates here.
|
|
175
|
+
*/
|
|
176
|
+
/**
|
|
177
|
+
* Phase one of an install: run the duplication guard, serve the SPA + meta,
|
|
178
|
+
* register the iframe dock, and queue the definition's declarative wire
|
|
179
|
+
* services, everything up to (but not including) `setup(ctx)`. Returns a
|
|
180
|
+
* deferred setup thunk, or `null` when the devframe was deduplicated.
|
|
181
|
+
*
|
|
182
|
+
* The hub's initial batch uses this to collect every devframe's services
|
|
183
|
+
* across the whole hub, `ready()` them once, and only then run the setups,
|
|
184
|
+
* so services are ready before any setup, and a plugin can consume a service
|
|
185
|
+
* another plugin declared regardless of mount order.
|
|
186
|
+
*/
|
|
187
|
+
/**
|
|
188
|
+
* Serve a devframe's SPA (and the hub's connection meta) under `base`, if the
|
|
189
|
+
* definition ships client assets.
|
|
190
|
+
*/
|
|
191
|
+
async function serveDevframeAssets(ctx, d, id, base) {
|
|
192
|
+
const clientAssets = resolveClientAssets(d);
|
|
193
|
+
if (!clientAssets) return;
|
|
194
|
+
if (ctx.host.mountConnectionMeta) await ctx.host.mountConnectionMeta(base);
|
|
195
|
+
else diagnostics.DF8106({
|
|
196
|
+
id,
|
|
197
|
+
name: d.name,
|
|
198
|
+
base
|
|
199
|
+
});
|
|
200
|
+
ctx.views.hostStatic(base, typeof clientAssets === "string" ? resolve(clientAssets) : clientAssets, d.importMetaUrl);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Whether a devframe stays out of a static hub build: `capabilities.build:
|
|
204
|
+
* false` declares its value inherently live (a terminal, a process proxy),
|
|
205
|
+
* so `buildHub` never mounts it, registers its dock, or bakes its RPCs.
|
|
206
|
+
*/
|
|
207
|
+
function skippedInStaticBuild(ctx, d) {
|
|
208
|
+
return ctx.mode === "build" && d.capabilities?.build === false;
|
|
209
|
+
}
|
|
210
|
+
async function prepareDevframe(ctx, d, options = {}) {
|
|
211
|
+
if (skippedInStaticBuild(ctx, d)) return null;
|
|
212
|
+
const strategy = d.duplicationStrategy ?? "warn";
|
|
213
|
+
const isDuplicate = ctx.docks.views.has(d.id);
|
|
214
|
+
if (isDuplicate && strategy !== "duplicate") {
|
|
215
|
+
if (strategy === "throw") throw diagnostics.DF8105({
|
|
216
|
+
id: d.id,
|
|
217
|
+
name: d.name
|
|
218
|
+
});
|
|
219
|
+
if (strategy === "warn") diagnostics.DF8105({
|
|
220
|
+
id: d.id,
|
|
221
|
+
name: d.name
|
|
222
|
+
});
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const id = isDuplicate ? nextAvailableDockId(ctx.docks.views, d.id) : d.id;
|
|
226
|
+
const base = options.base ?? (id === d.id ? resolveBasePath(d, "hosted") : resolveBasePath({
|
|
227
|
+
...d,
|
|
228
|
+
id,
|
|
229
|
+
basePath: void 0
|
|
230
|
+
}, "hosted"));
|
|
231
|
+
const dockDefaults = {
|
|
232
|
+
...d.dock,
|
|
233
|
+
...options.dock
|
|
234
|
+
};
|
|
235
|
+
const clientScript = await resolvePageScriptClientScript(ctx, dockDefaults.clientScript, base);
|
|
236
|
+
if (clientScript) dockDefaults.clientScript = clientScript;
|
|
237
|
+
await serveDevframeAssets(ctx, d, id, base);
|
|
238
|
+
ctx.docks.register({
|
|
239
|
+
id,
|
|
240
|
+
title: d.name,
|
|
241
|
+
icon: d.icon,
|
|
242
|
+
...dockDefaults,
|
|
243
|
+
type: "iframe",
|
|
244
|
+
url: base
|
|
245
|
+
});
|
|
246
|
+
for (const input of d.services ?? []) ctx.services.install(input, { resolveFrom: d.importMetaUrl });
|
|
247
|
+
return () => Promise.resolve(d.setup(ctx));
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Install a {@link DevframeDefinition} into a hub in one call: serve its SPA,
|
|
251
|
+
* register its dock, ready its services, and run `setup(ctx)`. The imperative
|
|
252
|
+
* counterpart to the hub's declarative `devframes` list (which batches the
|
|
253
|
+
* phases via {@link prepareDevframe}); use it from `configure(ctx)` or
|
|
254
|
+
* wherever you hold the context to plug in an extra devframe after startup.
|
|
255
|
+
*/
|
|
256
|
+
async function installDevframe(ctx, d, options = {}) {
|
|
257
|
+
const run = await prepareDevframe(ctx, d, options);
|
|
258
|
+
if (!run) return;
|
|
259
|
+
await ctx.services.ready();
|
|
260
|
+
await run();
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
116
263
|
//#region src/node/host-commands.ts
|
|
117
264
|
function findChildCommand(command, id) {
|
|
118
265
|
for (const child of command.children ?? []) {
|
|
@@ -145,7 +292,7 @@ var DevframeCommandsHost = class {
|
|
|
145
292
|
events = createEventEmitter();
|
|
146
293
|
/**
|
|
147
294
|
* Lazy agent projection: `ctx.agent` queries this provider at list/invoke
|
|
148
|
-
* time, deriving tools from {@link commands} on demand
|
|
295
|
+
* time, deriving tools from {@link commands} on demand, so the commands map
|
|
149
296
|
* stays the single source of truth, nothing is mirrored or kept in sync.
|
|
150
297
|
*/
|
|
151
298
|
agentProvider;
|
|
@@ -223,7 +370,7 @@ var DevframeCommandsHost = class {
|
|
|
223
370
|
* agent-flagged, handler-bearing command (children included) becomes a
|
|
224
371
|
* callable tool. Queried lazily by the provider registered in the
|
|
225
372
|
* constructor. `when` clauses evaluate client-side only and are not
|
|
226
|
-
* enforced here
|
|
373
|
+
* enforced here; opting in a `when`-gated command is a deliberate author
|
|
227
374
|
* decision (documented on `DevframeCommandAgentOptions`).
|
|
228
375
|
*/
|
|
229
376
|
collectAgentTools() {
|
|
@@ -236,7 +383,12 @@ var DevframeCommandsHost = class {
|
|
|
236
383
|
description: agent.description,
|
|
237
384
|
safety: agent.safety ?? "action",
|
|
238
385
|
tags: agent.tags,
|
|
386
|
+
/** The agent host derives the tool's JSON-Schema input from these. */
|
|
239
387
|
args: agent.args,
|
|
388
|
+
/**
|
|
389
|
+
* A command handler's positional parameters come solely from its
|
|
390
|
+
* declared `agent.args` schemas; undeclared payload is dropped.
|
|
391
|
+
*/
|
|
240
392
|
handler: async (args) => this.execute(command.id, ...coerceAgentPositionalArgs(args, agent.args, "drop"))
|
|
241
393
|
});
|
|
242
394
|
for (const child of command.children ?? []) walk(child);
|
|
@@ -266,6 +418,7 @@ var DevframeDocksHost = class {
|
|
|
266
418
|
}
|
|
267
419
|
async init() {
|
|
268
420
|
this.userSettings = await this.context.rpc.sharedState.get(HUB_EVENTS.sharedState.userSettings, { sharedState: createStorage({
|
|
421
|
+
/** Personal dock layout/preferences: per-checkout private state. */
|
|
269
422
|
filepath: join(this.context.host.getStorageDir("project"), "settings.json"),
|
|
270
423
|
initialValue: DEFAULT_STATE_USER_SETTINGS()
|
|
271
424
|
}) });
|
|
@@ -326,7 +479,7 @@ var DevframeDocksHost = class {
|
|
|
326
479
|
});
|
|
327
480
|
}
|
|
328
481
|
/**
|
|
329
|
-
* Warn (don't throw
|
|
482
|
+
* Warn (don't throw, since a client-runtime `resolveClientModule` override may still cover
|
|
330
483
|
* it) when a dock declares a **bare-specifier** client script on a host
|
|
331
484
|
* that advertises no `staticConfig.dock.clientModuleResolution`: the
|
|
332
485
|
* browser cannot resolve a bare npm specifier natively, so the script is
|
|
@@ -380,7 +533,7 @@ var DevframeMessagesHost = class {
|
|
|
380
533
|
_clock = 0;
|
|
381
534
|
/**
|
|
382
535
|
* The tick of the newest removal record dropped from the capped
|
|
383
|
-
* `removals` log
|
|
536
|
+
* `removals` log; cursors older than this can't get a reliable delta
|
|
384
537
|
* and fall back to a full snapshot in {@link listSince}.
|
|
385
538
|
*/
|
|
386
539
|
_removalsTrimmedAt = 0;
|
|
@@ -561,7 +714,7 @@ var DevframeTerminalsHost = class {
|
|
|
561
714
|
this.context = context;
|
|
562
715
|
}
|
|
563
716
|
/**
|
|
564
|
-
* Lazily acquire the streaming channel
|
|
717
|
+
* Lazily acquire the streaming channel, since `context.rpc` isn't assigned
|
|
565
718
|
* until after every host is constructed, so we can't grab it in the
|
|
566
719
|
* constructor.
|
|
567
720
|
*/
|
|
@@ -571,6 +724,41 @@ var DevframeTerminalsHost = class {
|
|
|
571
724
|
this._channel = this.context.rpc.streaming.create(TERMINAL_STREAM_CHANNEL, { replayWindow: TERMINAL_REPLAY_WINDOW });
|
|
572
725
|
return this._channel;
|
|
573
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Per-run stream lifecycle shared by the child-process and PTY paths: guards
|
|
729
|
+
* the single-close/error transition and keeps the session's `status` in step
|
|
730
|
+
* with the process lifecycle (a hub-aware client sees `running` →
|
|
731
|
+
* `stopped`/`error` instead of a value frozen at spawn time).
|
|
732
|
+
*/
|
|
733
|
+
createStreamLifecycle() {
|
|
734
|
+
const state = { streamClosed: false };
|
|
735
|
+
const markStatus = (next) => {
|
|
736
|
+
const session = state.session;
|
|
737
|
+
if (session.status === next) return;
|
|
738
|
+
session.status = next;
|
|
739
|
+
this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session);
|
|
740
|
+
};
|
|
741
|
+
const closeStream = () => {
|
|
742
|
+
if (state.streamClosed) return;
|
|
743
|
+
state.streamClosed = true;
|
|
744
|
+
try {
|
|
745
|
+
state.controller?.close();
|
|
746
|
+
} catch {}
|
|
747
|
+
};
|
|
748
|
+
const errorStream = (error) => {
|
|
749
|
+
if (state.streamClosed) return;
|
|
750
|
+
state.streamClosed = true;
|
|
751
|
+
try {
|
|
752
|
+
state.controller?.error(error);
|
|
753
|
+
} catch {}
|
|
754
|
+
};
|
|
755
|
+
return {
|
|
756
|
+
state,
|
|
757
|
+
markStatus,
|
|
758
|
+
closeStream,
|
|
759
|
+
errorStream
|
|
760
|
+
};
|
|
761
|
+
}
|
|
574
762
|
register(session) {
|
|
575
763
|
if (this.sessions.has(session.id)) throw diagnostics.DF8200({ id: session.id });
|
|
576
764
|
this.sessions.set(session.id, session);
|
|
@@ -592,6 +780,25 @@ var DevframeTerminalsHost = class {
|
|
|
592
780
|
this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session);
|
|
593
781
|
this._boundStreams.delete(session.id);
|
|
594
782
|
}
|
|
783
|
+
/** Drain a session's output stream into its scrollback buffer and streaming sink. */
|
|
784
|
+
async pumpStream(reader, sessionBuffer, sink, isDisposed) {
|
|
785
|
+
try {
|
|
786
|
+
while (!isDisposed()) {
|
|
787
|
+
const result = await reader.read();
|
|
788
|
+
if (isDisposed() || result.done) break;
|
|
789
|
+
sessionBuffer.push(result.value);
|
|
790
|
+
if (sessionBuffer.length > TERMINAL_BUFFER_LIMIT) sessionBuffer.splice(0, sessionBuffer.length - TERMINAL_BUFFER_LIMIT);
|
|
791
|
+
sink?.write(result.value);
|
|
792
|
+
}
|
|
793
|
+
if (!isDisposed() && sink && !sink.closed) sink.close();
|
|
794
|
+
} catch (error) {
|
|
795
|
+
if (!isDisposed() && sink && !sink.closed) sink.error(error);
|
|
796
|
+
} finally {
|
|
797
|
+
try {
|
|
798
|
+
reader.releaseLock();
|
|
799
|
+
} catch {}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
595
802
|
bindStream(session) {
|
|
596
803
|
if (this._boundStreams.has(session.id) && this._boundStreams.get(session.id)?.stream === session.stream) return;
|
|
597
804
|
this._boundStreams.get(session.id)?.dispose();
|
|
@@ -602,26 +809,7 @@ var DevframeTerminalsHost = class {
|
|
|
602
809
|
const sink = this.getStreamingChannel()?.start({ id: session.id });
|
|
603
810
|
const reader = session.stream.getReader();
|
|
604
811
|
let disposed = false;
|
|
605
|
-
(
|
|
606
|
-
try {
|
|
607
|
-
while (true) {
|
|
608
|
-
if (disposed) break;
|
|
609
|
-
const result = await reader.read();
|
|
610
|
-
if (disposed) break;
|
|
611
|
-
if (result.done) break;
|
|
612
|
-
sessionBuffer.push(result.value);
|
|
613
|
-
if (sessionBuffer.length > TERMINAL_BUFFER_LIMIT) sessionBuffer.splice(0, sessionBuffer.length - TERMINAL_BUFFER_LIMIT);
|
|
614
|
-
sink?.write(result.value);
|
|
615
|
-
}
|
|
616
|
-
if (!disposed && sink && !sink.closed) sink.close();
|
|
617
|
-
} catch (error) {
|
|
618
|
-
if (!disposed && sink && !sink.closed) sink.error(error);
|
|
619
|
-
} finally {
|
|
620
|
-
try {
|
|
621
|
-
reader.releaseLock();
|
|
622
|
-
} catch {}
|
|
623
|
-
}
|
|
624
|
-
})();
|
|
812
|
+
this.pumpStream(reader, sessionBuffer, sink, () => disposed);
|
|
625
813
|
this._boundStreams.set(session.id, {
|
|
626
814
|
dispose: () => {
|
|
627
815
|
disposed = true;
|
|
@@ -634,34 +822,13 @@ var DevframeTerminalsHost = class {
|
|
|
634
822
|
async startChildProcess(executeOptions, terminal) {
|
|
635
823
|
if (this.sessions.has(terminal.id)) throw diagnostics.DF8200({ id: terminal.id });
|
|
636
824
|
const { exec } = await import("tinyexec");
|
|
637
|
-
|
|
825
|
+
const { state, markStatus, closeStream, errorStream } = this.createStreamLifecycle();
|
|
638
826
|
let cp;
|
|
639
827
|
let currentResult;
|
|
640
828
|
let runId = 0;
|
|
641
|
-
let streamClosed = false;
|
|
642
|
-
let session;
|
|
643
|
-
const markStatus = (next) => {
|
|
644
|
-
if (session.status === next) return;
|
|
645
|
-
session.status = next;
|
|
646
|
-
this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session);
|
|
647
|
-
};
|
|
648
|
-
const closeStream = () => {
|
|
649
|
-
if (streamClosed) return;
|
|
650
|
-
streamClosed = true;
|
|
651
|
-
try {
|
|
652
|
-
controller?.close();
|
|
653
|
-
} catch {}
|
|
654
|
-
};
|
|
655
|
-
const errorStream = (error) => {
|
|
656
|
-
if (streamClosed) return;
|
|
657
|
-
streamClosed = true;
|
|
658
|
-
try {
|
|
659
|
-
controller?.error(error);
|
|
660
|
-
} catch {}
|
|
661
|
-
};
|
|
662
829
|
const stream = new ReadableStream({
|
|
663
830
|
start(_controller) {
|
|
664
|
-
controller = _controller;
|
|
831
|
+
state.controller = _controller;
|
|
665
832
|
},
|
|
666
833
|
cancel() {
|
|
667
834
|
cp?.kill();
|
|
@@ -701,13 +868,13 @@ var DevframeTerminalsHost = class {
|
|
|
701
868
|
if (currentRun !== runId) return;
|
|
702
869
|
const text = chunk.toString();
|
|
703
870
|
stdoutChunks.push(text);
|
|
704
|
-
if (!streamClosed) controller?.enqueue(text);
|
|
871
|
+
if (!state.streamClosed) state.controller?.enqueue(text);
|
|
705
872
|
});
|
|
706
873
|
cp.process?.stderr?.on("data", (chunk) => {
|
|
707
874
|
if (currentRun !== runId) return;
|
|
708
875
|
const text = chunk.toString();
|
|
709
876
|
stderrChunks.push(text);
|
|
710
|
-
if (!streamClosed) controller?.enqueue(text);
|
|
877
|
+
if (!state.streamClosed) state.controller?.enqueue(text);
|
|
711
878
|
});
|
|
712
879
|
cp.process?.once("error", (error) => {
|
|
713
880
|
if (currentRun !== runId) return;
|
|
@@ -739,7 +906,7 @@ var DevframeTerminalsHost = class {
|
|
|
739
906
|
}
|
|
740
907
|
cp = createChildProcess();
|
|
741
908
|
const restart = async () => {
|
|
742
|
-
if (streamClosed) throw diagnostics.DF8206({ id: terminal.id });
|
|
909
|
+
if (state.streamClosed) throw diagnostics.DF8206({ id: terminal.id });
|
|
743
910
|
cp?.kill();
|
|
744
911
|
cp = createChildProcess();
|
|
745
912
|
markStatus("running");
|
|
@@ -750,7 +917,7 @@ var DevframeTerminalsHost = class {
|
|
|
750
917
|
closeStream();
|
|
751
918
|
markStatus("stopped");
|
|
752
919
|
};
|
|
753
|
-
session = {
|
|
920
|
+
const session = {
|
|
754
921
|
...terminal,
|
|
755
922
|
status: "running",
|
|
756
923
|
stream,
|
|
@@ -761,6 +928,7 @@ var DevframeTerminalsHost = class {
|
|
|
761
928
|
terminate,
|
|
762
929
|
restart
|
|
763
930
|
};
|
|
931
|
+
state.session = session;
|
|
764
932
|
this.register(session);
|
|
765
933
|
return Promise.resolve(session);
|
|
766
934
|
}
|
|
@@ -769,35 +937,14 @@ var DevframeTerminalsHost = class {
|
|
|
769
937
|
const { spawn } = await import("zigpty");
|
|
770
938
|
const cols = executeOptions.cols ?? 80;
|
|
771
939
|
const rows = executeOptions.rows ?? 24;
|
|
772
|
-
|
|
940
|
+
const { state, markStatus, closeStream, errorStream } = this.createStreamLifecycle();
|
|
773
941
|
let pty;
|
|
774
942
|
let currentResult;
|
|
775
943
|
let killCurrentRun;
|
|
776
944
|
let runId = 0;
|
|
777
|
-
let streamClosed = false;
|
|
778
|
-
let session;
|
|
779
|
-
const markStatus = (next) => {
|
|
780
|
-
if (session.status === next) return;
|
|
781
|
-
session.status = next;
|
|
782
|
-
this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session);
|
|
783
|
-
};
|
|
784
|
-
const closeStream = () => {
|
|
785
|
-
if (streamClosed) return;
|
|
786
|
-
streamClosed = true;
|
|
787
|
-
try {
|
|
788
|
-
controller?.close();
|
|
789
|
-
} catch {}
|
|
790
|
-
};
|
|
791
|
-
const errorStream = (error) => {
|
|
792
|
-
if (streamClosed) return;
|
|
793
|
-
streamClosed = true;
|
|
794
|
-
try {
|
|
795
|
-
controller?.error(error);
|
|
796
|
-
} catch {}
|
|
797
|
-
};
|
|
798
945
|
const stream = new ReadableStream({
|
|
799
946
|
start(_controller) {
|
|
800
|
-
controller = _controller;
|
|
947
|
+
state.controller = _controller;
|
|
801
948
|
},
|
|
802
949
|
cancel() {
|
|
803
950
|
killCurrentRun?.();
|
|
@@ -842,7 +989,7 @@ var DevframeTerminalsHost = class {
|
|
|
842
989
|
ptyProcess.onData((data) => {
|
|
843
990
|
const text = typeof data === "string" ? data : data.toString("utf8");
|
|
844
991
|
outputChunks.push(text);
|
|
845
|
-
if (!streamClosed && currentRun === runId) controller?.enqueue(text);
|
|
992
|
+
if (!state.streamClosed && currentRun === runId) state.controller?.enqueue(text);
|
|
846
993
|
});
|
|
847
994
|
ptyProcess.onExit(({ exitCode, signal }) => {
|
|
848
995
|
settle(exitCode, signal);
|
|
@@ -882,7 +1029,7 @@ var DevframeTerminalsHost = class {
|
|
|
882
1029
|
reason: error instanceof Error ? error.message : String(error)
|
|
883
1030
|
});
|
|
884
1031
|
}
|
|
885
|
-
session = {
|
|
1032
|
+
const session = {
|
|
886
1033
|
...terminal,
|
|
887
1034
|
status: "running",
|
|
888
1035
|
interactive: true,
|
|
@@ -915,7 +1062,7 @@ var DevframeTerminalsHost = class {
|
|
|
915
1062
|
markStatus("stopped");
|
|
916
1063
|
},
|
|
917
1064
|
restart: async () => {
|
|
918
|
-
if (streamClosed) throw diagnostics.DF8206({ id: terminal.id });
|
|
1065
|
+
if (state.streamClosed) throw diagnostics.DF8206({ id: terminal.id });
|
|
919
1066
|
killCurrentRun?.();
|
|
920
1067
|
killCurrentRun = void 0;
|
|
921
1068
|
pty = void 0;
|
|
@@ -931,125 +1078,12 @@ var DevframeTerminalsHost = class {
|
|
|
931
1078
|
markStatus("running");
|
|
932
1079
|
}
|
|
933
1080
|
};
|
|
1081
|
+
state.session = session;
|
|
934
1082
|
this.register(session);
|
|
935
1083
|
return session;
|
|
936
1084
|
}
|
|
937
1085
|
};
|
|
938
1086
|
//#endregion
|
|
939
|
-
//#region src/node/install-devframe.ts
|
|
940
|
-
/**
|
|
941
|
-
* Find the next free dock id derived from `baseId`. Returns `baseId`
|
|
942
|
-
* when it is unused, otherwise appends `-2`, `-3`, … until a free slot
|
|
943
|
-
* is found. Used by the `'duplicate'` strategy so co-existing instances
|
|
944
|
-
* never collide in the dock registry.
|
|
945
|
-
*/
|
|
946
|
-
function nextAvailableDockId(views, baseId) {
|
|
947
|
-
if (!views.has(baseId)) return baseId;
|
|
948
|
-
let n = 2;
|
|
949
|
-
while (views.has(`${baseId}-${n}`)) n++;
|
|
950
|
-
return `${baseId}-${n}`;
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* When a dock's `clientScript.importFrom` is an absolute filesystem path, serve
|
|
954
|
-
* its directory under `<base>__page-script/` and rewrite `importFrom` to that
|
|
955
|
-
* URL. A URL or bare specifier (not existing on disk) passes through untouched.
|
|
956
|
-
*/
|
|
957
|
-
async function resolvePageScriptClientScript(ctx, clientScript, base) {
|
|
958
|
-
if (!clientScript?.importFrom) return clientScript;
|
|
959
|
-
const { importFrom } = clientScript;
|
|
960
|
-
if (!isAbsolute(importFrom) || !existsSync(importFrom)) return clientScript;
|
|
961
|
-
const scriptBase = withTrailingSlash(joinURL(base, "__page-script"));
|
|
962
|
-
await ctx.host.mountStatic(scriptBase, dirname(importFrom));
|
|
963
|
-
return {
|
|
964
|
-
...clientScript,
|
|
965
|
-
importFrom: joinURL(scriptBase, basename(importFrom))
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
/**
|
|
969
|
-
* Framework-neutral primitive backing {@link DevframeHubContext.install} —
|
|
970
|
-
* installs a {@link DevframeDefinition} as a dock inside a hub-aware context:
|
|
971
|
-
* serves the devframe's SPA at the resolved base path, synthesizes an iframe
|
|
972
|
-
* dock entry from the definition's metadata, and runs the definition's
|
|
973
|
-
* `setup(ctx)`. Reach for it through `ctx.install(devframe)` rather than
|
|
974
|
-
* calling it directly.
|
|
975
|
-
*
|
|
976
|
-
* Framework kits wrap `ctx.install` with their own plugin/middleware
|
|
977
|
-
* machinery — e.g. `@vitejs/devtools-kit`'s `createPluginFromDevframe`
|
|
978
|
-
* returns a Vite `Plugin` whose `devtools.setup` ultimately delegates here.
|
|
979
|
-
*/
|
|
980
|
-
/**
|
|
981
|
-
* Phase one of an install: run the duplication guard, serve the SPA + meta,
|
|
982
|
-
* register the iframe dock, and queue the definition's declarative wire
|
|
983
|
-
* services — everything up to (but not including) `setup(ctx)`. Returns a
|
|
984
|
-
* deferred setup thunk, or `null` when the devframe was deduplicated.
|
|
985
|
-
*
|
|
986
|
-
* The hub's initial batch uses this to collect every devframe's services
|
|
987
|
-
* across the whole hub, `ready()` them once, and only then run the setups —
|
|
988
|
-
* so services are ready before any setup, and a plugin can consume a service
|
|
989
|
-
* another plugin declared regardless of mount order.
|
|
990
|
-
*/
|
|
991
|
-
async function prepareDevframe(ctx, d, options = {}) {
|
|
992
|
-
const strategy = d.duplicationStrategy ?? "warn";
|
|
993
|
-
const isDuplicate = ctx.docks.views.has(d.id);
|
|
994
|
-
if (isDuplicate && strategy !== "duplicate") {
|
|
995
|
-
if (strategy === "throw") throw diagnostics.DF8105({
|
|
996
|
-
id: d.id,
|
|
997
|
-
name: d.name
|
|
998
|
-
});
|
|
999
|
-
if (strategy === "warn") diagnostics.DF8105({
|
|
1000
|
-
id: d.id,
|
|
1001
|
-
name: d.name
|
|
1002
|
-
});
|
|
1003
|
-
return null;
|
|
1004
|
-
}
|
|
1005
|
-
const id = isDuplicate ? nextAvailableDockId(ctx.docks.views, d.id) : d.id;
|
|
1006
|
-
const base = options.base ?? (id === d.id ? resolveBasePath(d, "hosted") : resolveBasePath({
|
|
1007
|
-
...d,
|
|
1008
|
-
id,
|
|
1009
|
-
basePath: void 0
|
|
1010
|
-
}, "hosted"));
|
|
1011
|
-
const dockDefaults = {
|
|
1012
|
-
...d.dock,
|
|
1013
|
-
...options.dock
|
|
1014
|
-
};
|
|
1015
|
-
const clientScript = await resolvePageScriptClientScript(ctx, dockDefaults.clientScript, base);
|
|
1016
|
-
if (clientScript) dockDefaults.clientScript = clientScript;
|
|
1017
|
-
const clientAssets = resolveClientAssets(d);
|
|
1018
|
-
if (clientAssets) {
|
|
1019
|
-
if (ctx.host.mountConnectionMeta) await ctx.host.mountConnectionMeta(base);
|
|
1020
|
-
else diagnostics.DF8106({
|
|
1021
|
-
id,
|
|
1022
|
-
name: d.name,
|
|
1023
|
-
base
|
|
1024
|
-
});
|
|
1025
|
-
const distSource = clientAssets;
|
|
1026
|
-
ctx.views.hostStatic(base, typeof distSource === "string" ? resolve(distSource) : distSource, d.importMetaUrl);
|
|
1027
|
-
}
|
|
1028
|
-
ctx.docks.register({
|
|
1029
|
-
id,
|
|
1030
|
-
title: d.name,
|
|
1031
|
-
icon: d.icon,
|
|
1032
|
-
...dockDefaults,
|
|
1033
|
-
type: "iframe",
|
|
1034
|
-
url: base
|
|
1035
|
-
});
|
|
1036
|
-
for (const input of d.services ?? []) ctx.services.install(input, { resolveFrom: d.importMetaUrl });
|
|
1037
|
-
return () => Promise.resolve(d.setup(ctx));
|
|
1038
|
-
}
|
|
1039
|
-
/**
|
|
1040
|
-
* Install a {@link DevframeDefinition} into a hub in one call — serve its SPA,
|
|
1041
|
-
* register its dock, ready its services, and run `setup(ctx)`. The imperative
|
|
1042
|
-
* counterpart to the hub's declarative `devframes` list (which batches the
|
|
1043
|
-
* phases via {@link prepareDevframe}); use it from `configure(ctx)` or
|
|
1044
|
-
* wherever you hold the context to plug in an extra devframe after startup.
|
|
1045
|
-
*/
|
|
1046
|
-
async function installDevframe(ctx, d, options = {}) {
|
|
1047
|
-
const run = await prepareDevframe(ctx, d, options);
|
|
1048
|
-
if (!run) return;
|
|
1049
|
-
await ctx.services.ready();
|
|
1050
|
-
await run();
|
|
1051
|
-
}
|
|
1052
|
-
//#endregion
|
|
1053
1087
|
//#region src/node/rpc-builtins.ts
|
|
1054
1088
|
/**
|
|
1055
1089
|
* Resolve an interactive (PTY) terminal session by id, or throw. Sessions
|
|
@@ -1073,7 +1107,7 @@ function resolveControllableSession(sessions, id) {
|
|
|
1073
1107
|
return session;
|
|
1074
1108
|
}
|
|
1075
1109
|
/**
|
|
1076
|
-
* `hub:commands:execute
|
|
1110
|
+
* `hub:commands:execute`: Invoke a registered server command by id. The
|
|
1077
1111
|
* arguments after `id` are forwarded to the command's `handler(...)`.
|
|
1078
1112
|
* Returns whatever the handler returns.
|
|
1079
1113
|
*
|
|
@@ -1088,7 +1122,7 @@ const hubCommandsExecute = defineHubRpcFunction({
|
|
|
1088
1122
|
} })
|
|
1089
1123
|
});
|
|
1090
1124
|
/**
|
|
1091
|
-
* `hub:messages:add
|
|
1125
|
+
* `hub:messages:add`: Add a message from a browser client into the hub's
|
|
1092
1126
|
* messages subsystem. Marked `from: 'browser'`. Returns the serializable
|
|
1093
1127
|
* entry (the mutation handle stays server-side).
|
|
1094
1128
|
*
|
|
@@ -1107,7 +1141,7 @@ const hubMessagesAdd = defineHubRpcFunction({
|
|
|
1107
1141
|
})).entry;
|
|
1108
1142
|
} })
|
|
1109
1143
|
});
|
|
1110
|
-
/** `hub:messages:update
|
|
1144
|
+
/** `hub:messages:update`: Patch a message by id; returns the updated entry (or `undefined`). */
|
|
1111
1145
|
const hubMessagesUpdate = defineHubRpcFunction({
|
|
1112
1146
|
name: HUB_EVENTS.rpc.messagesUpdate,
|
|
1113
1147
|
type: "action",
|
|
@@ -1116,7 +1150,7 @@ const hubMessagesUpdate = defineHubRpcFunction({
|
|
|
1116
1150
|
return context.messages.update(id, patch);
|
|
1117
1151
|
} })
|
|
1118
1152
|
});
|
|
1119
|
-
/** `hub:messages:remove
|
|
1153
|
+
/** `hub:messages:remove`: Remove a message by id. */
|
|
1120
1154
|
const hubMessagesRemove = defineHubRpcFunction({
|
|
1121
1155
|
name: HUB_EVENTS.rpc.messagesRemove,
|
|
1122
1156
|
type: "action",
|
|
@@ -1124,7 +1158,7 @@ const hubMessagesRemove = defineHubRpcFunction({
|
|
|
1124
1158
|
await context.messages.remove(id);
|
|
1125
1159
|
} })
|
|
1126
1160
|
});
|
|
1127
|
-
/** `hub:messages:clear
|
|
1161
|
+
/** `hub:messages:clear`: Remove every message. */
|
|
1128
1162
|
const hubMessagesClear = defineHubRpcFunction({
|
|
1129
1163
|
name: HUB_EVENTS.rpc.messagesClear,
|
|
1130
1164
|
type: "action",
|
|
@@ -1133,7 +1167,7 @@ const hubMessagesClear = defineHubRpcFunction({
|
|
|
1133
1167
|
} })
|
|
1134
1168
|
});
|
|
1135
1169
|
/**
|
|
1136
|
-
* `hub:terminals:write
|
|
1170
|
+
* `hub:terminals:write`: Send input to an interactive PTY session spawned
|
|
1137
1171
|
* via `ctx.terminals.startPtySession`. Lets a hub-aware terminal UI (e.g. the
|
|
1138
1172
|
* terminals plugin) drive a session owned by another plugin.
|
|
1139
1173
|
*/
|
|
@@ -1144,7 +1178,7 @@ const hubTerminalsWrite = defineHubRpcFunction({
|
|
|
1144
1178
|
resolveInteractiveSession(context.terminals.sessions, id).write(data);
|
|
1145
1179
|
} })
|
|
1146
1180
|
});
|
|
1147
|
-
/** `hub:terminals:resize
|
|
1181
|
+
/** `hub:terminals:resize`: Resize an interactive PTY session by id. */
|
|
1148
1182
|
const hubTerminalsResize = defineHubRpcFunction({
|
|
1149
1183
|
name: HUB_EVENTS.rpc.terminalsResize,
|
|
1150
1184
|
type: "action",
|
|
@@ -1153,7 +1187,7 @@ const hubTerminalsResize = defineHubRpcFunction({
|
|
|
1153
1187
|
} })
|
|
1154
1188
|
});
|
|
1155
1189
|
/**
|
|
1156
|
-
* `hub:terminals:terminate
|
|
1190
|
+
* `hub:terminals:terminate`: Kill a session's process while keeping the
|
|
1157
1191
|
* session registered (its output/scrollback stays). Works for both read-only
|
|
1158
1192
|
* child-process and interactive PTY sessions, letting a hub-aware terminal UI
|
|
1159
1193
|
* force-kill a session owned by another plugin.
|
|
@@ -1166,7 +1200,7 @@ const hubTerminalsTerminate = defineHubRpcFunction({
|
|
|
1166
1200
|
} })
|
|
1167
1201
|
});
|
|
1168
1202
|
/**
|
|
1169
|
-
* `hub:terminals:restart
|
|
1203
|
+
* `hub:terminals:restart`: Re-run a session's command in place. Rejected for
|
|
1170
1204
|
* sessions registered with `restartable: false`, whose lifecycle is owned
|
|
1171
1205
|
* elsewhere.
|
|
1172
1206
|
*/
|
|
@@ -1180,7 +1214,7 @@ const hubTerminalsRestart = defineHubRpcFunction({
|
|
|
1180
1214
|
} })
|
|
1181
1215
|
});
|
|
1182
1216
|
/**
|
|
1183
|
-
* `hub:terminals:remove
|
|
1217
|
+
* `hub:terminals:remove`: Kill a session's process (when it still owns one)
|
|
1184
1218
|
* and drop it from the registry, disposing its output stream. Lets a hub-aware
|
|
1185
1219
|
* terminal UI discard a stopped aggregated session.
|
|
1186
1220
|
*/
|
|
@@ -1196,13 +1230,13 @@ const hubTerminalsRemove = defineHubRpcFunction({
|
|
|
1196
1230
|
} })
|
|
1197
1231
|
});
|
|
1198
1232
|
/**
|
|
1199
|
-
* `hub:docks:activate
|
|
1233
|
+
* `hub:docks:activate`: Ask the active viewer to switch its focused dock to
|
|
1200
1234
|
* `dockId`, optionally carrying `params` for the target dock to interpret
|
|
1201
1235
|
* (e.g. `{ sessionId }` for the terminals dock to focus a session).
|
|
1202
1236
|
*
|
|
1203
1237
|
* Any connected client may call it, which is the point: a mounted devframe
|
|
1204
1238
|
* running in its own iframe (on its own RPC client) can steer the host shell's
|
|
1205
|
-
* dock selection
|
|
1239
|
+
* dock selection, client-local state it otherwise can't reach. The hub
|
|
1206
1240
|
* broadcasts the request live to connected clients (the host shell switches)
|
|
1207
1241
|
* and mirrors it into the `devframe:docks:active` shared state (a dock that
|
|
1208
1242
|
* mounts in response still converges on it).
|
|
@@ -1303,4 +1337,4 @@ async function createHubContext(options) {
|
|
|
1303
1337
|
return context;
|
|
1304
1338
|
}
|
|
1305
1339
|
//#endregion
|
|
1306
|
-
export {
|
|
1340
|
+
export { DevframeCommandsHost as _, hubMessagesAdd as a, diagnostics as b, hubMessagesUpdate as c, hubTerminalsRestart as d, hubTerminalsTerminate as f, DevframeDocksHost as g, DevframeMessagesHost as h, hubDocksActivate as i, hubTerminalsRemove as l, DevframeTerminalsHost as m, builtinHubRpcDeclarations as n, hubMessagesClear as o, hubTerminalsWrite as p, hubCommandsExecute as r, hubMessagesRemove as s, createHubContext as t, hubTerminalsResize as u, prepareDevframe as v, skippedInStaticBuild as y };
|