devframe 1.3.1 → 1.3.3

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 (153) hide show
  1. package/dist/{_shared-Cm4iD0L3.d.mts → _shared-BxeuukaL.d.mts} +2 -4
  2. package/dist/{_shared-C61zPwIz.mjs → _shared-CtXCQFTV.mjs} +6 -9
  3. package/dist/adapters/build.d.mts +3 -5
  4. package/dist/adapters/build.mjs +7 -8
  5. package/dist/adapters/cac.d.mts +1 -3
  6. package/dist/adapters/cac.mjs +5 -7
  7. package/dist/adapters/dev.d.mts +6 -8
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +2 -4
  10. package/dist/adapters/embedded.mjs +1 -2
  11. package/dist/adapters/initiate.d.mts +10 -10
  12. package/dist/adapters/initiate.mjs +1 -2
  13. package/dist/adapters/mcp.d.mts +2 -2
  14. package/dist/adapters/mcp.mjs +1 -2
  15. package/dist/{agentic-C6SCkdtj.d.mts → agentic-DVgUzwhV.d.mts} +1 -3
  16. package/dist/{browser-agent-Pu-FLk6I.mjs → browser-agent-DmAotroz.mjs} +1 -2
  17. package/dist/{browser-agent-rpc-KH4HHvHf.mjs → browser-agent-rpc-CS80Sphv.mjs} +3 -5
  18. package/dist/cli/main.d.mts +1 -3
  19. package/dist/cli/main.mjs +3 -4
  20. package/dist/client/index.d.mts +6 -30
  21. package/dist/client/index.mjs +47 -72
  22. package/dist/{client-agent-iBkMYxqn.mjs → client-agent-qNMgDUev.mjs} +7 -12
  23. package/dist/constants.d.mts +1 -5
  24. package/dist/constants.mjs +2 -3
  25. package/dist/{context-BGJi0ITA.d.mts → context-B-AARy_U.d.mts} +1 -3
  26. package/dist/{context-D_jSSdME.mjs → context-BTxC4QrJ.mjs} +4 -5
  27. package/dist/{context-CAJ5V017.mjs → context-C5FcbWQi.mjs} +25 -41
  28. package/dist/{crypto-token-TVbGGFuj.mjs → crypto-token-CUhPE_ei.mjs} +1 -2
  29. package/dist/{define-BLWPsH6y.mjs → define-C8h-fyD3.mjs} +1 -2
  30. package/dist/{dev-BKBjIpX3.mjs → dev-Cu_iwSCy.mjs} +9 -11
  31. package/dist/{diagnostics-E_PvUPsA.mjs → diagnostics-DC1-WcvN.mjs} +2 -3
  32. package/dist/{dist-CO98PBnQ.mjs → dist-BWGpvt-z.mjs} +1 -2
  33. package/dist/{dist-CZXfGEkd.mjs → dist-CZ-TySHD.mjs} +3 -11
  34. package/dist/{events-0wDwalmL.mjs → events-qWCKwD8-.mjs} +1 -2
  35. package/dist/{handler-DmaAfpGq.mjs → handler-DkLlj3-B.mjs} +3 -5
  36. package/dist/{hash-DdPnc4k3.mjs → hash-DHqpWeMO.mjs} +4 -8
  37. package/dist/{host-h3-BROI6SQ4.mjs → host-h3-D1uj5EM8.mjs} +1 -2
  38. package/dist/{import-runtime-module-BzlEQ9PI.mjs → import-runtime-module-DsdqJ1gK.mjs} +2 -3
  39. package/dist/in-page-channel/index.d.mts +3 -12
  40. package/dist/in-page-channel/index.mjs +10 -16
  41. package/dist/{index-CeEtxDmK.d.mts → index-BIRF0E5l.d.mts} +0 -2
  42. package/dist/{index-Bia1vKjL.d.mts → index-BKu9BxcT.d.mts} +0 -2
  43. package/dist/{context-BNxFOPbX.d.mts → index-BhUqJUHG.d.mts} +1408 -1442
  44. package/dist/index.d.mts +7 -8
  45. package/dist/index.mjs +3 -4
  46. package/dist/{instance-registry-C_4PpB6N.mjs → instance-registry-DwmnWyCK.mjs} +4 -10
  47. package/dist/{instance-shell-m4sY2oTG.mjs → instance-shell-Coid7CP6.mjs} +38 -17
  48. package/dist/{instance-shell-GihbmXoF.d.mts → instance-shell-DTk514M9.d.mts} +6 -10
  49. package/dist/{interactive-auth-DdDxs3WF.mjs → interactive-auth-BURwIAvE.mjs} +6 -8
  50. package/dist/internal/index.d.mts +9 -29
  51. package/dist/internal/index.mjs +13 -15
  52. package/dist/{mcp-content-QImBQO8e.d.mts → mcp-content-0a0mpCL5.d.mts} +1 -3
  53. package/dist/{mcp-content-DN3O212x.mjs → mcp-content-CqU8yaF7.mjs} +1 -2
  54. package/dist/{mrmime-40h5OOIp.mjs → mrmime-y_XyiVcd.mjs} +1 -2
  55. package/dist/node/auth.d.mts +84 -3
  56. package/dist/node/auth.mjs +2 -2
  57. package/dist/node/hub-internals.d.mts +11 -2
  58. package/dist/node/hub-internals.mjs +2 -2
  59. package/dist/node/index.d.mts +4 -6
  60. package/dist/node/index.mjs +2 -2
  61. package/dist/{nostics-D0PvLZsn.mjs → nostics-InlX4tmd.mjs} +2 -4
  62. package/dist/{origin-DZit9g29.mjs → origin-Ck7mLbKU.mjs} +1 -2
  63. package/dist/recipes/interactive-auth.d.mts +3 -5
  64. package/dist/recipes/interactive-auth.mjs +1 -2
  65. package/dist/{remote-assets-eBvSKZMS.d.mts → remote-assets-5AQMHZnV.d.mts} +0 -2
  66. package/dist/{remote-assets-B-n83kUY.mjs → remote-assets-DhonNeo_.mjs} +5 -8
  67. package/dist/{revoke-CeQUo6KI.mjs → revoke-D6KA3vwr.mjs} +2 -3
  68. package/dist/{rolldown-runtime-B4iAMlE-.mjs → rolldown-runtime-BVN1G_yT.mjs} +6 -7
  69. package/dist/rpc/client.d.mts +2 -4
  70. package/dist/rpc/client.mjs +2 -3
  71. package/dist/rpc/dump.d.mts +3 -11
  72. package/dist/rpc/dump.mjs +1 -2
  73. package/dist/rpc/index.d.mts +122 -4
  74. package/dist/rpc/index.mjs +6 -8
  75. package/dist/rpc/server.d.mts +2 -4
  76. package/dist/rpc/server.mjs +2 -3
  77. package/dist/rpc/transports/sse-client.d.mts +34 -2
  78. package/dist/rpc/transports/sse-client.mjs +2 -3
  79. package/dist/rpc/transports/sse-server.d.mts +5 -7
  80. package/dist/rpc/transports/sse-server.mjs +4 -5
  81. package/dist/rpc/transports/ws-bun.d.mts +3 -5
  82. package/dist/rpc/transports/ws-bun.mjs +2 -3
  83. package/dist/rpc/transports/ws-client.d.mts +27 -2
  84. package/dist/rpc/transports/ws-client.mjs +2 -3
  85. package/dist/rpc/transports/ws-deno.d.mts +3 -5
  86. package/dist/rpc/transports/ws-deno.mjs +2 -3
  87. package/dist/rpc/transports/ws-server.d.mts +151 -4
  88. package/dist/rpc/transports/ws-server.mjs +5 -6
  89. package/dist/{rpc-core-BLc5hm9X.mjs → rpc-core-BmDHiDR6.mjs} +4 -5
  90. package/dist/{rpc-core-CGTCla-R.d.mts → rpc-core-CblgPYJ_.d.mts} +5 -7
  91. package/dist/{serialization-BhuOoL80.mjs → serialization-iiTQ7n21.mjs} +3 -5
  92. package/dist/{services-install-D5YUwFXW.mjs → services-install-DCuDzMJ-.mjs} +1 -2
  93. package/dist/{session-DPfj6zQG.mjs → session-BjvoLH3r.mjs} +1 -2
  94. package/dist/{session-r6PDixP6.d.mts → session-Y9R6tqg4.d.mts} +0 -2
  95. package/dist/{shared-state-Cc-Jrhma.mjs → shared-state-COV7NCbd.mjs} +4 -6
  96. package/dist/{state-W4wI6X5X.mjs → state-B_yjfeqo.mjs} +3 -5
  97. package/dist/{storage-CWI9PZ-5.mjs → storage-DpJ52UB_.mjs} +3 -5
  98. package/dist/{structured-clone-CbAV5rFI.mjs → structured-clone-B1N_XQvA.mjs} +2 -4
  99. package/dist/{structured-clone-CgtQ61SA.mjs → structured-clone-D1dOCZvc.mjs} +2 -4
  100. package/dist/{tool-input-CES3Pc4y.mjs → tool-input-CcCgJ8jq.mjs} +6 -12
  101. package/dist/types/index.d.mts +5 -5
  102. package/dist/{types-BmDbfHCx.d.mts → types-KZOyhez5.d.mts} +2 -5
  103. package/dist/utils/agent-tool-name.d.mts +1 -3
  104. package/dist/utils/agent-tool-name.mjs +1 -2
  105. package/dist/utils/colors.d.mts +1 -3
  106. package/dist/utils/colors.mjs +1 -2
  107. package/dist/utils/crypto-token.d.mts +1 -3
  108. package/dist/utils/crypto-token.mjs +1 -2
  109. package/dist/utils/debounce.d.mts +6 -2
  110. package/dist/utils/debounce.mjs +1 -2
  111. package/dist/utils/events.d.mts +2 -4
  112. package/dist/utils/events.mjs +1 -2
  113. package/dist/utils/get-port.d.mts +0 -2
  114. package/dist/utils/get-port.mjs +1 -2
  115. package/dist/utils/hash.d.mts +1 -3
  116. package/dist/utils/hash.mjs +1 -2
  117. package/dist/utils/launch-editor.d.mts +1 -3
  118. package/dist/utils/launch-editor.mjs +25 -37
  119. package/dist/utils/nanoid.d.mts +1 -3
  120. package/dist/utils/nanoid.mjs +1 -2
  121. package/dist/utils/nostics.d.mts +40 -2
  122. package/dist/utils/nostics.mjs +1 -2
  123. package/dist/utils/open.d.mts +1 -3
  124. package/dist/utils/open.mjs +1 -2
  125. package/dist/utils/origin.d.mts +55 -2
  126. package/dist/utils/origin.mjs +1 -2
  127. package/dist/utils/remote-assets.d.mts +2 -4
  128. package/dist/utils/remote-assets.mjs +1 -2
  129. package/dist/utils/serve-static.d.mts +3 -5
  130. package/dist/utils/serve-static.mjs +3 -4
  131. package/dist/utils/shared-state.d.mts +1 -1
  132. package/dist/utils/shared-state.mjs +1 -2
  133. package/dist/utils/simple-schema.d.mts +1 -3
  134. package/dist/utils/simple-schema.mjs +1 -2
  135. package/dist/utils/streaming-channel.d.mts +1 -1
  136. package/dist/utils/streaming-channel.mjs +1 -2
  137. package/dist/utils/structured-clone.d.mts +1 -3
  138. package/dist/utils/structured-clone.mjs +1 -2
  139. package/dist/utils/url.d.mts +1 -3
  140. package/dist/utils/url.mjs +1 -2
  141. package/dist/utils/when.d.mts +1 -3
  142. package/dist/utils/when.mjs +2 -4
  143. package/dist/{validation-CMfDAti3.mjs → validation-Cjy1P58H.mjs} +7 -13
  144. package/dist/{wire-codec-B1W3zH3L.mjs → wire-codec-BdngvzfH.mjs} +3 -4
  145. package/dist/{write-DK7rBUN3.mjs → write-3miSvPaD.mjs} +10 -16
  146. package/package.json +8 -8
  147. package/dist/index-CxJatyA1.d.mts +0 -88
  148. package/dist/index-DuEm0sDH.d.mts +0 -135
  149. package/dist/nostics-DWvukRwa.d.mts +0 -42
  150. package/dist/origin-CY6BBHOy.d.mts +0 -58
  151. package/dist/sse-client-DNdscfu_.d.mts +0 -37
  152. package/dist/ws-client-6Mrz6eC4.d.mts +0 -30
  153. package/dist/ws-server-y2R76aSW.d.mts +0 -152
@@ -1,12 +1,11 @@
1
- import { i as BirpcReturn } from "./index-Bia1vKjL.mjs";
2
- import { d as RpcFunctionsCollectorBase } from "./index-DuEm0sDH.mjs";
3
- import { t as StandardSchemaV1 } from "./index-CeEtxDmK.mjs";
4
- import { g as RpcFunctionDefinitionAny, h as RpcFunctionDefinition } from "./types-BmDbfHCx.mjs";
5
- import { n as DevframeRpcConnection, t as DevframeNodeRpcSessionMeta } from "./session-r6PDixP6.mjs";
6
- import { a as RemoteAssetsStore, o as StaticAssetsSource } from "./remote-assets-eBvSKZMS.mjs";
7
- import { h as defineDiagnostics } from "./nostics-DWvukRwa.mjs";
1
+ import { t as StandardSchemaV1 } from "./index-BIRF0E5l.mjs";
2
+ import { g as RpcFunctionDefinitionAny, h as RpcFunctionDefinition } from "./types-KZOyhez5.mjs";
3
+ import { i as BirpcReturn } from "./index-BKu9BxcT.mjs";
4
+ import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
5
+ import { n as DevframeRpcConnection, t as DevframeNodeRpcSessionMeta } from "./session-Y9R6tqg4.mjs";
6
+ import { defineDiagnostics } from "./utils/nostics.mjs";
7
+ import { a as RemoteAssetsStore, o as StaticAssetsSource } from "./remote-assets-5AQMHZnV.mjs";
8
8
  import { CAC } from "cac";
9
- //#region src/types/events.d.ts
10
9
  interface EventsMap {
11
10
  [event: string]: any;
12
11
  }
@@ -75,8 +74,6 @@ interface EventEmitter<Events extends EventsMap> {
75
74
  */
76
75
  once: <K extends keyof Events>(event: K, cb: Events[K]) => EventUnsubscribe;
77
76
  }
78
- //#endregion
79
- //#region src/types/agent.d.ts
80
77
  /**
81
78
  * Serializable description of an agent-exposed tool. This is the shape
82
79
  * returned by the agent host manifest and surfaced over the wire by
@@ -280,322 +277,197 @@ interface DevframeAgentHost {
280
277
  /** Look up a resource by id. */
281
278
  getResource: (id: string) => AgentResource | undefined;
282
279
  }
283
- //#endregion
284
- //#region src/adapters/flags.d.ts
285
- /**
286
- * Schema map for typed CLI flags. Keys are flag names in camelCase, so
287
- * this matches CAC's parsed-flag output ( `--no-open` → `noOpen` ). Each
288
- * value is any [Standard Schema](https://standardschema.dev/) validator
289
- * (valibot, zod, arktype, devframe's built-in `s`, …) used to both (a)
290
- * derive the CAC option type when the flag is registered and (b) validate
291
- * the parsed value before it's forwarded to `setup(ctx, { flags })`.
292
- */
293
- type CliFlagsSchema = Record<string, StandardSchemaV1>;
280
+ type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
281
+ type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
282
+ type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
283
+ type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
284
+ type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
285
+ type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
294
286
  /**
295
- * Identity helper that preserves the literal schema-map type; use this
296
- * so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
297
- *
298
- * ```ts
299
- * const appFlags = defineCliFlags({
300
- * depth: v.pipe(v.number(), v.integer()),
301
- * config: v.optional(v.string()),
302
- * })
303
- *
304
- * defineDevframe({
305
- * cli: { flags: appFlags },
306
- * setup(ctx, info) {
307
- * const flags = info.flags as InferCliFlags<typeof appFlags>
308
- * flags.depth // number
309
- * flags.config // string | undefined
310
- * },
311
- * })
312
- * ```
287
+ * Serializable patch describing a single mutation to a `SharedState`.
288
+ * Structurally compatible with JSON-Patch and is safe to send over RPC.
313
289
  */
314
- declare function defineCliFlags<T extends CliFlagsSchema>(flags: T): T;
315
- /** Extract the parsed-output type from a {@link CliFlagsSchema}. */
316
- type InferCliFlags<T extends CliFlagsSchema> = { [K in keyof T]: StandardSchemaV1.InferOutput<T[K]>; };
317
- /** Validate the raw cac-parsed bag against a {@link CliFlagsSchema}. */
318
- declare function parseCliFlags(schema: CliFlagsSchema, raw: Record<string, unknown>): {
319
- flags: Record<string, unknown>;
320
- issues?: string[];
321
- };
322
- //#endregion
323
- //#region src/node/auth/handler.d.ts
290
+ interface SharedStatePatch {
291
+ op: 'add' | 'remove' | 'replace';
292
+ path: readonly (string | number)[];
293
+ value?: unknown;
294
+ }
324
295
  /**
325
- * A ready-made pre-auth RPC handler, as produced by
326
- * `devframe/recipes/interactive-auth`'s `createInteractiveAuth`. Bundles
327
- * everything a host adapter needs to wire an authenticated server:
328
- * the handshake RPC functions, the resolver gate, the connect-time trust
329
- * hook, and the startup banner.
330
- *
331
- * `initDevframe` / `initHub` accept one of these directly via their `auth` option
332
- * (see {@link https://devfra.me | devframe}'s server docs), or a host can
333
- * wire the four pieces itself against a lower-level transport.
296
+ * State host that is immutable by default with explicit mutate.
334
297
  */
