devframe 0.9.0-beta.9 → 0.9.1

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 (72) hide show
  1. package/dist/_shared-BM3PdYli.mjs +68 -0
  2. package/dist/{_shared-BPmX9fqK.d.mts → _shared-DqbKCAew.d.mts} +1 -1
  3. package/dist/adapters/build.d.mts +1 -1
  4. package/dist/adapters/build.mjs +8 -6
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +4 -3
  7. package/dist/adapters/dev.d.mts +3 -3
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/embedded.mjs +1 -0
  11. package/dist/adapters/initiate.d.mts +2 -2
  12. package/dist/adapters/initiate.mjs +1 -1
  13. package/dist/adapters/mcp.d.mts +1 -1
  14. package/dist/adapters/mcp.mjs +1 -1
  15. package/dist/cli/main.mjs +2 -2
  16. package/dist/client/index.d.mts +48 -2
  17. package/dist/client/index.mjs +131 -22
  18. package/dist/constants.d.mts +74 -2
  19. package/dist/constants.mjs +9 -2
  20. package/dist/{context-CEe4PBz1.mjs → context-Bdkakk8S.mjs} +319 -21
  21. package/dist/{context-xdynGSLP.mjs → context-CXhjygJU.mjs} +2 -2
  22. package/dist/{context-B3YUu_95.d.mts → context-dqyXS6o3.d.mts} +1 -1
  23. package/dist/{dev-Chvlqs7I.mjs → dev-C8VHtuPP.mjs} +12 -9
  24. package/dist/{devframe-DJMg0WLe.d.mts → devframe-BlLEZR-x.d.mts} +741 -588
  25. package/dist/{diagnostics-DOQPnQmX.mjs → diagnostics-Di8ytitn.mjs} +24 -0
  26. package/dist/{_shared-CKZPvSrN.mjs → dist-CZXfGEkd.mjs} +2 -68
  27. package/dist/events-DKfSmoTj.mjs +65 -0
  28. package/dist/{host-agent-xLCSe65D.mjs → host-agent-BDdcOXhG.mjs} +14 -13
  29. package/dist/{http-8Kpkap4_.mjs → http-BbHCU5Zs.mjs} +9 -6
  30. package/dist/{index-CB-fSUyn.d.mts → index-BKFT9-jA.d.mts} +2 -2
  31. package/dist/index.d.mts +2 -2
  32. package/dist/{instance-registry-WvQkt42E.mjs → instance-registry-CHunhMi5.mjs} +4 -4
  33. package/dist/{instance-shell-CljiKq1c.d.mts → instance-shell-0iw-cQ5V.d.mts} +2 -2
  34. package/dist/{instance-shell-XvN5USg4.mjs → instance-shell-DbDoozNM.mjs} +5 -5
  35. package/dist/internal/index.d.mts +46 -7
  36. package/dist/internal/index.mjs +8 -8
  37. package/dist/node/auth.d.mts +2 -2
  38. package/dist/node/auth.mjs +2 -2
  39. package/dist/node/hub-internals.d.mts +2 -2
  40. package/dist/node/hub-internals.mjs +2 -2
  41. package/dist/node/index.d.mts +1 -1
  42. package/dist/node/index.mjs +2 -2
  43. package/dist/recipes/common-rpc-functions.d.mts +1 -1
  44. package/dist/recipes/interactive-auth.d.mts +2 -2
  45. package/dist/recipes/interactive-auth.mjs +2 -2
  46. package/dist/{remote-assets-nTTxY2MU.mjs → remote-assets-DRfY2RjX.mjs} +6 -5
  47. package/dist/{revoke-DZbJ7Cl0.mjs → revoke-3Q6aDKWk.mjs} +2 -1
  48. package/dist/{rolldown-runtime-JspESFgx.mjs → rolldown-runtime-B4iAMlE-.mjs} +1 -1
  49. package/dist/rpc/transports/sse-client.mjs +1 -1
  50. package/dist/rpc/transports/sse-server.mjs +2 -2
  51. package/dist/rpc/transports/ws-bun.d.mts +1 -1
  52. package/dist/rpc/transports/ws-bun.mjs +1 -1
  53. package/dist/rpc/transports/ws-client.mjs +1 -1
  54. package/dist/rpc/transports/ws-server.mjs +1 -1
  55. package/dist/{rpc-core-Cc4lucI0.d.mts → rpc-core-CR8o34O9.d.mts} +2 -2
  56. package/dist/{rpc-core-DMTe-hLo.mjs → rpc-core-Dru9uoM0.mjs} +11 -4
  57. package/dist/{state-Cc1IeSmQ.mjs → state-CK9LjrnT.mjs} +1 -1
  58. package/dist/{storage-CYye7P7L.mjs → storage-D4a99Ung.mjs} +3 -3
  59. package/dist/types/index.d.mts +2 -2
  60. package/dist/utils/events.d.mts +1 -1
  61. package/dist/utils/get-port.d.mts +17 -0
  62. package/dist/utils/get-port.mjs +2 -0
  63. package/dist/utils/launch-editor.mjs +12 -11
  64. package/dist/utils/open.mjs +1 -1
  65. package/dist/utils/remote-assets.mjs +1 -1
  66. package/dist/utils/serve-static.mjs +6 -5
  67. package/dist/utils/shared-state.d.mts +1 -1
  68. package/dist/utils/streaming-channel.d.mts +1 -1
  69. package/dist/{wire-codec-GsoRxIsD.mjs → wire-codec-0K-o5MYW.mjs} +0 -2
  70. package/dist/{ws-server-BiUne4K7.mjs → ws-server-BdSLrhxE.mjs} +1 -1
  71. package/package.json +2 -1
  72. package/dist/{host-h3-Crkzm42q.mjs → host-h3-fRbF9yor.mjs} +1 -1
