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.
Files changed (121) hide show
  1. package/dist/_shared-CCzf9Hub.d.mts +83 -0
  2. package/dist/_shared-U8D-xzrp.mjs +115 -0
  3. package/dist/adapters/build.d.mts +6 -6
  4. package/dist/adapters/build.mjs +33 -36
  5. package/dist/adapters/cac.d.mts +12 -1
  6. package/dist/adapters/cac.mjs +8 -8
  7. package/dist/adapters/dev.d.mts +17 -17
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +31 -28
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +28 -14
  13. package/dist/adapters/mcp.mjs +76 -40
  14. package/dist/{agent-tool-name-EgfoFO8C.mjs → agent-tool-name-DGzeuWcO.mjs} +2 -2
  15. package/dist/cli/main.d.mts +1 -1
  16. package/dist/cli/main.mjs +41 -17
  17. package/dist/client/index.d.mts +36 -36
  18. package/dist/client/index.mjs +90 -52
  19. package/dist/{client-assets-C7SXgTWY.mjs → client-assets-DNdhagGm.mjs} +2 -2
  20. package/dist/constants.d.mts +8 -7
  21. package/dist/constants.mjs +7 -6
  22. package/dist/{context-omlma_1n.mjs → context-B29Oh0oA.mjs} +166 -93
  23. package/dist/{context-DftYyXaR.d.mts → context-BJiKJqyW.d.mts} +5 -5
  24. package/dist/{context-CLF4-8Pp.d.mts → context-CHW4BZOj.d.mts} +203 -134
  25. package/dist/{context-KUqAfawQ.mjs → context-jXO5vzjH.mjs} +3 -3
  26. package/dist/{crypto-token-XCqTSMg9.mjs → crypto-token-TVbGGFuj.mjs} +1 -1
  27. package/dist/{dev-CzkPCJwi.mjs → dev-CRYHx7iG.mjs} +73 -40
  28. package/dist/{diagnostics-BXWW3VK-.mjs → diagnostics-DZA6KQ_Z.mjs} +32 -19
  29. package/dist/{events-4daw0uK9.mjs → events-BV4Dj59a.mjs} +2 -2
  30. package/dist/{services-install-CqqQVls5.mjs → host-agent-CKqSTEX9.mjs} +15 -151
  31. package/dist/{host-h3-fRbF9yor.mjs → host-h3-BROI6SQ4.mjs} +2 -2
  32. package/dist/in-page-channel/index.d.mts +14 -14
  33. package/dist/in-page-channel/index.mjs +63 -54
  34. package/dist/{index-D3k0BIhe.d.mts → index-CNC390b_.d.mts} +4 -4
  35. package/dist/{index-BzyopqxV.d.mts → index-pIVt92jP.d.mts} +9 -9
  36. package/dist/index.d.mts +8 -8
  37. package/dist/index.mjs +2 -2
  38. package/dist/{instance-registry-BjlMQbFB.mjs → instance-registry-DCq762YJ.mjs} +9 -9
  39. package/dist/{instance-shell-Tcp5hGoq.d.mts → instance-shell-DZipaP9k.d.mts} +18 -18
  40. package/dist/{instance-shell-CpVW4zG0.mjs → instance-shell-DvYCwrDy.mjs} +223 -67
  41. package/dist/internal/index.d.mts +45 -26
  42. package/dist/internal/index.mjs +10 -10
  43. package/dist/node/auth.d.mts +2 -2
  44. package/dist/node/auth.mjs +2 -2
  45. package/dist/node/hub-internals.d.mts +2 -2
  46. package/dist/node/hub-internals.mjs +2 -2
  47. package/dist/node/index.d.mts +5 -5
  48. package/dist/node/index.mjs +2 -2
  49. package/dist/{nostics-SPGDgHEP.d.mts → nostics-0sKe5HIj.d.mts} +2 -2
  50. package/dist/{nostics-CzECRXpE.mjs → nostics-D0PvLZsn.mjs} +1 -1
  51. package/dist/origin-DGDH8BI9.d.mts +45 -0
  52. package/dist/origin-DZit9g29.mjs +80 -0
  53. package/dist/recipes/common-rpc-functions.d.mts +5 -5
  54. package/dist/recipes/common-rpc-functions.mjs +3 -3
  55. package/dist/recipes/interactive-auth.d.mts +6 -6
  56. package/dist/recipes/interactive-auth.mjs +6 -6
  57. package/dist/{remote-assets-DezQmPQU.mjs → remote-assets-DMFzLSoH.mjs} +27 -7
  58. package/dist/{remote-assets-B13_vRkJ.d.mts → remote-assets-eBvSKZMS.d.mts} +7 -7
  59. package/dist/{revoke-COLW0cOi.mjs → revoke-cqztiCX4.mjs} +1 -1
  60. package/dist/rpc/dump.d.mts +19 -12
  61. package/dist/rpc/dump.mjs +2 -2
  62. package/dist/rpc/index.d.mts +2 -2
  63. package/dist/rpc/index.mjs +2 -3
  64. package/dist/rpc/transports/sse-client.d.mts +1 -1
  65. package/dist/rpc/transports/sse-client.mjs +30 -14
  66. package/dist/rpc/transports/sse-server.d.mts +4 -4
  67. package/dist/rpc/transports/sse-server.mjs +7 -7
  68. package/dist/rpc/transports/ws-bun.d.mts +5 -5
  69. package/dist/rpc/transports/ws-bun.mjs +8 -2
  70. package/dist/rpc/transports/ws-client.d.mts +1 -1
  71. package/dist/rpc/transports/ws-client.mjs +2 -2
  72. package/dist/rpc/transports/ws-deno.d.mts +4 -4
  73. package/dist/rpc/transports/ws-deno.mjs +8 -2
  74. package/dist/rpc/transports/ws-server.d.mts +2 -1
  75. package/dist/rpc/transports/ws-server.mjs +232 -1
  76. package/dist/{rpc-core-DlHd276T.mjs → rpc-core-CvkU23bQ.mjs} +14 -2
  77. package/dist/{rpc-core-B3W3z9ff.d.mts → rpc-core-DsyDViQy.d.mts} +4 -4
  78. package/dist/serialization-DFg5sUt6.mjs +116 -0
  79. package/dist/services-install-Ben4yh52.mjs +154 -0
  80. package/dist/session-DPfj6zQG.mjs +16 -0
  81. package/dist/{shared-state--sduc6Xi.mjs → shared-state-CaxB1q0h.mjs} +2 -2
  82. package/dist/{simple-schema-DQPZrAaZ.mjs → simple-schema-Bi40l-P6.mjs} +2 -2
  83. package/dist/{sse-client-DwX021ST.d.mts → sse-client-BQ4jS3eO.d.mts} +4 -4
  84. package/dist/{state-CK9LjrnT.mjs → state-Mwt6--el.mjs} +17 -9
  85. package/dist/{storage-BXNVhKyR.mjs → storage-B70AXK64.mjs} +2 -2
  86. package/dist/types/index.d.mts +6 -6
  87. package/dist/{types-CMdPYhOx.d.mts → types-Dq4i156u.d.mts} +17 -17
  88. package/dist/utils/agent-tool-name.d.mts +2 -2
  89. package/dist/utils/agent-tool-name.mjs +2 -2
  90. package/dist/utils/colors.d.mts +1 -1
  91. package/dist/utils/crypto-token.d.mts +1 -1
  92. package/dist/utils/crypto-token.mjs +1 -1
  93. package/dist/utils/events.d.mts +1 -1
  94. package/dist/utils/nostics.d.mts +1 -1
  95. package/dist/utils/nostics.mjs +1 -1
  96. package/dist/utils/open.mjs +1 -1
  97. package/dist/utils/origin.d.mts +2 -0
  98. package/dist/utils/origin.mjs +80 -0
  99. package/dist/utils/remote-assets.d.mts +2 -2
  100. package/dist/utils/remote-assets.mjs +1 -1
  101. package/dist/utils/serve-static.d.mts +4 -4
  102. package/dist/utils/serve-static.mjs +56 -19
  103. package/dist/utils/shared-state.d.mts +1 -1
  104. package/dist/utils/shared-state.mjs +1 -1
  105. package/dist/utils/simple-schema.d.mts +5 -5
  106. package/dist/utils/simple-schema.mjs +2 -2
  107. package/dist/utils/streaming-channel.d.mts +1 -1
  108. package/dist/utils/streaming-channel.mjs +2 -2
  109. package/dist/utils/when.d.mts +5 -5
  110. package/dist/{validation-YrZH6atx.mjs → validation-CfV1s0SF.mjs} +2 -2
  111. package/dist/{wire-codec-0K-o5MYW.mjs → wire-codec-BHGt0sSs.mjs} +3 -3
  112. package/dist/{static-DV_qUSRS.mjs → write-BuLCrn9h.mjs} +153 -137
  113. package/dist/{ws-client-7_lZi-B9.d.mts → ws-client-Xpxt0oPA.d.mts} +3 -3
  114. package/dist/{ws-server-VyHlanzy.d.mts → ws-server-D2nv68Hg.d.mts} +13 -37
  115. package/package.json +3 -6
  116. package/skills/devframe/SKILL.md +5 -3
  117. package/dist/_shared-BM3PdYli.mjs +0 -68
  118. package/dist/_shared-mtXpcJIv.d.mts +0 -41
  119. package/dist/diagnostics-CD8nlgll.mjs +0 -39
  120. package/dist/serialization-BGzEwAdr.mjs +0 -77
  121. package/dist/ws-server-Y_AumUQH.mjs +0 -294
