devframe 0.9.0 → 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 (64) hide show
  1. package/dist/{_shared-CbZ_yhbz.mjs → _shared-BM3PdYli.mjs} +1 -1
  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 -21
  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--rAPzbvO.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/{dist-eNI9qM_I.mjs → dist-CZXfGEkd.mjs} +1 -1
  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-DYpK0cUi.mjs → http-BbHCU5Zs.mjs} +8 -5
  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-CMFyszWJ.mjs → instance-shell-DbDoozNM.mjs} +5 -5
  35. package/dist/internal/index.d.mts +46 -4
  36. package/dist/internal/index.mjs +7 -7
  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/ws-bun.d.mts +1 -1
  50. package/dist/{rpc-core-Cc4lucI0.d.mts → rpc-core-CR8o34O9.d.mts} +2 -2
  51. package/dist/{rpc-core-DMTe-hLo.mjs → rpc-core-Dru9uoM0.mjs} +11 -4
  52. package/dist/{state-Cc1IeSmQ.mjs → state-CK9LjrnT.mjs} +1 -1
  53. package/dist/{storage-CYye7P7L.mjs → storage-D4a99Ung.mjs} +3 -3
  54. package/dist/types/index.d.mts +2 -2
  55. package/dist/utils/events.d.mts +1 -1
  56. package/dist/utils/get-port.mjs +1 -1
  57. package/dist/utils/launch-editor.mjs +12 -11
  58. package/dist/utils/open.mjs +1 -1
  59. package/dist/utils/remote-assets.mjs +1 -1
  60. package/dist/utils/serve-static.mjs +6 -5
  61. package/dist/utils/shared-state.d.mts +1 -1
  62. package/dist/utils/streaming-channel.d.mts +1 -1
  63. package/package.json +1 -1
  64. package/dist/{host-h3-Crkzm42q.mjs → host-h3-fRbF9yor.mjs} +1 -1
@@ -1,5 +1,5 @@
1
1
  import "./constants.mjs";
2
- import { n as getPort } from "./dist-eNI9qM_I.mjs";
2
+ import { n as getPort } from "./dist-CZXfGEkd.mjs";
3
3
  import { cleanDoubleSlashes, withLeadingSlash, withTrailingSlash, withoutLeadingSlash } from "ufo";
4
4
  //#region src/adapters/_shared.ts
5
5
  const DEFAULT_PORT = 9999;
@@ -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-CbZ_yhbz.mjs";
2
+ import { r as resolveDevServerPort } from "../_shared-BM3PdYli.mjs";
3
3
  import { createBuild } from "./build.mjs";
4
- import { t as createDevServer } from "../dev--rAPzbvO.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-CbZ_yhbz.mjs";
2
- import { t as createDevServer } from "../dev--rAPzbvO.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--rAPzbvO.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-DYpK0cUi.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();
@@ -926,7 +1034,7 @@ function createLiveRpcClientMode(options) {
926
1034
  if (next === status) return;
927
1035
  const previous = status;
928
1036
  status = next;
929
- events.emit("connection:status", next, previous);
1037
+ events.emit(DEVFRAME_EVENTS.client.connectionStatus, next, previous);
930
1038
  }
931
1039
  const pending = /* @__PURE__ */ new Set();
932
1040
  function rejectAllPending(error) {
@@ -948,7 +1056,7 @@ function createLiveRpcClientMode(options) {
948
1056
  const entry = { reject(error) {
949
1057
  if (settled) return;
950
1058
  finish();
951
- events.emit("rpc:error", error, method);
1059
+ events.emit(DEVFRAME_EVENTS.client.error, error, method);
952
1060
  reject(error);
953
1061
  } };
954
1062
  function finish() {
@@ -968,7 +1076,7 @@ function createLiveRpcClientMode(options) {
968
1076
  if (settled) return;
969
1077
  finish();
970
1078
  const err = error instanceof Error ? error : new Error(String(error));
971
- events.emit("rpc:error", err, method);
1079
+ events.emit(DEVFRAME_EVENTS.client.error, err, method);
972
1080
  reject(err);
973
1081
  });
974
1082
  });