@@ -0,0 +1,68 @@
1
+ import "./constants.mjs";
2
+ import { n as getPort } from "./dist-CZXfGEkd.mjs";
3
+ import { cleanDoubleSlashes, withLeadingSlash, withTrailingSlash, withoutLeadingSlash } from "ufo";
4
+ //#region src/adapters/_shared.ts
5
+ const DEFAULT_PORT = 9999;
6
+ /**
7
+ * Resolve the mount base path for a devframe's SPA. Hosted adapters
8
+ * (`vite`, `embedded`) default to `/__<id>/` so they don't collide
9
+ * with the host app; standalone adapters (`cli`, `build`)
10
+ * default to `/` because they own the origin.
11
+ *
12
+ * The devframe author can override with `basePath` on the definition.
13
+ */
14
+ function resolveBasePath(def, kind) {
15
+ if (def.basePath) return normalizeBasePath(def.basePath);
16
+ return kind === "standalone" ? "/" : `/__${def.id}/`;
17
+ }
18
+ function normalizeBasePath(base) {
19
+ return cleanDoubleSlashes(withTrailingSlash(withLeadingSlash(base)));
20
+ }
21
+ /**
22
+ * Resolve the listening port for `createDevServer` (and `createHandler`'s
23
+ * side-car tiers), honoring the definition's `cli.port` / `cli.portRange` /
24
+ * `cli.random` settings. Exposed separately so authors who run their own
25
+ * argv parsing can resolve a port up-front (to print it, log it, etc.)
26
+ * before starting the server.
27
+ */
28
+ async function resolveDevServerPort(def, options = {}) {
29
+ const host = options.host ?? def.cli?.host ?? "localhost";
30
+ const portOptions = {
31
+ port: options.defaultPort ?? def.cli?.port ?? DEFAULT_PORT,
32
+ host
33
+ };
34
+ if (def.cli?.portRange) portOptions.portRange = def.cli.portRange;
35
+ if (def.cli?.random) portOptions.random = def.cli.random;
36
+ return getPort(portOptions);
37
+ }
38
+ /**
39
+ * Normalize the `cli.mcp` / `mcp` option (`boolean | McpRouteOptions`) into
40
+ * concrete options, or `undefined` when the MCP route is disabled.
41
+ */
42
+ function resolveMcpConfig(mcp) {
43
+ if (!mcp) return void 0;
44
+ return mcp === true ? {} : mcp;
45
+ }
46
+ /**
47
+ * Resolve the `mcp` entry a `__connection.json` should advertise for a dev
48
+ * server started with the given `mcp` option (falling back to `def.cli?.mcp`,
49
+ * exactly like `createDevServer`), or `undefined` when the route is
50
+ * disabled.
51
+ *
52
+ * Hosted bridges that hand-roll their connection meta pass the side-car
53
+ * `port`: the advertised path becomes absolute (the side-car mounts at `/`)
54
+ * and the client dials `<page-host>:<port><path>`. Without `port` the path
55
+ * stays relative, resolved against `__connection.json`'s own location (the
56
+ * same-server default).
57
+ */
58
+ function resolveMcpConnectionMeta(def, mcp, port) {
59
+ const config = resolveMcpConfig(mcp ?? def.cli?.mcp);
60
+ if (!config) return void 0;
61
+ const route = withoutLeadingSlash(config.path ?? "__mcp");
62
+ return port != null ? {
63
+ path: withLeadingSlash(route),
64
+ port
65
+ } : { path: route };
66
+ }
67
+ //#endregion
68
+ export { resolveMcpConnectionMeta as i, resolveBasePath as n, resolveDevServerPort as r, normalizeBasePath as t };
@@ -1,4 +1,4 @@
1
- import { d as ConnectionMeta, l as McpRouteOptions, n as DevframeDefinition, r as DevframeDeploymentKind } from "./devframe-DJMg0WLe.mjs";
1
+ import { d as ConnectionMeta, l as McpRouteOptions, n as DevframeDefinition, r as DevframeDeploymentKind } from "./devframe-BlLEZR-x.mjs";
2
2
  //#region src/adapters/_shared.d.ts