@@ -1,6 +1,6 @@
1
- import { n as randomToken } from "./crypto-token-XCqTSMg9.mjs";
2
- import { t as createStorage } from "./storage-BXNVhKyR.mjs";
3
- import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-COLW0cOi.mjs";
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 — e.g. the persistent client auth token or an ephemeral
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-C7SXgTWY.mjs";
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-BXWW3VK-.mjs";
4
- import { t as createHostContext } from "./context-omlma_1n.mjs";
5
- import { t as resolveStaticAssetsSource } from "./remote-assets-DezQmPQU.mjs";
6
- import { i as resolveMcpConnectionMeta, n as resolveBasePath, r as resolveDevServerPort, t as normalizeBasePath } from "./_shared-BM3PdYli.mjs";
7
- import { t as createH3DevframeHost } from "./host-h3-fRbF9yor.mjs";
8
- import { a as importRuntimeModule, i as normalizeHttpServerUrl, n as resolveInstanceRegister, t as createInstanceShell } from "./instance-shell-CpVW4zG0.mjs";
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 { resolve } from "pathe";
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 — `ws.port` (pinned
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} — while `ws.url`, when set, overrides the
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 mcpOption = options.mcp ?? def.cli?.mcp;
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
- ...mcpMeta ? { mcp: mcpMeta } : {},
114
- ...mcpDispose ? { dispose: mcpDispose } : {}
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) — see
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-CzECRXpE.mjs";
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 — \`description\` must be a non-empty string.`,
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 — no producer has called \`channel.start({ id: "${p.id}" })\`.`,
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 — guard writes with the `stream.signal.aborted` flag."
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 — see `isAnonymousRpcMethod`."
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\` — its static export is not meaningful (writes are excluded and any live-served data won't be there).`,
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/server: ${p.reason}`,
67
- fix: "Install it next to devframe (e.g. `npm install @modelcontextprotocol/server`) and run `devframe connect` again."
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 — the instance may have stopped or changed port."
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()` — the WebSocket binding it describes is only known once initialization completes."
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 — no upgrade wiring is needed. Remove `ws: false` if the instance should serve a WebSocket after all."
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\` — it does not support a live dev server (its value is a static export only).`,
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 — they are published in lockstep."
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}" — serving the installed one.`,
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`) — they are interpolated into CDN URLs and the cache path."
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 — keeping the first installation and ignoring this one's options.`,
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}" — installing it anyway.`,
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` — an object with `package`, `version`, `scope`, and a `setup` function."
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 — nothing to bake into the static build.`,
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 — the node-side host
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 — so these names live in one place instead of
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-4daw0uK9.mjs";
2
- import { t as diagnostics } from "./diagnostics-BXWW3VK-.mjs";
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
- //#region src/node/services-install.ts
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} — used by the standalone CLI adapter.
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();