@@ -979,7 +1087,7 @@ function createLiveRpcClientMode(options) {
979
1087
  definitions,
980
1088
  onError(error) {
981
1089
  setStatus("error", error);
982
- events.emit("connection:error", error);
1090
+ events.emit(DEVFRAME_EVENTS.client.connectionError, error);
983
1091
  rejectAllPending(new DevframeConnectionError("connection", "[devframe] Connection to the devframe server failed", { cause: error }));
984
1092
  },
985
1093
  onDisconnected() {
@@ -992,15 +1100,15 @@ function createLiveRpcClientMode(options) {
992
1100
  rpcOptions
993
1101
  });
994
1102
  clientRpc.register({
995
- name: "devframe:auth:revoked",
1103
+ name: DEVFRAME_EVENTS.broadcast.authRevoked,
996
1104
  type: "event",
997
1105
  handler: () => {
998
1106
  isTrusted = false;
999
1107
  const authError = new DevframeConnectionError("auth", "[devframe] The devframe server revoked this client's trust");
1000
1108
  setStatus("unauthorized", authError);
1001
- events.emit("connection:error", authError);
1109
+ events.emit(DEVFRAME_EVENTS.client.connectionError, authError);
1002
1110
  rejectAllPending(authError);
1003
- events.emit("rpc:is-trusted:updated", false);
1111
+ events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, false);
1004
1112
  }
1005
1113
  });
1006
1114
  let currentAuthToken = authToken;
@@ -1018,9 +1126,9 @@ function createLiveRpcClientMode(options) {
1018
1126
  } else {
1019
1127
  const authError = new DevframeConnectionError("auth", "[devframe] The devframe server refused this client's credentials");
1020
1128
  setStatus("unauthorized", authError);
1021
- events.emit("connection:error", authError);
1129
+ events.emit(DEVFRAME_EVENTS.client.connectionError, authError);
1022
1130
  }
1023
- events.emit("rpc:is-trusted:updated", isTrusted);
1131
+ events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, isTrusted);
1024
1132
  return result.isTrusted;
1025
1133
  }
1026
1134
  async function requestTrustWithCode(code) {
@@ -1034,7 +1142,7 @@ function createLiveRpcClientMode(options) {
1034
1142
  isTrusted = true;
1035
1143
  trustedPromise.resolve(true);
1036
1144
  setStatus("connected");
1037
- events.emit("rpc:is-trusted:updated", true);
1145
+ events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, true);
1038
1146
  }
1039
1147
  return token;
1040
1148
  }
