@devframes/hub 0.9.8 → 0.9.9
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/client/index.d.mts +64 -50
- package/dist/client/index.mjs +78 -42
- package/dist/{constants-D1zicoJ4.mjs → constants-BUd6oMci.mjs} +4 -4
- package/dist/{constants-DJrPlLi0.d.mts → constants-Coo1p3ja.d.mts} +5 -5
- package/dist/constants.d.mts +1 -1
- package/dist/constants.mjs +1 -1
- package/dist/{context-DYUkd3il.mjs → context-D4L966cN.mjs} +141 -120
- 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/index.d.mts +28 -19
- package/dist/node/index.mjs +1 -1
- package/dist/node/initiate.d.mts +35 -30
- package/dist/node/initiate.mjs +80 -53
- 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 +3 -3
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as DevframeDocksUserSettings } from "./settings-
|
|
1
|
+
import { t as DevframeDocksUserSettings } from "./settings-CWHPzCWS.mjs";
|
|
2
2
|
//#region src/events.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Centralized registry of every event, broadcast, RPC method, shared-state
|
|
5
|
-
* key, and channel name the hub uses
|
|
5
|
+
* key, and channel name the hub uses: the single source of truth that keeps
|
|
6
6
|
* these names out of scattered string literals.
|
|
7
7
|
*
|
|
8
8
|
* **Keep this in sync with [`docs/content/8.references/3.events.md`](../../../docs/content/8.references/3.events.md)**
|
|
@@ -75,7 +75,7 @@ declare const HUB_EVENTS: {
|
|
|
75
75
|
};
|
|
76
76
|
//#endregion
|
|
77
77
|
//#region src/constants.d.ts
|
|
78
|
-
/** Default mount base for a hub instance
|
|
78
|
+
/** Default mount base for a hub instance: one namespace, one catch-all. */
|
|
79
79
|
declare const DEVFRAMES_HUB_BASE = "/__devframes/";
|
|
80
80
|
/**
|
|
81
81
|
* Normalize a hub mount base to an absolute path with leading and trailing
|
|
@@ -86,7 +86,7 @@ declare const DEVFRAMES_HUB_BASE = "/__devframes/";
|
|
|
86
86
|
*/
|
|
87
87
|
declare function normalizeHubBase(base: string): string;
|
|
88
88
|
/**
|
|
89
|
-
* The default ordering weight for each known dock category
|
|
89
|
+
* The default ordering weight for each known dock category, where lower sorts
|
|
90
90
|
* earlier. Downstream viewers (e.g. `@vitejs/devtools-kit`) import this as the
|
|
91
91
|
* single source of truth so the hub and its viewers agree on category order.
|
|
92
92
|
* `framework` sorts first; `~builtin` (the viewer's own built-in views) last.
|
|
@@ -97,7 +97,7 @@ declare function normalizeHubBase(base: string): string;
|
|
|
97
97
|
*/
|
|
98
98
|
declare const DEFAULT_CATEGORIES_ORDER: Record<string, number>;
|
|
99
99
|
/**
|
|
100
|
-
* Shared-state slot carrying the hub's renderer manifest
|
|
100
|
+
* Shared-state slot carrying the hub's renderer manifest: one
|
|
101
101
|
* {@link import('./client/renderers').DockRendererManifest} entry per dock
|
|
102
102
|
* `type`, published by `initHub({ renderers })` and consumed by every
|
|
103
103
|
* hub-aware client (the headless client host and viewers alike).
|
package/dist/constants.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-
|
|
1
|
+
import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-Coo1p3ja.mjs";
|
|
2
2
|
export * from "devframe/constants";
|
|
3
3
|
export { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS, DEVFRAMES_HUB_BASE, DOCK_RENDERERS_STATE_KEY, HUB_EVENTS, normalizeHubBase };
|
package/dist/constants.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-
|
|
1
|
+
import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-BUd6oMci.mjs";
|
|
2
2
|
export * from "devframe/constants";
|
|
3
3
|
export { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS, DEVFRAMES_HUB_BASE, DOCK_RENDERERS_STATE_KEY, HUB_EVENTS, normalizeHubBase };
|
|
@@ -1,6 +1,6 @@
|
|
|
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-BUd6oMci.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
6
|
import { createHostContext, createStorage } from "devframe/node";
|
|
@@ -13,24 +13,39 @@ import { nanoid } from "devframe/utils/nanoid";
|
|
|
13
13
|
import process from "node:process";
|
|
14
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, 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. Enable the hub's own MCP (pass `mcp` to `initHub`) to surface this devframe's tools, or drop `mcp` from the devframe to silence this warning."
|
|
34
49
|
},
|
|
35
50
|
DF8100: {
|
|
36
51
|
why: (p) => `Dock with id "${p.id}" is already registered`,
|
|
@@ -57,12 +72,12 @@ const diagnostics = defineDiagnostics({
|
|
|
57
72
|
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
73
|
},
|
|
59
74
|
DF8106: {
|
|
60
|
-
why: (p) => `The host cannot serve the RPC connection meta for devframe "${p.name}" (id "${p.id}") at "${p.base}"
|
|
75
|
+
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
76
|
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
77
|
},
|
|
63
78
|
DF8107: {
|
|
64
79
|
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
|
|
80
|
+
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
81
|
},
|
|
67
82
|
DF8108: {
|
|
68
83
|
why: (p) => `A renderer module is already registered for dock type "${p.type}"`,
|
|
@@ -73,11 +88,11 @@ const diagnostics = defineDiagnostics({
|
|
|
73
88
|
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
89
|
},
|
|
75
90
|
DF8110: {
|
|
76
|
-
why: (p) => `Dock type "${p.type}" is not a servable renderer-module name
|
|
91
|
+
why: (p) => `Dock type "${p.type}" is not a servable renderer-module name; the hub serves each module at \`<base>__renderers/<type>.mjs\``,
|
|
77
92
|
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
93
|
},
|
|
79
94
|
DF8111: {
|
|
80
|
-
why: (p) => `Dock "${p.id}" declares the bare-specifier client script "${p.specifier}", but this host advertises no client-module resolution
|
|
95
|
+
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
96
|
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
97
|
},
|
|
83
98
|
DF8200: { why: (p) => `Terminal session with id "${p.id}" already registered` },
|
|
@@ -89,14 +104,14 @@ const diagnostics = defineDiagnostics({
|
|
|
89
104
|
DF8203: { why: (p) => `Failed to spawn PTY session for "${p.command}": ${p.reason}` },
|
|
90
105
|
DF8204: {
|
|
91
106
|
why: (p) => `Terminal session "${p.id}" cannot be controlled (no lifecycle handle)`,
|
|
92
|
-
fix: "Spawn it via ctx.terminals.startChildProcess() or startPtySession()
|
|
107
|
+
fix: "Spawn it via ctx.terminals.startChildProcess() or startPtySession(); sessions added with a bare register() expose no terminate/restart handle."
|
|
93
108
|
},
|
|
94
109
|
DF8205: {
|
|
95
110
|
why: (p) => `Terminal session "${p.id}" is not restartable`,
|
|
96
111
|
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
112
|
},
|
|
98
113
|
DF8206: {
|
|
99
|
-
why: (p) => `Terminal session "${p.id}" cannot be restarted
|
|
114
|
+
why: (p) => `Terminal session "${p.id}" cannot be restarted because its output stream is already closed`,
|
|
100
115
|
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
116
|
},
|
|
102
117
|
DF8400: { why: (p) => `Command "${p.id}" is already registered` },
|
|
@@ -145,7 +160,7 @@ var DevframeCommandsHost = class {
|
|
|
145
160
|
events = createEventEmitter();
|
|
146
161
|
/**
|
|
147
162
|
* Lazy agent projection: `ctx.agent` queries this provider at list/invoke
|
|
148
|
-
* time, deriving tools from {@link commands} on demand
|
|
163
|
+
* time, deriving tools from {@link commands} on demand, so the commands map
|
|
149
164
|
* stays the single source of truth, nothing is mirrored or kept in sync.
|
|
150
165
|
*/
|
|
151
166
|
agentProvider;
|
|
@@ -223,7 +238,7 @@ var DevframeCommandsHost = class {
|
|
|
223
238
|
* agent-flagged, handler-bearing command (children included) becomes a
|
|
224
239
|
* callable tool. Queried lazily by the provider registered in the
|
|
225
240
|
* constructor. `when` clauses evaluate client-side only and are not
|
|
226
|
-
* enforced here
|
|
241
|
+
* enforced here; opting in a `when`-gated command is a deliberate author
|
|
227
242
|
* decision (documented on `DevframeCommandAgentOptions`).
|
|
228
243
|
*/
|
|
229
244
|
collectAgentTools() {
|
|
@@ -236,7 +251,12 @@ var DevframeCommandsHost = class {
|
|
|
236
251
|
description: agent.description,
|
|
237
252
|
safety: agent.safety ?? "action",
|
|
238
253
|
tags: agent.tags,
|
|
254
|
+
/** The agent host derives the tool's JSON-Schema input from these. */
|
|
239
255
|
args: agent.args,
|
|
256
|
+
/**
|
|
257
|
+
* A command handler's positional parameters come solely from its
|
|
258
|
+
* declared `agent.args` schemas; undeclared payload is dropped.
|
|
259
|
+
*/
|
|
240
260
|
handler: async (args) => this.execute(command.id, ...coerceAgentPositionalArgs(args, agent.args, "drop"))
|
|
241
261
|
});
|
|
242
262
|
for (const child of command.children ?? []) walk(child);
|
|
@@ -266,6 +286,7 @@ var DevframeDocksHost = class {
|
|
|
266
286
|
}
|
|
267
287
|
async init() {
|
|
268
288
|
this.userSettings = await this.context.rpc.sharedState.get(HUB_EVENTS.sharedState.userSettings, { sharedState: createStorage({
|
|
289
|
+
/** Personal dock layout/preferences: per-checkout private state. */
|
|
269
290
|
filepath: join(this.context.host.getStorageDir("project"), "settings.json"),
|
|
270
291
|
initialValue: DEFAULT_STATE_USER_SETTINGS()
|
|
271
292
|
}) });
|
|
@@ -326,7 +347,7 @@ var DevframeDocksHost = class {
|
|
|
326
347
|
});
|
|
327
348
|
}
|
|
328
349
|
/**
|
|
329
|
-
* Warn (don't throw
|
|
350
|
+
* Warn (don't throw, since a client-runtime `resolveClientModule` override may still cover
|
|
330
351
|
* it) when a dock declares a **bare-specifier** client script on a host
|
|
331
352
|
* that advertises no `staticConfig.dock.clientModuleResolution`: the
|
|
332
353
|
* browser cannot resolve a bare npm specifier natively, so the script is
|
|
@@ -380,7 +401,7 @@ var DevframeMessagesHost = class {
|
|
|
380
401
|
_clock = 0;
|
|
381
402
|
/**
|
|
382
403
|
* The tick of the newest removal record dropped from the capped
|
|
383
|
-
* `removals` log
|
|
404
|
+
* `removals` log; cursors older than this can't get a reliable delta
|
|
384
405
|
* and fall back to a full snapshot in {@link listSince}.
|
|
385
406
|
*/
|
|
386
407
|
_removalsTrimmedAt = 0;
|
|
@@ -561,7 +582,7 @@ var DevframeTerminalsHost = class {
|
|
|
561
582
|
this.context = context;
|
|
562
583
|
}
|
|
563
584
|
/**
|
|
564
|
-
* Lazily acquire the streaming channel
|
|
585
|
+
* Lazily acquire the streaming channel, since `context.rpc` isn't assigned
|
|
565
586
|
* until after every host is constructed, so we can't grab it in the
|
|
566
587
|
* constructor.
|
|
567
588
|
*/
|
|
@@ -571,6 +592,41 @@ var DevframeTerminalsHost = class {
|
|
|
571
592
|
this._channel = this.context.rpc.streaming.create(TERMINAL_STREAM_CHANNEL, { replayWindow: TERMINAL_REPLAY_WINDOW });
|
|
572
593
|
return this._channel;
|
|
573
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* Per-run stream lifecycle shared by the child-process and PTY paths: guards
|
|
597
|
+
* the single-close/error transition and keeps the session's `status` in step
|
|
598
|
+
* with the process lifecycle (a hub-aware client sees `running` →
|
|
599
|
+
* `stopped`/`error` instead of a value frozen at spawn time).
|
|
600
|
+
*/
|
|
601
|
+
createStreamLifecycle() {
|
|
602
|
+
const state = { streamClosed: false };
|
|
603
|
+
const markStatus = (next) => {
|
|
604
|
+
const session = state.session;
|
|
605
|
+
if (session.status === next) return;
|
|
606
|
+
session.status = next;
|
|
607
|
+
this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session);
|
|
608
|
+
};
|
|
609
|
+
const closeStream = () => {
|
|
610
|
+
if (state.streamClosed) return;
|
|
611
|
+
state.streamClosed = true;
|
|
612
|
+
try {
|
|
613
|
+
state.controller?.close();
|
|
614
|
+
} catch {}
|
|
615
|
+
};
|
|
616
|
+
const errorStream = (error) => {
|
|
617
|
+
if (state.streamClosed) return;
|
|
618
|
+
state.streamClosed = true;
|
|
619
|
+
try {
|
|
620
|
+
state.controller?.error(error);
|
|
621
|
+
} catch {}
|
|
622
|
+
};
|
|
623
|
+
return {
|
|
624
|
+
state,
|
|
625
|
+
markStatus,
|
|
626
|
+
closeStream,
|
|
627
|
+
errorStream
|
|
628
|
+
};
|
|
629
|
+
}
|
|
574
630
|
register(session) {
|
|
575
631
|
if (this.sessions.has(session.id)) throw diagnostics.DF8200({ id: session.id });
|
|
576
632
|
this.sessions.set(session.id, session);
|
|
@@ -592,6 +648,25 @@ var DevframeTerminalsHost = class {
|
|
|
592
648
|
this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session);
|
|
593
649
|
this._boundStreams.delete(session.id);
|
|
594
650
|
}
|
|
651
|
+
/** Drain a session's output stream into its scrollback buffer and streaming sink. */
|
|
652
|
+
async pumpStream(reader, sessionBuffer, sink, isDisposed) {
|
|
653
|
+
try {
|
|
654
|
+
while (!isDisposed()) {
|
|
655
|
+
const result = await reader.read();
|
|
656
|
+
if (isDisposed() || result.done) break;
|
|
657
|
+
sessionBuffer.push(result.value);
|
|
658
|
+
if (sessionBuffer.length > TERMINAL_BUFFER_LIMIT) sessionBuffer.splice(0, sessionBuffer.length - TERMINAL_BUFFER_LIMIT);
|
|
659
|
+
sink?.write(result.value);
|
|
660
|
+
}
|
|
661
|
+
if (!isDisposed() && sink && !sink.closed) sink.close();
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (!isDisposed() && sink && !sink.closed) sink.error(error);
|
|
664
|
+
} finally {
|
|
665
|
+
try {
|
|
666
|
+
reader.releaseLock();
|
|
667
|
+
} catch {}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
595
670
|
bindStream(session) {
|
|
596
671
|
if (this._boundStreams.has(session.id) && this._boundStreams.get(session.id)?.stream === session.stream) return;
|
|
597
672
|
this._boundStreams.get(session.id)?.dispose();
|
|
@@ -602,26 +677,7 @@ var DevframeTerminalsHost = class {
|
|
|
602
677
|
const sink = this.getStreamingChannel()?.start({ id: session.id });
|
|
603
678
|
const reader = session.stream.getReader();
|
|
604
679
|
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
|
-
})();
|
|
680
|
+
this.pumpStream(reader, sessionBuffer, sink, () => disposed);
|
|
625
681
|
this._boundStreams.set(session.id, {
|
|
626
682
|
dispose: () => {
|
|
627
683
|
disposed = true;
|
|
@@ -634,34 +690,13 @@ var DevframeTerminalsHost = class {
|
|
|
634
690
|
async startChildProcess(executeOptions, terminal) {
|
|
635
691
|
if (this.sessions.has(terminal.id)) throw diagnostics.DF8200({ id: terminal.id });
|
|
636
692
|
const { exec } = await import("tinyexec");
|
|
637
|
-
|
|
693
|
+
const { state, markStatus, closeStream, errorStream } = this.createStreamLifecycle();
|
|
638
694
|
let cp;
|
|
639
695
|
let currentResult;
|
|
640
696
|
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
697
|
const stream = new ReadableStream({
|
|
663
698
|
start(_controller) {
|
|
664
|
-
controller = _controller;
|
|
699
|
+
state.controller = _controller;
|
|
665
700
|
},
|
|
666
701
|
cancel() {
|
|
667
702
|
cp?.kill();
|
|
@@ -701,13 +736,13 @@ var DevframeTerminalsHost = class {
|
|
|
701
736
|
if (currentRun !== runId) return;
|
|
702
737
|
const text = chunk.toString();
|
|
703
738
|
stdoutChunks.push(text);
|
|
704
|
-
if (!streamClosed) controller?.enqueue(text);
|
|
739
|
+
if (!state.streamClosed) state.controller?.enqueue(text);
|
|
705
740
|
});
|
|
706
741
|
cp.process?.stderr?.on("data", (chunk) => {
|
|
707
742
|
if (currentRun !== runId) return;
|
|
708
743
|
const text = chunk.toString();
|
|
709
744
|
stderrChunks.push(text);
|
|
710
|
-
if (!streamClosed) controller?.enqueue(text);
|
|
745
|
+
if (!state.streamClosed) state.controller?.enqueue(text);
|
|
711
746
|
});
|
|
712
747
|
cp.process?.once("error", (error) => {
|
|
713
748
|
if (currentRun !== runId) return;
|
|
@@ -739,7 +774,7 @@ var DevframeTerminalsHost = class {
|
|
|
739
774
|
}
|
|
740
775
|
cp = createChildProcess();
|
|
741
776
|
const restart = async () => {
|
|
742
|
-
if (streamClosed) throw diagnostics.DF8206({ id: terminal.id });
|
|
777
|
+
if (state.streamClosed) throw diagnostics.DF8206({ id: terminal.id });
|
|
743
778
|
cp?.kill();
|
|
744
779
|
cp = createChildProcess();
|
|
745
780
|
markStatus("running");
|
|
@@ -750,7 +785,7 @@ var DevframeTerminalsHost = class {
|
|
|
750
785
|
closeStream();
|
|
751
786
|
markStatus("stopped");
|
|
752
787
|
};
|
|
753
|
-
session = {
|
|
788
|
+
const session = {
|
|
754
789
|
...terminal,
|
|
755
790
|
status: "running",
|
|
756
791
|
stream,
|
|
@@ -761,6 +796,7 @@ var DevframeTerminalsHost = class {
|
|
|
761
796
|
terminate,
|
|
762
797
|
restart
|
|
763
798
|
};
|
|
799
|
+
state.session = session;
|
|
764
800
|
this.register(session);
|
|
765
801
|
return Promise.resolve(session);
|
|
766
802
|
}
|
|
@@ -769,35 +805,14 @@ var DevframeTerminalsHost = class {
|
|
|
769
805
|
const { spawn } = await import("zigpty");
|
|
770
806
|
const cols = executeOptions.cols ?? 80;
|
|
771
807
|
const rows = executeOptions.rows ?? 24;
|
|
772
|
-
|
|
808
|
+
const { state, markStatus, closeStream, errorStream } = this.createStreamLifecycle();
|
|
773
809
|
let pty;
|
|
774
810
|
let currentResult;
|
|
775
811
|
let killCurrentRun;
|
|
776
812
|
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
813
|
const stream = new ReadableStream({
|
|
799
814
|
start(_controller) {
|
|
800
|
-
controller = _controller;
|
|
815
|
+
state.controller = _controller;
|
|
801
816
|
},
|
|
802
817
|
cancel() {
|
|
803
818
|
killCurrentRun?.();
|
|
@@ -842,7 +857,7 @@ var DevframeTerminalsHost = class {
|
|
|
842
857
|
ptyProcess.onData((data) => {
|
|
843
858
|
const text = typeof data === "string" ? data : data.toString("utf8");
|
|
844
859
|
outputChunks.push(text);
|
|
845
|
-
if (!streamClosed && currentRun === runId) controller?.enqueue(text);
|
|
860
|
+
if (!state.streamClosed && currentRun === runId) state.controller?.enqueue(text);
|
|
846
861
|
});
|
|
847
862
|
ptyProcess.onExit(({ exitCode, signal }) => {
|
|
848
863
|
settle(exitCode, signal);
|
|
@@ -882,7 +897,7 @@ var DevframeTerminalsHost = class {
|
|
|
882
897
|
reason: error instanceof Error ? error.message : String(error)
|
|
883
898
|
});
|
|
884
899
|
}
|
|
885
|
-
session = {
|
|
900
|
+
const session = {
|
|
886
901
|
...terminal,
|
|
887
902
|
status: "running",
|
|
888
903
|
interactive: true,
|
|
@@ -915,7 +930,7 @@ var DevframeTerminalsHost = class {
|
|
|
915
930
|
markStatus("stopped");
|
|
916
931
|
},
|
|
917
932
|
restart: async () => {
|
|
918
|
-
if (streamClosed) throw diagnostics.DF8206({ id: terminal.id });
|
|
933
|
+
if (state.streamClosed) throw diagnostics.DF8206({ id: terminal.id });
|
|
919
934
|
killCurrentRun?.();
|
|
920
935
|
killCurrentRun = void 0;
|
|
921
936
|
pty = void 0;
|
|
@@ -931,6 +946,7 @@ var DevframeTerminalsHost = class {
|
|
|
931
946
|
markStatus("running");
|
|
932
947
|
}
|
|
933
948
|
};
|
|
949
|
+
state.session = session;
|
|
934
950
|
this.register(session);
|
|
935
951
|
return session;
|
|
936
952
|
}
|
|
@@ -966,7 +982,7 @@ async function resolvePageScriptClientScript(ctx, clientScript, base) {
|
|
|
966
982
|
};
|
|
967
983
|
}
|
|
968
984
|
/**
|
|
969
|
-
* Framework-neutral primitive backing {@link DevframeHubContext.install}
|
|
985
|
+
* Framework-neutral primitive backing {@link DevframeHubContext.install} -
|
|
970
986
|
* installs a {@link DevframeDefinition} as a dock inside a hub-aware context:
|
|
971
987
|
* serves the devframe's SPA at the resolved base path, synthesizes an iframe
|
|
972
988
|
* dock entry from the definition's metadata, and runs the definition's
|
|
@@ -974,20 +990,35 @@ async function resolvePageScriptClientScript(ctx, clientScript, base) {
|
|
|
974
990
|
* calling it directly.
|
|
975
991
|
*
|
|
976
992
|
* Framework kits wrap `ctx.install` with their own plugin/middleware
|
|
977
|
-
* machinery
|
|
993
|
+
* machinery, e.g. `@vitejs/devtools-kit`'s `createPluginFromDevframe`
|
|
978
994
|
* returns a Vite `Plugin` whose `devtools.setup` ultimately delegates here.
|
|
979
995
|
*/
|
|
980
996
|
/**
|
|
981
997
|
* Phase one of an install: run the duplication guard, serve the SPA + meta,
|
|
982
998
|
* register the iframe dock, and queue the definition's declarative wire
|
|
983
|
-
* services
|
|
999
|
+
* services, everything up to (but not including) `setup(ctx)`. Returns a
|
|
984
1000
|
* deferred setup thunk, or `null` when the devframe was deduplicated.
|
|
985
1001
|
*
|
|
986
1002
|
* 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
|
|
1003
|
+
* across the whole hub, `ready()` them once, and only then run the setups,
|
|
988
1004
|
* so services are ready before any setup, and a plugin can consume a service
|
|
989
1005
|
* another plugin declared regardless of mount order.
|
|
990
1006
|
*/
|
|
1007
|
+
/**
|
|
1008
|
+
* Serve a devframe's SPA (and the hub's connection meta) under `base`, if the
|
|
1009
|
+
* definition ships client assets.
|
|
1010
|
+
*/
|
|
1011
|
+
async function serveDevframeAssets(ctx, d, id, base) {
|
|
1012
|
+
const clientAssets = resolveClientAssets(d);
|
|
1013
|
+
if (!clientAssets) return;
|
|
1014
|
+
if (ctx.host.mountConnectionMeta) await ctx.host.mountConnectionMeta(base);
|
|
1015
|
+
else diagnostics.DF8106({
|
|
1016
|
+
id,
|
|
1017
|
+
name: d.name,
|
|
1018
|
+
base
|
|
1019
|
+
});
|
|
1020
|
+
ctx.views.hostStatic(base, typeof clientAssets === "string" ? resolve(clientAssets) : clientAssets, d.importMetaUrl);
|
|
1021
|
+
}
|
|
991
1022
|
async function prepareDevframe(ctx, d, options = {}) {
|
|
992
1023
|
const strategy = d.duplicationStrategy ?? "warn";
|
|
993
1024
|
const isDuplicate = ctx.docks.views.has(d.id);
|
|
@@ -1014,17 +1045,7 @@ async function prepareDevframe(ctx, d, options = {}) {
|
|
|
1014
1045
|
};
|
|
1015
1046
|
const clientScript = await resolvePageScriptClientScript(ctx, dockDefaults.clientScript, base);
|
|
1016
1047
|
if (clientScript) dockDefaults.clientScript = clientScript;
|
|
1017
|
-
|
|
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
|
-
}
|
|
1048
|
+
await serveDevframeAssets(ctx, d, id, base);
|
|
1028
1049
|
ctx.docks.register({
|
|
1029
1050
|
id,
|
|
1030
1051
|
title: d.name,
|
|
@@ -1037,7 +1058,7 @@ async function prepareDevframe(ctx, d, options = {}) {
|
|
|
1037
1058
|
return () => Promise.resolve(d.setup(ctx));
|
|
1038
1059
|
}
|
|
1039
1060
|
/**
|
|
1040
|
-
* Install a {@link DevframeDefinition} into a hub in one call
|
|
1061
|
+
* Install a {@link DevframeDefinition} into a hub in one call: serve its SPA,
|
|
1041
1062
|
* register its dock, ready its services, and run `setup(ctx)`. The imperative
|
|
1042
1063
|
* counterpart to the hub's declarative `devframes` list (which batches the
|
|
1043
1064
|
* phases via {@link prepareDevframe}); use it from `configure(ctx)` or
|
|
@@ -1073,7 +1094,7 @@ function resolveControllableSession(sessions, id) {
|
|
|
1073
1094
|
return session;
|
|
1074
1095
|
}
|
|
1075
1096
|
/**
|
|
1076
|
-
* `hub:commands:execute
|
|
1097
|
+
* `hub:commands:execute`: Invoke a registered server command by id. The
|
|
1077
1098
|
* arguments after `id` are forwarded to the command's `handler(...)`.
|
|
1078
1099
|
* Returns whatever the handler returns.
|
|
1079
1100
|
*
|
|
@@ -1088,7 +1109,7 @@ const hubCommandsExecute = defineHubRpcFunction({
|
|
|
1088
1109
|
} })
|
|
1089
1110
|
});
|
|
1090
1111
|
/**
|
|
1091
|
-
* `hub:messages:add
|
|
1112
|
+
* `hub:messages:add`: Add a message from a browser client into the hub's
|
|
1092
1113
|
* messages subsystem. Marked `from: 'browser'`. Returns the serializable
|
|
1093
1114
|
* entry (the mutation handle stays server-side).
|
|
1094
1115
|
*
|
|
@@ -1107,7 +1128,7 @@ const hubMessagesAdd = defineHubRpcFunction({
|
|
|
1107
1128
|
})).entry;
|
|
1108
1129
|
} })
|
|
1109
1130
|
});
|
|
1110
|
-
/** `hub:messages:update
|
|
1131
|
+
/** `hub:messages:update`: Patch a message by id; returns the updated entry (or `undefined`). */
|
|
1111
1132
|
const hubMessagesUpdate = defineHubRpcFunction({
|
|
1112
1133
|
name: HUB_EVENTS.rpc.messagesUpdate,
|
|
1113
1134
|
type: "action",
|
|
@@ -1116,7 +1137,7 @@ const hubMessagesUpdate = defineHubRpcFunction({
|
|
|
1116
1137
|
return context.messages.update(id, patch);
|
|
1117
1138
|
} })
|
|
1118
1139
|
});
|
|
1119
|
-
/** `hub:messages:remove
|
|
1140
|
+
/** `hub:messages:remove`: Remove a message by id. */
|
|
1120
1141
|
const hubMessagesRemove = defineHubRpcFunction({
|
|
1121
1142
|
name: HUB_EVENTS.rpc.messagesRemove,
|
|
1122
1143
|
type: "action",
|
|
@@ -1124,7 +1145,7 @@ const hubMessagesRemove = defineHubRpcFunction({
|
|
|
1124
1145
|
await context.messages.remove(id);
|
|
1125
1146
|
} })
|
|
1126
1147
|
});
|
|
1127
|
-
/** `hub:messages:clear
|
|
1148
|
+
/** `hub:messages:clear`: Remove every message. */
|
|
1128
1149
|
const hubMessagesClear = defineHubRpcFunction({
|
|
1129
1150
|
name: HUB_EVENTS.rpc.messagesClear,
|
|
1130
1151
|
type: "action",
|
|
@@ -1133,7 +1154,7 @@ const hubMessagesClear = defineHubRpcFunction({
|
|
|
1133
1154
|
} })
|
|
1134
1155
|
});
|
|
1135
1156
|
/**
|
|
1136
|
-
* `hub:terminals:write
|
|
1157
|
+
* `hub:terminals:write`: Send input to an interactive PTY session spawned
|
|
1137
1158
|
* via `ctx.terminals.startPtySession`. Lets a hub-aware terminal UI (e.g. the
|
|
1138
1159
|
* terminals plugin) drive a session owned by another plugin.
|
|
1139
1160
|
*/
|
|
@@ -1144,7 +1165,7 @@ const hubTerminalsWrite = defineHubRpcFunction({
|
|
|
1144
1165
|
resolveInteractiveSession(context.terminals.sessions, id).write(data);
|
|
1145
1166
|
} })
|
|
1146
1167
|
});
|
|
1147
|
-
/** `hub:terminals:resize
|
|
1168
|
+
/** `hub:terminals:resize`: Resize an interactive PTY session by id. */
|
|
1148
1169
|
const hubTerminalsResize = defineHubRpcFunction({
|
|
1149
1170
|
name: HUB_EVENTS.rpc.terminalsResize,
|
|
1150
1171
|
type: "action",
|
|
@@ -1153,7 +1174,7 @@ const hubTerminalsResize = defineHubRpcFunction({
|
|
|
1153
1174
|
} })
|
|
1154
1175
|
});
|
|
1155
1176
|
/**
|
|
1156
|
-
* `hub:terminals:terminate
|
|
1177
|
+
* `hub:terminals:terminate`: Kill a session's process while keeping the
|
|
1157
1178
|
* session registered (its output/scrollback stays). Works for both read-only
|
|
1158
1179
|
* child-process and interactive PTY sessions, letting a hub-aware terminal UI
|
|
1159
1180
|
* force-kill a session owned by another plugin.
|
|
@@ -1166,7 +1187,7 @@ const hubTerminalsTerminate = defineHubRpcFunction({
|
|
|
1166
1187
|
} })
|
|
1167
1188
|
});
|
|
1168
1189
|
/**
|
|
1169
|
-
* `hub:terminals:restart
|
|
1190
|
+
* `hub:terminals:restart`: Re-run a session's command in place. Rejected for
|
|
1170
1191
|
* sessions registered with `restartable: false`, whose lifecycle is owned
|
|
1171
1192
|
* elsewhere.
|
|
1172
1193
|
*/
|
|
@@ -1180,7 +1201,7 @@ const hubTerminalsRestart = defineHubRpcFunction({
|
|
|
1180
1201
|
} })
|
|
1181
1202
|
});
|
|
1182
1203
|
/**
|
|
1183
|
-
* `hub:terminals:remove
|
|
1204
|
+
* `hub:terminals:remove`: Kill a session's process (when it still owns one)
|
|
1184
1205
|
* and drop it from the registry, disposing its output stream. Lets a hub-aware
|
|
1185
1206
|
* terminal UI discard a stopped aggregated session.
|
|
1186
1207
|
*/
|
|
@@ -1196,13 +1217,13 @@ const hubTerminalsRemove = defineHubRpcFunction({
|
|
|
1196
1217
|
} })
|
|
1197
1218
|
});
|
|
1198
1219
|
/**
|
|
1199
|
-
* `hub:docks:activate
|
|
1220
|
+
* `hub:docks:activate`: Ask the active viewer to switch its focused dock to
|
|
1200
1221
|
* `dockId`, optionally carrying `params` for the target dock to interpret
|
|
1201
1222
|
* (e.g. `{ sessionId }` for the terminals dock to focus a session).
|
|
1202
1223
|
*
|
|
1203
1224
|
* Any connected client may call it, which is the point: a mounted devframe
|
|
1204
1225
|
* running in its own iframe (on its own RPC client) can steer the host shell's
|
|
1205
|
-
* dock selection
|
|
1226
|
+
* dock selection, client-local state it otherwise can't reach. The hub
|
|
1206
1227
|
* broadcasts the request live to connected clients (the host shell switches)
|
|
1207
1228
|
* and mirrors it into the `devframe:docks:active` shared state (a dock that
|
|
1208
1229
|
* mounts in response still converges on it).
|