3
3
  /**
4
4
  * Resolve the mount base path for a devframe's SPA. Hosted adapters
@@ -1,4 +1,4 @@
1
- import { n as DevframeDefinition } from "../devframe-DJMg0WLe.mjs";
1
+ import { n as DevframeDefinition } from "../devframe-BlLEZR-x.mjs";
2
2
  import { o as StaticAssetsSource } from "../remote-assets-Bg4gCUZ_.mjs";
3
3
  //#region src/adapters/build.d.ts
4
4
  interface CreateBuildOptions {
@@ -3,14 +3,14 @@ import { n as strictJsonStringify } from "../serialization-BGzEwAdr.mjs";
3
3
  import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME } from "../constants.mjs";
4
4
  import { t as collectStaticRpcDump } from "../static-BPZ1_OPA.mjs";
5
5
  import { n as structuredCloneStringify } from "../structured-clone-CbAV5rFI.mjs";
6
- import { t as diagnostics } from "../diagnostics-DOQPnQmX.mjs";
7
- import { t as createHostContext } from "../context-CEe4PBz1.mjs";
8
- import { t as resolveStaticAssetsSource } from "../remote-assets-nTTxY2MU.mjs";
9
- import { t as createH3DevframeHost } from "../host-h3-Crkzm42q.mjs";
6
+ import { t as diagnostics } from "../diagnostics-Di8ytitn.mjs";
7
+ import { t as createHostContext } from "../context-Bdkakk8S.mjs";
8
+ import { t as resolveStaticAssetsSource } from "../remote-assets-DRfY2RjX.mjs";
9
+ import { t as createH3DevframeHost } from "../host-h3-fRbF9yor.mjs";
10
+ import process from "node:process";
11
+ import { dirname, resolve } from "pathe";
10
12
  import { existsSync } from "node:fs";
11
13
  import fs$1 from "node:fs/promises";
12
- import { dirname, resolve } from "pathe";
13
- import process from "node:process";
14
14
  //#region src/adapters/build.ts
15
15
  /**
16
16
  * Produce a self-contained static deploy of a devframe:
@@ -47,7 +47,9 @@ async function createBuild(d, options = {}) {
47
47
  mode: "build",
48
48
  host
49
49
  });
50
+ for (const input of d.services ?? []) ctx.services.install(input, { resolveFrom: d.packageName });
50
51
  await d.setup(ctx);
52
+ await ctx.services.ready();
51
53
  await fs$1.mkdir(resolve(outDir, DEVFRAME_RPC_DUMP_DIRNAME), { recursive: true });
52
54
  const jsonSerializableMethods = [];
53
55
  for (const def of ctx.rpc.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
@@ -1,4 +1,4 @@
1
- import { At as defineCliFlags, Ot as CliFlagsSchema, jt as parseCliFlags, kt as InferCliFlags, n as DevframeDefinition } from "../devframe-DJMg0WLe.mjs";
1
+ import { Ft as CliFlagsSchema, It as InferCliFlags, Lt as defineCliFlags, Rt as parseCliFlags, n as DevframeDefinition } from "../devframe-BlLEZR-x.mjs";
2
2
  import { CAC } from "cac";
3
3
  import { H3 } from "h3";
4
4
  //#region src/adapters/cac.d.ts
@@ -1,7 +1,7 @@
1
1
  import { s as colors } from "../nostics-CzECRXpE.mjs";
2
- import { r as resolveDevServerPort } from "../_shared-CKZPvSrN.mjs";
2
+ import { r as resolveDevServerPort } from "../_shared-BM3PdYli.mjs";
3
3
  import { createBuild } from "./build.mjs";
4
- import { t as createDevServer } from "../dev-Chvlqs7I.mjs";
4
+ import { t as createDevServer } from "../dev-C8VHtuPP.mjs";
5
5
  import process from "node:process";
6
6
  import cac$1 from "cac";
7
7
  //#region src/adapters/flags.ts
@@ -93,7 +93,8 @@ function flagKeyToOption(camel) {
93
93
  function createCac(d, options = {}) {
94
94
  const defaultPort = options.defaultPort ?? d.cli?.port ?? 9999;
95
95
  const defaultHost = d.cli?.host ?? "localhost";
96
- const cli = cac$1(d.cli?.command ?? d.id);
96
+ const command = d.cli?.command ?? d.id;
97
+ const cli = cac$1(command);
97
98
  const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--no-auth", "Disable the interactive authentication gate").option("--mcp", "Expose an MCP server over HTTP at /__mcp (use --no-mcp to disable)");
98
99
  if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) {
99
100
  const optionName = flagKeyToOption(key);
@@ -1,8 +1,8 @@
1
- import { R as DevframeNodeRpcSession, c as DevframeWsOptions, l as McpRouteOptions, n as DevframeDefinition, s as DevframeSseOptions, u as DevframeAuthHandler } from "../devframe-DJMg0WLe.mjs";
1
+ import { _ as DevframeNodeRpcSession, c as DevframeWsOptions, l as McpRouteOptions, n as DevframeDefinition, s as DevframeSseOptions, u as DevframeAuthHandler } from "../devframe-BlLEZR-x.mjs";
2
2
  import { d as DevframeRpcConnection, n as WsOriginRegistry, u as DevframeNodeRpcSessionMeta } from "../ws-server-D1d3QM9f.mjs";
3
3
  import { o as StaticAssetsSource } from "../remote-assets-Bg4gCUZ_.mjs";
4
- import { c as StartedServer } from "../instance-shell-CljiKq1c.mjs";
5
- import { a as resolveMcpConnectionMeta, i as resolveDevServerPort, t as ResolveDevServerPortOptions } from "../_shared-BPmX9fqK.mjs";
4
+ import { c as StartedServer } from "../instance-shell-0iw-cQ5V.mjs";
5
+ import { a as resolveMcpConnectionMeta, i as resolveDevServerPort, t as ResolveDevServerPortOptions } from "../_shared-DqbKCAew.mjs";
6
6
  import { H3 } from "h3";
7
7
  //#region src/adapters/dev.d.ts
8
8
  interface CreateDevServerOptions {
@@ -1,3 +1,3 @@
1
- import { i as resolveMcpConnectionMeta, r as resolveDevServerPort } from "../_shared-CKZPvSrN.mjs";
2
- import { t as createDevServer } from "../dev-Chvlqs7I.mjs";
1
+ import { i as resolveMcpConnectionMeta, r as resolveDevServerPort } from "../_shared-BM3PdYli.mjs";
2
+ import { t as createDevServer } from "../dev-C8VHtuPP.mjs";
3
3
  export { createDevServer, resolveDevServerPort, resolveMcpConnectionMeta };
@@ -1,4 +1,4 @@
1
- import { g as DevframeNodeContext, n as DevframeDefinition } from "../devframe-DJMg0WLe.mjs";
1
+ import { g as DevframeNodeContext, n as DevframeDefinition } from "../devframe-BlLEZR-x.mjs";
2
2
  //#region src/adapters/embedded.d.ts
3
3
  interface CreateEmbeddedOptions {
4
4
  /** Target context the devframe is registered into. Required. */
@@ -9,6 +9,7 @@
9
9
  * effective default follows the hosted rule of `def.basePath ?? '/__<id>/'`.
10
10
  */
11
11
  async function createEmbedded(d, options) {
12
+ for (const input of d.services ?? []) options.ctx.services.install(input, { resolveFrom: d.packageName });
12
13
  await d.setup(options.ctx);
13
14
  }
14
15
  //#endregion
@@ -1,7 +1,7 @@
1
- import { F as DevframeStorageScope, R as DevframeNodeRpcSession, c as DevframeWsOptions, d as ConnectionMeta, g as DevframeNodeContext, l as McpRouteOptions, n as DevframeDefinition, s as DevframeSseOptions, u as DevframeAuthHandler } from "../devframe-DJMg0WLe.mjs";
1
+ import { $ as DevframeStorageScope, _ as DevframeNodeRpcSession, c as DevframeWsOptions, d as ConnectionMeta, g as DevframeNodeContext, l as McpRouteOptions, n as DevframeDefinition, s as DevframeSseOptions, u as DevframeAuthHandler } from "../devframe-BlLEZR-x.mjs";
2
2
  import { d as DevframeRpcConnection, n as WsOriginRegistry, u as DevframeNodeRpcSessionMeta } from "../ws-server-D1d3QM9f.mjs";
3
3
  import { o as StaticAssetsSource } from "../remote-assets-Bg4gCUZ_.mjs";
4
- import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-CljiKq1c.mjs";
4
+ import { c as StartedServer, f as DevframeInstanceRecord } from "../instance-shell-0iw-cQ5V.mjs";
5
5
  import { Buffer } from "node:buffer";
6
6
  import { IncomingMessage, Server, ServerResponse } from "node:http";
7
7
  import { Duplex } from "node:stream";
@@ -1,2 +1,2 @@
1
- import { n as getInstanceInternals, r as initDevframe } from "../dev-Chvlqs7I.mjs";
1
+ import { n as getInstanceInternals, r as initDevframe } from "../dev-C8VHtuPP.mjs";
2
2
  export { getInstanceInternals, initDevframe };
@@ -1,4 +1,4 @@
1
- import { g as DevframeNodeContext, n as DevframeDefinition } from "../devframe-DJMg0WLe.mjs";
1
+ import { g as DevframeNodeContext, n as DevframeDefinition } from "../devframe-BlLEZR-x.mjs";
2
2
  import { H3 } from "h3";
3
3
  import "@modelcontextprotocol/server";
4
4
  //#region src/adapters/mcp/build-server.d.ts
@@ -1,2 +1,2 @@
1
- import { i as createMcpServer, n as mountMcpHttp, r as createMcpFetchHandler } from "../http-8Kpkap4_.mjs";
1
+ import { i as createMcpServer, n as mountMcpHttp, r as createMcpFetchHandler } from "../http-BbHCU5Zs.mjs";
2
2
  export { createMcpFetchHandler, createMcpServer, mountMcpHttp };
package/dist/cli/main.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as Diagnostic } from "../nostics-CzECRXpE.mjs";
2
- import { t as diagnostics } from "../diagnostics-DOQPnQmX.mjs";
3
- import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-WvQkt42E.mjs";
2
+ import { t as diagnostics } from "../diagnostics-Di8ytitn.mjs";
3
+ import { n as listLiveDevframeInstances, r as probeDevframeOrigin } from "../instance-registry-CHunhMi5.mjs";
4
4
  import { t as toAgentToolName } from "../agent-tool-name-EgfoFO8C.mjs";
5
5
  import process from "node:process";
6
6
  import { joinURL } from "ufo";
@@ -1,4 +1,4 @@
1
- import { $ as StreamReader, A as ScopedServerFunctions, H as RpcSharedStateHost, K as DevframeRpcClientFunctions, M as SettingsForNamespace, Tt as EventEmitter, V as RpcSharedStateGetOptions, d as ConnectionMeta, et as StreamSink, j as ScopedSharedStates, k as ScopedRpcFn, lt as SharedState, q as DevframeRpcServerFunctions, w as DevframeSettings } from "../devframe-DJMg0WLe.mjs";
1
+ import { E as DevframeRpcServerFunctions, H as DevframeSettings, J as ScopedServerFunctions, M as DevframeServiceMeta, Mt as EventEmitter, P as DevframeServiceScopeOf, R as DevframeServicesState, T as DevframeRpcClientFunctions, X as SettingsForNamespace, Y as ScopedSharedStates, b as RpcSharedStateGetOptions, d as ConnectionMeta, gt as SharedState, ot as StreamReader, q as ScopedRpcFn, st as StreamSink, x as RpcSharedStateHost } from "../devframe-BlLEZR-x.mjs";
2
2
  import { f as RpcCacheManager, p as RpcCacheOptions } from "../index-tdrk1bzV.mjs";
3
3
  import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-Djq7CBXh.mjs";
4
4
  import { t as SseRpcChannelOptions } from "../sse-client-CFBOIaUi.mjs";
@@ -197,6 +197,45 @@ interface DevframeScopedClientContext<NS extends string = string, Settings exten
197
197
  */