@@ -1073,7 +1181,7 @@ function createLiveRpcClientMode(options) {
1073
1181
  const method = String(args[0]);
1074
1182
  const failFast = terminalError();
1075
1183
  if (failFast) {
1076
- events.emit("rpc:error", failFast, method);
1184
+ events.emit(DEVFRAME_EVENTS.client.error, failFast, method);
1077
1185
  return Promise.reject(failFast);
1078
1186
  }
1079
1187
  return guardCall(serverRpc.$call(...args), method);
@@ -1081,7 +1189,7 @@ function createLiveRpcClientMode(options) {
1081
1189
  callEvent: (...args) => {
1082
1190
  const failFast = terminalError();
1083
1191
  if (failFast) {
1084
- events.emit("rpc:error", failFast, String(args[0]));
1192
+ events.emit(DEVFRAME_EVENTS.client.error, failFast, String(args[0]));
1085
1193
  return;
1086
1194
  }
1087
1195
  return serverRpc.$callEvent(...args);
@@ -1090,7 +1198,7 @@ function createLiveRpcClientMode(options) {
1090
1198
  const method = String(args[0]);
1091
1199
  const failFast = terminalError();
1092
1200
  if (failFast) {
1093
- events.emit("rpc:error", failFast, method);
1201
+ events.emit(DEVFRAME_EVENTS.client.error, failFast, method);
1094
1202
  return Promise.reject(failFast);
1095
1203
  }
1096
1204
  return guardCall(serverRpc.$callOptional(...args), method);
@@ -1287,14 +1395,14 @@ function createRpcStreamingClientHost(rpc) {
1287
1395
  const readers = /* @__PURE__ */ new Map();
1288
1396
  const uploads = /* @__PURE__ */ new Map();
1289
1397
  rpc.client.register({
1290
- name: "devframe:streaming:chunk",
1398
+ name: DEVFRAME_EVENTS.broadcast.streamingChunk,
1291
1399
  type: "event",
1292
1400
  handler(channel, id, seq, chunk) {
1293
1401
  readers.get(streamKey(channel, id))?._push(seq, chunk);
1294
1402
  }
1295
1403
  });
1296
1404
  rpc.client.register({
1297
- name: "devframe:streaming:end",
1405
+ name: DEVFRAME_EVENTS.broadcast.streamingEnd,
1298
1406
  type: "event",
1299
1407
  handler(channel, id, error) {
1300
1408
  const key = streamKey(channel, id);
@@ -1305,7 +1413,7 @@ function createRpcStreamingClientHost(rpc) {
1305
1413
  }
1306
1414
  });
1307
1415
  rpc.client.register({
1308
- name: "devframe:streaming:upload-cancel",
1416
+ name: DEVFRAME_EVENTS.broadcast.streamingUploadCancel,
1309
1417
  type: "event",
1310
1418
  handler(channel, id) {
1311
1419
  const key = streamKey(channel, id);
@@ -1315,7 +1423,7 @@ function createRpcStreamingClientHost(rpc) {
1315
1423
  uploads.delete(key);
1316
1424
  }
1317
1425
  });
1318
- rpc.events.on("rpc:is-trusted:updated", (isTrusted) => {
1426
+ rpc.events.on(DEVFRAME_EVENTS.client.isTrustedUpdated, (isTrusted) => {
1319
1427
  if (!isTrusted) return;
1320
1428
  for (const [key, reader] of readers) {
1321
1429
  if (reader.cancelled || reader.done) continue;
@@ -1344,7 +1452,7 @@ function createRpcStreamingClientHost(rpc) {
1344
1452
  readers.set(key, reader);
1345
1453
  if (rpc.isTrusted) rpc.callEvent("devframe:streaming:subscribe", channel, id, { afterSeq: 0 });
1346
1454
  else {
1347
- const off = rpc.events.on("rpc:is-trusted:updated", (trusted) => {
1455
+ const off = rpc.events.on(DEVFRAME_EVENTS.client.isTrustedUpdated, (trusted) => {
1348
1456
  if (trusted) {
1349
1457
  off();
1350
1458
  if (readers.has(key) && !reader.cancelled && !reader.done) rpc.callEvent("devframe:streaming:subscribe", channel, id, { afterSeq: reader.lastSeenSeq });
@@ -1737,6 +1845,7 @@ async function getDevframeRpcClient(options = {}) {
1737
1845
  callOptional: gateOnBootstrapAuth(mode.callOptional),
1738
1846
  client: clientRpc,
1739
1847
  sharedState: void 0,
1848
+ services: void 0,
1740
1849
  streaming: void 0,
1741
1850
  cacheManager,
1742
1851
  scope: void 0,
@@ -1744,6 +1853,7 @@ async function getDevframeRpcClient(options = {}) {
1744
1853
  };
1745
1854
  rpc.sharedState = createRpcSharedStateClientHost(rpc);
1746
1855
  rpc.streaming = createRpcStreamingClientHost(rpc);
1856
+ rpc.services = createDevframeServicesClient(rpc);
1747
1857
  const scopedCache = /* @__PURE__ */ new Map();
1748
1858
  rpc.scope = ((namespace) => {
1749
1859
  if (!namespace) return rpc;
@@ -1,3 +1,69 @@
1
+ //#region src/events.d.ts
2
+ /**
3
+ * Centralized registry of the core devframe event names — the node-side host
4
+ * bus events, the client RPC connection events, and the server→client
5
+ * broadcast notifications — so these names live in one place instead of
6
+ * scattered string literals.
7
+ *
8
+ * **Keep this in sync with [`docs/guide/events.md`](../../../docs/guide/events.md)**
9
+ * (the "Core devframe events" section): every name here appears in that page's
10
+ * tables, and every name there resolves to an entry here. Add, rename, or
11
+ * remove a name in both places in the same change, and reference
12
+ * `DEVFRAME_EVENTS.*` from call sites instead of re-typing a literal.
13
+ *
14
+ * This map covers **notifications** (events, broadcasts). The request/response
15
+ * RPC endpoints of the shared-state, streaming, and auth-handshake protocols
16
+ * (`devframe:rpc:server-state:*`, `devframe:streaming:subscribe`,
17
+ * `anonymous:devframe:auth`, …) are defined at their handlers and typed in
18
+ * `types/rpc-augments.ts`; they aren't events and stay out of this map.
19
+ *
20
+ * The `EventEmitter` maps (`RpcClientEvents`, `DevframeAgentHostEvents`) and the
21
+ * `DevframeRpcClientFunctions` augmentation declare these names as type-level
22
+ * keys (a literal is unavoidable in a type position); those declarations mirror
23
+ * this map and move with it.
24
+ */
25
+ declare const DEVFRAME_EVENTS: {
26
+ /**
27
+ * Node-side host `EventEmitter` events. The agent host (`ctx.agent.events`)
28
+ * emits these as its tool/resource surface changes; protocol adapters (e.g.
29
+ * MCP) subscribe to re-publish their manifest.
30
+ */
31
+ readonly bus: {
32
+ readonly agentManifestChanged: "agent:manifest:changed";
33
+ readonly agentToolRegistered: "agent:tool:registered";
34
+ readonly agentToolUnregistered: "agent:tool:unregistered";
35
+ readonly agentResourceRegistered: "agent:resource:registered";
36
+ readonly agentResourceUnregistered: "agent:resource:unregistered";
37
+ };
38
+ /**
39
+ * Client-side RPC connection `EventEmitter` events (`rpc.events`) a UI
40
+ * subscribes to for connection lifecycle and error surfacing.
41
+ */
42
+ readonly client: {
43
+ readonly isTrustedUpdated: "rpc:is-trusted:updated";
44
+ readonly error: "rpc:error";
45
+ readonly connectionStatus: "connection:status";
46
+ readonly connectionError: "connection:error";
47
+ };
48
+ /**
49
+ * Broadcast notifications the server pushes to clients (server → client),
50
+ * `devframe:` prefix. The paired request methods (subscribe/get/set/…) are
51
+ * RPC endpoints, not events, and are omitted deliberately.
52
+ */
53
+ readonly broadcast: {
54
+ readonly authRevoked: "devframe:auth:revoked";
55
+ readonly clientStateUpdated: "devframe:rpc:client-state:updated";
56
+ readonly clientStatePatch: "devframe:rpc:client-state:patch";
57
+ readonly streamingChunk: "devframe:streaming:chunk";
58
+ readonly streamingEnd: "devframe:streaming:end";
59
+ readonly streamingUploadCancel: "devframe:streaming:upload-cancel";
60
+ };
61
+ /** `postMessage` channels the runtime posts across window boundaries. */
62
+ readonly postMessage: {
63
+ readonly remoteAssetsError: "devframe:remote-assets-error";
64
+ };
65
+ };
66
+ //#endregion
1
67
  //#region src/constants.d.ts
2
68
  declare const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
3
69
  /**
@@ -40,6 +106,12 @@ declare const DEVFRAME_MCP_ROUTE = "__mcp";
40
106
  declare const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
41
107
  declare const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
42
108
  declare const DEVFRAME_RPC_DUMP_DIRNAME = "__rpc-dump";
109
+ /**
110
+ * Shared-state key carrying the wire-service advertisements (package name →
111
+ * `DevframeServiceMeta`). Written by the node services host; mirrored to
112
+ * clients for feature-detection via `client.services`.
113
+ */
114
+ declare const DEVFRAME_SERVICES_STATE_KEY = "devframe:services";
43
115
  /**
44
116
  * URL fragment / query parameter name carrying the remote dock
45
117
  * connection descriptor (defined as `RemoteConnectionInfo` in
@@ -77,7 +149,7 @@ declare const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = "devframe_viewer_origin
77
149
  * (`@devframes/hub-ui` does, in its iframe view). Payload shape:
78
150
  * `RemoteAssetsErrorMessage` (`devframe/types`).
79
151
  */
80
- declare const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE = "devframe:remote-assets-error";
152
+ declare const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE: string;
81
153
  /**
82
154
  * Prefix that marks an RPC method as callable before a connection is
83
155
  * trusted. This is the *only* rule the pre-trust gate applies — there is no
@@ -94,4 +166,4 @@ declare const ANONYMOUS_RPC_PREFIX = "anonymous:";
94
166
  */
95
167
  declare function isAnonymousRpcMethod(name: string): boolean;
96
168
  //#endregion
97
- export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE, DEVFRAME_OTP_URL_PARAM, DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_SSE_ROUTE, DEVFRAME_SSE_SESSION_HEADER, DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
169
+ export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_EVENTS, DEVFRAME_MCP_ROUTE, DEVFRAME_OTP_URL_PARAM, DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_SERVICES_STATE_KEY, DEVFRAME_SSE_ROUTE, DEVFRAME_SSE_SESSION_HEADER, DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };