devframe 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/_shared-CCzf9Hub.d.mts +83 -0
- package/dist/_shared-U8D-xzrp.mjs +115 -0
- package/dist/adapters/build.d.mts +6 -6
- package/dist/adapters/build.mjs +33 -36
- package/dist/adapters/cac.d.mts +12 -1
- package/dist/adapters/cac.mjs +8 -8
- package/dist/adapters/dev.d.mts +17 -17
- package/dist/adapters/dev.mjs +2 -2
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +31 -28
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +28 -14
- package/dist/adapters/mcp.mjs +76 -40
- package/dist/{agent-tool-name-EgfoFO8C.mjs → agent-tool-name-DGzeuWcO.mjs} +2 -2
- package/dist/cli/main.d.mts +1 -1
- package/dist/cli/main.mjs +41 -17
- package/dist/client/index.d.mts +36 -36
- package/dist/client/index.mjs +90 -52
- package/dist/{client-assets-C7SXgTWY.mjs → client-assets-DNdhagGm.mjs} +2 -2
- package/dist/constants.d.mts +8 -7
- package/dist/constants.mjs +7 -6
- package/dist/{context-omlma_1n.mjs → context-B29Oh0oA.mjs} +166 -93
- package/dist/{context-DftYyXaR.d.mts → context-BJiKJqyW.d.mts} +5 -5
- package/dist/{context-CLF4-8Pp.d.mts → context-CHW4BZOj.d.mts} +203 -134
- package/dist/{context-KUqAfawQ.mjs → context-jXO5vzjH.mjs} +3 -3
- package/dist/{crypto-token-XCqTSMg9.mjs → crypto-token-TVbGGFuj.mjs} +1 -1
- package/dist/{dev-CzkPCJwi.mjs → dev-CRYHx7iG.mjs} +73 -40
- package/dist/{diagnostics-BXWW3VK-.mjs → diagnostics-DZA6KQ_Z.mjs} +32 -19
- package/dist/{events-4daw0uK9.mjs → events-BV4Dj59a.mjs} +2 -2
- package/dist/{services-install-CqqQVls5.mjs → host-agent-CKqSTEX9.mjs} +15 -151
- package/dist/{host-h3-fRbF9yor.mjs → host-h3-BROI6SQ4.mjs} +2 -2
- package/dist/in-page-channel/index.d.mts +14 -14
- package/dist/in-page-channel/index.mjs +63 -54
- package/dist/{index-D3k0BIhe.d.mts → index-CNC390b_.d.mts} +4 -4
- package/dist/{index-BzyopqxV.d.mts → index-pIVt92jP.d.mts} +9 -9
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +2 -2
- package/dist/{instance-registry-BjlMQbFB.mjs → instance-registry-DCq762YJ.mjs} +9 -9
- package/dist/{instance-shell-Tcp5hGoq.d.mts → instance-shell-DZipaP9k.d.mts} +18 -18
- package/dist/{instance-shell-CpVW4zG0.mjs → instance-shell-DvYCwrDy.mjs} +223 -67
- package/dist/internal/index.d.mts +45 -26
- package/dist/internal/index.mjs +10 -10
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/auth.mjs +2 -2
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +2 -2
- package/dist/node/index.d.mts +5 -5
- package/dist/node/index.mjs +2 -2
- package/dist/{nostics-SPGDgHEP.d.mts → nostics-0sKe5HIj.d.mts} +2 -2
- package/dist/{nostics-CzECRXpE.mjs → nostics-D0PvLZsn.mjs} +1 -1
- package/dist/origin-DGDH8BI9.d.mts +45 -0
- package/dist/origin-DZit9g29.mjs +80 -0
- package/dist/recipes/common-rpc-functions.d.mts +5 -5
- package/dist/recipes/common-rpc-functions.mjs +3 -3
- package/dist/recipes/interactive-auth.d.mts +6 -6
- package/dist/recipes/interactive-auth.mjs +6 -6
- package/dist/{remote-assets-DezQmPQU.mjs → remote-assets-DMFzLSoH.mjs} +27 -7
- package/dist/{remote-assets-B13_vRkJ.d.mts → remote-assets-eBvSKZMS.d.mts} +7 -7
- package/dist/{revoke-COLW0cOi.mjs → revoke-cqztiCX4.mjs} +1 -1
- package/dist/rpc/dump.d.mts +19 -12
- package/dist/rpc/dump.mjs +2 -2
- package/dist/rpc/index.d.mts +2 -2
- package/dist/rpc/index.mjs +2 -3
- package/dist/rpc/transports/sse-client.d.mts +1 -1
- package/dist/rpc/transports/sse-client.mjs +30 -14
- package/dist/rpc/transports/sse-server.d.mts +4 -4
- package/dist/rpc/transports/sse-server.mjs +7 -7
- package/dist/rpc/transports/ws-bun.d.mts +5 -5
- package/dist/rpc/transports/ws-bun.mjs +8 -2
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-client.mjs +2 -2
- package/dist/rpc/transports/ws-deno.d.mts +4 -4
- package/dist/rpc/transports/ws-deno.mjs +8 -2
- package/dist/rpc/transports/ws-server.d.mts +2 -1
- package/dist/rpc/transports/ws-server.mjs +232 -1
- package/dist/{rpc-core-DlHd276T.mjs → rpc-core-CvkU23bQ.mjs} +14 -2
- package/dist/{rpc-core-B3W3z9ff.d.mts → rpc-core-DsyDViQy.d.mts} +4 -4
- package/dist/serialization-DFg5sUt6.mjs +116 -0
- package/dist/services-install-Ben4yh52.mjs +154 -0
- package/dist/session-DPfj6zQG.mjs +16 -0
- package/dist/{shared-state--sduc6Xi.mjs → shared-state-CaxB1q0h.mjs} +2 -2
- package/dist/{simple-schema-DQPZrAaZ.mjs → simple-schema-Bi40l-P6.mjs} +2 -2
- package/dist/{sse-client-DwX021ST.d.mts → sse-client-BQ4jS3eO.d.mts} +4 -4
- package/dist/{state-CK9LjrnT.mjs → state-Mwt6--el.mjs} +17 -9
- package/dist/{storage-BXNVhKyR.mjs → storage-B70AXK64.mjs} +2 -2
- package/dist/types/index.d.mts +6 -6
- package/dist/{types-CMdPYhOx.d.mts → types-Dq4i156u.d.mts} +17 -17
- package/dist/utils/agent-tool-name.d.mts +2 -2
- package/dist/utils/agent-tool-name.mjs +2 -2
- package/dist/utils/colors.d.mts +1 -1
- package/dist/utils/crypto-token.d.mts +1 -1
- package/dist/utils/crypto-token.mjs +1 -1
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/nostics.d.mts +1 -1
- package/dist/utils/nostics.mjs +1 -1
- package/dist/utils/open.mjs +1 -1
- package/dist/utils/origin.d.mts +2 -0
- package/dist/utils/origin.mjs +80 -0
- package/dist/utils/remote-assets.d.mts +2 -2
- package/dist/utils/remote-assets.mjs +1 -1
- package/dist/utils/serve-static.d.mts +4 -4
- package/dist/utils/serve-static.mjs +56 -19
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/simple-schema.d.mts +5 -5
- package/dist/utils/simple-schema.mjs +2 -2
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/utils/streaming-channel.mjs +2 -2
- package/dist/utils/when.d.mts +5 -5
- package/dist/{validation-YrZH6atx.mjs → validation-CfV1s0SF.mjs} +2 -2
- package/dist/{wire-codec-0K-o5MYW.mjs → wire-codec-BHGt0sSs.mjs} +3 -3
- package/dist/{static-DV_qUSRS.mjs → write-BuLCrn9h.mjs} +153 -137
- package/dist/{ws-client-7_lZi-B9.d.mts → ws-client-Xpxt0oPA.d.mts} +3 -3
- package/dist/{ws-server-VyHlanzy.d.mts → ws-server-D2nv68Hg.d.mts} +13 -37
- package/package.json +3 -6
- package/skills/devframe/SKILL.md +5 -3
- package/dist/_shared-BM3PdYli.mjs +0 -68
- package/dist/_shared-mtXpcJIv.d.mts +0 -41
- package/dist/diagnostics-CD8nlgll.mjs +0 -39
- package/dist/serialization-BGzEwAdr.mjs +0 -77
- package/dist/ws-server-Y_AumUQH.mjs +0 -294
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as randomToken } from "./crypto-token-
|
|
2
|
-
import { t as createStorage } from "./storage-
|
|
3
|
-
import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-
|
|
1
|
+
import { n as randomToken } from "./crypto-token-TVbGGFuj.mjs";
|
|
2
|
+
import { t as createStorage } from "./storage-B70AXK64.mjs";
|
|
3
|
+
import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-cqztiCX4.mjs";
|
|
4
4
|
import { join } from "pathe";
|
|
5
5
|
//#region src/node/hub-internals/context.ts
|
|
6
6
|
const internalContextMap = /* @__PURE__ */ new WeakMap();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const HEX = "0123456789abcdef";
|
|
3
3
|
/**
|
|
4
4
|
* Generate a high-entropy, URL-safe (hex) random token suitable for use as a
|
|
5
|
-
* bearer credential
|
|
5
|
+
* bearer credential, e.g. the persistent client auth token or an ephemeral
|
|
6
6
|
* remote-dock token. Defaults to 16 bytes (128 bits) of entropy.
|
|
7
7
|
*/
|
|
8
8
|
function randomToken(byteLength = 16) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { t as resolveClientAssets } from "./client-assets-
|
|
1
|
+
import { t as resolveClientAssets } from "./client-assets-DNdhagGm.mjs";
|
|
2
2
|
import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
|
|
3
|
-
import { t as diagnostics } from "./diagnostics-
|
|
4
|
-
import { t as createHostContext } from "./context-
|
|
5
|
-
import { t as resolveStaticAssetsSource } from "./remote-assets-
|
|
6
|
-
import { i as
|
|
7
|
-
import { t as createH3DevframeHost } from "./host-h3-
|
|
8
|
-
import {
|
|
3
|
+
import { t as diagnostics } from "./diagnostics-DZA6KQ_Z.mjs";
|
|
4
|
+
import { t as createHostContext } from "./context-B29Oh0oA.mjs";
|
|
5
|
+
import { t as resolveStaticAssetsSource } from "./remote-assets-DMFzLSoH.mjs";
|
|
6
|
+
import { a as resolveMcpConfig, i as resolveDevServerPort, n as normalizeBasePath, r as resolveBasePath, s as importRuntimeModule, t as loadAutoMcpAdapter } from "./_shared-U8D-xzrp.mjs";
|
|
7
|
+
import { t as createH3DevframeHost } from "./host-h3-BROI6SQ4.mjs";
|
|
8
|
+
import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, t as createInstanceShell } from "./instance-shell-DvYCwrDy.mjs";
|
|
9
9
|
import { open } from "./utils/open.mjs";
|
|
10
10
|
import { mountStaticHandler } from "./utils/serve-static.mjs";
|
|
11
|
+
import { resolve } from "pathe";
|
|
11
12
|
import { createServer } from "node:http";
|
|
12
13
|
import process from "node:process";
|
|
13
|
-
import {
|
|
14
|
-
import { joinURL, withBase } from "ufo";
|
|
14
|
+
import { joinURL, withBase, withoutLeadingSlash } from "ufo";
|
|
15
15
|
import { H3, toNodeHandler } from "h3";
|
|
16
16
|
//#region src/adapters/initiate.ts
|
|
17
17
|
const INSTANCE_INTERNALS = /* @__PURE__ */ new WeakMap();
|
|
@@ -20,7 +20,7 @@ function getInstanceInternals(handler) {
|
|
|
20
20
|
return INSTANCE_INTERNALS.get(handler) ?? {};
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Serve a devframe through one framework-agnostic, web-standard handler
|
|
23
|
+
* Serve a devframe through one framework-agnostic, web-standard handler:
|
|
24
24
|
* the SPA, `__connection.json` discovery, the WebSocket RPC endpoint, the
|
|
25
25
|
* auth gate, and the optional MCP route, all under a single mount base.
|
|
26
26
|
* Mount `handler` on any framework's catch-all route (or `nodeMiddleware` on
|
|
@@ -28,10 +28,10 @@ function getInstanceInternals(handler) {
|
|
|
28
28
|
*
|
|
29
29
|
* The factory is synchronous and kicks off initialization eagerly;
|
|
30
30
|
* `handler`/`nodeMiddleware` await readiness internally. Nothing binds a port
|
|
31
|
-
* on its own: the WebSocket resolves in precedence order
|
|
31
|
+
* on its own: the WebSocket resolves in precedence order: `ws.port` (pinned
|
|
32
32
|
* side-car) > `server` (shared upgrade at `<base>__ws`) > `ws.sidecar`
|
|
33
33
|
* (auto-port side-car) > the host driving upgrades itself through
|
|
34
|
-
* {@link DevframeInstance.attach}
|
|
34
|
+
* {@link DevframeInstance.attach}. Meanwhile `ws.url`, when set, overrides the
|
|
35
35
|
* advertised* endpoint (the tunnel pattern) and on its own hands the whole
|
|
36
36
|
* transport to an external server. `__connection.json` reflects whichever
|
|
37
37
|
* combination is active.
|
|
@@ -46,6 +46,10 @@ function initDevframe(def, options) {
|
|
|
46
46
|
app,
|
|
47
47
|
host,
|
|
48
48
|
origin: options.origin,
|
|
49
|
+
/**
|
|
50
|
+
* Auth resolution mirrors `createDevServer`: gate by default, explicit
|
|
51
|
+
* `false` opts out, a handler object installs a custom scheme.
|
|
52
|
+
*/
|
|
49
53
|
auth: options.auth !== void 0 ? options.auth : def.cli?.auth,
|
|
50
54
|
server: options.server,
|
|
51
55
|
ws: options.ws ?? def.cli?.ws,
|
|
@@ -84,34 +88,11 @@ function initDevframe(def, options) {
|
|
|
84
88
|
for (const input of def.services ?? []) context.services.install(input, { resolveFrom: def.importMetaUrl });
|
|
85
89
|
await context.services.ready();
|
|
86
90
|
await def.setup(context, setupInfo);
|
|
87
|
-
const
|
|
88
|
-
const mcpMeta = resolveMcpConnectionMeta(def, mcpOption);
|
|
89
|
-
let mcpDispose;
|
|
90
|
-
if (mcpMeta) {
|
|
91
|
-
const mcpConfig = mcpOption === true || mcpOption === void 0 ? {} : mcpOption;
|
|
92
|
-
const mcpPath = joinURL(base, mcpMeta.path);
|
|
93
|
-
let mountMcpHttp;
|
|
94
|
-
try {
|
|
95
|
-
({mountMcpHttp} = await importRuntimeModule("devframe/adapters/mcp"));
|
|
96
|
-
} catch (error) {
|
|
97
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
98
|
-
throw diagnostics.DF0017({
|
|
99
|
-
transport: "http",
|
|
100
|
-
reason,
|
|
101
|
-
cause: error
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
mcpDispose = mountMcpHttp(app, context, mcpPath, {
|
|
105
|
-
serverName: `${def.id} (devframe)`,
|
|
106
|
-
serverVersion: def.version ?? "0.0.0",
|
|
107
|
-
exposeSharedState: true,
|
|
108
|
-
allowedOrigins: mcpConfig.allowedOrigins
|
|
109
|
-
}).dispose;
|
|
110
|
-
}
|
|
91
|
+
const mcp = await mountMcpRoute(app, context, def, base, options.mcp ?? def.cli?.mcp ?? "auto");
|
|
111
92
|
return {
|
|
112
93
|
context,
|
|
113
|
-
...
|
|
114
|
-
...
|
|
94
|
+
...mcp?.meta ? { mcp: mcp.meta } : {},
|
|
95
|
+
...mcp?.dispose ? { dispose: mcp.dispose } : {}
|
|
115
96
|
};
|
|
116
97
|
},
|
|
117
98
|
mount(context, meta) {
|
|
@@ -136,17 +117,56 @@ function initDevframe(def, options) {
|
|
|
136
117
|
INSTANCE_INTERNALS.set(instance, shell.internals);
|
|
137
118
|
return instance;
|
|
138
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Mount the route-based MCP server at `<base>__mcp`, before the SPA static
|
|
122
|
+
* catch-all so the exact route wins. The MCP SDK is pulled in dynamically
|
|
123
|
+
* only when the route mounts, keeping it out of consumer bundles: `'auto'`
|
|
124
|
+
* mounts once `setup()` left a non-empty agent surface (an empty surface
|
|
125
|
+
* loads no MCP code); an explicit setting mounts unconditionally. The
|
|
126
|
+
* resolved config is origin-only unless it opts into a bearer/callback.
|
|
127
|
+
*/
|
|
128
|
+
async function mountMcpRoute(app, context, def, base, setting) {
|
|
129
|
+
let module;
|
|
130
|
+
let config;
|
|
131
|
+
if (setting === "auto") {
|
|
132
|
+
module = await loadAutoMcpAdapter(context.agent);
|
|
133
|
+
if (module) config = { authorization: false };
|
|
134
|
+
} else config = resolveMcpConfig(setting);
|
|
135
|
+
if (!config) return void 0;
|
|
136
|
+
const route = withoutLeadingSlash(config.path ?? "__mcp");
|
|
137
|
+
if (!module) try {
|
|
138
|
+
module = await importRuntimeModule("devframe/adapters/mcp");
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
141
|
+
throw diagnostics.DF0017({
|
|
142
|
+
transport: "http",
|
|
143
|
+
reason,
|
|
144
|
+
cause: error
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const mounted = module.mountMcpHttp(app, context, joinURL(base, route), {
|
|
148
|
+
serverName: `${def.id} (devframe)`,
|
|
149
|
+
serverVersion: def.version ?? "0.0.0",
|
|
150
|
+
exposeSharedState: true,
|
|
151
|
+
authorization: config.authorization,
|
|
152
|
+
allowedOrigins: config.allowedOrigins
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
meta: { path: route },
|
|
156
|
+
dispose: mounted.dispose
|
|
157
|
+
};
|
|
158
|
+
}
|
|
139
159
|
//#endregion
|
|
140
160
|
//#region src/adapters/dev.ts
|
|
141
161
|
/**
|
|
142
|
-
* Start a devframe dev server for a {@link DevframeDefinition}
|
|
162
|
+
* Start a devframe dev server for a {@link DevframeDefinition}:
|
|
143
163
|
* h3 + WebSocket RPC + (optionally) the author's SPA mounted at the
|
|
144
164
|
* resolved base path.
|
|
145
165
|
*
|
|
146
166
|
* When `distDir` is omitted (and the definition's client assets are unset) the
|
|
147
167
|
* server runs in **bridge mode**: only `__connection.json` and the WS
|
|
148
168
|
* endpoint are mounted, with no SPA mount. The SPA is expected to be
|
|
149
|
-
* hosted elsewhere (e.g. by a parent Vite/Nuxt dev server)
|
|
169
|
+
* hosted elsewhere (e.g. by a parent Vite/Nuxt dev server); see
|
|
150
170
|
* `devframeViteBridge` from `@devframes/vite`.
|
|
151
171
|
*
|
|
152
172
|
* Returns the underlying {@link StartedServer} handle so callers can
|
|
@@ -194,12 +214,25 @@ async function createDevServer(def, options = {}) {
|
|
|
194
214
|
ws: options.ws,
|
|
195
215
|
allowedOrigins: options.allowedOrigins,
|
|
196
216
|
sse: options.sse,
|
|
217
|
+
/**
|
|
218
|
+
* The `--no-auth` flag forces the gate off regardless of the `auth`
|
|
219
|
+
* option / definition default (which the instance resolves itself).
|
|
220
|
+
*/
|
|
197
221
|
auth: flags.auth === false ? false : options.auth,
|
|
198
222
|
mcp: options.mcp,
|
|
199
223
|
flags,
|
|
200
224
|
onPeerConnect: options.onPeerConnect,
|
|
201
225
|
onPeerDisconnect: options.onPeerDisconnect,
|
|
226
|
+
/**
|
|
227
|
+
* Publish in the global registry so discovery tooling (`devframe connect`)
|
|
228
|
+
* finds the server without port guessing. The instance owns the record's
|
|
229
|
+
* lifecycle, written once its (pinned) origin resolves, removed on close.
|
|
230
|
+
*/
|
|
202
231
|
register: true,
|
|
232
|
+
/**
|
|
233
|
+
* This server is devframe's own, and nothing else handles its upgrades, so
|
|
234
|
+
* off-route upgrade attempts are rejected promptly.
|
|
235
|
+
*/
|
|
203
236
|
destroyUnmatchedUpgrades: true
|
|
204
237
|
});
|
|
205
238
|
try {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { i as defineDiagnostics } from "./nostics-
|
|
1
|
+
import { i as defineDiagnostics } from "./nostics-D0PvLZsn.mjs";
|
|
2
2
|
//#region src/node/diagnostics.ts
|
|
3
|
+
/**
|
|
4
|
+
* DF00xx codes are allocated across packages (e.g. @devframes/json-render
|
|
5
|
+
* owns DF0037–DF0041), so this file alone doesn't show the next free
|
|
6
|
+
* number; check `docs/content/6.errors/` for the full allocation before adding one.
|
|
7
|
+
*/
|
|
3
8
|
const diagnostics = defineDiagnostics({
|
|
4
9
|
docsBase: "https://devfra.me/errors",
|
|
5
10
|
codes: {
|
|
@@ -9,7 +14,7 @@ const diagnostics = defineDiagnostics({
|
|
|
9
14
|
DF0012: { why: (p) => `Failed to parse storage file: ${p.filepath}, falling back to defaults.` },
|
|
10
15
|
DF0013: { why: (p) => `Shared state of "${p.key}" is not found, please provide an initial value for the first time` },
|
|
11
16
|
DF0014: {
|
|
12
|
-
why: (p) => `RPC function "${p.name}" has an invalid \`agent\` field
|
|
17
|
+
why: (p) => `RPC function "${p.name}" has an invalid \`agent\` field: \`description\` must be a non-empty string.`,
|
|
13
18
|
fix: "Provide a short description (~1–3 sentences) explaining what the tool does and when agents should invoke it."
|
|
14
19
|
},
|
|
15
20
|
DF0015: {
|
|
@@ -23,12 +28,12 @@ const diagnostics = defineDiagnostics({
|
|
|
23
28
|
fix: "The consumer is too slow for the producer. Raise `highWaterMark` on the subscription, slow the producer, or batch chunks."
|
|
24
29
|
},
|
|
25
30
|
DF0030: {
|
|
26
|
-
why: (p) => `Stream "${p.channel}#${p.id}" is unknown
|
|
31
|
+
why: (p) => `Stream "${p.channel}#${p.id}" is unknown; no producer has called \`channel.start({ id: "${p.id}" })\`.`,
|
|
27
32
|
fix: "Ensure the server-side producer is running before clients subscribe, or check for typos in the stream id."
|
|
28
33
|
},
|
|
29
34
|
DF0031: {
|
|
30
35
|
why: (p) => `Cannot write to closed stream "${p.channel}#${p.id}".`,
|
|
31
|
-
fix: "Track the producer lifecycle
|
|
36
|
+
fix: "Track the producer lifecycle by guarding writes with the `stream.signal.aborted` flag."
|
|
32
37
|
},
|
|
33
38
|
DF0032: {
|
|
34
39
|
why: (p) => `Streaming channel "${p.channel}" is already registered.`,
|
|
@@ -48,14 +53,14 @@ const diagnostics = defineDiagnostics({
|
|
|
48
53
|
},
|
|
49
54
|
DF0036: {
|
|
50
55
|
why: (p) => `RPC call to "${p.name}" was rejected: the caller is not authorized.`,
|
|
51
|
-
fix: "Complete the auth handshake (or connect with a static/pre-shared token) before calling a trusted method. Untrusted callers may only call `anonymous:`-prefixed methods
|
|
56
|
+
fix: "Complete the auth handshake (or connect with a static/pre-shared token) before calling a trusted method. Untrusted callers may only call `anonymous:`-prefixed methods; see `isAnonymousRpcMethod`."
|
|
52
57
|
},
|
|
53
58
|
DF0037: {
|
|
54
59
|
why: (p) => `A service is already provided under "${p.id}".`,
|
|
55
60
|
fix: "Service ids are unique per context. Revoke the existing provider first (the `provide()` call returns a revoke function), or namespace the id with your plugin id to avoid collisions."
|
|
56
61
|
},
|
|
57
62
|
DF0042: {
|
|
58
|
-
why: (p) => `"${p.id}" declares \`capabilities.build: false
|
|
63
|
+
why: (p) => `"${p.id}" declares \`capabilities.build: false\`, so its static export is not meaningful (writes are excluded and any live-served data won't be there).`,
|
|
59
64
|
fix: "Pass `{ force: true }` to `createBuild()` if the degraded export is still useful to you, or drop `capabilities.build: false` on the definition."
|
|
60
65
|
},
|
|
61
66
|
DF0045: {
|
|
@@ -63,8 +68,8 @@ const diagnostics = defineDiagnostics({
|
|
|
63
68
|
fix: "Discovery tooling (`devframe connect`) will not see this instance. Check that the registry directory is writable, point `DEVFRAME_INSTANCES_DIR` at a writable directory, or set `DEVFRAME_DISABLE_INSTANCE_REGISTRY=1` to opt out of registration."
|
|
64
69
|
},
|
|
65
70
|
DF0046: {
|
|
66
|
-
why: (p) => `\`devframe connect\` requires the optional peer dependency @modelcontextprotocol/
|
|
67
|
-
fix: "Install it next to devframe (e.g. `npm install @modelcontextprotocol/
|
|
71
|
+
why: (p) => `\`devframe connect\` requires the optional peer dependency @modelcontextprotocol/client: ${p.reason}`,
|
|
72
|
+
fix: "Install it next to devframe (e.g. `npm install @modelcontextprotocol/client`) and run `devframe connect` again."
|
|
68
73
|
},
|
|
69
74
|
DF0047: {
|
|
70
75
|
why: (p) => `Agent tool "${p.id}" is hidden from the MCP surface: its wire name "${p.name}" collides with the tool "${p.existing}".`,
|
|
@@ -80,7 +85,7 @@ const diagnostics = defineDiagnostics({
|
|
|
80
85
|
},
|
|
81
86
|
DF0050: {
|
|
82
87
|
why: (p) => `No running devframe instance on port ${p.port}.`,
|
|
83
|
-
fix: "Call devframe_connect_list-instances for the current instance list
|
|
88
|
+
fix: "Call devframe_connect_list-instances for the current instance list; the instance may have stopped or changed port."
|
|
84
89
|
},
|
|
85
90
|
DF0051: {
|
|
86
91
|
why: (p) => `The devframe instance on port ${p.port} has no MCP endpoint.`,
|
|
@@ -92,7 +97,7 @@ const diagnostics = defineDiagnostics({
|
|
|
92
97
|
},
|
|
93
98
|
DF0054: {
|
|
94
99
|
why: (p) => `connectionMeta() was called before initDevframe("${p.id}") finished initializing.`,
|
|
95
|
-
fix: "Await `instance.ready` (or any request through `instance.handler`) before reading `connectionMeta()
|
|
100
|
+
fix: "Await `instance.ready` (or any request through `instance.handler`) before reading `connectionMeta()`; the WebSocket binding it describes is only known once initialization completes."
|
|
96
101
|
},
|
|
97
102
|
DF0055: {
|
|
98
103
|
why: (p) => `This instance already owns its WebSocket transport (${p.tier}), so it cannot take over the host's upgrade events.`,
|
|
@@ -104,10 +109,10 @@ const diagnostics = defineDiagnostics({
|
|
|
104
109
|
},
|
|
105
110
|
DF0057: {
|
|
106
111
|
why: () => "This instance disables its WebSocket transport (`ws: false`), so there is no socket to drive upgrades into.",
|
|
107
|
-
fix: "Clients connect over the SSE endpoint instead
|
|
112
|
+
fix: "Clients connect over the SSE endpoint instead, so no upgrade wiring is needed. Remove `ws: false` if the instance should serve a WebSocket after all."
|
|
108
113
|
},
|
|
109
114
|
DF0058: {
|
|
110
|
-
why: (p) => `"${p.id}" declares \`capabilities.dev: false
|
|
115
|
+
why: (p) => `"${p.id}" declares \`capabilities.dev: false\`, so it does not support a live dev server (its value is a static export only).`,
|
|
111
116
|
fix: "Pass `{ force: true }` to `createDevServer()` to run it anyway, or drop `capabilities.dev: false` on the definition."
|
|
112
117
|
},
|
|
113
118
|
DF0059: {
|
|
@@ -120,10 +125,10 @@ const diagnostics = defineDiagnostics({
|
|
|
120
125
|
},
|
|
121
126
|
DF0061: {
|
|
122
127
|
why: (p) => `The locally installed "${p.package}@${p.installed}" is a different major version than the required "${p.required}".`,
|
|
123
|
-
fix: "Align the installed assets package with the version its node package declares
|
|
128
|
+
fix: "Align the installed assets package with the version its node package declares; they are published in lockstep."
|
|
124
129
|
},
|
|
125
130
|
DF0062: {
|
|
126
|
-
why: (p) => `The locally installed "${p.package}@${p.installed}" differs from the required "${p.required}"
|
|
131
|
+
why: (p) => `The locally installed "${p.package}@${p.installed}" differs from the required "${p.required}"; serving the installed one.`,
|
|
127
132
|
fix: "Install the exact declared version to serve byte-identical assets."
|
|
128
133
|
},
|
|
129
134
|
DF0063: {
|
|
@@ -136,10 +141,10 @@ const diagnostics = defineDiagnostics({
|
|
|
136
141
|
},
|
|
137
142
|
DF0065: {
|
|
138
143
|
why: (p) => `Invalid remote-assets ${p.field} "${p.value}".`,
|
|
139
|
-
fix: "A remote-assets `package` must be a valid npm package name and `version` an exact semver version (e.g. `1.2.3`)
|
|
144
|
+
fix: "A remote-assets `package` must be a valid npm package name and `version` an exact semver version (e.g. `1.2.3`); they are interpolated into CDN URLs and the cache path."
|
|
140
145
|
},
|
|
141
146
|
DF0066: {
|
|
142
|
-
why: (p) => `Service "${p.package}" is already installed
|
|
147
|
+
why: (p) => `Service "${p.package}" is already installed; keeping the first installation and ignoring this one's options.`,
|
|
143
148
|
fix: "Option sets only merge before `ctx.services.ready()` fires. Install the service (or declare it in `DevframeDefinition.services`) before the barrier so its options join the merge."
|
|
144
149
|
},
|
|
145
150
|
DF0067: {
|
|
@@ -151,16 +156,24 @@ const diagnostics = defineDiagnostics({
|
|
|
151
156
|
fix: "Align the installed service package with the range its declarer requires, or drop `required: true` to downgrade the mismatch to a warning."
|
|
152
157
|
},
|
|
153
158
|
DF0069: {
|
|
154
|
-
why: (p) => `The installed service "${p.package}@${p.installed}" does not satisfy the declared range "${p.required}"
|
|
159
|
+
why: (p) => `The installed service "${p.package}@${p.installed}" does not satisfy the declared range "${p.required}"; installing it anyway.`,
|
|
155
160
|
fix: "The advertised meta carries the real version, so clients can gate on it. Align the installed service package with the declared range to silence this warning."
|
|
156
161
|
},
|
|
157
162
|
DF0070: {
|
|
158
163
|
why: (p) => `Invalid service "${p.package}": ${p.reason}`,
|
|
159
|
-
fix: "A service package's default export must be a factory returning a `DevframeServiceDefinition
|
|
164
|
+
fix: "A service package's default export must be a factory returning a `DevframeServiceDefinition`, an object with `package`, `version`, `scope`, and a `setup` function."
|
|
160
165
|
},
|
|
161
166
|
DF0072: {
|
|
162
|
-
why: (p) => `\`rpc.snapshot\` names "${p.method}", but no RPC function is registered under that id
|
|
167
|
+
why: (p) => `\`rpc.snapshot\` names "${p.method}", but no RPC function is registered under that id, so nothing to bake into the static build.`,
|
|
163
168
|
fix: "Check the method id, and ensure the service/plugin that registers it is installed (e.g. declared in `services`) before the build collects the dump."
|
|
169
|
+
},
|
|
170
|
+
DF0075: {
|
|
171
|
+
why: (p) => `On ${p.runtime} the shared server's WebSocket upgrade needs crossws's Node adapter, which refuses to run off Node, and SSE is disabled, so this instance advertises no RPC transport at all.`,
|
|
172
|
+
fix: "Keep the SSE endpoint enabled (drop `sse: false`) so clients connect over it on Bun/Deno, or move the socket to a side-car (`ws: { sidecar: true }`) which binds the native WebSocket adapter on its own port."
|
|
173
|
+
},
|
|
174
|
+
DF0076: {
|
|
175
|
+
why: (p) => `\`attach\` / \`handleUpgrade\` drive a raw \`node:http\` upgrade into crossws's Node adapter, which refuses to run on ${p.runtime}.`,
|
|
176
|
+
fix: "On Bun/Deno, serve the advertised `__ws` route from `Bun.serve` / `Deno.serve` with `attachBunWsTransport` / `attachDenoWsTransport` (see the hub-deno example), or connect over the SSE endpoint instead."
|
|
164
177
|
}
|
|
165
178
|
}
|
|
166
179
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
//#region src/events.ts
|
|
2
2
|
/**
|
|
3
|
-
* Centralized registry of the core devframe event names
|
|
3
|
+
* Centralized registry of the core devframe event names: the node-side host
|
|
4
4
|
* bus events, the client RPC connection events, and the server→client
|
|
5
|
-
* broadcast notifications
|
|
5
|
+
* broadcast notifications, so these names live in one place instead of
|
|
6
6
|
* 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)**
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { t as DEVFRAME_EVENTS } from "./events-
|
|
2
|
-
import { t as diagnostics } from "./diagnostics-
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { isAbsolute, join } from "pathe";
|
|
1
|
+
import { t as DEVFRAME_EVENTS } from "./events-BV4Dj59a.mjs";
|
|
2
|
+
import { t as diagnostics } from "./diagnostics-DZA6KQ_Z.mjs";
|
|
6
3
|
//#region src/utils/events.ts
|
|
7
4
|
/**
|
|
8
5
|
* Create event emitter.
|
|
@@ -174,6 +171,12 @@ var DevframeAgentHost = class {
|
|
|
174
171
|
resources
|
|
175
172
|
};
|
|
176
173
|
}
|
|
174
|
+
hasSurface() {
|
|
175
|
+
if (this.tools.size > 0 || this.resources.size > 0) return true;
|
|
176
|
+
for (const [, def] of this.context.rpc.definitions) if (def.agent) return true;
|
|
177
|
+
for (const provider of this.providers) if (provider().length > 0) return true;
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
177
180
|
getTool(id) {
|
|
178
181
|
const plain = this.tools.get(id);
|
|
179
182
|
if (plain) return plain.tool;
|
|
@@ -219,6 +222,12 @@ var DevframeAgentHost = class {
|
|
|
219
222
|
description: input.description,
|
|
220
223
|
safety: input.safety ?? "action",
|
|
221
224
|
tags: input.tags,
|
|
225
|
+
/**
|
|
226
|
+
* Standard Schema `args` are carried raw (mirroring how an RPC-backed
|
|
227
|
+
* tool defers to `ctx.rpc.definitions`); consumers (the MCP adapter)
|
|
228
|
+
* convert to JSON Schema on demand. An explicit `inputSchema` override
|
|
229
|
+
* wins when given.
|
|
230
|
+
*/
|
|
222
231
|
args: input.args,
|
|
223
232
|
inputSchema: input.inputSchema,
|
|
224
233
|
outputSchema: input.outputSchema,
|
|
@@ -265,149 +274,4 @@ function inferSafety(type) {
|
|
|
265
274
|
return "action";
|
|
266
275
|
}
|
|
267
276
|
//#endregion
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Turn a `resolveFrom` value (a file path, a file URL like `import.meta.url`,
|
|
271
|
-
* or a directory) into something `createRequire` accepts — a directory gets a
|
|
272
|
-
* synthetic filename appended so resolution starts inside it.
|
|
273
|
-
*/
|
|
274
|
-
function toRequireBase(resolveFrom) {
|
|
275
|
-
if (resolveFrom.startsWith("file://")) return resolveFrom;
|
|
276
|
-
if ((resolveFrom.split(/[/\\]/).pop() ?? "").includes(".")) return resolveFrom;
|
|
277
|
-
return join(resolveFrom, "_devframe_resolve.js");
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Normalize an `install()` `resolveFrom` into a resolution base. Paths and
|
|
281
|
-
* file URLs pass through (the common case: the declaring plugin's
|
|
282
|
-
* `importMetaUrl`, so a service it declares resolves against the plugin's own
|
|
283
|
-
* dependencies); a bare npm package name resolves to that package's location
|
|
284
|
-
* from `cwd`. An unresolvable package name reads as no base (the caller's
|
|
285
|
-
* workspace fallbacks apply).
|
|
286
|
-
*/
|
|
287
|
-
function expandResolveFrom(resolveFrom, cwd) {
|
|
288
|
-
if (resolveFrom.startsWith("file://") || resolveFrom.startsWith(".") || isAbsolute(resolveFrom)) return resolveFrom;
|
|
289
|
-
const require = createRequire(join(cwd, "_devframe_resolve.js"));
|
|
290
|
-
try {
|
|
291
|
-
return require.resolve(`${resolveFrom}/package.json`);
|
|
292
|
-
} catch {}
|
|
293
|
-
try {
|
|
294
|
-
return require.resolve(resolveFrom);
|
|
295
|
-
} catch {}
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Import a service package's module, trying each `resolveFrom` candidate in
|
|
299
|
-
* order (so a plugin-declared service resolves against the plugin's own
|
|
300
|
-
* dependency tree first, then the workspace fallback). Throws the last
|
|
301
|
-
* resolution error when no candidate succeeds.
|
|
302
|
-
*/
|
|
303
|
-
async function importServicePackage(pkg, resolveFroms) {
|
|
304
|
-
const candidates = [...new Set(resolveFroms.filter((x) => typeof x === "string" && x.length > 0))];
|
|
305
|
-
let lastError = /* @__PURE__ */ new Error(`no resolution base available for "${pkg}"`);
|
|
306
|
-
for (const from of candidates) {
|
|
307
|
-
let resolved;
|
|
308
|
-
try {
|
|
309
|
-
resolved = createRequire(toRequireBase(from)).resolve(pkg);
|
|
310
|
-
} catch (error) {
|
|
311
|
-
lastError = error;
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
return await import(
|
|
315
|
-
/* webpackIgnore: true */
|
|
316
|
-
/* @vite-ignore */
|
|
317
|
-
/* turbopackIgnore: true */
|
|
318
|
-
pathToFileURL(resolved).href
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
throw lastError;
|
|
322
|
-
}
|
|
323
|
-
function parseVersion(input) {
|
|
324
|
-
const [core, ...prerelease] = input.trim().replace(/^v/, "").split("-");
|
|
325
|
-
if (!core) return void 0;
|
|
326
|
-
const parts = core.split(".").map((part) => Number.parseInt(part, 10));
|
|
327
|
-
if (parts.length === 0 || parts.some((part) => Number.isNaN(part) || part < 0)) return void 0;
|
|
328
|
-
while (parts.length < 3) parts.push(0);
|
|
329
|
-
return {
|
|
330
|
-
parts,
|
|
331
|
-
...prerelease.length ? { prerelease: prerelease.join("-") } : {}
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
function compareVersions(a, b) {
|
|
335
|
-
for (let i = 0; i < 3; i++) {
|
|
336
|
-
const diff = (a.parts[i] ?? 0) - (b.parts[i] ?? 0);
|
|
337
|
-
if (diff !== 0) return diff;
|
|
338
|
-
}
|
|
339
|
-
if (a.prerelease && !b.prerelease) return -1;
|
|
340
|
-
if (!a.prerelease && b.prerelease) return 1;
|
|
341
|
-
if (a.prerelease && b.prerelease) return a.prerelease < b.prerelease ? -1 : a.prerelease > b.prerelease ? 1 : 0;
|
|
342
|
-
return 0;
|
|
343
|
-
}
|
|
344
|
-
function satisfiesComparator(version, comparator) {
|
|
345
|
-
const raw = comparator.trim();
|
|
346
|
-
if (!raw || raw === "*" || raw === "x") return true;
|
|
347
|
-
const operatorMatch = raw.match(/^([\^~]|>=|<=|[><=])?(.+)$/);
|
|
348
|
-
if (!operatorMatch) return false;
|
|
349
|
-
const operator = operatorMatch[1];
|
|
350
|
-
const rest = operatorMatch[2].trim();
|
|
351
|
-
const segments = rest.replace(/\.[x*]/gi, "").split(".").filter(Boolean);
|
|
352
|
-
const base = parseVersion(rest.replace(/[x*]/gi, "0"));
|
|
353
|
-
if (!base) return false;
|
|
354
|
-
switch (operator) {
|
|
355
|
-
case ">": return compareVersions(version, base) > 0;
|
|
356
|
-
case ">=": return compareVersions(version, base) >= 0;
|
|
357
|
-
case "<": return compareVersions(version, base) < 0;
|
|
358
|
-
case "<=": return compareVersions(version, base) <= 0;
|
|
359
|
-
case "^": {
|
|
360
|
-
if (compareVersions(version, base) < 0) return false;
|
|
361
|
-
const fixedIndex = base.parts.findIndex((part) => part !== 0);
|
|
362
|
-
const lockUpTo = fixedIndex === -1 ? base.parts.length - 1 : fixedIndex;
|
|
363
|
-
for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
case "~": {
|
|
367
|
-
if (compareVersions(version, base) < 0) return false;
|
|
368
|
-
const lockUpTo = segments.length >= 2 ? 1 : 0;
|
|
369
|
-
for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
|
|
370
|
-
return true;
|
|
371
|
-
}
|
|
372
|
-
default:
|
|
373
|
-
for (let i = 0; i < Math.max(segments.length, 3); i++) if (i < segments.length && (version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
|
|
374
|
-
return segments.length >= 3 ? compareVersions(version, base) === 0 : true;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Pragmatic semver range check for service version declarations — supports
|
|
379
|
-
* the common forms (`1.2.3`, `^1.2.3`, `~1.2`, `>=1 <3`, `1.x`, `*`, and
|
|
380
|
-
* `||`-joined alternatives) without pulling in a semver dependency. An
|
|
381
|
-
* unparseable version or range reads as **not satisfied**.
|
|
382
|
-
*/
|
|
383
|
-
function satisfiesVersionRange(version, range) {
|
|
384
|
-
const parsed = parseVersion(version);
|
|
385
|
-
if (!parsed) return false;
|
|
386
|
-
const alternatives = range.split("||").map((alt) => alt.trim()).filter(Boolean);
|
|
387
|
-
if (alternatives.length === 0) return true;
|
|
388
|
-
return alternatives.some((alternative) => alternative.split(/\s+/).every((comparator) => satisfiesComparator(parsed, comparator)));
|
|
389
|
-
}
|
|
390
|
-
function isPlainObject(value) {
|
|
391
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
392
|
-
}
|
|
393
|
-
/** Deep-merge two values with the service option-set rules (see below). */
|
|
394
|
-
function deepMergeTwo(a, b) {
|
|
395
|
-
if (Array.isArray(a) && Array.isArray(b)) return [.../* @__PURE__ */ new Set([...a, ...b])];
|
|
396
|
-
if (isPlainObject(a) && isPlainObject(b)) {
|
|
397
|
-
const out = { ...a };
|
|
398
|
-
for (const key of Object.keys(b)) out[key] = key in a ? deepMergeTwo(a[key], b[key]) : b[key];
|
|
399
|
-
return out;
|
|
400
|
-
}
|
|
401
|
-
return b;
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Default option-set merge when a service declares no `mergeOptions`:
|
|
405
|
-
* deep-merge in declaration order — objects recurse, arrays union-dedupe,
|
|
406
|
-
* scalars take the later value. Covers the built-in services (`roots` /
|
|
407
|
-
* `langs` union, `themes` per-key last-wins) without a custom hook.
|
|
408
|
-
*/
|
|
409
|
-
function deepMergeOptionSets(sets) {
|
|
410
|
-
return sets.reduce((merged, set) => deepMergeTwo(merged, set));
|
|
411
|
-
}
|
|
412
|
-
//#endregion
|
|
413
|
-
export { DevframeAgentHost as a, satisfiesVersionRange as i, expandResolveFrom as n, coerceAgentPositionalArgs as o, importServicePackage as r, createEventEmitter as s, deepMergeOptionSets as t };
|
|
277
|
+
export { coerceAgentPositionalArgs as n, createEventEmitter as r, DevframeAgentHost as t };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import { join } from "pathe";
|
|
2
|
+
import process from "node:process";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
//#region src/node/host-h3.ts
|
|
5
5
|
/**
|
|
6
|
-
* h3-backed {@link DevframeHost}
|
|
6
|
+
* h3-backed {@link DevframeHost}, used by the standalone CLI adapter.
|
|
7
7
|
*/
|
|
8
8
|
function createH3DevframeHost(options) {
|
|
9
9
|
const workspaceRoot = options.workspaceRoot ?? process.cwd();
|