198
198
  declare function createScopedClientContext<NS extends string = string>(rpc: DevframeRpcClient, namespace: NS): DevframeScopedClientContext<NS>;
199
199
  //#endregion
200
+ //#region src/client/rpc-services.d.ts
201
+ /**
202
+ * A typed handle on one advertised wire service — the service's
203
+ * advertisement meta plus an RPC surface scoped to its namespace, so
204
+ * `handle.rpc.call('fn-name', …)` targets `<scope>:fn-name`. Service
205
+ * packages type the calls by augmenting `DevframeRpcServerFunctions` with
206
+ * their fully-qualified ids and `DevframeServicesScopeRegistry` with their
207
+ * package → scope mapping.
208
+ */
209
+ interface DevframeServiceClientHandle<NS extends string = string> extends DevframeServiceMeta {
210
+ readonly scope: NS;
211
+ /** RPC surface scoped to the service's namespace. */
212
+ readonly rpc: DevframeScopedClientRpc<NS>;
213
+ }
214
+ /**
215
+ * Client-side view of the server's wire-service registry, mirrored through
216
+ * the reactive `devframe:services` shared state. The accessors are
217
+ * synchronous snapshots — before the first sync lands (or on a server with
218
+ * no services) they read as empty. For reactive UI (e.g. hiding an
219
+ * "open in editor" button until the service appears), subscribe to the
220
+ * shared state itself via {@link DevframeServicesClient.state}.
221
+ */
222
+ interface DevframeServicesClient {
223
+ /** Whether the service package is advertised as installed. */
224
+ has: (pkg: string) => boolean;
225
+ /**
226
+ * A typed handle on an advertised service — its meta plus a scoped RPC
227
+ * surface — or `undefined` while it isn't available (never throws).
228
+ */
229
+ get: <PKG extends string>(pkg: PKG) => DevframeServiceClientHandle<DevframeServiceScopeOf<PKG>> | undefined;
230
+ /** Package names of every advertised service. */
231
+ keys: () => string[];
232
+ /**
233
+ * The mirrored `devframe:services` shared state — subscribe to its
234
+ * `updated` event for reactivity.
235
+ */
236
+ state: () => Promise<SharedState<DevframeServicesState>>;
237
+ }
238
+ //#endregion
200
239
  //#region src/client/rpc.d.ts