335
- interface DevframeAuthHandler {
298
+ interface SharedState<T> {
336
299
  /**
337
- * `anonymous:devframe:auth` + `anonymous:devframe:auth:exchange` (the
338
- * handshake), `anonymous:devframe:auth:request-code` (client-requested
339
- * banner print), and `devframe:auth:revoke` (self-revoke); register these
340
- * on the RPC host (e.g. `rpcHost.register(fn)` for each).
300
+ * Get the current state. Immutable.
341
301
  */
342
- rpcFunctions: RpcFunctionDefinitionAny[];
302
+ value: () => Immutable<T>;
343
303
  /**
344
- * Resolver gate: whether `methodName` is callable given `session`'s
345
- * current trust state. Defaults to allowing any `anonymous:`-prefixed
346
- * method (see `isAnonymousRpcMethod`) plus anything once the session is
347
- * trusted.
304
+ * Subscribe to state changes.
348
305
  */
349
- authorize: (methodName: string, session: DevframeNodeRpcSession) => boolean;
306
+ on: EventEmitter<SharedStateEvents<T>>['on'];
350
307
  /**
351
- * Connect-time trust: reads a bearer token off the connection's initial
352
- * request (a static/pre-shared token from `clientAuthTokens`, or a token
353
- * minted by the code exchange) and, when valid, marks the session
354
- * trusted immediately, before the client's own handshake call.
308
+ * Mutate the state.
355
309
  */
356
- onConnect: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
310
+ mutate: (fn: (state: T) => void, syncId?: string) => void;
357
311
  /**
358
- * Print the current one-time code and its magic-link URL. An untrusted
359
- * browser client triggers this itself over
360
- * `anonymous:devframe:auth:request-code`; call it directly when the host
361
- * wants the code on screen without waiting for a client. Safe to call
362
- * repeatedly; it only prints once per code.
312
+ * Apply patches to the state.
363
313
  */
364
- printBanner: () => void;
314
+ patch: (patches: SharedStatePatch[], syncId?: string) => void;
365
315
  /**
366
- * Rewrite a URL the CLI is about to open in the browser (`--open`) so the
367
- * tab lands already authenticated, e.g. appending the current OTP as a
368
- * query param. Called with the fully-resolved target URL; return it
369
- * unchanged to opt out. Optional: a handler that doesn't need this (e.g.
370
- * one gated by a pre-shared bearer token instead of an interactive code)
371
- * can omit it and the URL opens as-is.
316
+ * Sync IDs that have been applied to the state.
372
317
  */
373
- buildOpenUrl?: (url: string) => string;
318
+ syncIds: Set<string>;
319
+ }
320
+ interface SharedStateEvents<T> {
321
+ updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
322
+ }
323
+ interface SharedStateOptions<T> {
324
+ /**
325
+ * Initial state.
326
+ */
327
+ initialValue: T;
328
+ /**
329
+ * Enable patches.
330
+ *
331
+ * @default false
332
+ */
333
+ enablePatches?: boolean;
374
334
  }
375
- //#endregion
376
- //#region src/types/mcp.d.ts
335
+ declare function createSharedState<T extends object>(options: SharedStateOptions<T>): SharedState<T>;
377
336
  /**
378
- * The MCP adapter surface, typed here so `devframe` (which probes for and
379
- * lazily loads `@devframes/agentic`) and `@devframes/agentic/mcp` (which
380
- * implements it) share one contract without a type-level dependency cycle.
381
- * None of these shapes reference the MCP SDK: the SDK is an implementation
382
- * detail of `@devframes/agentic`, freely swappable behind this surface.
383
- * The h3-bound `mountMcpHttp` shapes live in `node/agentic.ts` (exported via
384
- * `devframe/internal`) instead: `devframe/types` must stay lib-neutral, and
385
- * h3's declarations are not.
337
+ * Serialized error shape sent over the wire when a stream ends with a failure.
338
+ * Stays JSON-safe so the strict-JSON encoder can carry it without coercion.
386
339
  */
387
- interface CreateMcpServerOptions {
340
+ interface StreamErrorPayload {
341
+ name: string;
342
+ message: string;
343
+ }
344
+ /**
345
+ * Single buffered chunk in the server-side ring buffer.
346
+ *
347
+ * Sequence numbers start at 1 and increment per write. Subscribers track
348
+ * `lastSeenSeq` and ask for `afterSeq` on resubscribe so the server can
349
+ * replay any chunks the client missed during a brief disconnect.
350
+ */
351
+ interface BufferedChunk<T> {
352
+ seq: number;
353
+ chunk: T;
354
+ }
355
+ interface StreamSinkEvents<T> {
356
+ /** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
357
+ chunk: (seq: number, chunk: T) => void;
358
+ /** Terminal; fired exactly once per sink lifetime. */
359
+ end: (error?: StreamErrorPayload) => void;
360
+ }
361
+ interface CreateStreamSinkOptions {
362
+ id?: string;
388
363
  /**
389
- * Transport to use. `createMcpServer` itself runs `'stdio'` (a standalone
390
- * process with its own host context); the Streamable-HTTP transport is
391
- * served route-based by the dev server instead; see `mountMcpHttp` and
392
- * the `mcp` option on `createDevServer` / `createCac`'s `--mcp` flag.
364
+ * Size of the per-stream ring buffer kept for replay-on-resubscribe.
365
+ * `0` (default) disables replay.
393
366
  */
394
- transport?: 'stdio';
367
+ replayWindow?: number;
368
+ }
369
+ /**
370
+ * Server-side producer handle. Two equivalent surfaces share one piece of
371
+ * state: the imperative `write/error/close` triple, and a `WritableStream<T>`
372
+ * for `pipeTo`-style consumption.
373
+ */
374
+ interface StreamSink<T> {
375
+ /** Stable id used by clients to subscribe. */
376
+ readonly id: string;
395
377
  /**
396
- * Expose shared-state keys as MCP resources.
397
- * - `true` (default): every key the host publishes
398
- * - `false`: none
399
- * - `(key) => boolean`: filter
378
+ * Aborts when the consumer cancels (server-side) or when the transport
379
+ * loses every subscriber. Producers should poll `signal.aborted` and exit
380
+ * cleanly.
400
381
  */
401
- exposeSharedState?: boolean | ((key: string) => boolean);
382
+ readonly signal: AbortSignal;
383
+ /** `true` after `close()` / `error()`. Further writes throw. */
384
+ readonly closed: boolean;
385
+ /** Last allocated sequence number. `0` until the first write. */
386
+ readonly lastSeq: number;
387
+ write: (chunk: T) => void;
388
+ error: (reason: unknown) => void;
389
+ close: () => void;
390
+ /** External-cancel path. Aborts the signal so handlers can short-circuit. */
391
+ abort: (reason?: unknown) => void;
392
+ /** `WritableStream<T>` adapter; same in-memory state as the imperative API. */
393
+ readonly writable: WritableStream<T>;
402
394
  /**
403
- * Usage guidance sent as `instructions` in the MCP `initialize` (and
404
- * `server/discover`) result. Omitted or empty sends no instructions.
395
+ * Internal: RPC layer subscribes to receive chunk/end notifications.
396
+ * Not part of the public contract; do not call directly.
397
+ *
398
+ * @internal
405
399
  */
406
- instructions?: string;
400
+ readonly events: EventEmitter<StreamSinkEvents<T>>;
407
401
  /**
408
- * Resource templates served through `resources/templates/list`, and read
409
- * through `resources/read`. Omitted or empty keeps the resources unchanged.
402
+ * Internal replay buffer. RPC layer reads on (re)subscribe to feed
403
+ * missed chunks before going live.
404
+ *
405
+ * @internal
410
406
  */
411
- resourceTemplates?: McpResourceTemplate[];
407
+ readonly buffer: ReadonlyArray<BufferedChunk<T>>;
408
+ }
409
+ interface CreateStreamReaderOptions {
410
+ id?: string;
412
411
  /**
413
- * Host subscription for resource-list changes; each `notify()` sends
414
- * `notifications/resources/list_changed`. See {@link McpResourcesChangedHook}.
412
+ * Maximum number of buffered chunks held client-side while the consumer
413
+ * isn't draining. On overflow, the oldest chunk is dropped.
415
414
  */
416
- onResourcesChanged?: McpResourcesChangedHook;
415
+ highWaterMark?: number;
417
416
  /**
418
- * Prompt templates served through `prompts/list` and `prompts/get`.
419
- * Omitted or empty declares no `prompts` capability.
417
+ * Called when the chunk queue overflows the high-water mark. The RPC
418
+ * layer wires this to a coded warning; the primitive itself is
419
+ * RPC-agnostic.
420
420
  */
421
- prompts?: McpPrompt[];
422
- /** Override the name reported in the MCP handshake. */
423
- serverName?: string;
424
- /** Override the version reported in the MCP handshake. Defaults to `definition.version ?? '0.0.0'`. */
425
- serverVersion?: string;
426
- /** Called once the transport is connected. */
427
- onReady?: (info: {
428
- transport: 'stdio';
429
- }) => void;
421
+ onOverflow?: (dropped: number) => void;
422
+ /** Called when the consumer cancels; the RPC layer sends `:cancel` upstream. */
423
+ onCancel?: () => void;
430
424
  }
431
425
  /**
432
- * One MCP content block in a tool result. Attach blocks to a tool's return
433
- * value with `withMcpContent` (from `devframe`) to send them in place of the
434
- * default JSON text block. `data` is base64.
426
+ * Client-side consumer handle. Both an `AsyncIterable<T>` (for `for await`)
427
+ * and exposes `readable: ReadableStream<T>` (for `pipeTo`). Pick one; they
428
+ * share a single internal queue, so concurrent draining will race.
435
429
  */
436
- type McpContentBlock = {
437
- type: 'text';
438
- text: string;
439
- } | {
440
- type: 'image';
441
- data: string;
442
- mimeType: string;
443
- } | {
444
- type: 'audio';
445
- data: string;
446
- mimeType: string;
447
- };
448
- interface McpServerHandle {
449
- stop: () => Promise<void>;
450
- }
451
- interface CreateMcpFetchHandlerOptions {
452
- /** Name reported in the MCP handshake. */
453
- serverName: string;
454
- /** Version reported in the MCP handshake. */
455
- serverVersion: string;
456
- /** Expose shared-state keys as MCP resources; see {@link CreateMcpServerOptions.exposeSharedState}. */
457
- exposeSharedState: boolean | ((key: string) => boolean);
458
- /** Usage guidance for the handshake; see {@link CreateMcpServerOptions.instructions}. */
459
- instructions?: string;
460
- /** Resource templates; see {@link CreateMcpServerOptions.resourceTemplates}. */
461
- resourceTemplates?: McpResourceTemplate[];
462
- /** Host subscription for resource-list changes; see {@link CreateMcpServerOptions.onResourcesChanged}. */
463
- onResourcesChanged?: McpResourcesChangedHook;
464
- /** Prompt templates; see {@link CreateMcpServerOptions.prompts}. */
465
- prompts?: McpPrompt[];
466
- /**
467
- * Optional identity check, layered on top of the origin gate and checked
468
- * **after** it: a bearer token string (matched in constant time against
469
- * `Authorization: Bearer <token>`), a `(request) => boolean` callback, or
470
- * `false` (the default) for origin-only, trusting same-machine callers. A
471
- * callback governs identity only and cannot relax the origin gate. See
472
- * {@link McpAuthorization}.
473
- */
474
- authorization?: McpAuthorization;
475
- /**
476
- * Origin allow-list beyond the loopback default. `false` disables the
477
- * origin gate entirely. Default: loopback-only.
478
- *
479
- * Unlike the WS transport, the MCP route does **not** allow `Origin`-less
480
- * requests: a route-based endpoint is reachable by any local process, so a
481
- * request must carry an `Origin` that passes the gate. Native clients
482
- * (e.g. `devframe connect`) send their loopback origin explicitly.
483
- */
484
- allowedOrigins?: readonly string[] | false;
485
- }
486
- /** Connection facts a host knows about a request beyond the `Request` itself. */
487
- interface McpConnectionInfo {
488
- /**
489
- * The connecting peer's remote address (a node socket's `remoteAddress`),
490
- * used to prove a same-machine caller when the endpoint relies on the
491
- * loopback origin default with no identity check. A host that can resolve a
492
- * trustworthy peer address (the h3/node mount) supplies it; when it's
493
- * omitted the origin gate stays the only locality signal.
494
- */
495
- remoteAddress?: string;
496
- }
497
- interface McpFetchHandler {
498
- /**
499
- * WHATWG-`fetch` handler for the MCP endpoint. Hand every method
500
- * (POST/GET/DELETE) on the endpoint's path to it; routing by path is the
501
- * host's job. Pass {@link McpConnectionInfo} when the host can resolve the
502
- * peer address so the default trust boundary can enforce same-machine
503
- * locality.
504
- */
505
- fetch: (request: Request, connection?: McpConnectionInfo) => Promise<Response>;
506
- /** Tear down the handler (aborts in-flight exchanges, drops the change bridge). */
507
- dispose: () => Promise<void>;
508
- }
509
- /** One concrete resource that an {@link McpResourceTemplate} lists in `resources/list`. */
510
- interface McpResourceTemplateEntry {
511
- /** The resource URI. It SHOULD match the `uriTemplate` of its template, so `resources/read` finds it. */
512
- uri: string;
513
- name: string;
514
- /** Human-readable name for display. */
515
- title?: string;
516
- description?: string;
517
- /** Defaults to the `mimeType` of the template. */
518
- mimeType?: string;
430
+ interface StreamReader<T> extends AsyncIterable<T> {
431
+ readonly id: string;
432
+ readonly cancelled: boolean;
433
+ readonly done: boolean;
434
+ /** Highest `seq` observed. Used for replay on reconnect. */
435
+ readonly lastSeenSeq: number;
436
+ /** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
437
+ readonly readable: ReadableStream<T>;
438
+ cancel: () => void;
439
+ /** @internal */
440
+ _push: (seq: number, chunk: T) => void;
441
+ /** @internal */
442
+ _end: (error?: StreamErrorPayload) => void;
519
443
  }
520
444
  /**
521
- * What an {@link McpResourceTemplate} returns from `read`, sent as the
522
- * `resources/read` result: `text` as is, or else `json` as JSON text.
445
+ * Build a server-side stream sink. RPC-agnostic; the RPC host wires
446
+ * `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
447
+ * for late or reconnecting subscribers.
523
448
  */
524
- interface McpResourceTemplateContent {
525
- text?: string;
526
- json?: unknown;
527
- /** Defaults to the `mimeType` of the template, then `application/json`. */
528
- mimeType?: string;
529
- }
449
+ declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
530
450
  /**
531
- * A resource template the MCP server lists in `resources/templates/list` and
532
- * reads in `resources/read`. The `uriTemplate` uses RFC 6570 level 1 only:
533
- * plain `{name}` expressions, with text between any two of them. Each
534
- * expression matches a non-empty value with no `/`, and its value is
535
- * percent-decoded.
536
- *
537
- * `list` and `read` get the node context of the devframe (or of the hub), so
538
- * they can read live state such as `ctx.rpc.sharedState` or `ctx.agent`. The
539
- * route options are resolved before `setup()` runs, so a template cannot
540
- * capture that context in a closure.
451
+ * Build a client-side stream reader. RPC-agnostic; the RPC host calls
452
+ * `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
453
+ * terminal frame. Consumers iterate with `for await` or pipe `readable`.
541
454
  */
542
- interface McpResourceTemplate {
543
- /** RFC 6570 level 1 URI template, e.g. `my-tool://file/{path}`. Anything else throws `DF0084`. */
544
- uriTemplate: string;
545
- name: string;
546
- /** Human-readable name for display. */
455
+ declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
456
+ interface BrowserAgentToolManifest {
457
+ id: string;
547
458
  title?: string;
548
- description?: string;
549
- /** MIME type of every resource the template stands for. */
550
- mimeType?: string;
551
- /** The concrete resources the template stands for right now. They are added to `resources/list`. */
552
- list?: (ctx: DevframeNodeContext) => McpResourceTemplateEntry[] | Promise<McpResourceTemplateEntry[]>;
553
- /** Read one resource: `params` holds the decoded value of each `{name}`, `uri` the requested URI. */
554
- read: (params: Record<string, string>, uri: string, ctx: DevframeNodeContext) => McpResourceTemplateContent | Promise<McpResourceTemplateContent>;
555
- }
556
- /**
557
- * A host subscription for resource-list changes. Call `notify()` each time
558
- * the resources change; the MCP server then sends
559
- * `notifications/resources/list_changed`. Return a function that removes the
560
- * subscription; the MCP server calls it on teardown.
561
- */
562
- type McpResourcesChangedHook = (notify: () => void, ctx: DevframeNodeContext) => () => void;
563
- /** One argument of an {@link McpPrompt}. */
564
- interface McpPromptArgument {
565
- name: string;
566
- description?: string;
567
- /** `prompts/get` answers invalid params when a required argument is missing or empty. */
568
- required?: boolean;
569
- }
570
- /** One message of the answer to `prompts/get`. */
571
- interface McpPromptMessage {
572
- role: 'user' | 'assistant';
573
- content: {
574
- type: 'text';
575
- text: string;
576
- };
577
- }
578
- /** What an {@link McpPrompt} returns from `get`, sent as the `prompts/get` result. */
579
- interface McpPromptResult {
580
- description?: string;
581
- messages: McpPromptMessage[];
459
+ description: string;
460
+ safety: 'read' | 'action' | 'destructive';
461
+ tags?: readonly string[];
462
+ inputSchema?: unknown;
582
463
  }
583
- /**
584
- * A prompt template the MCP server lists in `prompts/list` and builds in
585
- * `prompts/get`. A coding agent usually offers it to the user as a slash
586
- * command. Each `name` is used once; a repeated name throws `DF0085`.
587
- */
588
- interface McpPrompt {
589
- name: string;
590
- /** Human-readable name for display. */
591
- title?: string;
592
- description?: string;
593
- arguments?: McpPromptArgument[];
594
- /** Build the messages from the arguments the coding agent sent. */
595
- get: (args: Record<string, string>) => McpPromptResult | Promise<McpPromptResult>;
464
+ /** What a connected document reports about itself alongside its tool manifest. */
465
+ interface BrowserAgentClientInfo {
466
+ url: string;
467
+ title: string;
468
+ visible: boolean;
469
+ focused: boolean;
596
470
  }
597
- //#endregion
598
- //#region src/types/diagnostics.d.ts
599
471
  /**
600
472
  * The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
601
473
  * any registered code name to its `nostics` handle (a callable that builds
@@ -652,8 +524,6 @@ interface DevframeDiagnosticsHost {
652
524
  */
653
525
  defineDiagnostics: typeof defineDiagnostics;
654
526
  }
655
- //#endregion
656
- //#region src/types/host.d.ts
657
527
  interface DevframeHost {
658
528
  /**
659
529
  * Serve static assets at the given URL base: a local directory, or a
@@ -721,274 +591,44 @@ interface DevframeHost {
721
591
  * private, `global` is per-user.
722
592
  */
723
593
  type DevframeStorageScope = 'workspace' | 'project' | 'global';
724
- //#endregion
725
- //#region src/client/browser-agent.d.ts
726
- interface BrowserAgentToolManifest {
727
- id: string;
728
- title?: string;
729
- description: string;
730
- safety: 'read' | 'action' | 'destructive';
731
- tags?: readonly string[];
732
- inputSchema?: unknown;
733
- }
734
- /** What a connected document reports about itself alongside its tool manifest. */
735
- interface BrowserAgentClientInfo {
736
- url: string;
737
- title: string;
738
- visible: boolean;
739
- focused: boolean;
740
- }
741
- //#endregion
742
- //#region src/types/rpc-augments.d.ts
743
- /**
744
- * To be extended
745
- */
746
- interface DevframeRpcClientFunctions {
747
- /** Clear cached RPC results and refresh cache eligibility. */
748
- 'devframe:rpc:cache:invalidate': () => Promise<void>;
749
- /** Invoke a tool registered in this browser document. @internal */
750
- 'devframe:agent:invoke-client-tool': (id: string, args: Record<string, unknown>) => Promise<unknown>;
751
- /**
752
- * Server→client notification that this connection's auth token has been
753
- * revoked. The client drops to untrusted on receipt. Broadcast by
754
- * `revokeActiveConnectionsForToken`.
755
- *
756
- * @internal
757
- */
758
- 'devframe:auth:revoked': () => Promise<void>;
759
- /**
760
- * Streaming chunk pushed from server to subscribed clients. Wired by
761
- * `RpcStreamingHost`; do not register manually.
762
- *
763
- * @internal
764
- */
765
- 'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
766
- /**
767
- * Streaming terminator pushed from server to subscribed clients. Wired by
768
- * `RpcStreamingHost`; do not register manually.
769
- *
770
- * @internal
771
- */
772
- 'devframe:streaming:end': (channel: string, id: string, error?: {
773
- name: string;
774
- message: string;
775
- }) => Promise<void>;
594
+ interface DevframeViewHost {
776
595
  /**
777
- * Server→client cancel for an in-flight upload. Wired by
778
- * `RpcStreamingHost`; do not register manually.
596
+ * Static mounts registered through {@link DevframeViewHost.hostStatic}, each
597
+ * carrying the `resolveFrom` base it was mounted with so a build step that
598
+ * copies these itself (rather than serving them live) re-resolves a remote
599
+ * source to the same locally-installed copy it would serve live.
779
600
  *
780
601
  * @internal
781
602
  */
782
- 'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>;
603
+ buildStaticDirs: {
604
+ baseUrl: string;
605
+ source: StaticAssetsSource;
606
+ resolveFrom?: string | null;
607
+ }[];
783
608
  /**
784
- * Full shared-state snapshot pushed from server to subscribed clients.
785
- * Wired by `RpcSharedStateHost`; do not register manually.
609
+ * Helper to host static files
610
+ * - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
611
+ * - In `build` mode, it will copy the static files to the dist directory
786
612
  *
787
- * @internal
788
- */
789
- 'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>;
790
- /**
791
- * Incremental shared-state patch pushed from server to subscribed clients.
792
- * Wired by `RpcSharedStateHost`; do not register manually.
613
+ * Accepts a local dist directory, or a {@link StaticAssetsSource} remote
614
+ * declaration served through devframe's caching CDN back-proxy.
793
615
  *
794
- * @internal
616
+ * `defaultResolveFrom` overrides, for this call only, the context's own
617
+ * `importMetaUrl` as the default `resolveFrom` for a remote source that
618
+ * doesn't set one. A shared host that mounts assets on behalf of another
619
+ * devframe (a hub installing a plugin) passes that plugin's `importMetaUrl`
620
+ * so the assets resolve against the plugin's dependency graph rather than
621
+ * the host's.
795
622
  */
796
- 'devframe:rpc:client-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
623
+ hostStatic: (baseUrl: string, source: StaticAssetsSource, defaultResolveFrom?: string | null) => void;
797
624
  }
625
+ type AnyRpcFn = (...args: any[]) => any;
798
626
  /**
799
- * To be extended
800
- */
801
- interface DevframeRpcServerFunctions {
802
- /** Methods eligible for automatic client caching. @internal */
803
- 'devframe:rpc:cacheable-functions': () => Promise<string[]>;
804
- /** Replace this connection's browser-agent tool manifest, tagged with the calling tab's stable client id. @internal */
805
- 'devframe:agent:sync-client-tools': (clientId: string, tools: BrowserAgentToolManifest[], info: BrowserAgentClientInfo) => Promise<void>;
806
- /**
807
- * Authenticate a connection with a previously-issued bearer token; resolves
808
- * whether the connection is now trusted. The interactive handler is provided
809
- * by the host adapter (e.g. Vite DevTools); the standalone server registers
810
- * an auto-trust noop when `auth: false`.
811
- *
812
- * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
813
- * reachable before the connection is trusted; this is the *only* rule the
814
- * pre-trust gate applies, no per-method allowlist.
815
- *
816
- * @internal
817
- */
818
- 'anonymous:devframe:auth': (params: {
819
- authToken: string;
820
- ua: string;
821
- origin: string;
822
- }) => Promise<{
823
- isTrusted: boolean;
824
- }>;
825
- /**
826
- * Exchange a one-time authentication code (shown by the dev server) for a fresh,
827
- * node-issued bearer token, returning the token on success or `null`. The
828
- * handler is provided by the host adapter on top of `exchangeTempAuthCode`.
829
- *
830
- * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
831
- * reachable before the connection is trusted.
832
- *
833
- * @internal
834
- */
835
- 'anonymous:devframe:auth:exchange': (params: {
836
- code: string;
837
- ua: string;
838
- origin: string;
839
- }) => Promise<{
840
- authToken: string | null;
841
- }>;
842
- /**
843
- * Ask the server to print its auth banner (code + magic link) for this
844
- * client, e.g. when an auth UI first shows; `reissue: true` rotates the code
845
- * first (the manual "re-issue" action). Registered by
846
- * `recipes/interactive-auth`; a repeat request for an already-printed code
847
- * is a no-op.
848
- *
849
- * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
850
- * reachable before the connection is trusted.
851
- *
852
- * @internal
853
- */
854
- 'anonymous:devframe:auth:request-code': (params: {
855
- ua: string;
856
- origin: string;
857
- reissue?: boolean;
858
- }) => Promise<void>;
859
- /**
860
- * Self-revoke: the caller asks the server to revoke its own bearer token
861
- * (if any) and drop to untrusted. Requires an already-trusted caller, so
862
- * unlike the two handshake methods above it does **not** carry the
863
- * `anonymous:` prefix. Registered by `recipes/interactive-auth`.
864
- *
865
- * @internal
866
- */
867
- 'devframe:auth:revoke': () => Promise<void>;
868
- /**
869
- * Subscribe a client to a shared-state key. Wired by
870
- * `RpcSharedStateHost`; do not register manually.
871
- *
872
- * @internal
873
- */
874
- 'devframe:rpc:server-state:subscribe': (key: string) => Promise<void>;
875
- /**
876
- * Read the current value for a shared-state key. Wired by
877
- * `RpcSharedStateHost`; do not register manually.
878
- *
879
- * @internal
880
- */
881
- 'devframe:rpc:server-state:get': (key: string) => Promise<any>;
882
- /**
883
- * Replace a shared-state value (from the client). Wired by
884
- * `RpcSharedStateHost`; do not register manually.
885
- *
886
- * @internal
887
- */
888
- 'devframe:rpc:server-state:set': (key: string, value: any, syncId: string) => Promise<void>;
889
- /**
890
- * Apply a patch to a shared-state value (from the client). Wired by
891
- * `RpcSharedStateHost`; do not register manually.
892
- *
893
- * @internal
894
- */
895
- 'devframe:rpc:server-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
896
- /**
897
- * Client→server streaming subscription with optional replay cursor.
898
- * Wired by `RpcStreamingHost`; do not register manually.
899
- *
900
- * @internal
901
- */
902
- 'devframe:streaming:subscribe': (channel: string, id: string, opts?: {
903
- afterSeq?: number;
904
- }) => Promise<void>;
905
- /**
906
- * Client→server streaming unsubscribe. Wired by `RpcStreamingHost`;
907
- * do not register manually.
908
- *
909
- * @internal
910
- */
911
- 'devframe:streaming:unsubscribe': (channel: string, id: string) => Promise<void>;
912
- /**
913
- * Client→server streaming cancellation request. Wired by
914
- * `RpcStreamingHost`; do not register manually.
915
- *
916
- * @internal
917
- */
918
- 'devframe:streaming:cancel': (channel: string, id: string) => Promise<void>;
919
- /**
920
- * Client→server upload chunk. Wired by `RpcStreamingHost`; do not
921
- * register manually.
922
- *
923
- * @internal
924
- */
925
- 'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
926
- /**
927
- * Client→server upload terminator. Wired by `RpcStreamingHost`; do not
928
- * register manually.
929
- *
930
- * @internal
931
- */
932
- 'devframe:streaming:upload-end': (channel: string, id: string, error?: {
933
- name: string;
934
- message: string;
935
- }) => Promise<void>;
936
- }
937
- /**
938
- * To be extended
939
- */
940
- interface DevframeRpcSharedStates {
941
- /**
942
- * Wire-service advertisements: package name → `{ package, version,
943
- * scope, meta }` for every installed {@link import('./services').DevframeServiceDefinition}.
944
- * Written by the node services host at the `ready()` barrier; clients read
945
- * it through `client.services` (or subscribe to it directly for
946
- * reactivity). Read-only from the browser.
947
- */
948
- 'devframe:services': DevframeServicesState;
949
- }
950
- //#endregion
951
- //#region src/types/views.d.ts
952
- interface DevframeViewHost {
953
- /**
954
- * Static mounts registered through {@link DevframeViewHost.hostStatic}, each
955
- * carrying the `resolveFrom` base it was mounted with so a build step that
956
- * copies these itself (rather than serving them live) re-resolves a remote
957
- * source to the same locally-installed copy it would serve live.
958
- *
959
- * @internal
960
- */
961
- buildStaticDirs: {
962
- baseUrl: string;
963
- source: StaticAssetsSource;
964
- resolveFrom?: string | null;
965
- }[];
966
- /**
967
- * Helper to host static files
968
- * - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
969
- * - In `build` mode, it will copy the static files to the dist directory
970
- *
971
- * Accepts a local dist directory, or a {@link StaticAssetsSource} remote
972
- * declaration served through devframe's caching CDN back-proxy.
973
- *
974
- * `defaultResolveFrom` overrides, for this call only, the context's own
975
- * `importMetaUrl` as the default `resolveFrom` for a remote source that
976
- * doesn't set one. A shared host that mounts assets on behalf of another
977
- * devframe (a hub installing a plugin) passes that plugin's `importMetaUrl`
978
- * so the assets resolve against the plugin's dependency graph rather than
979
- * the host's.
980
- */
981
- hostStatic: (baseUrl: string, source: StaticAssetsSource, defaultResolveFrom?: string | null) => void;
982
- }
983
- //#endregion
984
- //#region src/types/scope.d.ts
985
- type AnyRpcFn = (...args: any[]) => any;
986
- /**
987
- * Resolve a scoped bare name `T` back to its fully-qualified entry in
988
- * `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
989
- * guard keeps TypeScript from rejecting a generic index into the
990
- * key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
991
- * the registry, while preserving each name's argument/return typing.
627
+ * Resolve a scoped bare name `T` back to its fully-qualified entry in
628
+ * `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
629
+ * guard keeps TypeScript from rejecting a generic index into the
630
+ * key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
631
+ * the registry, while preserving each name's argument/return typing.
992
632
  */
993
633
  type ScopedRpcFn<Registry, NS extends string, T extends string> = `${NS}:${T}` extends keyof Registry ? Extract<Registry[`${NS}:${T}`], AnyRpcFn> : AnyRpcFn;
994
634
  /**
@@ -1161,8 +801,6 @@ interface DevframeScopedNodeContext<NS extends string = string, Settings extends
1161
801
  */
1162
802
  scope: DevframeNodeContext['scope'];
1163
803
  }
1164
- //#endregion
1165
- //#region src/types/services.d.ts
1166
804
  /**
1167
805
  * Augmentation point mapping service ids to their implementation types.
1168
806
  * Providers extend this via `declare module 'devframe'`.
@@ -1356,1097 +994,1425 @@ interface DevframeServicesHost {
1356
994
  */
1357
995
  ready: () => Promise<void>;
1358
996
  }
1359
- //#endregion
1360
- //#region src/types/devframe.d.ts
1361
- /**
1362
- * Classification of how a devframe is being deployed. Hosted adapters
1363
- * (`vite`, `embedded`) share their origin with a host app and must
1364
- * namespace their mount path under `/__<id>/`. Standalone adapters
1365
- * (`cli`, `build`) own the origin and default to `/`.
1366
- */
1367
- type DevframeDeploymentKind = 'standalone' | 'hosted';
1368
997
  /**
1369
- * How a hub deduplicates devframes that share an `id` when more than one
1370
- * is mounted onto the same hub. See {@link DevframeDefinition.duplicationStrategy}.
1371
- *
1372
- * - `'warn'` (default): keep the first registration, drop later
1373
- * duplicates, and emit a warning diagnostic (`DF8105`).
1374
- * - `'silent'`: drop later duplicates without warning.
1375
- * - `'throw'`: throw when a duplicate is mounted.
1376
- * - `'duplicate'`: let every instance coexist under a disambiguated
1377
- * dock id.
1378
- */
1379
- type DevframeDuplicationStrategy = 'warn' | 'silent' | 'throw' | 'duplicate';
1380
- /**
1381
- * Controls where the browser opens the RPC WebSocket, advertised in
1382
- * `__connection.json` and used to bind the dev server. The three shapes map
1383
- * to the three connection scenarios; precedence is `url` > `port` > `route`:
1384
- *
1385
- * 1. **Same server, different route** (default): leave `port`/`url` unset.
1386
- * The socket shares the HTTP server's port and binds to `route`
1387
- * (`__ws`). The client connects to its own origin, so the link
1388
- * survives a reverse proxy that rewrites the host/port/subpath.
1389
- *
1390
- * 2. **Different port**: set `port`. The socket binds on its own port on the
1391
- * same host; the client targets `ws(s)://<page-host>:<port>/<route>`.
1392
- *
1393
- * 3. **Remote, different origin**: set `url` to a full `ws://`/`wss://`
1394
- * endpoint (e.g. a public tunnel or relay). The client uses it verbatim.
998
+ * To be extended
1395
999
  */
1396
- interface DevframeWsOptions {
1000
+ interface DevframeRpcClientFunctions {
1001
+ /** Clear cached RPC results and refresh cache eligibility. */
1002
+ 'devframe:rpc:cache:invalidate': () => Promise<void>;
1003
+ /** Invoke a tool registered in this browser document. @internal */
1004
+ 'devframe:agent:invoke-client-tool': (id: string, args: Record<string, unknown>) => Promise<unknown>;
1397
1005
  /**
1398
- * Upgrade route segment the socket binds to and is advertised at, relative
1399
- * to the SPA base. Default: `__ws`.
1006
+ * Server→client notification that this connection's auth token has been
1007
+ * revoked. The client drops to untrusted on receipt. Broadcast by
1008
+ * `revokeActiveConnectionsForToken`.
1009
+ *
1010
+ * @internal
1400
1011
  */
1401
- route?: string;
1012
+ 'devframe:auth:revoked': () => Promise<void>;
1402
1013
  /**
1403
- * Bind the socket on its own port instead of sharing the HTTP port. The
1404
- * browser connects to this port on the page's hostname. Implies
1405
- * {@link DevframeWsOptions.sidecar}.
1014
+ * Streaming chunk pushed from server to subscribed clients. Wired by
1015
+ * `RpcStreamingHost`; do not register manually.
1016
+ *
1017
+ * @internal
1406
1018
  */
1407
- port?: number;
1019
+ 'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
1408
1020
  /**
1409
- * Start a side-car WebSocket server on a free port, for hosts whose request
1410
- * handlers can't accept upgrades (Next.js route handlers, Nitro, Rsbuild).
1411
- * The resolved port is advertised in `__connection.json`, so the browser
1412
- * finds it without any further wiring. Set `port` instead to pin it.
1021
+ * Streaming terminator pushed from server to subscribed clients. Wired by
1022
+ * `RpcStreamingHost`; do not register manually.
1023
+ *
1024
+ * @internal
1413
1025
  */
1414
- sidecar?: boolean;
1026
+ 'devframe:streaming:end': (channel: string, id: string, error?: {
1027
+ name: string;
1028
+ message: string;
1029
+ }) => Promise<void>;
1415
1030
  /**
1416
- * Advertise a fixed, fully-qualified endpoint on another origin (a full
1417
- * `ws://`/`wss://` URL). Takes precedence over `port`/`route` in the meta.
1031
+ * Server→client cancel for an in-flight upload. Wired by
1032
+ * `RpcStreamingHost`; do not register manually.
1033
+ *
1034
+ * @internal
1418
1035
  */
1419
- url?: string;
1420
- }
1421
- /**
1422
- * Controls the SSE RPC endpoint, the WebSocket-free transport for hosts
1423
- * and proxies where the upgrade isn't available. It rides whatever HTTP
1424
- * surface serves the instance (the same one serving `__connection.json`),
1425
- * so a relative `route` always resolves; there is no port plumbing of its
1426
- * own. Enabled by default alongside the WebSocket; pass `sse: false` to
1427
- * disable, or `ws: false` to run SSE-only (`backend: 'sse'`).
1428
- */
1429
- interface DevframeSseOptions {
1036
+ 'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>;
1430
1037
  /**
1431
- * Route segment the SSE endpoint binds to and is advertised at, relative
1432
- * to the SPA base. Default: `__sse`.
1038
+ * Full shared-state snapshot pushed from server to subscribed clients.
1039
+ * Wired by `RpcSharedStateHost`; do not register manually.
1040
+ *
1041
+ * @internal
1433
1042
  */
1434
- route?: string;
1043
+ 'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>;
1044
+ /**
1045
+ * Incremental shared-state patch pushed from server to subscribed clients.
1046
+ * Wired by `RpcSharedStateHost`; do not register manually.
1047
+ *
1048
+ * @internal
1049
+ */
1050
+ 'devframe:rpc:client-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
1435
1051
  }
1436
1052
  /**
1437
- * An **optional** identity check layered on top of the origin gate. The
1438
- * route-based MCP endpoint trusts same-machine callers by default (the
1439
- * loopback origin gate is enough), so this is opt-in hardening for the cases
1440
- * where a same-machine process is not a trust boundary, like a LAN/tunnel
1441
- * origin, a shared/CI box, or a destructive tool surface:
1442
- *
1443
- * - a non-empty **bearer token string**: the request must carry
1444
- * `Authorization: Bearer <token>`, compared in constant time. Back it with
1445
- * an environment variable rather than a literal;
1446
- * - a **callback** `(request) => boolean | Promise<boolean>`: a custom
1447
- * identity check (validate a signed header, an mTLS-derived claim, …). It
1448
- * only governs identity and cannot relax the origin gate;
1449
- * - `false`, the default: **origin-only**, trusting same-machine callers.
1450
- */
1451
- type McpAuthorization = string | ((request: Request) => boolean | Promise<boolean>) | false;
1452
- /**
1453
- * The route-based MCP setting accepted everywhere a host mounts a devframe
1454
- * (`initDevframe` / `initHub` / `createDevServer` options, `createCac` /
1455
- * `--mcp`, the framework kits):
1456
- *
1457
- * - `'auto'`, the default: mount the route when the devframe exposes an
1458
- * agent surface (an `agent`-flagged RPC function, or a tool / resource /
1459
- * provider registered on `ctx.agent`) AND the optional `@devframes/agentic`
1460
- * peer (the MCP adapter and SDK) is installed. An empty agent surface
1461
- * mounts nothing and loads no MCP code; a non-empty one without the peer
1462
- * warns once (DF0078) and mounts nothing.
1463
- * - `true`: always mount at the default `__mcp` route; a missing
1464
- * `@devframes/agentic` throws DF0079.
1465
- * - `false`: never mount, and never probe or warn.
1466
- * - {@link McpRouteOptions}: always mount (like `true`), with a custom route
1467
- * path, origin allow-list, or {@link McpAuthorization} identity check.
1468
- *
1469
- * A mounted route trusts same-machine callers by default (the loopback
1470
- * origin gate), exactly like `mcp: true`.
1471
- */
1472
- type McpSetting = boolean | 'auto' | McpRouteOptions;
1473
- /**
1474
- * Configuration for the route-based MCP server mounted alongside the dev
1475
- * server (via {@link DevframeCliOptions.mcp}). The endpoint speaks
1476
- * the MCP Streamable-HTTP transport over the same origin as the SPA,
1477
- * exposing the definition's `ctx.agent` tools + shared-state resources to
1478
- * external MCP clients connected to the *running* server.
1053
+ * To be extended
1479
1054
  */
1480
- interface McpRouteOptions {
1055
+ interface DevframeRpcServerFunctions {
1056
+ /** Methods eligible for automatic client caching. @internal */
1057
+ 'devframe:rpc:cacheable-functions': () => Promise<string[]>;
1058
+ /** Replace this connection's browser-agent tool manifest, tagged with the calling tab's stable client id. @internal */
1059
+ 'devframe:agent:sync-client-tools': (clientId: string, tools: BrowserAgentToolManifest[], info: BrowserAgentClientInfo) => Promise<void>;
1481
1060
  /**
1482
- * Route segment the MCP endpoint binds to, relative to the SPA base.
1483
- * Default: `__mcp` (i.e. `/__mcp` standalone, `/__<id>/__mcp` hosted).
1061
+ * Authenticate a connection with a previously-issued bearer token; resolves
1062
+ * whether the connection is now trusted. The interactive handler is provided
1063
+ * by the host adapter (e.g. Vite DevTools); the standalone server registers
1064
+ * an auto-trust noop when `auth: false`.
1065
+ *
1066
+ * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
1067
+ * reachable before the connection is trusted; this is the *only* rule the
1068
+ * pre-trust gate applies, no per-method allowlist.
1069
+ *
1070
+ * @internal
1484
1071
  */
1485
- path?: string;
1072
+ 'anonymous:devframe:auth': (params: {
1073
+ authToken: string;
1074
+ ua: string;
1075
+ origin: string;
1076
+ }) => Promise<{
1077
+ isTrusted: boolean;
1078
+ }>;
1486
1079
  /**
1487
- * Optional identity check, layered on top of the origin gate and checked
1488
- * **after** it. Defaults to origin-only (`false`): the route trusts
1489
- * same-machine callers, since the loopback origin gate already keeps
1490
- * arbitrary remote/browser callers out. Set a bearer token or a callback to
1491
- * harden the route when a same-machine process is not your trust boundary
1492
- * (LAN/tunnel origin, shared/CI host, destructive tools). See
1493
- * {@link McpAuthorization}.
1080
+ * Exchange a one-time authentication code (shown by the dev server) for a fresh,
1081
+ * node-issued bearer token, returning the token on success or `null`. The
1082
+ * handler is provided by the host adapter on top of `exchangeTempAuthCode`.
1083
+ *
1084
+ * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
1085
+ * reachable before the connection is trusted.
1086
+ *
1087
+ * @internal
1494
1088
  */
1495
- authorization?: McpAuthorization;
1089
+ 'anonymous:devframe:auth:exchange': (params: {
1090
+ code: string;
1091
+ ua: string;
1092
+ origin: string;
1093
+ }) => Promise<{
1094
+ authToken: string | null;
1095
+ }>;
1496
1096
  /**
1497
- * Extra `Origin` header values to accept beyond the loopback default
1498
- * (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less native client).
1499
- * Add your LAN/tunnel origin here when reaching the endpoint from another
1500
- * host, mirroring the WS transport's origin gate. Pass `false` to disable
1501
- * origin checking entirely (not recommended). Default: loopback-only.
1097
+ * Ask the server to print its auth banner (code + magic link) for this
1098
+ * client, e.g. when an auth UI first shows; `reissue: true` rotates the code
1099
+ * first (the manual "re-issue" action). Registered by
1100
+ * `recipes/interactive-auth`; a repeat request for an already-printed code
1101
+ * is a no-op.
1502
1102
  *
1503
- * This is the endpoint's DNS-rebinding protection: the shared
1504
- * `isAllowedOrigin` gate the WS upgrade already uses, applied as external
1505
- * middleware (the approach the MCP SDK now recommends over its own
1506
- * deprecated `allowedHosts`/`allowedOrigins` transport flags). When you
1507
- * widen it past loopback, layer on {@link McpRouteOptions.authorization} to
1508
- * prove identity too.
1103
+ * Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
1104
+ * reachable before the connection is trusted.
1105
+ *
1106
+ * @internal
1509
1107
  */
1510
- allowedOrigins?: readonly string[] | false;
1108
+ 'anonymous:devframe:auth:request-code': (params: {
1109
+ ua: string;
1110
+ origin: string;
1111
+ reissue?: boolean;
1112
+ }) => Promise<void>;
1511
1113
  /**
1512
- * Which shared-state keys the route exposes to agents, through the built-in
1513
- * `devframe_state_read` tool and the `devframe://state/<key>` resources.
1514
- * - `true` (default): every key the host publishes
1515
- * - `false`: none
1516
- * - `(key) => boolean`: only the keys the filter accepts
1114
+ * Self-revoke: the caller asks the server to revoke its own bearer token
1115
+ * (if any) and drop to untrusted. Requires an already-trusted caller, so
1116
+ * unlike the two handshake methods above it does **not** carry the
1117
+ * `anonymous:` prefix. Registered by `recipes/interactive-auth`.
1517
1118
  *
1518
- * The states themselves stay available to the UI over RPC.
1119
+ * @internal
1519
1120
  */
1520
- exposeSharedState?: boolean | ((key: string) => boolean);
1121
+ 'devframe:auth:revoke': () => Promise<void>;
1521
1122
  /**
1522
- * Usage guidance for the route, sent as `instructions` in the MCP
1523
- * `initialize` (and `server/discover`) result. An MCP client can add it to
1524
- * the coding agent's context, for example to say which tool to call first.
1525
- * Omitted or empty sends no instructions.
1123
+ * Subscribe a client to a shared-state key. Wired by
1124
+ * `RpcSharedStateHost`; do not register manually.
1125
+ *
1126
+ * @internal
1526
1127
  */
1527
- instructions?: string;
1128
+ 'devframe:rpc:server-state:subscribe': (key: string) => Promise<void>;
1528
1129
  /**
1529
- * Resource templates the route serves through MCP
1530
- * `resources/templates/list`. `resources/read` tries them after the
1531
- * registered resources and the shared-state resources (a
1532
- * `devframe://resource/` or `devframe://state/` URI never reaches them);
1533
- * the first template whose `uriTemplate` matches the URI reads it. `resources/list` adds the
1534
- * entries each template's `list` returns. Omitted or empty keeps the
1535
- * resources unchanged.
1130
+ * Read the current value for a shared-state key. Wired by
1131
+ * `RpcSharedStateHost`; do not register manually.
1132
+ *
1133
+ * @internal
1536
1134
  */
1537
- resourceTemplates?: McpResourceTemplate[];
1135
+ 'devframe:rpc:server-state:get': (key: string) => Promise<any>;
1538
1136
  /**
1539
- * Host subscription for resource-list changes. Call `notify()` each time
1540
- * the resources change (for example, the entries of a resource template);
1541
- * the route then sends `notifications/resources/list_changed`. Return a
1542
- * function that removes the subscription; the route calls it on teardown.
1137
+ * Replace a shared-state value (from the client). Wired by
1138
+ * `RpcSharedStateHost`; do not register manually.
1139
+ *
1140
+ * @internal
1543
1141
  */
1544
- onResourcesChanged?: McpResourcesChangedHook;
1142
+ 'devframe:rpc:server-state:set': (key: string, value: any, syncId: string) => Promise<void>;
1545
1143
  /**
1546
- * Prompt templates the route serves through MCP `prompts/list` and
1547
- * `prompts/get`. Omitted or empty declares no `prompts` capability.
1144
+ * Apply a patch to a shared-state value (from the client). Wired by
1145
+ * `RpcSharedStateHost`; do not register manually.
1146
+ *
1147
+ * @internal
1548
1148
  */
1549
- prompts?: McpPrompt[];
1550
- }
1551
- interface DevframeCliOptions {
1552
- /** Binary name; default: the devframe's `id`. */
1553
- command?: string;
1554
- /** Preferred port for the dev server (default 9999). */
1555
- port?: number;
1556
- /** Port scan range, forwarded to `get-port-please`. */
1557
- portRange?: [number, number];
1558
- /** Prefer a random open port. */
1559
- random?: boolean;
1560
- /** Default host to bind to; `--host` overrides. */
1561
- host?: string;
1149
+ 'devframe:rpc:server-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
1562
1150
  /**
1563
- * Auto-open the browser when the dev server starts.
1564
- * `true` opens the resolved origin; a string opens that relative path.
1565
- * The `--open` / `--no-open` flags override this.
1151
+ * Client→server streaming subscription with optional replay cursor.
1152
+ * Wired by `RpcStreamingHost`; do not register manually.
1153
+ *
1154
+ * @internal
1566
1155
  */
1567
- open?: boolean | string;
1156
+ 'devframe:streaming:subscribe': (channel: string, id: string, opts?: {
1157
+ afterSeq?: number;
1158
+ }) => Promise<void>;
1568
1159
  /**
1569
- * Authentication for the standalone dev server.
1570
- *
1571
- * - `undefined` / `true`: the standalone adapters (`cli` /
1572
- * served `build`) auto-wire devframe's interactive OTP auth
1573
- * (`createInteractiveAuth`): an untrusted client can only reach
1574
- * `anonymous:` methods until it exchanges the printed one-time code.
1575
- * The adapter prints the code + magic-link banner once the server is
1576
- * listening.
1577
- * - `false`: no gate, for trusted single-user localhost tools where an
1578
- * auth round-trip only gets in the way (the built-in plugins set this).
1579
- * The `--no-auth` CLI flag maps here for one-off runs.
1580
- * - A {@link DevframeAuthHandler}: a custom handler (e.g. a tuned
1581
- * `createInteractiveAuth`, or an entirely different scheme) passed
1582
- * straight through to the RPC transport binding.
1160
+ * Client→server streaming unsubscribe. Wired by `RpcStreamingHost`;
1161
+ * do not register manually.
1583
1162
  *
1584
- * Hosted adapters (`vite`, `embedded`) ignore this and defer to the host's
1585
- * auth; `@vitejs/devtools` honors the equivalent `devtools.clientAuth`.
1163
+ * @internal
1164
+ */
1165
+ 'devframe:streaming:unsubscribe': (channel: string, id: string) => Promise<void>;
1166
+ /**
1167
+ * Client→server streaming cancellation request. Wired by
1168
+ * `RpcStreamingHost`; do not register manually.
1586
1169
  *
1587
- * @default true
1170
+ * @internal
1588
1171
  */
1589
- auth?: boolean | DevframeAuthHandler;
1172
+ 'devframe:streaming:cancel': (channel: string, id: string) => Promise<void>;
1590
1173
  /**
1591
- * How the browser reaches the RPC WebSocket. Defaults to sharing the HTTP
1592
- * port on the `__ws` route. See {@link DevframeWsOptions} for the
1593
- * different-port and remote-origin variants. Pass `false` to serve no
1594
- * WebSocket at all; clients connect over SSE instead (`backend: 'sse'`).
1595
- */
1596
- ws?: DevframeWsOptions | false;
1597
- /**
1598
- * How the browser reaches the SSE RPC endpoint, enabled by default
1599
- * alongside the WebSocket as the more portable transport. Pass `false`
1600
- * to disable, or a {@link DevframeSseOptions} to change its route.
1601
- */
1602
- sse?: boolean | DevframeSseOptions;
1603
- /**
1604
- * Capability-side CAC hook. Called with the CAC instance after the
1605
- * adapter registers its built-in commands (`build` / `mcp`)
1606
- * but before `createCac`'s own `configureCli` caller. Use this to
1607
- * contribute tool-specific flags and subcommands from the definition
1608
- * itself.
1174
+ * Client→server upload chunk. Wired by `RpcStreamingHost`; do not
1175
+ * register manually.
1176
+ *
1177
+ * @internal
1609
1178
  */
1610
- configure?: (cli: CAC) => void;
1179
+ 'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
1611
1180
  /**
1612
- * Typed CLI flags for the default `dev` command, backed by any
1613
- * [Standard Schema](https://standardschema.dev/) validator (valibot,
1614
- * zod, arktype, or devframe's built-in `s`). The adapter registers
1615
- * matching `--kebab-key` options on CAC, validates the parsed values,
1616
- * and forwards the typed bag to `setup(ctx, { flags })`.
1617
- *
1618
- * Use {@link defineCliFlags} to preserve the literal schema-map
1619
- * shape, and {@link InferCliFlags} to recover the typed output at the
1620
- * call site:
1621
- *
1622
- * ```ts
1623
- * const appFlags = defineCliFlags({
1624
- * depth: v.pipe(v.number(), v.integer()),
1625
- * config: v.optional(v.string()),
1626
- * })
1181
+ * Client→server upload terminator. Wired by `RpcStreamingHost`; do not
1182
+ * register manually.
1627
1183
  *
1628
- * defineDevframe({
1629
- * cli: { flags: appFlags },
1630
- * setup(ctx, info) {
1631
- * const flags = info.flags as InferCliFlags<typeof appFlags>
1632
- * },
1633
- * })
1634
- * ```
1184
+ * @internal
1635
1185
  */
1636
- flags?: CliFlagsSchema;
1186
+ 'devframe:streaming:upload-end': (channel: string, id: string, error?: {
1187
+ name: string;
1188
+ message: string;
1189
+ }) => Promise<void>;
1637
1190
  }
1638
1191
  /**
1639
- * Default dock attributes for the iframe entry a hub synthesizes when it
1640
- * mounts this devframe. Framework-neutral metadata only; the hub layer
1641
- * (`ctx.install`) merges these beneath its per-mount `dock` overrides,
1642
- * which in turn sit beneath the locked, derived `id` / `type` / `url`.
1643
- *
1644
- * Every field is optional. `title` / `icon` default to the definition's
1645
- * `name` / `icon` when omitted here; the rest are unset by default.
1646
- * Standalone adapters (`cli` / `build`) ignore this entirely.
1192
+ * To be extended
1647
1193
  */
1648
- interface DevframeDockDefaults {
1649
- /** Dock entry title. Defaults to the definition's `name`. */
1650
- title?: string;
1651
- /** Translations of `title` by BCP 47 tag; see `@devframes/hub`'s `resolveTitle()`. */
1652
- titleLocales?: Record<string, string>;
1653
- /** Dock entry icon. Defaults to the definition's `icon`. */
1654
- icon?: string | {
1655
- light: string;
1656
- dark: string;
1657
- };
1194
+ interface DevframeRpcSharedStates {
1658
1195
  /**
1659
- * Sort weight within the dock; higher sorts earlier.
1660
- * @default 0
1196
+ * Wire-service advertisements: package name → `{ package, version,
1197
+ * scope, meta }` for every installed {@link import('./services').DevframeServiceDefinition}.
1198
+ * Written by the node services host at the `ready()` barrier; clients read
1199
+ * it through `client.services` (or subscribe to it directly for
1200
+ * reactivity). Read-only from the browser.
1661
1201
  */
1662
- defaultOrder?: number;
1202
+ 'devframe:services': DevframeServicesState;
1203
+ }
1204
+ interface DevframeNodeRpcSession {
1205
+ meta: DevframeNodeRpcSessionMeta;
1206
+ rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
1207
+ }
1208
+ interface RpcBroadcastOptions<METHOD, Args extends any[]> {
1209
+ method: METHOD;
1210
+ args: Args;
1211
+ optional?: boolean;
1212
+ event?: boolean;
1213
+ filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
1214
+ }
1215
+ type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
1663
1216
  /**
1664
- * Category the entry groups under in the dock.
1665
- * @default 'default'
1217
+ * Invoke a locally registered server RPC function directly.
1218
+ *
1219
+ * This bypasses transport and is useful for server-side cross-function calls.
1666
1220
  */
1667
- category?: string;
1221
+ invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
1668
1222
  /**
1669
- * Conditional-visibility expression (same syntax as command `when`
1670
- * clauses). Set to `'false'` to hide the entry unconditionally.
1223
+ * Broadcast a message to all connected clients
1671
1224
  */
1672
- when?: string;
1225
+ broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
1673
1226
  /**
1674
- * Render-only visibility expression, same syntax as {@link when}. Hides the
1675
- * entry's own dock-bar button when it evaluates to `false` while leaving it
1676
- * registered and reachable (activation, RPC lookups, etc.), unlike `when`,
1677
- * which is the general relevance switch for the entry as a whole.
1227
+ * Get the current RPC client
1228
+ *
1229
+ * Available in RPC functions to get the current RPC client
1678
1230
  */
1679
- visibility?: string;
1680
- /** Badge text rendered on the dock icon (e.g. an unread count). */
1681
- badge?: string;
1682
- /** Id of the dock group this entry collapses under, if any. */
1683
- groupId?: string;
1231
+ getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
1684
1232
  /**
1685
- * A client script the hub imports into the host page (this devframe's **page
1686
- * script**). An absolute-path `importFrom` is served by the hub under the
1687
- * mount base and rewritten to that URL, so mounting by package name needs no
1688
- * host wiring; a URL or bare specifier passes through untouched.
1233
+ * The shared state host
1689
1234
  */
1690
- clientScript?: {
1691
- /**
1692
- * Initialize after RPC trust without waiting for dock activation.
1693
- * @default false
1694
- */
1695
- eager?: boolean;
1696
- /** An absolute filesystem path, a served URL, or a bare npm specifier. */
1697
- importFrom: string;
1698
- /**
1699
- * The name to import the module as.
1700
- * @default 'default'
1701
- */
1702
- importName?: string;
1703
- };
1704
- }
1705
- /**
1706
- * Runtime information threaded into `setup(ctx, info)`. Adapters
1707
- * populate the fields that make sense for their deployment. In
1708
- * particular, `createCac` fills `flags` with the parsed CAC bag.
1709
- */
1710
- interface DevframeSetupInfo {
1711
- /** Parsed CLI flags, populated by the CLI adapter. */
1712
- flags?: Record<string, unknown>;
1713
- }
1714
- interface DevframeDefinition {
1715
- id: string;
1716
- name: string;
1717
- /** Semver of the tool, surfaced in hub UIs and diagnostics. */
1718
- version: string;
1719
- /** npm package name the devframe ships in (e.g. `@scope/my-tool`). */
1720
- packageName: string;
1235
+ sharedState: RpcSharedStateHost;
1721
1236
  /**
1722
- * `import.meta.url` of the module that defines this devframe. **Always
1723
- * provide it** (`importMetaUrl: import.meta.url`): it is the resolution base
1724
- * for the tool's own dependency graph, which lets the host resolve
1725
- * everything against the plugin's own installed packages rather than the
1726
- * consuming app's.
1727
- *
1728
- * - **Remote assets**: becomes the default `resolveFrom` for any remote
1729
- * {@link StaticAssetsSource} the devframe hosts (its `clientAssets`, and
1730
- * every `ctx.views.hostStatic` call) that doesn't set one explicitly, so a
1731
- * locally installed copy of the assets package is served with zero
1732
- * network. A per-asset `resolveFrom` still wins, and an explicit
1733
- * `resolveFrom: null` still opts out.
1734
- * - **Service dependencies**: becomes the base the host resolves declared
1735
- * {@link DevframeServiceInput | services} from, so a plugin can ship a
1736
- * service package as its own dependency instead of asking users to install
1737
- * it.
1237
+ * The streaming channel host. Provides per-channel `start()` /
1238
+ * `pipeFrom()` producers; clients consume via `rpc.streaming.subscribe()`.
1738
1239
  *
1739
- * Optional for backward compatibility; omitting it falls back to
1740
- * runtime-directory resolution and disables the zero-network installed-copy
1741
- * fast paths above.
1240
+ * @see RpcStreamingHost
1742
1241
  */
1743
- importMetaUrl?: string;
1744
- /** Project homepage or documentation URL. */
1745
- homepage: string;
1746
- /** One-line summary of what the tool does. */
1747
- description: string;
1748
- icon?: string | {
1749
- light: string;
1750
- dark: string;
1751
- };
1242
+ streaming: RpcStreamingHost;
1243
+ };
1244
+ interface RpcSharedStateGetOptions<T> {
1245
+ sharedState?: SharedState<T>;
1246
+ initialValue?: T;
1247
+ }
1248
+ interface RpcSharedStateHost {
1249
+ get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
1250
+ keys: () => string[];
1752
1251
  /**
1753
- * Default dock attributes applied when a hub mounts this devframe as an
1754
- * iframe dock entry. Consulted only by the hub install path (`ctx.install`),
1755
- * which merge these beneath the per-mount `dock` overrides; standalone
1756
- * adapters (`cli` / `build`) ignore it.
1757
- *
1758
- * @see {@link DevframeDockDefaults}
1252
+ * Subscribe to new shared-state keys becoming available. Fires when
1253
+ * `get(key, ...)` creates a fresh entry (not on subsequent gets).
1254
+ * Useful for protocol adapters (e.g. MCP) that surface shared state
1255
+ * as dynamic resources.
1759
1256
  */
1760
- dock?: DevframeDockDefaults;
1257
+ onKeyAdded: (fn: (key: string) => void) => () => void;
1761
1258
  /**
1762
- * Mount path override. Defaults depend on the adapter:
1763
- * `/` for standalone (`cli` / `build`), `/__<id>/` for hosted
1764
- * (`vite` / `embedded`).
1259
+ * Unregister a shared state and drop its broadcast listeners. Returns
1260
+ * `true` when a state was removed, `false` when the key was unknown.
1261
+ * Used by short-lived states (e.g. a disposed JSON-render view) to avoid
1262
+ * leaking listeners and lingering entries for the context lifetime.
1765
1263
  */
1766
- basePath?: string;
1264
+ delete: (key: string) => boolean;
1265
+ }
1266
+ /**
1267
+ * Options for `RpcStreamingHost.create()`.
1268
+ */
1269
+ interface RpcStreamingChannelOptions {
1767
1270
  /**
1768
- * How a hub reacts when another devframe sharing this one's `id` is
1769
- * mounted onto the same hub. Consulted only by hub adapters
1770
- * (`ctx.install`); standalone adapters (`cli` / `build`)
1771
- * ignore it.
1271
+ * Size of the per-stream ring buffer kept on the server for
1272
+ * replay-on-resubscribe. `0` (default) disables replay; on reconnect
1273
+ * the consumer only sees chunks that arrive after subscribing.
1772
1274
  *
1773
- * @default 'warn'
1275
+ * The buffer is per stream id, not per channel; each `channel.start()`
1276
+ * gets its own.
1774
1277
  */
1775
- duplicationStrategy?: DevframeDuplicationStrategy;
1278
+ replayWindow?: number;
1776
1279
  /**
1777
- * Declares which runtimes meaningfully support this devframe. Adapters
1778
- * act on a `false` before doing any work:
1280
+ * Milliseconds a closed stream is retained on the server after its
1281
+ * last subscriber leaves (or if no subscriber ever arrived). During
1282
+ * this window, late subscribers can still join and replay the buffer
1283
+ * + receive the `end` frame.
1779
1284
  *
1780
- * - `build: false`: `createCac` skips registering the `build` subcommand,
1781
- * `createBuild` refuses (throws `DF0042`) unless `{ force: true }`, and
1782
- * a hub static build (`buildHub`) silently skips the devframe - no dock,
1783
- * no SPA copy, no RPCs in the dump. Useful for a devframe whose value is
1784
- * inherently live (e.g. it manages real files on disk), so a static
1785
- * export would only ever produce a broken, write-less shell of the tool.
1786
- * - `dev: false`: `createDevServer` refuses (throws `DF0058`) unless
1787
- * `{ force: true }`. Useful for a devframe that only makes sense as a
1788
- * static export (e.g. a report generator with nothing to serve live).
1285
+ * Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
1286
+ * (immediate free). Set to `0` to opt out, or higher for longer
1287
+ * post-mortem replay.
1789
1288
  */
1790
- capabilities?: {
1791
- dev?: boolean;
1792
- build?: boolean;
1793
- };
1289
+ closedStreamRetention?: number;
1290
+ }
1291
+ /**
1292
+ * Channel handle returned by `ctx.rpc.streaming.create(name, opts)`. A
1293
+ * channel owns a wire namespace; calling `start()` produces individual
1294
+ * streams keyed by id.
1295
+ *
1296
+ * @see {@link https://devfra.me/guide/streaming Streaming guide}
1297
+ */
1298
+ interface RpcStreamingChannel<T = unknown> {
1299
+ /** Channel name as registered with `ctx.rpc.streaming.create()`. */
1300
+ readonly name: string;
1794
1301
  /**
1795
- * Wire services this devframe consumes (see `DevframeServiceDefinition`).
1796
- * Each entry is either a declarative descriptor
1797
- * (`{ package, version?, required?, options? }`, where the host imports the
1798
- * package's default-export factory, resolving it against **this plugin's
1799
- * own dependencies**) or a ready `DevframeServiceDefinition` (the factory
1800
- * was already called). The adapter queues these before `setup(ctx)` runs
1801
- * and constructs each service once at the `ctx.services.ready()` barrier,
1802
- * merging option sets across every declarer. Missing services are skipped
1803
- * unless marked `required`; clients feature-detect via
1804
- * `client.services.has(pkg)` and degrade.
1302
+ * Start a new stream. Returns a server-side sink with both an imperative
1303
+ * (`write` / `close` / `error`) surface and a `WritableStream<T>` for
1304
+ * `pipeTo` consumption. The sink's `signal` aborts when every subscriber
1305
+ * disconnects or cancels.
1805
1306
  */
1806
- services?: DevframeServiceInput[];
1307
+ start: (opts?: {
1308
+ id?: string;
1309
+ }) => StreamSink<T>;
1807
1310
  /**
1808
- * Author's SPA dist, served as the devframe's UI. A local directory, or
1809
- * a {@link StaticAssetsSource} remote declaration (`{ package, version }`)
1810
- * served through devframe's caching CDN back-proxy so the assets need not
1811
- * ship inside the node package.
1311
+ * Convenience: start a stream and pipe a `ReadableStream<T>` into it.
1312
+ * The pipe uses `sink.signal` so cancellation propagates upstream.
1812
1313
  *
1813
- * Consumed by every adapter that serves the UI (`dev`, `build`, `vite`,
1814
- * `next`, and the hub install path).
1314
+ * Node-stream interop: convert a `Readable` with `Readable.toWeb(node)`
1315
+ * before passing it here.
1815
1316
  */
1816
- clientAssets?: StaticAssetsSource;
1817
- /** RPC-level configuration for this devframe (see {@link DevframeRpcOptions}). */
1818
- rpc?: DevframeRpcOptions;
1819
- /** Server-side setup: the primary entrypoint. Runs in every runtime. */
1820
- setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>;
1821
- cli?: DevframeCliOptions;
1822
- }
1823
- interface DevframeRpcOptions {
1317
+ pipeFrom: (readable: ReadableStream<T>, opts?: {
1318
+ id?: string;
1319
+ }) => Promise<StreamSink<T>>;
1320
+ /** Look up an active stream by id. Returns `undefined` if none. */
1321
+ get: (id: string) => StreamSink<T> | undefined;
1322
+ /** All active outbound stream ids on this channel. */
1323
+ ids: () => string[];
1824
1324
  /**
1825
- * Opt an RPC function into the static-build snapshot **without owning its
1826
- * definition**, the mechanism a devframe uses to bake a wire service's
1827
- * RPC (e.g. `@devframes/service-git`'s `status`/`log`/`show`) into its
1828
- * `build` export, since the service itself defines no `dump`/`snapshot`.
1325
+ * Open an inbound stream: the server side of a client-to-server
1326
+ * upload. Allocates an id, returns a `StreamReader<T>` that fills as
1327
+ * the client writes chunks. Typical pattern is to call this from an
1328
+ * action handler, kick off background processing, and return the id
1329
+ * so the caller can start uploading:
1829
1330
  *
1830
- * Each entry is either a bare method id (bakes the no-argument call, like
1831
- * `snapshot: true`) or `{ method, inputs }` where `inputs` is the list of
1832
- * argument-tuples to bake, or an async provider given the node context
1833
- * (so it can enumerate at build time, e.g. read commit hashes via the
1834
- * service's node API). `createBuild` resolves these after setup and
1835
- * executes the target's own handler per tuple; the first tuple's result
1836
- * becomes the fallback so any call variant resolves to a baked value.
1331
+ * ```ts
1332
+ * handler: async () => {
1333
+ * const reader = channel.openInbound()
1334
+ * ;(async () => {
1335
+ * for await (const chunk of reader) processChunk(chunk)
1336
+ * })()
1337
+ * return { uploadId: reader.id }
1338
+ * }
1339
+ * ```
1340
+ *
1341
+ * Calling `reader.cancel()` on the server sends an `upload-cancel` to
1342
+ * the uploading client, which aborts its sink.
1837
1343
  */
1838
- snapshot?: DevframeSnapshotRpcEntry[];
1344
+ openInbound: (opts?: {
1345
+ id?: string;
1346
+ }) => StreamReader<T>;
1839
1347
  }
1840
- /** Argument-tuples to bake for a {@link DevframeSnapshotRpcEntry}, or a provider that computes them at build time. */
1841
- type DevframeSnapshotRpcInputs = readonly (readonly unknown[])[] | ((ctx: DevframeNodeContext) => readonly (readonly unknown[])[] | Promise<readonly (readonly unknown[])[]>);
1842
- /**
1843
- * One {@link DevframeRpcOptions.snapshot} entry: a bare method id (bakes
1844
- * the no-argument call) or a method plus the argument-tuples to bake.
1845
- */
1846
- type DevframeSnapshotRpcEntry = string | {
1847
- method: string;
1848
- inputs: DevframeSnapshotRpcInputs;
1849
- };
1850
- //#endregion
1851
- //#region src/utils/shared-state.d.ts
1852
- type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
1853
- type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
1854
- type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
1855
- type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
1856
- type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
1857
- type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
1858
1348
  /**
1859
- * Serializable patch describing a single mutation to a `SharedState`.
1860
- * Structurally compatible with JSON-Patch and is safe to send over RPC.
1349
+ * Server-side streaming host. Lives on `ctx.rpc.streaming` alongside
1350
+ * `ctx.rpc.sharedState`. Each named channel owns its own stream registry
1351
+ * and wire namespace.
1861
1352
  */
1862
- interface SharedStatePatch {
1863
- op: 'add' | 'remove' | 'replace';
1864
- path: readonly (string | number)[];
1865
- value?: unknown;
1353
+ interface RpcStreamingHost {
1354
+ /**
1355
+ * Register a streaming channel. Names follow the `<plugin-id>:<channel>`
1356
+ * convention (e.g. `'my-devtool:chat-stream'`). Throws `DF0032` if the
1357
+ * name is already taken.
1358
+ */
1359
+ create: <T = unknown>(name: string, opts?: RpcStreamingChannelOptions) => RpcStreamingChannel<T>;
1360
+ /**
1361
+ * Adapters call this when a session disconnects so the host can drop
1362
+ * subscribers and abort orphaned streams. Most users do not need this;
1363
+ * it's wired by the RPC server binding automatically.
1364
+ *
1365
+ * @internal
1366
+ */
1367
+ _onSessionDisconnected: (meta: DevframeNodeRpcSessionMeta) => void;
1368
+ }
1369
+ interface DevframeCapabilities {
1370
+ rpc?: boolean;
1371
+ views?: boolean;
1866
1372
  }
1867
1373
  /**
1868
- * State host that is immutable by default with explicit mutate.
1374
+ * Framework- and build-tool-agnostic node context: RPC + diagnostics +
1375
+ * agent + the view-host (HTTP file-serving). Host adapters can wrap this
1376
+ * to add their own surfaces; for example, `@vitejs/devtools-kit`'s
1377
+ * `createKitContext` adds `docks`, `terminals`, `messages`, and
1378
+ * `commands` when mounted into Vite DevTools. JSON rendering is an opt-in
1379
+ * integration (`@devframes/json-render`) layered on top, not part of this
1380
+ * core surface.
1869
1381
  */
1870
- interface SharedState<T> {
1382
+ interface DevframeNodeContext {
1383
+ readonly workspaceRoot: string;
1384
+ readonly cwd: string;
1871
1385
  /**
1872
- * Get the current state. Immutable.
1386
+ * Lifecycle distinction surfaced to plugin authors:
1387
+ *
1388
+ * - `'dev'`: long-running, interactive session. Connections come and
1389
+ * go; broadcasts and shared-state mutations are debounced
1390
+ * to keep the UI responsive.
1391
+ * - `'build'`: one-shot batch run. The context is set up, the devtool
1392
+ * collects what it needs, and a snapshot is written. No
1393
+ * live UI, no WS server.
1394
+ *
1395
+ * Names are inherited from Vite's serve/build dichotomy but the meaning
1396
+ * is general: the same distinction applies to any tool that runs in
1397
+ * either an interactive or a static-output mode.
1873
1398
  */
1874
- value: () => Immutable<T>;
1399
+ readonly mode: 'dev' | 'build';
1875
1400
  /**
1876
- * Subscribe to state changes.
1401
+ * Host runtime abstraction, exposing `mountStatic` / `resolveOrigin` /
1402
+ * `getStorageDir`.
1877
1403
  */
1878
- on: EventEmitter<SharedStateEvents<T>>['on'];
1404
+ host: DevframeHost;
1405
+ rpc: RpcFunctionsHost;
1406
+ views: DevframeViewHost;
1879
1407
  /**
1880
- * Mutate the state.
1408
+ * Structured diagnostics host; wraps `nostics` and lets integrations
1409
+ * register their own coded errors/warnings into the shared lookup.
1881
1410
  */
1882
- mutate: (fn: (state: T) => void, syncId?: string) => void;
1411
+ diagnostics: DevframeDiagnosticsHost;
1883
1412
  /**
1884
- * Apply patches to the state.
1413
+ * Agent host; aggregates the agent-exposed surface of this devtool.
1885
1414
  */
1886
- patch: (patches: SharedStatePatch[], syncId?: string) => void;
1415
+ agent: DevframeAgentHost;
1887
1416
  /**
1888
- * Sync IDs that have been applied to the state.
1417
+ * Cross-plugin services: a typed, namespaced registry through which one
1418
+ * integration exposes a capability (e.g. a data-source registry) and
1419
+ * others consume it without a hard package dependency. Ids follow the RPC
1420
+ * namespacing rule (`<plugin-id>:<service>`); types come from augmenting
1421
+ * the `DevframeServicesRegistry` interface. `whenAvailable` subscriptions
1422
+ * absorb setup-order differences between provider and consumer.
1889
1423
  */
1890
- syncIds: Set<string>;
1891
- }
1892
- interface SharedStateEvents<T> {
1893
- updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
1894
- }
1895
- interface SharedStateOptions<T> {
1424
+ services: DevframeServicesHost;
1896
1425
  /**
1897
- * Initial state.
1426
+ * This context's own {@link ConnectionMeta.configs}: static, boot-time
1427
+ * config a host publishes once through the connection handshake and every
1428
+ * client reads read-only. A plain, **non-reactive** object: mutate it
1429
+ * during `setup(ctx)` (a plugin sets its keys, a hub aggregates across
1430
+ * every installed devframe), never during the session, since it's serialized
1431
+ * once, after setup, and changing it afterwards reaches no client.
1432
+ *
1433
+ * ```ts
1434
+ * ctx.staticConfig.dock = {
1435
+ * ...ctx.staticConfig.dock,
1436
+ * categoryOrder: { ...ctx.staticConfig.dock?.categoryOrder, ...myOrder },
1437
+ * }
1438
+ * ```
1898
1439
  */
1899
- initialValue: T;
1440
+ staticConfig: Partial<DevframeConnectionConfigsRegistry>;
1900
1441
  /**
1901
- * Enable patches.
1442
+ * Create a namespace-scoped view of this context. The returned
1443
+ * `ctx.scope('my-plugin')` auto-namespaces every RPC id, shared-state
1444
+ * key, and streaming channel with `my-plugin:`, and exposes a typed
1445
+ * top-level `settings` store. This is the preferred way to consume the
1446
+ * context from a single tool's setup code.
1902
1447
  *
1903
- * @default false
1448
+ * Pass `null` or `''` to un-scope and get the base context.
1904
1449
  */
1905
- enablePatches?: boolean;
1450
+ scope: {
1451
+ <NS extends string>(namespace: NS): DevframeScopedNodeContext<NS, SettingsForNamespace<NS>>;
1452
+ (namespace?: null | ''): DevframeNodeContext;
1453
+ };
1906
1454
  }
1907
- declare function createSharedState<T extends object>(options: SharedStateOptions<T>): SharedState<T>;
1908
- //#endregion
1909
- //#region src/utils/streaming-channel.d.ts
1910
1455
  /**
1911
- * Serialized error shape sent over the wire when a stream ends with a failure.
1912
- * Stays JSON-safe so the strict-JSON encoder can carry it without coercion.
1456
+ * Describes where the browser client should open its RPC WebSocket. The
1457
+ * object form is the proxy-flexible default: `path` is resolved relative to
1458
+ * where `__connection.json` was loaded, and the connection is made to the
1459
+ * page's own origin (only the `http`→`ws` / `https`→`wss` protocol swap is
1460
+ * applied). This survives reverse proxies that change the host/port, because
1461
+ * the client never trusts a server-baked hostname; it reuses its own.
1462
+ *
1463
+ * Set `port` (and/or `host`) only when the WS endpoint genuinely lives on a
1464
+ * different origin than the page, e.g. a side-car server on its own port.
1913
1465
  */
1914
- interface StreamErrorPayload {
1915
- name: string;
1916
- message: string;
1466
+ interface ConnectionMetaWebsocket {
1467
+ /**
1468
+ * Path to the WS endpoint. Relative paths (the default, e.g. `__ws`) are
1469
+ * resolved against `__connection.json`'s location; absolute paths (`/__ws`)
1470
+ * resolve against the page origin.
1471
+ */
1472
+ path?: string;
1473
+ /** Override the port. Combined with the page hostname unless `host` is set. */
1474
+ port?: number;
1475
+ /** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
1476
+ host?: string;
1917
1477
  }
1918
1478
  /**
1919
- * Single buffered chunk in the server-side ring buffer.
1920
- *
1921
- * Sequence numbers start at 1 and increment per write. Subscribers track
1922
- * `lastSeenSeq` and ask for `afterSeq` on resubscribe so the server can
1923
- * replay any chunks the client missed during a brief disconnect.
1479
+ * Object form of {@link ConnectionMeta.sse}: the same proxy-safe shape and
1480
+ * resolution rules as {@link ConnectionMetaWebsocket}, producing an
1481
+ * `http(s)://` endpoint instead of a `ws(s)://` one. Set `port` (and/or
1482
+ * `host`) only when the SSE endpoint genuinely lives on a different origin
1483
+ * than the page, e.g. a side-car server on its own port.
1924
1484
  */
1925
- interface BufferedChunk<T> {
1926
- seq: number;
1927
- chunk: T;
1928
- }
1929
- interface StreamSinkEvents<T> {
1930
- /** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
1931
- chunk: (seq: number, chunk: T) => void;
1932
- /** Terminal; fired exactly once per sink lifetime. */
1933
- end: (error?: StreamErrorPayload) => void;
1934
- }
1935
- interface CreateStreamSinkOptions {
1936
- id?: string;
1485
+ interface ConnectionMetaSse {
1937
1486
  /**
1938
- * Size of the per-stream ring buffer kept for replay-on-resubscribe.
1939
- * `0` (default) disables replay.
1487
+ * Path to the SSE endpoint. Relative paths (the default, e.g. `__sse`)
1488
+ * are resolved against `__connection.json`'s location; absolute paths
1489
+ * (`/__sse`) resolve against the page origin.
1940
1490
  */
1941
- replayWindow?: number;
1491
+ path?: string;
1492
+ /** Override the port. Combined with the page hostname unless `host` is set. */
1493
+ port?: number;
1494
+ /** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
1495
+ host?: string;
1942
1496
  }
1943
- /**
1944
- * Server-side producer handle. Two equivalent surfaces share one piece of
1945
- * state: the imperative `write/error/close` triple, and a `WritableStream<T>`
1946
- * for `pipeTo`-style consumption.
1947
- */
1948
- interface StreamSink<T> {
1949
- /** Stable id used by clients to subscribe. */
1950
- readonly id: string;
1497
+ interface ConnectionMeta {
1951
1498
  /**
1952
- * Aborts when the consumer cancels (server-side) or when the transport
1953
- * loses every subscriber. Producers should poll `signal.aborted` and exit
1954
- * cleanly.
1499
+ * The server's primary live-RPC transport (`websocket` / `sse`), `static`
1500
+ * for a pre-computed RPC dump with no live server, or `none` for a server
1501
+ * exposing no RPC transport at all (e.g. an MCP-only deployment).
1955
1502
  */
1956
- readonly signal: AbortSignal;
1957
- /** `true` after `close()` / `error()`. Further writes throw. */
1958
- readonly closed: boolean;
1959
- /** Last allocated sequence number. `0` until the first write. */
1960
- readonly lastSeq: number;
1961
- write: (chunk: T) => void;
1962
- error: (reason: unknown) => void;
1963
- close: () => void;
1964
- /** External-cancel path. Aborts the signal so handlers can short-circuit. */
1965
- abort: (reason?: unknown) => void;
1966
- /** `WritableStream<T>` adapter; same in-memory state as the imperative API. */
1967
- readonly writable: WritableStream<T>;
1503
+ backend: 'websocket' | 'sse' | 'static' | 'none';
1968
1504
  /**
1969
- * Internal: RPC layer subscribes to receive chunk/end notifications.
1970
- * Not part of the public contract; do not call directly.
1505
+ * WebSocket endpoint, resolved by the client into a `ws(s)://` URL:
1971
1506
  *
1972
- * @internal
1507
+ * - {@link ConnectionMetaWebsocket}: the proxy-flexible default; a
1508
+ * same-origin path relative to `__connection.json`.
1509
+ * - `number`: a port on the page's hostname (`ws(s)://<host>:<port>`).
1510
+ * - `string`: a full `ws://`/`wss://` URL used verbatim, an `http(s)://`
1511
+ * URL with its protocol swapped, or a path resolved same-origin.
1973
1512
  */
1974
- readonly events: EventEmitter<StreamSinkEvents<T>>;
1513
+ websocket?: number | string | ConnectionMetaWebsocket;
1975
1514
  /**
1976
- * Internal replay buffer. RPC layer reads on (re)subscribe to feed
1977
- * missed chunks before going live.
1515
+ * SSE endpoint (`GET` opens the event stream, `POST` carries RPC calls),
1516
+ * resolved by the client into an `http(s)://` URL with the same
1517
+ * proxy-safe rules as {@link ConnectionMeta.websocket}:
1978
1518
  *
1979
- * @internal
1519
+ * - {@link ConnectionMetaSse}: the proxy-flexible default; a
1520
+ * same-origin path relative to `__connection.json`.
1521
+ * - `string`: a full `http(s)://` URL used verbatim, or a path
1522
+ * resolved same-origin.
1980
1523
  */
1981
- readonly buffer: ReadonlyArray<BufferedChunk<T>>;
1982
- }
1983
- interface CreateStreamReaderOptions {
1984
- id?: string;
1524
+ sse?: string | ConnectionMetaSse;
1985
1525
  /**
1986
- * Maximum number of buffered chunks held client-side while the consumer
1987
- * isn't draining. On overflow, the oldest chunk is dropped.
1526
+ * Present when the dev server exposes a route-based MCP endpoint
1527
+ * (the host's `mcp` setting). Advertises the MCP Streamable-HTTP route so in-browser
1528
+ * tooling (e.g. an MCP inspector) can discover it without guessing the
1529
+ * path. `path` is relative to `__connection.json`'s location, like the
1530
+ * WebSocket `path`. `port` is set when the endpoint lives on a side-car
1531
+ * server on its own port (bridge mode: `devframeViteBridge`,
1532
+ * `@devframes/next`): the client combines the page hostname with `port`
1533
+ * and resolves `path` against that origin, mirroring
1534
+ * {@link ConnectionMetaWebsocket.port}.
1988
1535
  */
1989
- highWaterMark?: number;
1536
+ mcp?: {
1537
+ path: string;
1538
+ port?: number;
1539
+ };
1990
1540
  /**
1991
- * Called when the chunk queue overflows the high-water mark. The RPC
1992
- * layer wires this to a coded warning; the primitive itself is
1993
- * RPC-agnostic.
1541
+ * Names of RPC functions that have declared `jsonSerializable: true`.
1542
+ * Used by the WS / static client to dispatch the per-call wire
1543
+ * serializer (strict JSON for these methods, structured-clone for
1544
+ * the rest). Populated by the server / build adapter; absent on
1545
+ * legacy clients, in which case all outgoing messages fall back to
1546
+ * structured-clone.
1994
1547
  */
1995
- onOverflow?: (dropped: number) => void;
1996
- /** Called when the consumer cancels; the RPC layer sends `:cancel` upstream. */
1997
- onCancel?: () => void;
1998
- }
1999
- /**
2000
- * Client-side consumer handle. Both an `AsyncIterable<T>` (for `for await`)
2001
- * and exposes `readable: ReadableStream<T>` (for `pipeTo`). Pick one; they
2002
- * share a single internal queue, so concurrent draining will race.
2003
- */
2004
- interface StreamReader<T> extends AsyncIterable<T> {
2005
- readonly id: string;
2006
- readonly cancelled: boolean;
2007
- readonly done: boolean;
2008
- /** Highest `seq` observed. Used for replay on reconnect. */
2009
- readonly lastSeenSeq: number;
2010
- /** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
2011
- readonly readable: ReadableStream<T>;
2012
- cancel: () => void;
2013
- /** @internal */
2014
- _push: (seq: number, chunk: T) => void;
2015
- /** @internal */
2016
- _end: (error?: StreamErrorPayload) => void;
2017
- }
2018
- /**
2019
- * Build a server-side stream sink. RPC-agnostic; the RPC host wires
2020
- * `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
2021
- * for late or reconnecting subscribers.
2022
- */
2023
- declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
2024
- /**
2025
- * Build a client-side stream reader. RPC-agnostic; the RPC host calls
2026
- * `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
2027
- * terminal frame. Consumers iterate with `for await` or pipe `readable`.
2028
- */
2029
- declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
2030
- //#endregion
2031
- //#region src/types/rpc.d.ts
2032
- interface DevframeNodeRpcSession {
2033
- meta: DevframeNodeRpcSessionMeta;
2034
- rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
2035
- }
2036
- interface RpcBroadcastOptions<METHOD, Args extends any[]> {
2037
- method: METHOD;
2038
- args: Args;
2039
- optional?: boolean;
2040
- event?: boolean;
2041
- filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
2042
- }
2043
- type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
1548
+ jsonSerializableMethods?: string[];
2044
1549
  /**
2045
- * Invoke a locally registered server RPC function directly.
1550
+ * URL of the `__connection.json` that owns this meta's relative paths.
1551
+ * Two producers write it:
2046
1552
  *
2047
- * This bypasses transport and is useful for server-side cross-function calls.
2048
- */
2049
- invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
2050
- /**
2051
- * Broadcast a message to all connected clients
1553
+ * - The client annotates it (absolute) when it publishes the meta on a
1554
+ * shared window for same-origin inheritance: a relative
1555
+ * `websocket.path` resolves against this, so a child SPA mounted at
1556
+ * another base (e.g. a hub mounting several devframes at `/__foo/`,
1557
+ * `/__bar/`, …) inherits a dialable endpoint rather than resolving the
1558
+ * path against its own mount.
1559
+ * - A static hub build serves it (base-absolute, e.g.
1560
+ * `/__devframes/__connection.json`) in each **per-frame** meta, so a
1561
+ * frame SPA that fetched its own copy still resolves the shared RPC
1562
+ * dump from the hub base. Resolved against the fetched URL.
2052
1563
  */
2053
- broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
1564
+ baseUrl?: string;
2054
1565
  /**
2055
- * Get the current RPC client
1566
+ * A pre-issued bearer token embedded in the meta so a client trusts the
1567
+ * server on connect without any prompt or `localStorage` lookup. Only a
1568
+ * **hub** serving a **per-frame** connection meta populates this; it
1569
+ * authenticates once at the top level and bakes the resulting token into
1570
+ * the meta each plugin iframe fetches, so a cross-origin frame (which
1571
+ * cannot read the hub's `localStorage`) is still pre-authorized. The
1572
+ * standalone `__connection.json` never carries a token.
2056
1573
  *
2057
- * Available in RPC functions to get the current RPC client
2058
- */
2059
- getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
2060
- /**
2061
- * The shared state host
1574
+ * > [!WARNING]
1575
+ * > A token in a fetchable JSON is only as protected as the URL serving
1576
+ * > it. Emit it exclusively from hub-controlled, per-frame meta, never
1577
+ * > from a publicly reachable static `__connection.json`.
2062
1578
  */
2063
- sharedState: RpcSharedStateHost;
1579
+ authToken?: string;
2064
1580
  /**
2065
- * The streaming channel host. Provides per-channel `start()` /
2066
- * `pipeFrom()` producers; clients consume via `rpc.streaming.subscribe()`.
1581
+ * Session-scoped token that lets a trusted external viewer register its
1582
+ * browser origin before opening the WebSocket. The host includes it in the
1583
+ * connection metadata, which the viewer obtains through the host page.
2067
1584
  *
2068
- * @see RpcStreamingHost
2069
- */
2070
- streaming: RpcStreamingHost;
2071
- };
2072
- interface RpcSharedStateGetOptions<T> {
2073
- sharedState?: SharedState<T>;
2074
- initialValue?: T;
2075
- }
2076
- interface RpcSharedStateHost {
2077
- get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
2078
- keys: () => string[];
2079
- /**
2080
- * Subscribe to new shared-state keys becoming available. Fires when
2081
- * `get(key, ...)` creates a fresh entry (not on subsequent gets).
2082
- * Useful for protocol adapters (e.g. MCP) that surface shared state
2083
- * as dynamic resources.
1585
+ * Treat this as a bearer credential. Keep connection metadata containing the
1586
+ * token same-origin until the requesting origin has been verified.
2084
1587
  */
2085
- onKeyAdded: (fn: (key: string) => void) => () => void;
1588
+ viewerOriginToken?: string;
2086
1589
  /**
2087
- * Unregister a shared state and drop its broadcast listeners. Returns
2088
- * `true` when a state was removed, `false` when the key was unknown.
2089
- * Used by short-lived states (e.g. a disposed JSON-render view) to avoid
2090
- * leaking listeners and lingering entries for the context lifetime.
1590
+ * Static, host-declared configuration, baked in once at connect time and
1591
+ * fixed for the life of the server (e.g. a hub's UI rebrand, or its
1592
+ * aggregated dock-bar layout preferences). Read-only from the browser: a
1593
+ * client only ever reads `rpc.connectionMeta.configs`, never writes to it.
1594
+ *
1595
+ * Contrast this with {@link DevframeSettingsRegistry} (`ctx.scope(ns).settings`)
1596
+ * and a hub's `devframe:user-settings` shared-state key; both are
1597
+ * mutable, user-editable, and synced bidirectionally over RPC for the
1598
+ * life of the session. `configs` is the opposite: one-way, immutable,
1599
+ * decided by whoever assembled the server.
1600
+ *
1601
+ * Each key is owned by one package, contributed via declaration merging:
1602
+ *
1603
+ * ```ts
1604
+ * declare module 'devframe/types' {
1605
+ * interface DevframeConnectionConfigsRegistry {
1606
+ * 'my-key': { some: 'shape' }
1607
+ * }
1608
+ * }
1609
+ * ```
2091
1610
  */
2092
- delete: (key: string) => boolean;
1611
+ configs?: Partial<DevframeConnectionConfigsRegistry>;
2093
1612
  }
2094
1613
  /**
2095
- * Options for `RpcStreamingHost.create()`.
1614
+ * Augmentation point for {@link ConnectionMeta.configs}. Empty by default;
1615
+ * a package that wants to publish static, boot-time config through the
1616
+ * connection handshake augments this interface with its own key (see
1617
+ * {@link ConnectionMeta.configs} for the pattern). `@devframes/hub`
1618
+ * augments it with `dock`; `@devframes/hub-ui` augments it with `ui`.
2096
1619
  */
2097
- interface RpcStreamingChannelOptions {
2098
- /**
2099
- * Size of the per-stream ring buffer kept on the server for
2100
- * replay-on-resubscribe. `0` (default) disables replay; on reconnect
2101
- * the consumer only sees chunks that arrive after subscribing.
2102
- *
2103
- * The buffer is per stream id, not per channel; each `channel.start()`
2104
- * gets its own.
2105
- */
2106
- replayWindow?: number;
1620
+ interface DevframeConnectionConfigsRegistry {}
1621
+ /**
1622
+ * Schema map for typed CLI flags. Keys are flag names in camelCase, so
1623
+ * this matches CAC's parsed-flag output ( `--no-open` → `noOpen` ). Each
1624
+ * value is any [Standard Schema](https://standardschema.dev/) validator
1625
+ * (valibot, zod, arktype, devframe's built-in `s`, …) used to both (a)
1626
+ * derive the CAC option type when the flag is registered and (b) validate
1627
+ * the parsed value before it's forwarded to `setup(ctx, { flags })`.
1628
+ */
1629
+ type CliFlagsSchema = Record<string, StandardSchemaV1>;
1630
+ /**
1631
+ * Identity helper that preserves the literal schema-map type; use this
1632
+ * so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
1633
+ *
1634
+ * ```ts
1635
+ * const appFlags = defineCliFlags({
1636
+ * depth: v.pipe(v.number(), v.integer()),
1637
+ * config: v.optional(v.string()),
1638
+ * })
1639
+ *
1640
+ * defineDevframe({
1641
+ * cli: { flags: appFlags },
1642
+ * setup(ctx, info) {
1643
+ * const flags = info.flags as InferCliFlags<typeof appFlags>
1644
+ * flags.depth // number
1645
+ * flags.config // string | undefined
1646
+ * },
1647
+ * })
1648
+ * ```
1649
+ */
1650
+ declare function defineCliFlags<T extends CliFlagsSchema>(flags: T): T;
1651
+ /** Extract the parsed-output type from a {@link CliFlagsSchema}. */
1652
+ type InferCliFlags<T extends CliFlagsSchema> = { [K in keyof T]: StandardSchemaV1.InferOutput<T[K]>; };
1653
+ /** Validate the raw cac-parsed bag against a {@link CliFlagsSchema}. */
1654
+ declare function parseCliFlags(schema: CliFlagsSchema, raw: Record<string, unknown>): {
1655
+ flags: Record<string, unknown>;
1656
+ issues?: string[];
1657
+ };
1658
+ /**
1659
+ * A ready-made pre-auth RPC handler, as produced by
1660
+ * `devframe/recipes/interactive-auth`'s `createInteractiveAuth`. Bundles
1661
+ * everything a host adapter needs to wire an authenticated server:
1662
+ * the handshake RPC functions, the resolver gate, the connect-time trust
1663
+ * hook, and the startup banner.
1664
+ *
1665
+ * `initDevframe` / `initHub` accept one of these directly via their `auth` option
1666
+ * (see {@link https://devfra.me | devframe}'s server docs), or a host can
1667
+ * wire the four pieces itself against a lower-level transport.
1668
+ */
1669
+ interface DevframeAuthHandler {
2107
1670
  /**
2108
- * Milliseconds a closed stream is retained on the server after its
2109
- * last subscriber leaves (or if no subscriber ever arrived). During
2110
- * this window, late subscribers can still join and replay the buffer
2111
- * + receive the `end` frame.
1671
+ * `anonymous:devframe:auth` + `anonymous:devframe:auth:exchange` (the
1672
+ * handshake), `anonymous:devframe:auth:request-code` (client-requested
1673
+ * banner print), and `devframe:auth:revoke` (self-revoke); register these
1674
+ * on the RPC host (e.g. `rpcHost.register(fn)` for each).
1675
+ */
1676
+ rpcFunctions: RpcFunctionDefinitionAny[];
1677
+ /**
1678
+ * Resolver gate: whether `methodName` is callable given `session`'s
1679
+ * current trust state. Defaults to allowing any `anonymous:`-prefixed
1680
+ * method (see `isAnonymousRpcMethod`) plus anything once the session is
1681
+ * trusted.
1682
+ */
1683
+ authorize: (methodName: string, session: DevframeNodeRpcSession) => boolean;
1684
+ /**
1685
+ * Connect-time trust: reads a bearer token off the connection's initial
1686
+ * request (a static/pre-shared token from `clientAuthTokens`, or a token
1687
+ * minted by the code exchange) and, when valid, marks the session
1688
+ * trusted immediately, before the client's own handshake call.
1689
+ */
1690
+ onConnect: (connection: DevframeRpcConnection, session: DevframeNodeRpcSession) => void;
1691
+ /**
1692
+ * Print the current one-time code and its magic-link URL. An untrusted
1693
+ * browser client triggers this itself over
1694
+ * `anonymous:devframe:auth:request-code`; call it directly when the host
1695
+ * wants the code on screen without waiting for a client. Safe to call
1696
+ * repeatedly; it only prints once per code.
1697
+ */
1698
+ printBanner: () => void;
1699
+ /**
1700
+ * Rewrite a URL the CLI is about to open in the browser (`--open`) so the
1701
+ * tab lands already authenticated, e.g. appending the current OTP as a
1702
+ * query param. Called with the fully-resolved target URL; return it
1703
+ * unchanged to opt out. Optional: a handler that doesn't need this (e.g.
1704
+ * one gated by a pre-shared bearer token instead of an interactive code)
1705
+ * can omit it and the URL opens as-is.
1706
+ */
1707
+ buildOpenUrl?: (url: string) => string;
1708
+ }
1709
+ /**
1710
+ * The MCP adapter surface, typed here so `devframe` (which probes for and
1711
+ * lazily loads `@devframes/agentic`) and `@devframes/agentic/mcp` (which
1712
+ * implements it) share one contract without a type-level dependency cycle.
1713
+ * None of these shapes reference the MCP SDK: the SDK is an implementation
1714
+ * detail of `@devframes/agentic`, freely swappable behind this surface.
1715
+ * The h3-bound `mountMcpHttp` shapes live in `node/agentic.ts` (exported via
1716
+ * `devframe/internal`) instead: `devframe/types` must stay lib-neutral, and
1717
+ * h3's declarations are not.
1718
+ */
1719
+ interface CreateMcpServerOptions {
1720
+ /**
1721
+ * Transport to use. `createMcpServer` itself runs `'stdio'` (a standalone
1722
+ * process with its own host context); the Streamable-HTTP transport is
1723
+ * served route-based by the dev server instead; see `mountMcpHttp` and
1724
+ * the `mcp` option on `createDevServer` / `createCac`'s `--mcp` flag.
1725
+ */
1726
+ transport?: 'stdio';
1727
+ /**
1728
+ * Expose shared-state keys as MCP resources.
1729
+ * - `true` (default): every key the host publishes
1730
+ * - `false`: none
1731
+ * - `(key) => boolean`: filter
1732
+ */
1733
+ exposeSharedState?: boolean | ((key: string) => boolean);
1734
+ /**
1735
+ * Usage guidance sent as `instructions` in the MCP `initialize` (and
1736
+ * `server/discover`) result. Omitted or empty sends no instructions.
1737
+ */
1738
+ instructions?: string;
1739
+ /**
1740
+ * Resource templates served through `resources/templates/list`, and read
1741
+ * through `resources/read`. Omitted or empty keeps the resources unchanged.
1742
+ */
1743
+ resourceTemplates?: McpResourceTemplate[];
1744
+ /**
1745
+ * Host subscription for resource-list changes; each `notify()` sends
1746
+ * `notifications/resources/list_changed`. See {@link McpResourcesChangedHook}.
1747
+ */
1748
+ onResourcesChanged?: McpResourcesChangedHook;
1749
+ /**
1750
+ * Prompt templates served through `prompts/list` and `prompts/get`.
1751
+ * Omitted or empty declares no `prompts` capability.
1752
+ */
1753
+ prompts?: McpPrompt[];
1754
+ /** Override the name reported in the MCP handshake. */
1755
+ serverName?: string;
1756
+ /** Override the version reported in the MCP handshake. Defaults to `definition.version ?? '0.0.0'`. */
1757
+ serverVersion?: string;
1758
+ /** Called once the transport is connected. */
1759
+ onReady?: (info: {
1760
+ transport: 'stdio';
1761
+ }) => void;
1762
+ }
1763
+ /**
1764
+ * One MCP content block in a tool result. Attach blocks to a tool's return
1765
+ * value with `withMcpContent` (from `devframe`) to send them in place of the
1766
+ * default JSON text block. `data` is base64.
1767
+ */
1768
+ type McpContentBlock = {
1769
+ type: 'text';
1770
+ text: string;
1771
+ } | {
1772
+ type: 'image';
1773
+ data: string;
1774
+ mimeType: string;
1775
+ } | {
1776
+ type: 'audio';
1777
+ data: string;
1778
+ mimeType: string;
1779
+ };
1780
+ interface McpServerHandle {
1781
+ stop: () => Promise<void>;
1782
+ }
1783
+ interface CreateMcpFetchHandlerOptions {
1784
+ /** Name reported in the MCP handshake. */
1785
+ serverName: string;
1786
+ /** Version reported in the MCP handshake. */
1787
+ serverVersion: string;
1788
+ /** Expose shared-state keys as MCP resources; see {@link CreateMcpServerOptions.exposeSharedState}. */
1789
+ exposeSharedState: boolean | ((key: string) => boolean);
1790
+ /** Usage guidance for the handshake; see {@link CreateMcpServerOptions.instructions}. */
1791
+ instructions?: string;
1792
+ /** Resource templates; see {@link CreateMcpServerOptions.resourceTemplates}. */
1793
+ resourceTemplates?: McpResourceTemplate[];
1794
+ /** Host subscription for resource-list changes; see {@link CreateMcpServerOptions.onResourcesChanged}. */
1795
+ onResourcesChanged?: McpResourcesChangedHook;
1796
+ /** Prompt templates; see {@link CreateMcpServerOptions.prompts}. */
1797
+ prompts?: McpPrompt[];
1798
+ /**
1799
+ * Optional identity check, layered on top of the origin gate and checked
1800
+ * **after** it: a bearer token string (matched in constant time against
1801
+ * `Authorization: Bearer <token>`), a `(request) => boolean` callback, or
1802
+ * `false` (the default) for origin-only, trusting same-machine callers. A
1803
+ * callback governs identity only and cannot relax the origin gate. See
1804
+ * {@link McpAuthorization}.
1805
+ */
1806
+ authorization?: McpAuthorization;
1807
+ /**
1808
+ * Origin allow-list beyond the loopback default. `false` disables the
1809
+ * origin gate entirely. Default: loopback-only.
2112
1810
  *
2113
- * Defaults to `30_000` (30 s) when `replayWindow > 0`, else `0`
2114
- * (immediate free). Set to `0` to opt out, or higher for longer
2115
- * post-mortem replay.
1811
+ * Unlike the WS transport, the MCP route does **not** allow `Origin`-less
1812
+ * requests: a route-based endpoint is reachable by any local process, so a
1813
+ * request must carry an `Origin` that passes the gate. Native clients
1814
+ * (e.g. `devframe connect`) send their loopback origin explicitly.
2116
1815
  */
2117
- closedStreamRetention?: number;
1816
+ allowedOrigins?: readonly string[] | false;
1817
+ }
1818
+ /** Connection facts a host knows about a request beyond the `Request` itself. */
1819
+ interface McpConnectionInfo {
1820
+ /**
1821
+ * The connecting peer's remote address (a node socket's `remoteAddress`),
1822
+ * used to prove a same-machine caller when the endpoint relies on the
1823
+ * loopback origin default with no identity check. A host that can resolve a
1824
+ * trustworthy peer address (the h3/node mount) supplies it; when it's
1825
+ * omitted the origin gate stays the only locality signal.
1826
+ */
1827
+ remoteAddress?: string;
1828
+ }
1829
+ interface McpFetchHandler {
1830
+ /**
1831
+ * WHATWG-`fetch` handler for the MCP endpoint. Hand every method
1832
+ * (POST/GET/DELETE) on the endpoint's path to it; routing by path is the
1833
+ * host's job. Pass {@link McpConnectionInfo} when the host can resolve the
1834
+ * peer address so the default trust boundary can enforce same-machine
1835
+ * locality.
1836
+ */
1837
+ fetch: (request: Request, connection?: McpConnectionInfo) => Promise<Response>;
1838
+ /** Tear down the handler (aborts in-flight exchanges, drops the change bridge). */
1839
+ dispose: () => Promise<void>;
1840
+ }
1841
+ /** One concrete resource that an {@link McpResourceTemplate} lists in `resources/list`. */
1842
+ interface McpResourceTemplateEntry {
1843
+ /** The resource URI. It SHOULD match the `uriTemplate` of its template, so `resources/read` finds it. */
1844
+ uri: string;
1845
+ name: string;
1846
+ /** Human-readable name for display. */
1847
+ title?: string;
1848
+ description?: string;
1849
+ /** Defaults to the `mimeType` of the template. */
1850
+ mimeType?: string;
2118
1851
  }
2119
1852
  /**
2120
- * Channel handle returned by `ctx.rpc.streaming.create(name, opts)`. A
2121
- * channel owns a wire namespace; calling `start()` produces individual
2122
- * streams keyed by id.
1853
+ * What an {@link McpResourceTemplate} returns from `read`, sent as the
1854
+ * `resources/read` result: `text` as is, or else `json` as JSON text.
1855
+ */
1856
+ interface McpResourceTemplateContent {
1857
+ text?: string;
1858
+ json?: unknown;
1859
+ /** Defaults to the `mimeType` of the template, then `application/json`. */
1860
+ mimeType?: string;
1861
+ }
1862
+ /**
1863
+ * A resource template the MCP server lists in `resources/templates/list` and
1864
+ * reads in `resources/read`. The `uriTemplate` uses RFC 6570 level 1 only:
1865
+ * plain `{name}` expressions, with text between any two of them. Each
1866
+ * expression matches a non-empty value with no `/`, and its value is
1867
+ * percent-decoded.
2123
1868
  *
2124
- * @see {@link https://devfra.me/guide/streaming Streaming guide}
1869
+ * `list` and `read` get the node context of the devframe (or of the hub), so
1870
+ * they can read live state such as `ctx.rpc.sharedState` or `ctx.agent`. The
1871
+ * route options are resolved before `setup()` runs, so a template cannot
1872
+ * capture that context in a closure.
2125
1873
  */
2126
- interface RpcStreamingChannel<T = unknown> {
2127
- /** Channel name as registered with `ctx.rpc.streaming.create()`. */
2128
- readonly name: string;
1874
+ interface McpResourceTemplate {
1875
+ /** RFC 6570 level 1 URI template, e.g. `my-tool://file/{path}`. Anything else throws `DF0084`. */
1876
+ uriTemplate: string;
1877
+ name: string;
1878
+ /** Human-readable name for display. */
1879
+ title?: string;
1880
+ description?: string;
1881
+ /** MIME type of every resource the template stands for. */
1882
+ mimeType?: string;
1883
+ /** The concrete resources the template stands for right now. They are added to `resources/list`. */
1884
+ list?: (ctx: DevframeNodeContext) => McpResourceTemplateEntry[] | Promise<McpResourceTemplateEntry[]>;
1885
+ /** Read one resource: `params` holds the decoded value of each `{name}`, `uri` the requested URI. */
1886
+ read: (params: Record<string, string>, uri: string, ctx: DevframeNodeContext) => McpResourceTemplateContent | Promise<McpResourceTemplateContent>;
1887
+ }
1888
+ /**
1889
+ * A host subscription for resource-list changes. Call `notify()` each time
1890
+ * the resources change; the MCP server then sends
1891
+ * `notifications/resources/list_changed`. Return a function that removes the
1892
+ * subscription; the MCP server calls it on teardown.
1893
+ */
1894
+ type McpResourcesChangedHook = (notify: () => void, ctx: DevframeNodeContext) => () => void;
1895
+ /** One argument of an {@link McpPrompt}. */
1896
+ interface McpPromptArgument {
1897
+ name: string;
1898
+ description?: string;
1899
+ /** `prompts/get` answers invalid params when a required argument is missing or empty. */
1900
+ required?: boolean;
1901
+ }
1902
+ /** One message of the answer to `prompts/get`. */
1903
+ interface McpPromptMessage {
1904
+ role: 'user' | 'assistant';
1905
+ content: {
1906
+ type: 'text';
1907
+ text: string;
1908
+ };
1909
+ }
1910
+ /** What an {@link McpPrompt} returns from `get`, sent as the `prompts/get` result. */
1911
+ interface McpPromptResult {
1912
+ description?: string;
1913
+ messages: McpPromptMessage[];
1914
+ }
1915
+ /**
1916
+ * A prompt template the MCP server lists in `prompts/list` and builds in
1917
+ * `prompts/get`. A coding agent usually offers it to the user as a slash
1918
+ * command. Each `name` is used once; a repeated name throws `DF0085`.
1919
+ */
1920
+ interface McpPrompt {
1921
+ name: string;
1922
+ /** Human-readable name for display. */
1923
+ title?: string;
1924
+ description?: string;
1925
+ arguments?: McpPromptArgument[];
1926
+ /** Build the messages from the arguments the coding agent sent. */
1927
+ get: (args: Record<string, string>) => McpPromptResult | Promise<McpPromptResult>;
1928
+ }
1929
+ /**
1930
+ * Classification of how a devframe is being deployed. Hosted adapters
1931
+ * (`vite`, `embedded`) share their origin with a host app and must
1932
+ * namespace their mount path under `/__<id>/`. Standalone adapters
1933
+ * (`cli`, `build`) own the origin and default to `/`.
1934
+ */
1935
+ type DevframeDeploymentKind = 'standalone' | 'hosted';
1936
+ /**
1937
+ * How a hub deduplicates devframes that share an `id` when more than one
1938
+ * is mounted onto the same hub. See {@link DevframeDefinition.duplicationStrategy}.
1939
+ *
1940
+ * - `'warn'` (default): keep the first registration, drop later
1941
+ * duplicates, and emit a warning diagnostic (`DF8105`).
1942
+ * - `'silent'`: drop later duplicates without warning.
1943
+ * - `'throw'`: throw when a duplicate is mounted.
1944
+ * - `'duplicate'`: let every instance coexist under a disambiguated
1945
+ * dock id.
1946
+ */
1947
+ type DevframeDuplicationStrategy = 'warn' | 'silent' | 'throw' | 'duplicate';
1948
+ /**
1949
+ * Controls where the browser opens the RPC WebSocket, advertised in
1950
+ * `__connection.json` and used to bind the dev server. The three shapes map
1951
+ * to the three connection scenarios; precedence is `url` > `port` > `route`:
1952
+ *
1953
+ * 1. **Same server, different route** (default): leave `port`/`url` unset.
1954
+ * The socket shares the HTTP server's port and binds to `route`
1955
+ * (`__ws`). The client connects to its own origin, so the link
1956
+ * survives a reverse proxy that rewrites the host/port/subpath.
1957
+ *
1958
+ * 2. **Different port**: set `port`. The socket binds on its own port on the
1959
+ * same host; the client targets `ws(s)://<page-host>:<port>/<route>`.
1960
+ *
1961
+ * 3. **Remote, different origin**: set `url` to a full `ws://`/`wss://`
1962
+ * endpoint (e.g. a public tunnel or relay). The client uses it verbatim.
1963
+ */
1964
+ interface DevframeWsOptions {
2129
1965
  /**
2130
- * Start a new stream. Returns a server-side sink with both an imperative
2131
- * (`write` / `close` / `error`) surface and a `WritableStream<T>` for
2132
- * `pipeTo` consumption. The sink's `signal` aborts when every subscriber
2133
- * disconnects or cancels.
1966
+ * Upgrade route segment the socket binds to and is advertised at, relative
1967
+ * to the SPA base. Default: `__ws`.
2134
1968
  */
2135
- start: (opts?: {
2136
- id?: string;
2137
- }) => StreamSink<T>;
1969
+ route?: string;
1970
+ /**
1971
+ * Bind the socket on its own port instead of sharing the HTTP port. The
1972
+ * browser connects to this port on the page's hostname. Implies
1973
+ * {@link DevframeWsOptions.sidecar}.
1974
+ */
1975
+ port?: number;
1976
+ /**
1977
+ * Start a side-car WebSocket server on a free port, for hosts whose request
1978
+ * handlers can't accept upgrades (Next.js route handlers, Nitro, Rsbuild).
1979
+ * The resolved port is advertised in `__connection.json`, so the browser
1980
+ * finds it without any further wiring. Set `port` instead to pin it.
1981
+ */
1982
+ sidecar?: boolean;
1983
+ /**
1984
+ * Advertise a fixed, fully-qualified endpoint on another origin (a full
1985
+ * `ws://`/`wss://` URL). Takes precedence over `port`/`route` in the meta.
1986
+ */
1987
+ url?: string;
1988
+ }
1989
+ /**
1990
+ * Controls the SSE RPC endpoint, the WebSocket-free transport for hosts
1991
+ * and proxies where the upgrade isn't available. It rides whatever HTTP
1992
+ * surface serves the instance (the same one serving `__connection.json`),
1993
+ * so a relative `route` always resolves; there is no port plumbing of its
1994
+ * own. Enabled by default alongside the WebSocket; pass `sse: false` to
1995
+ * disable, or `ws: false` to run SSE-only (`backend: 'sse'`).
1996
+ */
1997
+ interface DevframeSseOptions {
1998
+ /**
1999
+ * Route segment the SSE endpoint binds to and is advertised at, relative
2000
+ * to the SPA base. Default: `__sse`.
2001
+ */
2002
+ route?: string;
2003
+ }
2004
+ /**
2005
+ * An **optional** identity check layered on top of the origin gate. The
2006
+ * route-based MCP endpoint trusts same-machine callers by default (the
2007
+ * loopback origin gate is enough), so this is opt-in hardening for the cases
2008
+ * where a same-machine process is not a trust boundary, like a LAN/tunnel
2009
+ * origin, a shared/CI box, or a destructive tool surface:
2010
+ *
2011
+ * - a non-empty **bearer token string**: the request must carry
2012
+ * `Authorization: Bearer <token>`, compared in constant time. Back it with
2013
+ * an environment variable rather than a literal;
2014
+ * - a **callback** `(request) => boolean | Promise<boolean>`: a custom
2015
+ * identity check (validate a signed header, an mTLS-derived claim, …). It
2016
+ * only governs identity and cannot relax the origin gate;
2017
+ * - `false`, the default: **origin-only**, trusting same-machine callers.
2018
+ */
2019
+ type McpAuthorization = string | ((request: Request) => boolean | Promise<boolean>) | false;
2020
+ /**
2021
+ * The route-based MCP setting accepted everywhere a host mounts a devframe
2022
+ * (`initDevframe` / `initHub` / `createDevServer` options, `createCac` /
2023
+ * `--mcp`, the framework kits):
2024
+ *
2025
+ * - `'auto'`, the default: mount the route when the devframe exposes an
2026
+ * agent surface (an `agent`-flagged RPC function, or a tool / resource /
2027
+ * provider registered on `ctx.agent`) AND the optional `@devframes/agentic`
2028
+ * peer (the MCP adapter and SDK) is installed. An empty agent surface
2029
+ * mounts nothing and loads no MCP code; a non-empty one without the peer
2030
+ * warns once (DF0078) and mounts nothing.
2031
+ * - `true`: always mount at the default `__mcp` route; a missing
2032
+ * `@devframes/agentic` throws DF0079.
2033
+ * - `false`: never mount, and never probe or warn.
2034
+ * - {@link McpRouteOptions}: always mount (like `true`), with a custom route
2035
+ * path, origin allow-list, or {@link McpAuthorization} identity check.
2036
+ *
2037
+ * A mounted route trusts same-machine callers by default (the loopback
2038
+ * origin gate), exactly like `mcp: true`.
2039
+ */
2040
+ type McpSetting = boolean | 'auto' | McpRouteOptions;
2041
+ /**
2042
+ * Configuration for the route-based MCP server mounted alongside the dev
2043
+ * server (via {@link DevframeCliOptions.mcp}). The endpoint speaks
2044
+ * the MCP Streamable-HTTP transport over the same origin as the SPA,
2045
+ * exposing the definition's `ctx.agent` tools + shared-state resources to
2046
+ * external MCP clients connected to the *running* server.
2047
+ */
2048
+ interface McpRouteOptions {
2049
+ /**
2050
+ * Route segment the MCP endpoint binds to, relative to the SPA base.
2051
+ * Default: `__mcp` (i.e. `/__mcp` standalone, `/__<id>/__mcp` hosted).
2052
+ */
2053
+ path?: string;
2054
+ /**
2055
+ * Optional identity check, layered on top of the origin gate and checked
2056
+ * **after** it. Defaults to origin-only (`false`): the route trusts
2057
+ * same-machine callers, since the loopback origin gate already keeps
2058
+ * arbitrary remote/browser callers out. Set a bearer token or a callback to
2059
+ * harden the route when a same-machine process is not your trust boundary
2060
+ * (LAN/tunnel origin, shared/CI host, destructive tools). See
2061
+ * {@link McpAuthorization}.
2062
+ */
2063
+ authorization?: McpAuthorization;
2064
+ /**
2065
+ * Extra `Origin` header values to accept beyond the loopback default
2066
+ * (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less native client).
2067
+ * Add your LAN/tunnel origin here when reaching the endpoint from another
2068
+ * host, mirroring the WS transport's origin gate. Pass `false` to disable
2069
+ * origin checking entirely (not recommended). Default: loopback-only.
2070
+ *
2071
+ * This is the endpoint's DNS-rebinding protection: the shared
2072
+ * `isAllowedOrigin` gate the WS upgrade already uses, applied as external
2073
+ * middleware (the approach the MCP SDK now recommends over its own
2074
+ * deprecated `allowedHosts`/`allowedOrigins` transport flags). When you
2075
+ * widen it past loopback, layer on {@link McpRouteOptions.authorization} to
2076
+ * prove identity too.
2077
+ */
2078
+ allowedOrigins?: readonly string[] | false;
2079
+ /**
2080
+ * Which shared-state keys the route exposes to agents, through the built-in
2081
+ * `devframe_state_read` tool and the `devframe://state/<key>` resources.
2082
+ * - `true` (default): every key the host publishes
2083
+ * - `false`: none
2084
+ * - `(key) => boolean`: only the keys the filter accepts
2085
+ *
2086
+ * The states themselves stay available to the UI over RPC.
2087
+ */
2088
+ exposeSharedState?: boolean | ((key: string) => boolean);
2089
+ /**
2090
+ * Usage guidance for the route, sent as `instructions` in the MCP
2091
+ * `initialize` (and `server/discover`) result. An MCP client can add it to
2092
+ * the coding agent's context, for example to say which tool to call first.
2093
+ * Omitted or empty sends no instructions.
2094
+ */
2095
+ instructions?: string;
2096
+ /**
2097
+ * Resource templates the route serves through MCP
2098
+ * `resources/templates/list`. `resources/read` tries them after the
2099
+ * registered resources and the shared-state resources (a
2100
+ * `devframe://resource/` or `devframe://state/` URI never reaches them);
2101
+ * the first template whose `uriTemplate` matches the URI reads it. `resources/list` adds the
2102
+ * entries each template's `list` returns. Omitted or empty keeps the
2103
+ * resources unchanged.
2104
+ */
2105
+ resourceTemplates?: McpResourceTemplate[];
2138
2106
  /**
2139
- * Convenience: start a stream and pipe a `ReadableStream<T>` into it.
2140
- * The pipe uses `sink.signal` so cancellation propagates upstream.
2141
- *
2142
- * Node-stream interop: convert a `Readable` with `Readable.toWeb(node)`
2143
- * before passing it here.
2107
+ * Host subscription for resource-list changes. Call `notify()` each time
2108
+ * the resources change (for example, the entries of a resource template);
2109
+ * the route then sends `notifications/resources/list_changed`. Return a
2110
+ * function that removes the subscription; the route calls it on teardown.
2144
2111
  */
2145
- pipeFrom: (readable: ReadableStream<T>, opts?: {
2146
- id?: string;
2147
- }) => Promise<StreamSink<T>>;
2148
- /** Look up an active stream by id. Returns `undefined` if none. */
2149
- get: (id: string) => StreamSink<T> | undefined;
2150
- /** All active outbound stream ids on this channel. */
2151
- ids: () => string[];
2112
+ onResourcesChanged?: McpResourcesChangedHook;
2152
2113
  /**
2153
- * Open an inbound stream: the server side of a client-to-server
2154
- * upload. Allocates an id, returns a `StreamReader<T>` that fills as
2155
- * the client writes chunks. Typical pattern is to call this from an
2156
- * action handler, kick off background processing, and return the id
2157
- * so the caller can start uploading:
2158
- *
2159
- * ```ts
2160
- * handler: async () => {
2161
- * const reader = channel.openInbound()
2162
- * ;(async () => {
2163
- * for await (const chunk of reader) processChunk(chunk)
2164
- * })()
2165
- * return { uploadId: reader.id }
2166
- * }
2167
- * ```
2168
- *
2169
- * Calling `reader.cancel()` on the server sends an `upload-cancel` to
2170
- * the uploading client, which aborts its sink.
2114
+ * Prompt templates the route serves through MCP `prompts/list` and
2115
+ * `prompts/get`. Omitted or empty declares no `prompts` capability.
2171
2116
  */
2172
- openInbound: (opts?: {
2173
- id?: string;
2174
- }) => StreamReader<T>;
2117
+ prompts?: McpPrompt[];
2175
2118
  }
2176
- /**
2177
- * Server-side streaming host. Lives on `ctx.rpc.streaming` alongside
2178
- * `ctx.rpc.sharedState`. Each named channel owns its own stream registry
2179
- * and wire namespace.
2180
- */
2181
- interface RpcStreamingHost {
2119
+ interface DevframeCliOptions {
2120
+ /** Binary name; default: the devframe's `id`. */
2121
+ command?: string;
2122
+ /** Preferred port for the dev server (default 9999). */
2123
+ port?: number;
2124
+ /** Port scan range, forwarded to `get-port-please`. */
2125
+ portRange?: [number, number];
2126
+ /** Prefer a random open port. */
2127
+ random?: boolean;
2128
+ /** Default host to bind to; `--host` overrides. */
2129
+ host?: string;
2182
2130
  /**
2183
- * Register a streaming channel. Names follow the `<plugin-id>:<channel>`
2184
- * convention (e.g. `'my-devtool:chat-stream'`). Throws `DF0032` if the
2185
- * name is already taken.
2131
+ * Auto-open the browser when the dev server starts.
2132
+ * `true` opens the resolved origin; a string opens that relative path.
2133
+ * The `--open` / `--no-open` flags override this.
2186
2134
  */
2187
- create: <T = unknown>(name: string, opts?: RpcStreamingChannelOptions) => RpcStreamingChannel<T>;
2135
+ open?: boolean | string;
2188
2136
  /**
2189
- * Adapters call this when a session disconnects so the host can drop
2190
- * subscribers and abort orphaned streams. Most users do not need this;
2191
- * it's wired by the RPC server binding automatically.
2137
+ * Authentication for the standalone dev server.
2192
2138
  *
2193
- * @internal
2194
- */
2195
- _onSessionDisconnected: (meta: DevframeNodeRpcSessionMeta) => void;
2196
- }
2197
- //#endregion
2198
- //#region src/types/context.d.ts
2199
- interface DevframeCapabilities {
2200
- rpc?: boolean;
2201
- views?: boolean;
2202
- }
2203
- /**
2204
- * Framework- and build-tool-agnostic node context: RPC + diagnostics +
2205
- * agent + the view-host (HTTP file-serving). Host adapters can wrap this
2206
- * to add their own surfaces; for example, `@vitejs/devtools-kit`'s
2207
- * `createKitContext` adds `docks`, `terminals`, `messages`, and
2208
- * `commands` when mounted into Vite DevTools. JSON rendering is an opt-in
2209
- * integration (`@devframes/json-render`) layered on top, not part of this
2210
- * core surface.
2211
- */
2212
- interface DevframeNodeContext {
2213
- readonly workspaceRoot: string;
2214
- readonly cwd: string;
2215
- /**
2216
- * Lifecycle distinction surfaced to plugin authors:
2139
+ * - `undefined` / `true`: the standalone adapters (`cli` /
2140
+ * served `build`) auto-wire devframe's interactive OTP auth
2141
+ * (`createInteractiveAuth`): an untrusted client can only reach
2142
+ * `anonymous:` methods until it exchanges the printed one-time code.
2143
+ * The adapter prints the code + magic-link banner once the server is
2144
+ * listening.
2145
+ * - `false`: no gate, for trusted single-user localhost tools where an
2146
+ * auth round-trip only gets in the way (the built-in plugins set this).
2147
+ * The `--no-auth` CLI flag maps here for one-off runs.
2148
+ * - A {@link DevframeAuthHandler}: a custom handler (e.g. a tuned
2149
+ * `createInteractiveAuth`, or an entirely different scheme) passed
2150
+ * straight through to the RPC transport binding.
2217
2151
  *
2218
- * - `'dev'`: long-running, interactive session. Connections come and
2219
- * go; broadcasts and shared-state mutations are debounced
2220
- * to keep the UI responsive.
2221
- * - `'build'`: one-shot batch run. The context is set up, the devtool
2222
- * collects what it needs, and a snapshot is written. No
2223
- * live UI, no WS server.
2152
+ * Hosted adapters (`vite`, `embedded`) ignore this and defer to the host's
2153
+ * auth; `@vitejs/devtools` honors the equivalent `devtools.clientAuth`.
2224
2154
  *
2225
- * Names are inherited from Vite's serve/build dichotomy but the meaning
2226
- * is general: the same distinction applies to any tool that runs in
2227
- * either an interactive or a static-output mode.
2228
- */
2229
- readonly mode: 'dev' | 'build';
2230
- /**
2231
- * Host runtime abstraction, exposing `mountStatic` / `resolveOrigin` /
2232
- * `getStorageDir`.
2155
+ * @default true
2233
2156
  */
2234
- host: DevframeHost;
2235
- rpc: RpcFunctionsHost;
2236
- views: DevframeViewHost;
2157
+ auth?: boolean | DevframeAuthHandler;
2237
2158
  /**
2238
- * Structured diagnostics host; wraps `nostics` and lets integrations
2239
- * register their own coded errors/warnings into the shared lookup.
2159
+ * How the browser reaches the RPC WebSocket. Defaults to sharing the HTTP
2160
+ * port on the `__ws` route. See {@link DevframeWsOptions} for the
2161
+ * different-port and remote-origin variants. Pass `false` to serve no
2162
+ * WebSocket at all; clients connect over SSE instead (`backend: 'sse'`).
2240
2163
  */
2241
- diagnostics: DevframeDiagnosticsHost;
2164
+ ws?: DevframeWsOptions | false;
2242
2165
  /**
2243
- * Agent host; aggregates the agent-exposed surface of this devtool.
2166
+ * How the browser reaches the SSE RPC endpoint, enabled by default
2167
+ * alongside the WebSocket as the more portable transport. Pass `false`
2168
+ * to disable, or a {@link DevframeSseOptions} to change its route.
2244
2169
  */
2245
- agent: DevframeAgentHost;
2170
+ sse?: boolean | DevframeSseOptions;
2246
2171
  /**
2247
- * Cross-plugin services: a typed, namespaced registry through which one
2248
- * integration exposes a capability (e.g. a data-source registry) and
2249
- * others consume it without a hard package dependency. Ids follow the RPC
2250
- * namespacing rule (`<plugin-id>:<service>`); types come from augmenting
2251
- * the `DevframeServicesRegistry` interface. `whenAvailable` subscriptions
2252
- * absorb setup-order differences between provider and consumer.
2172
+ * Capability-side CAC hook. Called with the CAC instance after the
2173
+ * adapter registers its built-in commands (`build` / `mcp`)
2174
+ * but before `createCac`'s own `configureCli` caller. Use this to
2175
+ * contribute tool-specific flags and subcommands from the definition
2176
+ * itself.
2253
2177
  */
2254
- services: DevframeServicesHost;
2178
+ configure?: (cli: CAC) => void;
2255
2179
  /**
2256
- * This context's own {@link ConnectionMeta.configs}: static, boot-time
2257
- * config a host publishes once through the connection handshake and every
2258
- * client reads read-only. A plain, **non-reactive** object: mutate it
2259
- * during `setup(ctx)` (a plugin sets its keys, a hub aggregates across
2260
- * every installed devframe), never during the session, since it's serialized
2261
- * once, after setup, and changing it afterwards reaches no client.
2180
+ * Typed CLI flags for the default `dev` command, backed by any
2181
+ * [Standard Schema](https://standardschema.dev/) validator (valibot,
2182
+ * zod, arktype, or devframe's built-in `s`). The adapter registers
2183
+ * matching `--kebab-key` options on CAC, validates the parsed values,
2184
+ * and forwards the typed bag to `setup(ctx, { flags })`.
2185
+ *
2186
+ * Use {@link defineCliFlags} to preserve the literal schema-map
2187
+ * shape, and {@link InferCliFlags} to recover the typed output at the
2188
+ * call site:
2262
2189
  *
2263
2190
  * ```ts
2264
- * ctx.staticConfig.dock = {
2265
- * ...ctx.staticConfig.dock,
2266
- * categoryOrder: { ...ctx.staticConfig.dock?.categoryOrder, ...myOrder },
2267
- * }
2268
- * ```
2269
- */
2270
- staticConfig: Partial<DevframeConnectionConfigsRegistry>;
2271
- /**
2272
- * Create a namespace-scoped view of this context. The returned
2273
- * `ctx.scope('my-plugin')` auto-namespaces every RPC id, shared-state
2274
- * key, and streaming channel with `my-plugin:`, and exposes a typed
2275
- * top-level `settings` store. This is the preferred way to consume the
2276
- * context from a single tool's setup code.
2191
+ * const appFlags = defineCliFlags({
2192
+ * depth: v.pipe(v.number(), v.integer()),
2193
+ * config: v.optional(v.string()),
2194
+ * })
2277
2195
  *
2278
- * Pass `null` or `''` to un-scope and get the base context.
2196
+ * defineDevframe({
2197
+ * cli: { flags: appFlags },
2198
+ * setup(ctx, info) {
2199
+ * const flags = info.flags as InferCliFlags<typeof appFlags>
2200
+ * },
2201
+ * })
2202
+ * ```
2279
2203
  */
2280
- scope: {
2281
- <NS extends string>(namespace: NS): DevframeScopedNodeContext<NS, SettingsForNamespace<NS>>;
2282
- (namespace?: null | ''): DevframeNodeContext;
2283
- };
2204
+ flags?: CliFlagsSchema;
2284
2205
  }
2285
2206
  /**
2286
- * Describes where the browser client should open its RPC WebSocket. The
2287
- * object form is the proxy-flexible default: `path` is resolved relative to
2288
- * where `__connection.json` was loaded, and the connection is made to the
2289
- * page's own origin (only the `http`→`ws` / `https`→`wss` protocol swap is
2290
- * applied). This survives reverse proxies that change the host/port, because
2291
- * the client never trusts a server-baked hostname; it reuses its own.
2207
+ * Default dock attributes for the iframe entry a hub synthesizes when it
2208
+ * mounts this devframe. Framework-neutral metadata only; the hub layer
2209
+ * (`ctx.install`) merges these beneath its per-mount `dock` overrides,
2210
+ * which in turn sit beneath the locked, derived `id` / `type` / `url`.
2292
2211
  *
2293
- * Set `port` (and/or `host`) only when the WS endpoint genuinely lives on a
2294
- * different origin than the page, e.g. a side-car server on its own port.
2212
+ * Every field is optional. `title` / `icon` default to the definition's
2213
+ * `name` / `icon` when omitted here; the rest are unset by default.
2214
+ * Standalone adapters (`cli` / `build`) ignore this entirely.
2295
2215
  */
2296
- interface ConnectionMetaWebsocket {
2216
+ interface DevframeDockDefaults {
2217
+ /** Dock entry title. Defaults to the definition's `name`. */
2218
+ title?: string;
2219
+ /** Translations of `title` by BCP 47 tag; see `@devframes/hub`'s `resolveTitle()`. */
2220
+ titleLocales?: Record<string, string>;
2221
+ /** Dock entry icon. Defaults to the definition's `icon`. */
2222
+ icon?: string | {
2223
+ light: string;
2224
+ dark: string;
2225
+ };
2297
2226
  /**
2298
- * Path to the WS endpoint. Relative paths (the default, e.g. `__ws`) are
2299
- * resolved against `__connection.json`'s location; absolute paths (`/__ws`)
2300
- * resolve against the page origin.
2227
+ * Sort weight within the dock; higher sorts earlier.
2228
+ * @default 0
2229
+ */
2230
+ defaultOrder?: number;
2231
+ /**
2232
+ * Category the entry groups under in the dock.
2233
+ * @default 'default'
2234
+ */
2235
+ category?: string;
2236
+ /**
2237
+ * Conditional-visibility expression (same syntax as command `when`
2238
+ * clauses). Set to `'false'` to hide the entry unconditionally.
2239
+ */
2240
+ when?: string;
2241
+ /**
2242
+ * Render-only visibility expression, same syntax as {@link when}. Hides the
2243
+ * entry's own dock-bar button when it evaluates to `false` while leaving it
2244
+ * registered and reachable (activation, RPC lookups, etc.), unlike `when`,
2245
+ * which is the general relevance switch for the entry as a whole.
2246
+ */
2247
+ visibility?: string;
2248
+ /** Badge text rendered on the dock icon (e.g. an unread count). */
2249
+ badge?: string;
2250
+ /** Id of the dock group this entry collapses under, if any. */
2251
+ groupId?: string;
2252
+ /**
2253
+ * A client script the hub imports into the host page (this devframe's **page
2254
+ * script**). An absolute-path `importFrom` is served by the hub under the
2255
+ * mount base and rewritten to that URL, so mounting by package name needs no
2256
+ * host wiring; a URL or bare specifier passes through untouched.
2301
2257
  */
2302
- path?: string;
2303
- /** Override the port. Combined with the page hostname unless `host` is set. */
2304
- port?: number;
2305
- /** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
2306
- host?: string;
2258
+ clientScript?: {
2259
+ /**
2260
+ * Initialize after RPC trust without waiting for dock activation.
2261
+ * @default false
2262
+ */
2263
+ eager?: boolean;
2264
+ /** An absolute filesystem path, a served URL, or a bare npm specifier. */
2265
+ importFrom: string;
2266
+ /**
2267
+ * The name to import the module as.
2268
+ * @default 'default'
2269
+ */
2270
+ importName?: string;
2271
+ };
2307
2272
  }
2308
2273
  /**
2309
- * Object form of {@link ConnectionMeta.sse}: the same proxy-safe shape and
2310
- * resolution rules as {@link ConnectionMetaWebsocket}, producing an
2311
- * `http(s)://` endpoint instead of a `ws(s)://` one. Set `port` (and/or
2312
- * `host`) only when the SSE endpoint genuinely lives on a different origin
2313
- * than the page, e.g. a side-car server on its own port.
2274
+ * Runtime information threaded into `setup(ctx, info)`. Adapters
2275
+ * populate the fields that make sense for their deployment. In
2276
+ * particular, `createCac` fills `flags` with the parsed CAC bag.
2314
2277
  */
2315
- interface ConnectionMetaSse {
2316
- /**
2317
- * Path to the SSE endpoint. Relative paths (the default, e.g. `__sse`)
2318
- * are resolved against `__connection.json`'s location; absolute paths
2319
- * (`/__sse`) resolve against the page origin.
2320
- */
2321
- path?: string;
2322
- /** Override the port. Combined with the page hostname unless `host` is set. */
2323
- port?: number;
2324
- /** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
2325
- host?: string;
2278
+ interface DevframeSetupInfo {
2279
+ /** Parsed CLI flags, populated by the CLI adapter. */
2280
+ flags?: Record<string, unknown>;
2326
2281
  }
2327
- interface ConnectionMeta {
2328
- /**
2329
- * The server's primary live-RPC transport (`websocket` / `sse`), `static`
2330
- * for a pre-computed RPC dump with no live server, or `none` for a server
2331
- * exposing no RPC transport at all (e.g. an MCP-only deployment).
2332
- */
2333
- backend: 'websocket' | 'sse' | 'static' | 'none';
2282
+ interface DevframeDefinition {
2283
+ id: string;
2284
+ name: string;
2285
+ /** Semver of the tool, surfaced in hub UIs and diagnostics. */
2286
+ version: string;
2287
+ /** npm package name the devframe ships in (e.g. `@scope/my-tool`). */
2288
+ packageName: string;
2334
2289
  /**
2335
- * WebSocket endpoint, resolved by the client into a `ws(s)://` URL:
2290
+ * `import.meta.url` of the module that defines this devframe. **Always
2291
+ * provide it** (`importMetaUrl: import.meta.url`): it is the resolution base
2292
+ * for the tool's own dependency graph, which lets the host resolve
2293
+ * everything against the plugin's own installed packages rather than the
2294
+ * consuming app's.
2336
2295
  *
2337
- * - {@link ConnectionMetaWebsocket}: the proxy-flexible default; a
2338
- * same-origin path relative to `__connection.json`.
2339
- * - `number`: a port on the page's hostname (`ws(s)://<host>:<port>`).
2340
- * - `string`: a full `ws://`/`wss://` URL used verbatim, an `http(s)://`
2341
- * URL with its protocol swapped, or a path resolved same-origin.
2296
+ * - **Remote assets**: becomes the default `resolveFrom` for any remote
2297
+ * {@link StaticAssetsSource} the devframe hosts (its `clientAssets`, and
2298
+ * every `ctx.views.hostStatic` call) that doesn't set one explicitly, so a
2299
+ * locally installed copy of the assets package is served with zero
2300
+ * network. A per-asset `resolveFrom` still wins, and an explicit
2301
+ * `resolveFrom: null` still opts out.
2302
+ * - **Service dependencies**: becomes the base the host resolves declared
2303
+ * {@link DevframeServiceInput | services} from, so a plugin can ship a
2304
+ * service package as its own dependency instead of asking users to install
2305
+ * it.
2306
+ *
2307
+ * Optional for backward compatibility; omitting it falls back to
2308
+ * runtime-directory resolution and disables the zero-network installed-copy
2309
+ * fast paths above.
2342
2310
  */
2343
- websocket?: number | string | ConnectionMetaWebsocket;
2311
+ importMetaUrl?: string;
2312
+ /** Project homepage or documentation URL. */
2313
+ homepage: string;
2314
+ /** One-line summary of what the tool does. */
2315
+ description: string;
2316
+ icon?: string | {
2317
+ light: string;
2318
+ dark: string;
2319
+ };
2344
2320
  /**
2345
- * SSE endpoint (`GET` opens the event stream, `POST` carries RPC calls),
2346
- * resolved by the client into an `http(s)://` URL with the same
2347
- * proxy-safe rules as {@link ConnectionMeta.websocket}:
2321
+ * Default dock attributes applied when a hub mounts this devframe as an
2322
+ * iframe dock entry. Consulted only by the hub install path (`ctx.install`),
2323
+ * which merge these beneath the per-mount `dock` overrides; standalone
2324
+ * adapters (`cli` / `build`) ignore it.
2348
2325
  *
2349
- * - {@link ConnectionMetaSse}: the proxy-flexible default; a
2350
- * same-origin path relative to `__connection.json`.
2351
- * - `string`: a full `http(s)://` URL used verbatim, or a path
2352
- * resolved same-origin.
2326
+ * @see {@link DevframeDockDefaults}
2353
2327
  */
2354
- sse?: string | ConnectionMetaSse;
2328
+ dock?: DevframeDockDefaults;
2355
2329
  /**
2356
- * Present when the dev server exposes a route-based MCP endpoint
2357
- * (the host's `mcp` setting). Advertises the MCP Streamable-HTTP route so in-browser
2358
- * tooling (e.g. an MCP inspector) can discover it without guessing the
2359
- * path. `path` is relative to `__connection.json`'s location, like the
2360
- * WebSocket `path`. `port` is set when the endpoint lives on a side-car
2361
- * server on its own port (bridge mode: `devframeViteBridge`,
2362
- * `@devframes/next`): the client combines the page hostname with `port`
2363
- * and resolves `path` against that origin, mirroring
2364
- * {@link ConnectionMetaWebsocket.port}.
2330
+ * Mount path override. Defaults depend on the adapter:
2331
+ * `/` for standalone (`cli` / `build`), `/__<id>/` for hosted
2332
+ * (`vite` / `embedded`).
2365
2333
  */
2366
- mcp?: {
2367
- path: string;
2368
- port?: number;
2369
- };
2334
+ basePath?: string;
2370
2335
  /**
2371
- * Names of RPC functions that have declared `jsonSerializable: true`.
2372
- * Used by the WS / static client to dispatch the per-call wire
2373
- * serializer (strict JSON for these methods, structured-clone for
2374
- * the rest). Populated by the server / build adapter; absent on
2375
- * legacy clients, in which case all outgoing messages fall back to
2376
- * structured-clone.
2336
+ * How a hub reacts when another devframe sharing this one's `id` is
2337
+ * mounted onto the same hub. Consulted only by hub adapters
2338
+ * (`ctx.install`); standalone adapters (`cli` / `build`)
2339
+ * ignore it.
2340
+ *
2341
+ * @default 'warn'
2377
2342
  */
2378
- jsonSerializableMethods?: string[];
2343
+ duplicationStrategy?: DevframeDuplicationStrategy;
2379
2344
  /**
2380
- * URL of the `__connection.json` that owns this meta's relative paths.
2381
- * Two producers write it:
2345
+ * Declares which runtimes meaningfully support this devframe. Adapters
2346
+ * act on a `false` before doing any work:
2382
2347
  *
2383
- * - The client annotates it (absolute) when it publishes the meta on a
2384
- * shared window for same-origin inheritance: a relative
2385
- * `websocket.path` resolves against this, so a child SPA mounted at
2386
- * another base (e.g. a hub mounting several devframes at `/__foo/`,
2387
- * `/__bar/`, …) inherits a dialable endpoint rather than resolving the
2388
- * path against its own mount.
2389
- * - A static hub build serves it (base-absolute, e.g.
2390
- * `/__devframes/__connection.json`) in each **per-frame** meta, so a
2391
- * frame SPA that fetched its own copy still resolves the shared RPC
2392
- * dump from the hub base. Resolved against the fetched URL.
2348
+ * - `build: false`: `createCac` skips registering the `build` subcommand,
2349
+ * `createBuild` refuses (throws `DF0042`) unless `{ force: true }`, and
2350
+ * a hub static build (`buildHub`) silently skips the devframe - no dock,
2351
+ * no SPA copy, no RPCs in the dump. Useful for a devframe whose value is
2352
+ * inherently live (e.g. it manages real files on disk), so a static
2353
+ * export would only ever produce a broken, write-less shell of the tool.
2354
+ * - `dev: false`: `createDevServer` refuses (throws `DF0058`) unless
2355
+ * `{ force: true }`. Useful for a devframe that only makes sense as a
2356
+ * static export (e.g. a report generator with nothing to serve live).
2393
2357
  */
2394
- baseUrl?: string;
2358
+ capabilities?: {
2359
+ dev?: boolean;
2360
+ build?: boolean;
2361
+ };
2395
2362
  /**
2396
- * A pre-issued bearer token embedded in the meta so a client trusts the
2397
- * server on connect without any prompt or `localStorage` lookup. Only a
2398
- * **hub** serving a **per-frame** connection meta populates this; it
2399
- * authenticates once at the top level and bakes the resulting token into
2400
- * the meta each plugin iframe fetches, so a cross-origin frame (which
2401
- * cannot read the hub's `localStorage`) is still pre-authorized. The
2402
- * standalone `__connection.json` never carries a token.
2403
- *
2404
- * > [!WARNING]
2405
- * > A token in a fetchable JSON is only as protected as the URL serving
2406
- * > it. Emit it exclusively from hub-controlled, per-frame meta, never
2407
- * > from a publicly reachable static `__connection.json`.
2363
+ * Wire services this devframe consumes (see `DevframeServiceDefinition`).
2364
+ * Each entry is either a declarative descriptor
2365
+ * (`{ package, version?, required?, options? }`, where the host imports the
2366
+ * package's default-export factory, resolving it against **this plugin's
2367
+ * own dependencies**) or a ready `DevframeServiceDefinition` (the factory
2368
+ * was already called). The adapter queues these before `setup(ctx)` runs
2369
+ * and constructs each service once at the `ctx.services.ready()` barrier,
2370
+ * merging option sets across every declarer. Missing services are skipped
2371
+ * unless marked `required`; clients feature-detect via
2372
+ * `client.services.has(pkg)` and degrade.
2408
2373
  */
2409
- authToken?: string;
2374
+ services?: DevframeServiceInput[];
2410
2375
  /**
2411
- * Session-scoped token that lets a trusted external viewer register its
2412
- * browser origin before opening the WebSocket. The host includes it in the
2413
- * connection metadata, which the viewer obtains through the host page.
2376
+ * Author's SPA dist, served as the devframe's UI. A local directory, or
2377
+ * a {@link StaticAssetsSource} remote declaration (`{ package, version }`)
2378
+ * served through devframe's caching CDN back-proxy so the assets need not
2379
+ * ship inside the node package.
2414
2380
  *
2415
- * Treat this as a bearer credential. Keep connection metadata containing the
2416
- * token same-origin until the requesting origin has been verified.
2381
+ * Consumed by every adapter that serves the UI (`dev`, `build`, `vite`,
2382
+ * `next`, and the hub install path).
2417
2383
  */
2418
- viewerOriginToken?: string;
2384
+ clientAssets?: StaticAssetsSource;
2385
+ /** RPC-level configuration for this devframe (see {@link DevframeRpcOptions}). */
2386
+ rpc?: DevframeRpcOptions;
2387
+ /** Server-side setup: the primary entrypoint. Runs in every runtime. */
2388
+ setup: (ctx: DevframeNodeContext, info?: DevframeSetupInfo) => void | Promise<void>;
2389
+ cli?: DevframeCliOptions;
2390
+ }
2391
+ interface DevframeRpcOptions {
2419
2392
  /**
2420
- * Static, host-declared configuration, baked in once at connect time and
2421
- * fixed for the life of the server (e.g. a hub's UI rebrand, or its
2422
- * aggregated dock-bar layout preferences). Read-only from the browser: a
2423
- * client only ever reads `rpc.connectionMeta.configs`, never writes to it.
2424
- *
2425
- * Contrast this with {@link DevframeSettingsRegistry} (`ctx.scope(ns).settings`)
2426
- * and a hub's `devframe:user-settings` shared-state key; both are
2427
- * mutable, user-editable, and synced bidirectionally over RPC for the
2428
- * life of the session. `configs` is the opposite: one-way, immutable,
2429
- * decided by whoever assembled the server.
2430
- *
2431
- * Each key is owned by one package, contributed via declaration merging:
2393
+ * Opt an RPC function into the static-build snapshot **without owning its
2394
+ * definition**, the mechanism a devframe uses to bake a wire service's
2395
+ * RPC (e.g. `@devframes/service-git`'s `status`/`log`/`show`) into its
2396
+ * `build` export, since the service itself defines no `dump`/`snapshot`.
2432
2397
  *
2433
- * ```ts
2434
- * declare module 'devframe/types' {
2435
- * interface DevframeConnectionConfigsRegistry {
2436
- * 'my-key': { some: 'shape' }
2437
- * }
2438
- * }
2439
- * ```
2398
+ * Each entry is either a bare method id (bakes the no-argument call, like
2399
+ * `snapshot: true`) or `{ method, inputs }` where `inputs` is the list of
2400
+ * argument-tuples to bake, or an async provider given the node context
2401
+ * (so it can enumerate at build time, e.g. read commit hashes via the
2402
+ * service's node API). `createBuild` resolves these after setup and
2403
+ * executes the target's own handler per tuple; the first tuple's result
2404
+ * becomes the fallback so any call variant resolves to a baked value.
2440
2405
  */
2441
- configs?: Partial<DevframeConnectionConfigsRegistry>;
2406
+ snapshot?: DevframeSnapshotRpcEntry[];
2442
2407
  }
2408
+ /** Argument-tuples to bake for a {@link DevframeSnapshotRpcEntry}, or a provider that computes them at build time. */
2409
+ type DevframeSnapshotRpcInputs = readonly (readonly unknown[])[] | ((ctx: DevframeNodeContext) => readonly (readonly unknown[])[] | Promise<readonly (readonly unknown[])[]>);
2443
2410
  /**
2444
- * Augmentation point for {@link ConnectionMeta.configs}. Empty by default;
2445
- * a package that wants to publish static, boot-time config through the
2446
- * connection handshake augments this interface with its own key (see
2447
- * {@link ConnectionMeta.configs} for the pattern). `@devframes/hub`
2448
- * augments it with `dock`; `@devframes/hub-ui` augments it with `ui`.
2411
+ * One {@link DevframeRpcOptions.snapshot} entry: a bare method id (bakes
2412
+ * the no-argument call) or a method plus the argument-tuples to bake.
2449
2413
  */
2450
- interface DevframeConnectionConfigsRegistry {}
2451
- //#endregion
2452
- export { DevframeServiceOf as $, AgentToolProviderHandle as $t, SharedStateEvents as A, McpFetchHandler as At, DevframeSetupInfo as B, DevframeAuthHandler as Bt, createStreamSink as C, DevframeStorageScope as Ct, ImmutableObject as D, CreateMcpServerOptions as Dt, ImmutableMap as E, CreateMcpFetchHandlerOptions as Et, DevframeDefinition as F, McpResourceTemplate as Ft, McpAuthorization as G, AgentHandle as Gt, DevframeSnapshotRpcInputs as H, InferCliFlags as Ht, DevframeDeploymentKind as I, McpResourceTemplateContent as It, DevframeServiceDefinition as J, AgentResourceContent as Jt, McpRouteOptions as K, AgentManifest as Kt, DevframeDockDefaults as L, McpResourceTemplateEntry as Lt, SharedStatePatch as M, McpPromptArgument as Mt, createSharedState as N, McpPromptMessage as Nt, ImmutableSet as O, McpConnectionInfo as Ot, DevframeCliOptions as P, McpPromptResult as Pt, DevframeServiceMeta as Q, AgentToolProvider as Qt, DevframeDuplicationStrategy as R, McpResourcesChangedHook as Rt, createStreamReader as S, DevframeHost as St, ImmutableArray as T, DevframeDiagnosticsLogger as Tt, DevframeSseOptions as U, defineCliFlags as Ut, DevframeSnapshotRpcEntry as V, CliFlagsSchema as Vt, DevframeWsOptions as W, parseCliFlags as Wt, DevframeServiceId as X, AgentTool as Xt, DevframeServiceDescriptor as Y, AgentResourceInput as Yt, DevframeServiceInput as Z, AgentToolInput as Zt, CreateStreamSinkOptions as _, DevframeViewHost as _t, DevframeConnectionConfigsRegistry as a, DevframeScopedNodeContext as at, StreamSink as b, DevframeRpcSharedStates as bt, RpcBroadcastOptions as c, DevframeSettings as ct, RpcSharedStateHost as d, ScopedBroadcastOptions as dt, DevframeAgentHost as en, DevframeServiceScopeOf as et, RpcStreamingChannel as f, ScopedClientFunctions as ft, CreateStreamReaderOptions as g, SettingsForNamespace as gt, BufferedChunk as h, ScopedSharedStates as ht, DevframeCapabilities as i, EventsMap as in, DevframeServicesState as it, SharedStateOptions as j, McpPrompt as jt, SharedState as k, McpContentBlock as kt, RpcFunctionsHost as l, DevframeSettingsRegistry as lt, RpcStreamingHost as m, ScopedServerFunctions as mt, ConnectionMetaSse as n, EventEmitter as nn, DevframeServicesRegistry as nt, DevframeNodeContext as o, DevframeScopedNodeRpc as ot, RpcStreamingChannelOptions as p, ScopedRpcFn as pt, McpSetting as q, AgentResource as qt, ConnectionMetaWebsocket as r, EventUnsubscribe as rn, DevframeServicesScopeRegistry as rt, DevframeNodeRpcSession as s, DevframeScopedStreamingHost as st, ConnectionMeta as t, DevframeAgentHostEvents as tn, DevframeServicesHost as tt, RpcSharedStateGetOptions as u, DevframeSettingsStore as ut, StreamErrorPayload as v, DevframeRpcClientFunctions as vt, Immutable as w, DevframeDiagnosticsHost as wt, StreamSinkEvents as x, BrowserAgentClientInfo as xt, StreamReader as y, DevframeRpcServerFunctions as yt, DevframeRpcOptions as z, McpServerHandle as zt };
2414
+ type DevframeSnapshotRpcEntry = string | {
2415
+ method: string;
2416
+ inputs: DevframeSnapshotRpcInputs;
2417
+ };
2418
+ export { DevframeServiceId as $, AgentToolProviderHandle as $t, CliFlagsSchema as A, StreamReader as At, DevframeNodeRpcSession as B, SharedState as Bt, McpPromptResult as C, DevframeDiagnosticsHost as Ct, McpResourcesChangedHook as D, CreateStreamReaderOptions as Dt, McpResourceTemplateEntry as E, BufferedChunk as Et, ConnectionMetaSse as F, Immutable as Ft, RpcStreamingChannel as G, AgentHandle as Gt, RpcFunctionsHost as H, SharedStateOptions as Ht, ConnectionMetaWebsocket as I, ImmutableArray as It, DevframeRpcClientFunctions as J, AgentResourceContent as Jt, RpcStreamingChannelOptions as K, AgentManifest as Kt, DevframeCapabilities as L, ImmutableMap as Lt, defineCliFlags as M, StreamSinkEvents as Mt, parseCliFlags as N, createStreamReader as Nt, McpServerHandle as O, CreateStreamSinkOptions as Ot, ConnectionMeta as P, createStreamSink as Pt, DevframeServiceDescriptor as Q, AgentToolProvider as Qt, DevframeConnectionConfigsRegistry as R, ImmutableObject as Rt, McpPromptMessage as S, DevframeStorageScope as St, McpResourceTemplateContent as T, BrowserAgentClientInfo as Tt, RpcSharedStateGetOptions as U, SharedStatePatch as Ut, RpcBroadcastOptions as V, SharedStateEvents as Vt, RpcSharedStateHost as W, createSharedState as Wt, DevframeRpcSharedStates as X, AgentTool as Xt, DevframeRpcServerFunctions as Y, AgentResourceInput as Yt, DevframeServiceDefinition as Z, AgentToolInput as Zt, McpConnectionInfo as _, ScopedServerFunctions as _t, DevframeDuplicationStrategy as a, DevframeServicesRegistry as at, McpPrompt as b, DevframeViewHost as bt, DevframeSnapshotRpcEntry as c, DevframeScopedNodeContext as ct, DevframeWsOptions as d, DevframeSettings as dt, DevframeAgentHost as en, DevframeServiceInput as et, McpAuthorization as f, DevframeSettingsRegistry as ft, CreateMcpServerOptions as g, ScopedRpcFn as gt, CreateMcpFetchHandlerOptions as h, ScopedClientFunctions as ht, DevframeDockDefaults as i, EventsMap as in, DevframeServicesHost as it, InferCliFlags as j, StreamSink as jt, DevframeAuthHandler as k, StreamErrorPayload as kt, DevframeSnapshotRpcInputs as l, DevframeScopedNodeRpc as lt, McpSetting as m, ScopedBroadcastOptions as mt, DevframeDefinition as n, EventEmitter as nn, DevframeServiceOf as nt, DevframeRpcOptions as o, DevframeServicesScopeRegistry as ot, McpRouteOptions as p, DevframeSettingsStore as pt, RpcStreamingHost as q, AgentResource as qt, DevframeDeploymentKind as r, EventUnsubscribe as rn, DevframeServiceScopeOf as rt, DevframeSetupInfo as s, DevframeServicesState as st, DevframeCliOptions as t, DevframeAgentHostEvents as tn, DevframeServiceMeta as tt, DevframeSseOptions as u, DevframeScopedStreamingHost as ut, McpContentBlock as v, ScopedSharedStates as vt, McpResourceTemplate as w, DevframeDiagnosticsLogger as wt, McpPromptArgument as x, DevframeHost as xt, McpFetchHandler as y, SettingsForNamespace as yt, DevframeNodeContext as z, ImmutableSet as zt };