201
240
  interface DevframeRpcContext {
202
241
  /**
@@ -363,6 +402,13 @@ interface DevframeRpcClient {
363
402
  * The shared state host
364
403
  */
365
404
  sharedState: RpcSharedStateHost;
405
+ /**
406
+ * The server's advertised wire services (mirrored `devframe:services`
407
+ * shared state) — feature-detect a capability with
408
+ * `rpc.services.has('@devframes/service-x')` and get a scoped, typed RPC
409
+ * handle with `rpc.services.get(...)`. See {@link DevframeServicesClient}.
410
+ */
411
+ services: DevframeServicesClient;
366
412
  /**
367
413
  * The streaming channel host. Subscribe to a server-side stream by
368
414
  * channel + id; the returned reader is both `AsyncIterable<T>` and
@@ -505,4 +551,4 @@ declare function createClientSettings<T extends Record<string, any> = Record<str
505
551
  //#region src/client/index.d.ts
506
552
  declare const connectDevframe: typeof getDevframeRpcClient;
507
553
  //#endregion
508
- export { DevframeClientRpcHost, DevframeConnection, DevframeConnectionError, DevframeConnectionErrorKind, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientCall, DevframeRpcClientCallEvent, DevframeRpcClientCallOptional, DevframeRpcClientMode, DevframeRpcClientOptions, DevframeRpcContext, DevframeScopedClientContext, DevframeScopedClientRpc, DevframeScopedClientStreamingHost, RpcClientEvents, RpcStreamingClientHost, SetupDevframeConnectionOptions, StreamingSubscribeOptions, type WsUrlLocation, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, registerDevframeViewerOrigin, resolveClientTransport, resolveSseUrl, resolveWsUrl, setupDevframeConnection };
554
+ export { DevframeClientRpcHost, DevframeConnection, DevframeConnectionError, DevframeConnectionErrorKind, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientCall, DevframeRpcClientCallEvent, DevframeRpcClientCallOptional, DevframeRpcClientMode, DevframeRpcClientOptions, DevframeRpcContext, DevframeScopedClientContext, DevframeScopedClientRpc, DevframeScopedClientStreamingHost, type DevframeServiceClientHandle, type DevframeServicesClient, RpcClientEvents, RpcStreamingClientHost, SetupDevframeConnectionOptions, StreamingSubscribeOptions, type WsUrlLocation, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, registerDevframeViewerOrigin, resolveClientTransport, resolveSseUrl, resolveWsUrl, setupDevframeConnection };
@@ -8,6 +8,70 @@ import { defineDiagnostics } from "nostics";
8
8
  import { ansiFormatter } from "nostics/formatters/ansi";
9
9
  import { withBase, withProtocol } from "ufo";
10
10
  import { createBirpc } from "birpc";
11
+ //#region src/events.ts
12
+ /**
13
+ * Centralized registry of the core devframe event names — the node-side host
14
+ * bus events, the client RPC connection events, and the server→client
15
+ * broadcast notifications — so these names live in one place instead of
16
+ * scattered string literals.
17
+ *
18
+ * **Keep this in sync with [`docs/guide/events.md`](../../../docs/guide/events.md)**
19
+ * (the "Core devframe events" section): every name here appears in that page's
20
+ * tables, and every name there resolves to an entry here. Add, rename, or
21
+ * remove a name in both places in the same change, and reference
22
+ * `DEVFRAME_EVENTS.*` from call sites instead of re-typing a literal.
23
+ *
24
+ * This map covers **notifications** (events, broadcasts). The request/response
25
+ * RPC endpoints of the shared-state, streaming, and auth-handshake protocols
26
+ * (`devframe:rpc:server-state:*`, `devframe:streaming:subscribe`,
27
+ * `anonymous:devframe:auth`, …) are defined at their handlers and typed in
28
+ * `types/rpc-augments.ts`; they aren't events and stay out of this map.
29
+ *
30
+ * The `EventEmitter` maps (`RpcClientEvents`, `DevframeAgentHostEvents`) and the
31
+ * `DevframeRpcClientFunctions` augmentation declare these names as type-level
32
+ * keys (a literal is unavoidable in a type position); those declarations mirror
33
+ * this map and move with it.
34
+ */
35
+ const DEVFRAME_EVENTS = {
36
+ /**
37
+ * Node-side host `EventEmitter` events. The agent host (`ctx.agent.events`)
38
+ * emits these as its tool/resource surface changes; protocol adapters (e.g.
39
+ * MCP) subscribe to re-publish their manifest.
40
+ */
41
+ bus: {
42
+ agentManifestChanged: "agent:manifest:changed",
43
+ agentToolRegistered: "agent:tool:registered",
44
+ agentToolUnregistered: "agent:tool:unregistered",
45
+ agentResourceRegistered: "agent:resource:registered",
46
+ agentResourceUnregistered: "agent:resource:unregistered"
47
+ },
48
+ /**
49
+ * Client-side RPC connection `EventEmitter` events (`rpc.events`) a UI
50
+ * subscribes to for connection lifecycle and error surfacing.
51
+ */
52
+ client: {
53
+ isTrustedUpdated: "rpc:is-trusted:updated",
54
+ error: "rpc:error",
55
+ connectionStatus: "connection:status",
56
+ connectionError: "connection:error"
57
+ },
58
+ /**
59
+ * Broadcast notifications the server pushes to clients (server → client),
60
+ * `devframe:` prefix. The paired request methods (subscribe/get/set/…) are
61
+ * RPC endpoints, not events, and are omitted deliberately.
62
+ */
63
+ broadcast: {
64
+ authRevoked: "devframe:auth:revoked",
65
+ clientStateUpdated: "devframe:rpc:client-state:updated",
66
+ clientStatePatch: "devframe:rpc:client-state:patch",
67
+ streamingChunk: "devframe:streaming:chunk",
68
+ streamingEnd: "devframe:streaming:end",
69
+ streamingUploadCancel: "devframe:streaming:upload-cancel"
70
+ },
71
+ /** `postMessage` channels the runtime posts across window boundaries. */
72
+ postMessage: { remoteAssetsError: "devframe:remote-assets-error" }
73
+ };
74
+ //#endregion
11
75
  //#region src/constants.ts
12
76
  const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
13
77
  /**
@@ -25,6 +89,12 @@ const DEVFRAME_CONNECTION_KEY = "__DEVFRAME_CONNECTION__";
25
89
  const DEVFRAME_SSE_SESSION_HEADER = "x-birpc-session";
26
90
  const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
27
91
  /**
92
+ * Shared-state key carrying the wire-service advertisements (package name →
93
+ * `DevframeServiceMeta`). Written by the node services host; mirrored to
94
+ * clients for feature-detection via `client.services`.
95
+ */
96
+ const DEVFRAME_SERVICES_STATE_KEY = "devframe:services";
97
+ /**
28
98
  * Page-URL **fragment** parameter carrying a one-time authentication code (OTP)
29
99
  * for "magic link" auth. A host can print a link like
30
100
  * `<origin>/#devframe_otp=<code>`; the client reads the code, exchanges it for a
@@ -47,6 +117,7 @@ const DEVFRAME_AUTH_TOKEN_QUERY_PARAM = "devframe_auth_token";
47
117
  const DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM = "devframe_viewer_origin";
48
118
  /** Token that authorizes an external viewer origin registration. */
49
119
  const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = "devframe_viewer_origin_token";
120
+ DEVFRAME_EVENTS.postMessage.remoteAssetsError;
50
121
  //#endregion
51
122
  //#region src/rpc/cache.ts
52
123
  /**
@@ -606,6 +677,43 @@ async function authenticateWithUrlOtp(rpc, options = {}) {
606
677
  return rpc.requestTrustWithCode(code);
607
678
  }
608
679
  //#endregion
680
+ //#region src/client/rpc-services.ts
681
+ /** @internal */
682
+ function createDevframeServicesClient(rpc) {
683
+ let current = {};
684
+ const handles = /* @__PURE__ */ new WeakMap();
685
+ let statePromise;
686
+ const state = () => {
687
+ statePromise ??= rpc.sharedState.get(DEVFRAME_SERVICES_STATE_KEY, { initialValue: {} }).then((shared) => {
688
+ current = shared.value();
689
+ shared.on("updated", (value) => {
690
+ current = value;
691
+ });
692
+ return shared;
693
+ });
694
+ return statePromise;
695
+ };
696
+ state();
697
+ return {
698
+ state,
699
+ has: (pkg) => pkg in current,
700
+ keys: () => Object.keys(current),
701
+ get: (pkg) => {
702
+ const entry = current[pkg];
703
+ if (!entry) return void 0;
704
+ let handle = handles.get(entry);
705
+ if (!handle) {
706
+ handle = {
707
+ ...entry,
708
+ rpc: rpc.scope(entry.scope).rpc
709
+ };
710
+ handles.set(entry, handle);
711
+ }
712
+ return handle;
713
+ }
714
+ };
715
+ }
716
+ //#endregion
609
717
  //#region src/client/rpc-shared-state.ts
610
718
  function createRpcSharedStateClientHost(rpc) {
611
719
  const sharedState = /* @__PURE__ */ new Map();
@@ -622,7 +730,7 @@ function createRpcSharedStateClientHost(rpc) {
622
730
  return serverState;
623
731
  }
624
732
  rpc.client.register({
625
- name: "devframe:rpc:client-state:updated",
733
+ name: DEVFRAME_EVENTS.broadcast.clientStateUpdated,
626
734
  type: "event",
627
735
  handler: (key, fullState, syncId) => {
628
736
  const state = sharedState.get(key);
@@ -631,7 +739,7 @@ function createRpcSharedStateClientHost(rpc) {
631
739
  }
632
740
  });
633
741
  rpc.client.register({
634
- name: "devframe:rpc:client-state:patch",
742
+ name: DEVFRAME_EVENTS.broadcast.clientStatePatch,
635
743
  type: "event",
636
744
  handler: (key, patches, syncId) => {
637
745
  const state = sharedState.get(key);
@@ -698,7 +806,7 @@ function createRpcSharedStateClientHost(rpc) {
698
806
  if (!rpc.isTrusted) {
699
807
  resolve(state);
700
808
  let initialized = false;
701
- rpc.events.on("rpc:is-trusted:updated", (isTrusted) => {
809
+ rpc.events.on(DEVFRAME_EVENTS.client.isTrustedUpdated, (isTrusted) => {
702
810
  if (isTrusted && !initialized) {
703
811
  initialized = true;
704
812
  initSharedState();
@@ -722,7 +830,6 @@ const EMPTY_WIRE_DEFS = /* @__PURE__ */ new Map();
722
830
  * don't collide across connections.
723
831
  *
724
832
  * @internal
725
- * implementations; not part of the stable public API.
726
833
  */
727
834
  function createRpcWireCodec(definitions = EMPTY_WIRE_DEFS) {
728
835
  const pendingRequestMethods = /* @__PURE__ */ new Map();
@@ -927,7 +1034,7 @@ function createLiveRpcClientMode(options) {
927
1034
  if (next === status) return;
928
1035
  const previous = status;
929
1036
  status = next;
930
- events.emit("connection:status", next, previous);
1037
+ events.emit(DEVFRAME_EVENTS.client.connectionStatus, next, previous);
931
1038
  }
932
1039
  const pending = /* @__PURE__ */ new Set();
933
1040
  function rejectAllPending(error) {
@@ -949,7 +1056,7 @@ function createLiveRpcClientMode(options) {
949
1056
  const entry = { reject(error) {
950
1057
  if (settled) return;
951
1058
  finish();
952
- events.emit("rpc:error", error, method);
1059
+ events.emit(DEVFRAME_EVENTS.client.error, error, method);
953
1060
  reject(error);
954
1061
  } };
955
1062
  function finish() {
@@ -969,7 +1076,7 @@ function createLiveRpcClientMode(options) {
969
1076
  if (settled) return;
970
1077
  finish();
971
1078
  const err = error instanceof Error ? error : new Error(String(error));
972
- events.emit("rpc:error", err, method);
1079
+ events.emit(DEVFRAME_EVENTS.client.error, err, method);
973
1080
  reject(err);
974
1081
  });
975
1082
  });
@@ -980,7 +1087,7 @@ function createLiveRpcClientMode(options) {
980
1087
  definitions,
981
1088
  onError(error) {
982
1089
  setStatus("error", error);
983
- events.emit("connection:error", error);
1090
+ events.emit(DEVFRAME_EVENTS.client.connectionError, error);
984
1091
  rejectAllPending(new DevframeConnectionError("connection", "[devframe] Connection to the devframe server failed", { cause: error }));
985
1092
  },
986
1093
  onDisconnected() {
@@ -993,15 +1100,15 @@ function createLiveRpcClientMode(options) {
993
1100
  rpcOptions
994
1101
  });
995
1102
  clientRpc.register({
996
- name: "devframe:auth:revoked",
1103
+ name: DEVFRAME_EVENTS.broadcast.authRevoked,
997
1104
  type: "event",
998
1105
  handler: () => {
999
1106
  isTrusted = false;
1000
1107
  const authError = new DevframeConnectionError("auth", "[devframe] The devframe server revoked this client's trust");
1001
1108
  setStatus("unauthorized", authError);
1002
- events.emit("connection:error", authError);
1109
+ events.emit(DEVFRAME_EVENTS.client.connectionError, authError);
1003
1110
  rejectAllPending(authError);
1004
- events.emit("rpc:is-trusted:updated", false);
1111
+ events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, false);
1005
1112
  }
1006
1113
  });
1007
1114
  let currentAuthToken = authToken;
@@ -1019,9 +1126,9 @@ function createLiveRpcClientMode(options) {
1019
1126
  } else {
1020
1127
  const authError = new DevframeConnectionError("auth", "[devframe] The devframe server refused this client's credentials");
1021
1128
  setStatus("unauthorized", authError);
1022
- events.emit("connection:error", authError);
1129
+ events.emit(DEVFRAME_EVENTS.client.connectionError, authError);
1023
1130
  }
1024
- events.emit("rpc:is-trusted:updated", isTrusted);
1131
+ events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, isTrusted);
1025
1132
  return result.isTrusted;
1026
1133
  }
1027
1134
  async function requestTrustWithCode(code) {
@@ -1035,7 +1142,7 @@ function createLiveRpcClientMode(options) {
1035
1142
  isTrusted = true;
1036
1143
  trustedPromise.resolve(true);
1037
1144
  setStatus("connected");
1038
- events.emit("rpc:is-trusted:updated", true);
1145
+ events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, true);
1039
1146
  }
1040
1147
  return token;
1041
1148
  }
@@ -1074,7 +1181,7 @@ function createLiveRpcClientMode(options) {
1074
1181
  const method = String(args[0]);
1075
1182
  const failFast = terminalError();
1076
1183
  if (failFast) {
1077
- events.emit("rpc:error", failFast, method);
1184
+ events.emit(DEVFRAME_EVENTS.client.error, failFast, method);
1078
1185
  return Promise.reject(failFast);
1079
1186
  }
1080
1187
  return guardCall(serverRpc.$call(...args), method);
@@ -1082,7 +1189,7 @@ function createLiveRpcClientMode(options) {
1082
1189
  callEvent: (...args) => {
1083
1190
  const failFast = terminalError();
1084
1191
  if (failFast) {
1085
- events.emit("rpc:error", failFast, String(args[0]));
1192
+ events.emit(DEVFRAME_EVENTS.client.error, failFast, String(args[0]));
1086
1193
  return;
1087
1194
  }
1088
1195
  return serverRpc.$callEvent(...args);
@@ -1091,7 +1198,7 @@ function createLiveRpcClientMode(options) {
1091
1198
  const method = String(args[0]);
1092
1199
  const failFast = terminalError();
1093
1200
  if (failFast) {
1094
- events.emit("rpc:error", failFast, method);
1201
+ events.emit(DEVFRAME_EVENTS.client.error, failFast, method);
1095
1202
  return Promise.reject(failFast);
1096
1203
  }
1097
1204
  return guardCall(serverRpc.$callOptional(...args), method);
@@ -1288,14 +1395,14 @@ function createRpcStreamingClientHost(rpc) {
1288
1395
  const readers = /* @__PURE__ */ new Map();
1289
1396
  const uploads = /* @__PURE__ */ new Map();
1290
1397
  rpc.client.register({
1291
- name: "devframe:streaming:chunk",
1398
+ name: DEVFRAME_EVENTS.broadcast.streamingChunk,
1292
1399
  type: "event",
1293
1400
  handler(channel, id, seq, chunk) {
1294
1401
  readers.get(streamKey(channel, id))?._push(seq, chunk);
1295
1402
  }
1296
1403
  });
1297
1404
  rpc.client.register({
1298
- name: "devframe:streaming:end",
1405
+ name: DEVFRAME_EVENTS.broadcast.streamingEnd,
1299
1406
  type: "event",
1300
1407
  handler(channel, id, error) {
1301
1408
  const key = streamKey(channel, id);
@@ -1306,7 +1413,7 @@ function createRpcStreamingClientHost(rpc) {
1306
1413
  }
1307
1414
  });
1308
1415
  rpc.client.register({
1309
- name: "devframe:streaming:upload-cancel",
1416
+ name: DEVFRAME_EVENTS.broadcast.streamingUploadCancel,
1310
1417
  type: "event",
1311
1418
  handler(channel, id) {
1312
1419
  const key = streamKey(channel, id);
@@ -1316,7 +1423,7 @@ function createRpcStreamingClientHost(rpc) {
1316
1423
  uploads.delete(key);
1317
1424
  }
1318
1425
  });
1319
- rpc.events.on("rpc:is-trusted:updated", (isTrusted) => {
1426
+ rpc.events.on(DEVFRAME_EVENTS.client.isTrustedUpdated, (isTrusted) => {
1320
1427
  if (!isTrusted) return;
1321
1428
  for (const [key, reader] of readers) {
1322
1429
  if (reader.cancelled || reader.done) continue;
@@ -1345,7 +1452,7 @@ function createRpcStreamingClientHost(rpc) {
1345
1452
  readers.set(key, reader);
1346
1453
  if (rpc.isTrusted) rpc.callEvent("devframe:streaming:subscribe", channel, id, { afterSeq: 0 });
1347
1454
  else {
1348
- const off = rpc.events.on("rpc:is-trusted:updated", (trusted) => {
1455
+ const off = rpc.events.on(DEVFRAME_EVENTS.client.isTrustedUpdated, (trusted) => {
1349
1456
  if (trusted) {
1350
1457
  off();
1351
1458
  if (readers.has(key) && !reader.cancelled && !reader.done) rpc.callEvent("devframe:streaming:subscribe", channel, id, { afterSeq: reader.lastSeenSeq });
@@ -1738,6 +1845,7 @@ async function getDevframeRpcClient(options = {}) {
1738
1845
  callOptional: gateOnBootstrapAuth(mode.callOptional),
1739
1846
  client: clientRpc,
1740
1847
  sharedState: void 0,
1848
+ services: void 0,
1741
1849
  streaming: void 0,
1742
1850
  cacheManager,
1743
1851
  scope: void 0,
@@ -1745,6 +1853,7 @@ async function getDevframeRpcClient(options = {}) {
1745
1853
  };
1746
1854
  rpc.sharedState = createRpcSharedStateClientHost(rpc);
1747
1855
  rpc.streaming = createRpcStreamingClientHost(rpc);
1856
+ rpc.services = createDevframeServicesClient(rpc);
1748
1857
  const scopedCache = /* @__PURE__ */ new Map();
1749
1858
  rpc.scope = ((namespace) => {
1750
1859
  if (!namespace) return rpc;