devframe 1.3.2 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{_shared-CnKu5myY.d.mts → _shared-BxeuukaL.d.mts} +2 -2
- package/dist/{_shared-BGb9aDxr.mjs → _shared-CtXCQFTV.mjs} +1 -1
- package/dist/adapters/build.d.mts +1 -1
- package/dist/adapters/cac.d.mts +1 -1
- package/dist/adapters/cac.mjs +2 -2
- package/dist/adapters/dev.d.mts +4 -4
- package/dist/adapters/dev.mjs +2 -2
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +4 -4
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +2 -2
- package/dist/{agentic-CCstJ5yv.d.mts → agentic-DVgUzwhV.d.mts} +1 -1
- package/dist/client/index.d.mts +5 -5
- package/dist/client/index.mjs +1 -1
- package/dist/{context-CsdPs3po.d.mts → context-B-AARy_U.d.mts} +1 -1
- package/dist/{dev-DaHYh5YK.mjs → dev-DF1pEq0n.mjs} +2 -2
- package/dist/{dist-DJydMELP.mjs → dist-CZ-TySHD.mjs} +1 -2
- package/dist/in-page-channel/index.d.mts +3 -2
- package/dist/in-page-channel/index.mjs +1 -1
- package/dist/{context-WxTq6kqb.d.mts → index-BhUqJUHG.d.mts} +1404 -1404
- package/dist/index.d.mts +5 -4
- package/dist/{instance-registry-CmZfF5u3.mjs → instance-registry-DwmnWyCK.mjs} +1 -1
- package/dist/{instance-shell-Dk2SejFT.mjs → instance-shell-C0yMifrP.mjs} +5 -5
- package/dist/{instance-shell-Bujp0BhE.d.mts → instance-shell-DTk514M9.d.mts} +4 -4
- package/dist/{interactive-auth-BURwIAvE.mjs → interactive-auth-CZ3CPfEs.mjs} +1 -1
- package/dist/internal/index.d.mts +7 -7
- package/dist/internal/index.mjs +4 -4
- package/dist/{mcp-content-CQQBt2An.d.mts → mcp-content-0a0mpCL5.d.mts} +1 -1
- package/dist/node/auth.d.mts +84 -3
- package/dist/node/auth.mjs +1 -1
- package/dist/node/hub-internals.d.mts +11 -2
- package/dist/node/hub-internals.mjs +1 -1
- package/dist/node/index.d.mts +4 -2
- package/dist/recipes/interactive-auth.d.mts +2 -2
- package/dist/recipes/interactive-auth.mjs +1 -1
- package/dist/{rolldown-runtime-DYf_mQCB.mjs → rolldown-runtime-BVN1G_yT.mjs} +4 -4
- package/dist/rpc/dump.d.mts +1 -1
- package/dist/rpc/index.d.mts +121 -3
- package/dist/rpc/transports/sse-client.d.mts +34 -2
- package/dist/rpc/transports/sse-server.d.mts +2 -2
- package/dist/rpc/transports/ws-bun.d.mts +2 -2
- package/dist/rpc/transports/ws-client.d.mts +27 -2
- package/dist/rpc/transports/ws-deno.d.mts +2 -2
- package/dist/rpc/transports/ws-server.d.mts +150 -3
- package/dist/{rpc-core-CDUizUQB.mjs → rpc-core-BmDHiDR6.mjs} +1 -1
- package/dist/{rpc-core-CFwyV10r.d.mts → rpc-core-CblgPYJ_.d.mts} +3 -3
- package/dist/{shared-state-COV7NCbd.mjs → shared-state-DiAsLHP8.mjs} +3 -2
- package/dist/{state-B_yjfeqo.mjs → state-TdDWqiG-.mjs} +2 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/{types-844OBepb.d.mts → types-KZOyhez5.d.mts} +1 -0
- package/dist/utils/debounce.d.mts +6 -0
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/get-port.mjs +1 -1
- package/dist/utils/launch-editor.mjs +1 -1
- package/dist/utils/nostics.d.mts +40 -2
- package/dist/utils/origin.d.mts +55 -2
- package/dist/utils/serve-static.d.mts +1 -1
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/package.json +6 -6
- package/dist/index-D0qrBkhO.d.mts +0 -121
- package/dist/index-HMJPlexc.d.mts +0 -84
- package/dist/nostics-Dgwh7ytT.d.mts +0 -40
- package/dist/origin-D5i7K_Zb.d.mts +0 -56
- package/dist/sse-client-B2PrSz-H.d.mts +0 -35
- package/dist/ws-client-lqOeANcn.d.mts +0 -28
- package/dist/ws-server-CYHkeyA-.d.mts +0 -150
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { i as BirpcReturn } from "./index-BKu9BxcT.mjs";
|
|
2
|
-
import { d as RpcFunctionsCollectorBase } from "./index-D0qrBkhO.mjs";
|
|
3
1
|
import { t as StandardSchemaV1 } from "./index-BIRF0E5l.mjs";
|
|
4
|
-
import { g as RpcFunctionDefinitionAny, h as RpcFunctionDefinition } from "./types-
|
|
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
5
|
import { n as DevframeRpcConnection, t as DevframeNodeRpcSessionMeta } from "./session-Y9R6tqg4.mjs";
|
|
6
|
+
import { defineDiagnostics } from "./utils/nostics.mjs";
|
|
6
7
|
import { a as RemoteAssetsStore, o as StaticAssetsSource } from "./remote-assets-5AQMHZnV.mjs";
|
|
7
|
-
import { h as defineDiagnostics } from "./nostics-Dgwh7ytT.mjs";
|
|
8
8
|
import { CAC } from "cac";
|
|
9
9
|
interface EventsMap {
|
|
10
10
|
[event: string]: any;
|
|
@@ -277,313 +277,196 @@ interface DevframeAgentHost {
|
|
|
277
277
|
/** Look up a resource by id. */
|
|
278
278
|
getResource: (id: string) => AgentResource | undefined;
|
|
279
279
|
}
|
|
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]>; };
|
|
280
286
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* value is any [Standard Schema](https://standardschema.dev/) validator
|
|
284
|
-
* (valibot, zod, arktype, devframe's built-in `s`, …) used to both (a)
|
|
285
|
-
* derive the CAC option type when the flag is registered and (b) validate
|
|
286
|
-
* the parsed value before it's forwarded to `setup(ctx, { flags })`.
|
|
287
|
-
*/
|
|
288
|
-
type CliFlagsSchema = Record<string, StandardSchemaV1>;
|
|
289
|
-
/**
|
|
290
|
-
* Identity helper that preserves the literal schema-map type; use this
|
|
291
|
-
* so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
|
|
292
|
-
*
|
|
293
|
-
* ```ts
|
|
294
|
-
* const appFlags = defineCliFlags({
|
|
295
|
-
* depth: v.pipe(v.number(), v.integer()),
|
|
296
|
-
* config: v.optional(v.string()),
|
|
297
|
-
* })
|
|
298
|
-
*
|
|
299
|
-
* defineDevframe({
|
|
300
|
-
* cli: { flags: appFlags },
|
|
301
|
-
* setup(ctx, info) {
|
|
302
|
-
* const flags = info.flags as InferCliFlags<typeof appFlags>
|
|
303
|
-
* flags.depth // number
|
|
304
|
-
* flags.config // string | undefined
|
|
305
|
-
* },
|
|
306
|
-
* })
|
|
307
|
-
* ```
|
|
287
|
+
* Serializable patch describing a single mutation to a `SharedState`.
|
|
288
|
+
* Structurally compatible with JSON-Patch and is safe to send over RPC.
|
|
308
289
|
*/
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
flags: Record<string, unknown>;
|
|
315
|
-
issues?: string[];
|
|
316
|
-
};
|
|
290
|
+
interface SharedStatePatch {
|
|
291
|
+
op: 'add' | 'remove' | 'replace';
|
|
292
|
+
path: readonly (string | number)[];
|
|
293
|
+
value?: unknown;
|
|
294
|
+
}
|
|
317
295
|
/**
|
|
318
|
-
*
|
|
319
|
-
* `devframe/recipes/interactive-auth`'s `createInteractiveAuth`. Bundles
|
|
320
|
-
* everything a host adapter needs to wire an authenticated server:
|
|
321
|
-
* the handshake RPC functions, the resolver gate, the connect-time trust
|
|
322
|
-
* hook, and the startup banner.
|
|
323
|
-
*
|
|
324
|
-
* `initDevframe` / `initHub` accept one of these directly via their `auth` option
|
|
325
|
-
* (see {@link https://devfra.me | devframe}'s server docs), or a host can
|
|
326
|
-
* wire the four pieces itself against a lower-level transport.
|
|
296
|
+
* State host that is immutable by default with explicit mutate.
|
|
327
297
|
*/
|
|
328
|
-
interface
|
|
298
|
+
interface SharedState<T> {
|
|
329
299
|
/**
|
|
330
|
-
*
|
|
331
|
-
* handshake), `anonymous:devframe:auth:request-code` (client-requested
|
|
332
|
-
* banner print), and `devframe:auth:revoke` (self-revoke); register these
|
|
333
|
-
* on the RPC host (e.g. `rpcHost.register(fn)` for each).
|
|
300
|
+
* Get the current state. Immutable.
|
|
334
301
|
*/
|
|
335
|
-
|
|
302
|
+
value: () => Immutable<T>;
|
|
336
303
|
/**
|
|
337
|
-
*
|
|
338
|
-
* current trust state. Defaults to allowing any `anonymous:`-prefixed
|
|
339
|
-
* method (see `isAnonymousRpcMethod`) plus anything once the session is
|
|
340
|
-
* trusted.
|
|
304
|
+
* Subscribe to state changes.
|
|
341
305
|
*/
|
|
342
|
-
|
|
306
|
+
on: EventEmitter<SharedStateEvents<T>>['on'];
|
|
343
307
|
/**
|
|
344
|
-
*
|
|
345
|
-
* request (a static/pre-shared token from `clientAuthTokens`, or a token
|
|
346
|
-
* minted by the code exchange) and, when valid, marks the session
|
|
347
|
-
* trusted immediately, before the client's own handshake call.
|
|
308
|
+
* Mutate the state.
|
|
348
309
|
*/
|
|
349
|
-
|
|
310
|
+
mutate: (fn: (state: T) => void, syncId?: string) => void;
|
|
350
311
|
/**
|
|
351
|
-
*
|
|
352
|
-
* browser client triggers this itself over
|
|
353
|
-
* `anonymous:devframe:auth:request-code`; call it directly when the host
|
|
354
|
-
* wants the code on screen without waiting for a client. Safe to call
|
|
355
|
-
* repeatedly; it only prints once per code.
|
|
312
|
+
* Apply patches to the state.
|
|
356
313
|
*/
|
|
357
|
-
|
|
314
|
+
patch: (patches: SharedStatePatch[], syncId?: string) => void;
|
|
358
315
|
/**
|
|
359
|
-
*
|
|
360
|
-
* tab lands already authenticated, e.g. appending the current OTP as a
|
|
361
|
-
* query param. Called with the fully-resolved target URL; return it
|
|
362
|
-
* unchanged to opt out. Optional: a handler that doesn't need this (e.g.
|
|
363
|
-
* one gated by a pre-shared bearer token instead of an interactive code)
|
|
364
|
-
* can omit it and the URL opens as-is.
|
|
316
|
+
* Sync IDs that have been applied to the state.
|
|
365
317
|
*/
|
|
366
|
-
|
|
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;
|
|
367
334
|
}
|
|
335
|
+
declare function createSharedState<T extends object>(options: SharedStateOptions<T>): SharedState<T>;
|
|
368
336
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
* implements it) share one contract without a type-level dependency cycle.
|
|
372
|
-
* None of these shapes reference the MCP SDK: the SDK is an implementation
|
|
373
|
-
* detail of `@devframes/agentic`, freely swappable behind this surface.
|
|
374
|
-
* The h3-bound `mountMcpHttp` shapes live in `node/agentic.ts` (exported via
|
|
375
|
-
* `devframe/internal`) instead: `devframe/types` must stay lib-neutral, and
|
|
376
|
-
* 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.
|
|
377
339
|
*/
|
|
378
|
-
interface
|
|
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;
|
|
379
363
|
/**
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* served route-based by the dev server instead; see `mountMcpHttp` and
|
|
383
|
-
* 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.
|
|
384
366
|
*/
|
|
385
|
-
|
|
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;
|
|
386
377
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
* - `(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.
|
|
391
381
|
*/
|
|
392
|
-
|
|
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>;
|
|
393
394
|
/**
|
|
394
|
-
*
|
|
395
|
-
*
|
|
395
|
+
* Internal: RPC layer subscribes to receive chunk/end notifications.
|
|
396
|
+
* Not part of the public contract; do not call directly.
|
|
397
|
+
*
|
|
398
|
+
* @internal
|
|
396
399
|
*/
|
|
397
|
-
|
|
400
|
+
readonly events: EventEmitter<StreamSinkEvents<T>>;
|
|
398
401
|
/**
|
|
399
|
-
*
|
|
400
|
-
*
|
|
402
|
+
* Internal replay buffer. RPC layer reads on (re)subscribe to feed
|
|
403
|
+
* missed chunks before going live.
|
|
404
|
+
*
|
|
405
|
+
* @internal
|
|
401
406
|
*/
|
|
402
|
-
|
|
407
|
+
readonly buffer: ReadonlyArray<BufferedChunk<T>>;
|
|
408
|
+
}
|
|
409
|
+
interface CreateStreamReaderOptions {
|
|
410
|
+
id?: string;
|
|
403
411
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
412
|
+
* Maximum number of buffered chunks held client-side while the consumer
|
|
413
|
+
* isn't draining. On overflow, the oldest chunk is dropped.
|
|
406
414
|
*/
|
|
407
|
-
|
|
415
|
+
highWaterMark?: number;
|
|
408
416
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
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.
|
|
411
420
|
*/
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
|
|
415
|
-
/** Override the version reported in the MCP handshake. Defaults to `definition.version ?? '0.0.0'`. */
|
|
416
|
-
serverVersion?: string;
|
|
417
|
-
/** Called once the transport is connected. */
|
|
418
|
-
onReady?: (info: {
|
|
419
|
-
transport: 'stdio';
|
|
420
|
-
}) => void;
|
|
421
|
+
onOverflow?: (dropped: number) => void;
|
|
422
|
+
/** Called when the consumer cancels; the RPC layer sends `:cancel` upstream. */
|
|
423
|
+
onCancel?: () => void;
|
|
421
424
|
}
|
|
422
425
|
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
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.
|
|
426
429
|
*/
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
stop: () => Promise<void>;
|
|
441
|
-
}
|
|
442
|
-
interface CreateMcpFetchHandlerOptions {
|
|
443
|
-
/** Name reported in the MCP handshake. */
|
|
444
|
-
serverName: string;
|
|
445
|
-
/** Version reported in the MCP handshake. */
|
|
446
|
-
serverVersion: string;
|
|
447
|
-
/** Expose shared-state keys as MCP resources; see {@link CreateMcpServerOptions.exposeSharedState}. */
|
|
448
|
-
exposeSharedState: boolean | ((key: string) => boolean);
|
|
449
|
-
/** Usage guidance for the handshake; see {@link CreateMcpServerOptions.instructions}. */
|
|
450
|
-
instructions?: string;
|
|
451
|
-
/** Resource templates; see {@link CreateMcpServerOptions.resourceTemplates}. */
|
|
452
|
-
resourceTemplates?: McpResourceTemplate[];
|
|
453
|
-
/** Host subscription for resource-list changes; see {@link CreateMcpServerOptions.onResourcesChanged}. */
|
|
454
|
-
onResourcesChanged?: McpResourcesChangedHook;
|
|
455
|
-
/** Prompt templates; see {@link CreateMcpServerOptions.prompts}. */
|
|
456
|
-
prompts?: McpPrompt[];
|
|
457
|
-
/**
|
|
458
|
-
* Optional identity check, layered on top of the origin gate and checked
|
|
459
|
-
* **after** it: a bearer token string (matched in constant time against
|
|
460
|
-
* `Authorization: Bearer <token>`), a `(request) => boolean` callback, or
|
|
461
|
-
* `false` (the default) for origin-only, trusting same-machine callers. A
|
|
462
|
-
* callback governs identity only and cannot relax the origin gate. See
|
|
463
|
-
* {@link McpAuthorization}.
|
|
464
|
-
*/
|
|
465
|
-
authorization?: McpAuthorization;
|
|
466
|
-
/**
|
|
467
|
-
* Origin allow-list beyond the loopback default. `false` disables the
|
|
468
|
-
* origin gate entirely. Default: loopback-only.
|
|
469
|
-
*
|
|
470
|
-
* Unlike the WS transport, the MCP route does **not** allow `Origin`-less
|
|
471
|
-
* requests: a route-based endpoint is reachable by any local process, so a
|
|
472
|
-
* request must carry an `Origin` that passes the gate. Native clients
|
|
473
|
-
* (e.g. `devframe connect`) send their loopback origin explicitly.
|
|
474
|
-
*/
|
|
475
|
-
allowedOrigins?: readonly string[] | false;
|
|
476
|
-
}
|
|
477
|
-
/** Connection facts a host knows about a request beyond the `Request` itself. */
|
|
478
|
-
interface McpConnectionInfo {
|
|
479
|
-
/**
|
|
480
|
-
* The connecting peer's remote address (a node socket's `remoteAddress`),
|
|
481
|
-
* used to prove a same-machine caller when the endpoint relies on the
|
|
482
|
-
* loopback origin default with no identity check. A host that can resolve a
|
|
483
|
-
* trustworthy peer address (the h3/node mount) supplies it; when it's
|
|
484
|
-
* omitted the origin gate stays the only locality signal.
|
|
485
|
-
*/
|
|
486
|
-
remoteAddress?: string;
|
|
487
|
-
}
|
|
488
|
-
interface McpFetchHandler {
|
|
489
|
-
/**
|
|
490
|
-
* WHATWG-`fetch` handler for the MCP endpoint. Hand every method
|
|
491
|
-
* (POST/GET/DELETE) on the endpoint's path to it; routing by path is the
|
|
492
|
-
* host's job. Pass {@link McpConnectionInfo} when the host can resolve the
|
|
493
|
-
* peer address so the default trust boundary can enforce same-machine
|
|
494
|
-
* locality.
|
|
495
|
-
*/
|
|
496
|
-
fetch: (request: Request, connection?: McpConnectionInfo) => Promise<Response>;
|
|
497
|
-
/** Tear down the handler (aborts in-flight exchanges, drops the change bridge). */
|
|
498
|
-
dispose: () => Promise<void>;
|
|
499
|
-
}
|
|
500
|
-
/** One concrete resource that an {@link McpResourceTemplate} lists in `resources/list`. */
|
|
501
|
-
interface McpResourceTemplateEntry {
|
|
502
|
-
/** The resource URI. It SHOULD match the `uriTemplate` of its template, so `resources/read` finds it. */
|
|
503
|
-
uri: string;
|
|
504
|
-
name: string;
|
|
505
|
-
/** Human-readable name for display. */
|
|
506
|
-
title?: string;
|
|
507
|
-
description?: string;
|
|
508
|
-
/** Defaults to the `mimeType` of the template. */
|
|
509
|
-
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;
|
|
510
443
|
}
|
|
511
444
|
/**
|
|
512
|
-
*
|
|
513
|
-
* `
|
|
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.
|
|
514
448
|
*/
|
|
515
|
-
|
|
516
|
-
text?: string;
|
|
517
|
-
json?: unknown;
|
|
518
|
-
/** Defaults to the `mimeType` of the template, then `application/json`. */
|
|
519
|
-
mimeType?: string;
|
|
520
|
-
}
|
|
449
|
+
declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
|
|
521
450
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
* expression matches a non-empty value with no `/`, and its value is
|
|
526
|
-
* percent-decoded.
|
|
527
|
-
*
|
|
528
|
-
* `list` and `read` get the node context of the devframe (or of the hub), so
|
|
529
|
-
* they can read live state such as `ctx.rpc.sharedState` or `ctx.agent`. The
|
|
530
|
-
* route options are resolved before `setup()` runs, so a template cannot
|
|
531
|
-
* 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`.
|
|
532
454
|
*/
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
name: string;
|
|
537
|
-
/** Human-readable name for display. */
|
|
455
|
+
declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
|
|
456
|
+
interface BrowserAgentToolManifest {
|
|
457
|
+
id: string;
|
|
538
458
|
title?: string;
|
|
539
|
-
description
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
list?: (ctx: DevframeNodeContext) => McpResourceTemplateEntry[] | Promise<McpResourceTemplateEntry[]>;
|
|
544
|
-
/** Read one resource: `params` holds the decoded value of each `{name}`, `uri` the requested URI. */
|
|
545
|
-
read: (params: Record<string, string>, uri: string, ctx: DevframeNodeContext) => McpResourceTemplateContent | Promise<McpResourceTemplateContent>;
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* A host subscription for resource-list changes. Call `notify()` each time
|
|
549
|
-
* the resources change; the MCP server then sends
|
|
550
|
-
* `notifications/resources/list_changed`. Return a function that removes the
|
|
551
|
-
* subscription; the MCP server calls it on teardown.
|
|
552
|
-
*/
|
|
553
|
-
type McpResourcesChangedHook = (notify: () => void, ctx: DevframeNodeContext) => () => void;
|
|
554
|
-
/** One argument of an {@link McpPrompt}. */
|
|
555
|
-
interface McpPromptArgument {
|
|
556
|
-
name: string;
|
|
557
|
-
description?: string;
|
|
558
|
-
/** `prompts/get` answers invalid params when a required argument is missing or empty. */
|
|
559
|
-
required?: boolean;
|
|
560
|
-
}
|
|
561
|
-
/** One message of the answer to `prompts/get`. */
|
|
562
|
-
interface McpPromptMessage {
|
|
563
|
-
role: 'user' | 'assistant';
|
|
564
|
-
content: {
|
|
565
|
-
type: 'text';
|
|
566
|
-
text: string;
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
/** What an {@link McpPrompt} returns from `get`, sent as the `prompts/get` result. */
|
|
570
|
-
interface McpPromptResult {
|
|
571
|
-
description?: string;
|
|
572
|
-
messages: McpPromptMessage[];
|
|
459
|
+
description: string;
|
|
460
|
+
safety: 'read' | 'action' | 'destructive';
|
|
461
|
+
tags?: readonly string[];
|
|
462
|
+
inputSchema?: unknown;
|
|
573
463
|
}
|
|
574
|
-
/**
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
name: string;
|
|
581
|
-
/** Human-readable name for display. */
|
|
582
|
-
title?: string;
|
|
583
|
-
description?: string;
|
|
584
|
-
arguments?: McpPromptArgument[];
|
|
585
|
-
/** Build the messages from the arguments the coding agent sent. */
|
|
586
|
-
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;
|
|
587
470
|
}
|
|
588
471
|
/**
|
|
589
472
|
* The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
|
|
@@ -708,266 +591,44 @@ interface DevframeHost {
|
|
|
708
591
|
* private, `global` is per-user.
|
|
709
592
|
*/
|
|
710
593
|
type DevframeStorageScope = 'workspace' | 'project' | 'global';
|
|
711
|
-
interface
|
|
712
|
-
id: string;
|
|
713
|
-
title?: string;
|
|
714
|
-
description: string;
|
|
715
|
-
safety: 'read' | 'action' | 'destructive';
|
|
716
|
-
tags?: readonly string[];
|
|
717
|
-
inputSchema?: unknown;
|
|
718
|
-
}
|
|
719
|
-
/** What a connected document reports about itself alongside its tool manifest. */
|
|
720
|
-
interface BrowserAgentClientInfo {
|
|
721
|
-
url: string;
|
|
722
|
-
title: string;
|
|
723
|
-
visible: boolean;
|
|
724
|
-
focused: boolean;
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* To be extended
|
|
728
|
-
*/
|
|
729
|
-
interface DevframeRpcClientFunctions {
|
|
730
|
-
/** Clear cached RPC results and refresh cache eligibility. */
|
|
731
|
-
'devframe:rpc:cache:invalidate': () => Promise<void>;
|
|
732
|
-
/** Invoke a tool registered in this browser document. @internal */
|
|
733
|
-
'devframe:agent:invoke-client-tool': (id: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
734
|
-
/**
|
|
735
|
-
* Server→client notification that this connection's auth token has been
|
|
736
|
-
* revoked. The client drops to untrusted on receipt. Broadcast by
|
|
737
|
-
* `revokeActiveConnectionsForToken`.
|
|
738
|
-
*
|
|
739
|
-
* @internal
|
|
740
|
-
*/
|
|
741
|
-
'devframe:auth:revoked': () => Promise<void>;
|
|
742
|
-
/**
|
|
743
|
-
* Streaming chunk pushed from server to subscribed clients. Wired by
|
|
744
|
-
* `RpcStreamingHost`; do not register manually.
|
|
745
|
-
*
|
|
746
|
-
* @internal
|
|
747
|
-
*/
|
|
748
|
-
'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
749
|
-
/**
|
|
750
|
-
* Streaming terminator pushed from server to subscribed clients. Wired by
|
|
751
|
-
* `RpcStreamingHost`; do not register manually.
|
|
752
|
-
*
|
|
753
|
-
* @internal
|
|
754
|
-
*/
|
|
755
|
-
'devframe:streaming:end': (channel: string, id: string, error?: {
|
|
756
|
-
name: string;
|
|
757
|
-
message: string;
|
|
758
|
-
}) => Promise<void>;
|
|
594
|
+
interface DevframeViewHost {
|
|
759
595
|
/**
|
|
760
|
-
*
|
|
761
|
-
* `
|
|
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.
|
|
762
600
|
*
|
|
763
601
|
* @internal
|
|
764
602
|
*/
|
|
765
|
-
|
|
603
|
+
buildStaticDirs: {
|
|
604
|
+
baseUrl: string;
|
|
605
|
+
source: StaticAssetsSource;
|
|
606
|
+
resolveFrom?: string | null;
|
|
607
|
+
}[];
|
|
766
608
|
/**
|
|
767
|
-
*
|
|
768
|
-
*
|
|
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
|
|
769
612
|
*
|
|
770
|
-
* @
|
|
771
|
-
|
|
772
|
-
'devframe:rpc:client-state:updated': (key: string, fullState: any, syncId: string) => Promise<void>;
|
|
773
|
-
/**
|
|
774
|
-
* Incremental shared-state patch pushed from server to subscribed clients.
|
|
775
|
-
* 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.
|
|
776
615
|
*
|
|
777
|
-
*
|
|
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.
|
|
778
622
|
*/
|
|
779
|
-
|
|
623
|
+
hostStatic: (baseUrl: string, source: StaticAssetsSource, defaultResolveFrom?: string | null) => void;
|
|
780
624
|
}
|
|
625
|
+
type AnyRpcFn = (...args: any[]) => any;
|
|
781
626
|
/**
|
|
782
|
-
*
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
/** Replace this connection's browser-agent tool manifest, tagged with the calling tab's stable client id. @internal */
|
|
788
|
-
'devframe:agent:sync-client-tools': (clientId: string, tools: BrowserAgentToolManifest[], info: BrowserAgentClientInfo) => Promise<void>;
|
|
789
|
-
/**
|
|
790
|
-
* Authenticate a connection with a previously-issued bearer token; resolves
|
|
791
|
-
* whether the connection is now trusted. The interactive handler is provided
|
|
792
|
-
* by the host adapter (e.g. Vite DevTools); the standalone server registers
|
|
793
|
-
* an auto-trust noop when `auth: false`.
|
|
794
|
-
*
|
|
795
|
-
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
796
|
-
* reachable before the connection is trusted; this is the *only* rule the
|
|
797
|
-
* pre-trust gate applies, no per-method allowlist.
|
|
798
|
-
*
|
|
799
|
-
* @internal
|
|
800
|
-
*/
|
|
801
|
-
'anonymous:devframe:auth': (params: {
|
|
802
|
-
authToken: string;
|
|
803
|
-
ua: string;
|
|
804
|
-
origin: string;
|
|
805
|
-
}) => Promise<{
|
|
806
|
-
isTrusted: boolean;
|
|
807
|
-
}>;
|
|
808
|
-
/**
|
|
809
|
-
* Exchange a one-time authentication code (shown by the dev server) for a fresh,
|
|
810
|
-
* node-issued bearer token, returning the token on success or `null`. The
|
|
811
|
-
* handler is provided by the host adapter on top of `exchangeTempAuthCode`.
|
|
812
|
-
*
|
|
813
|
-
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
814
|
-
* reachable before the connection is trusted.
|
|
815
|
-
*
|
|
816
|
-
* @internal
|
|
817
|
-
*/
|
|
818
|
-
'anonymous:devframe:auth:exchange': (params: {
|
|
819
|
-
code: string;
|
|
820
|
-
ua: string;
|
|
821
|
-
origin: string;
|
|
822
|
-
}) => Promise<{
|
|
823
|
-
authToken: string | null;
|
|
824
|
-
}>;
|
|
825
|
-
/**
|
|
826
|
-
* Ask the server to print its auth banner (code + magic link) for this
|
|
827
|
-
* client, e.g. when an auth UI first shows; `reissue: true` rotates the code
|
|
828
|
-
* first (the manual "re-issue" action). Registered by
|
|
829
|
-
* `recipes/interactive-auth`; a repeat request for an already-printed code
|
|
830
|
-
* is a no-op.
|
|
831
|
-
*
|
|
832
|
-
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
833
|
-
* reachable before the connection is trusted.
|
|
834
|
-
*
|
|
835
|
-
* @internal
|
|
836
|
-
*/
|
|
837
|
-
'anonymous:devframe:auth:request-code': (params: {
|
|
838
|
-
ua: string;
|
|
839
|
-
origin: string;
|
|
840
|
-
reissue?: boolean;
|
|
841
|
-
}) => Promise<void>;
|
|
842
|
-
/**
|
|
843
|
-
* Self-revoke: the caller asks the server to revoke its own bearer token
|
|
844
|
-
* (if any) and drop to untrusted. Requires an already-trusted caller, so
|
|
845
|
-
* unlike the two handshake methods above it does **not** carry the
|
|
846
|
-
* `anonymous:` prefix. Registered by `recipes/interactive-auth`.
|
|
847
|
-
*
|
|
848
|
-
* @internal
|
|
849
|
-
*/
|
|
850
|
-
'devframe:auth:revoke': () => Promise<void>;
|
|
851
|
-
/**
|
|
852
|
-
* Subscribe a client to a shared-state key. Wired by
|
|
853
|
-
* `RpcSharedStateHost`; do not register manually.
|
|
854
|
-
*
|
|
855
|
-
* @internal
|
|
856
|
-
*/
|
|
857
|
-
'devframe:rpc:server-state:subscribe': (key: string) => Promise<void>;
|
|
858
|
-
/**
|
|
859
|
-
* Read the current value for a shared-state key. Wired by
|
|
860
|
-
* `RpcSharedStateHost`; do not register manually.
|
|
861
|
-
*
|
|
862
|
-
* @internal
|
|
863
|
-
*/
|
|
864
|
-
'devframe:rpc:server-state:get': (key: string) => Promise<any>;
|
|
865
|
-
/**
|
|
866
|
-
* Replace a shared-state value (from the client). Wired by
|
|
867
|
-
* `RpcSharedStateHost`; do not register manually.
|
|
868
|
-
*
|
|
869
|
-
* @internal
|
|
870
|
-
*/
|
|
871
|
-
'devframe:rpc:server-state:set': (key: string, value: any, syncId: string) => Promise<void>;
|
|
872
|
-
/**
|
|
873
|
-
* Apply a patch to a shared-state value (from the client). Wired by
|
|
874
|
-
* `RpcSharedStateHost`; do not register manually.
|
|
875
|
-
*
|
|
876
|
-
* @internal
|
|
877
|
-
*/
|
|
878
|
-
'devframe:rpc:server-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
|
|
879
|
-
/**
|
|
880
|
-
* Client→server streaming subscription with optional replay cursor.
|
|
881
|
-
* Wired by `RpcStreamingHost`; do not register manually.
|
|
882
|
-
*
|
|
883
|
-
* @internal
|
|
884
|
-
*/
|
|
885
|
-
'devframe:streaming:subscribe': (channel: string, id: string, opts?: {
|
|
886
|
-
afterSeq?: number;
|
|
887
|
-
}) => Promise<void>;
|
|
888
|
-
/**
|
|
889
|
-
* Client→server streaming unsubscribe. Wired by `RpcStreamingHost`;
|
|
890
|
-
* do not register manually.
|
|
891
|
-
*
|
|
892
|
-
* @internal
|
|
893
|
-
*/
|
|
894
|
-
'devframe:streaming:unsubscribe': (channel: string, id: string) => Promise<void>;
|
|
895
|
-
/**
|
|
896
|
-
* Client→server streaming cancellation request. Wired by
|
|
897
|
-
* `RpcStreamingHost`; do not register manually.
|
|
898
|
-
*
|
|
899
|
-
* @internal
|
|
900
|
-
*/
|
|
901
|
-
'devframe:streaming:cancel': (channel: string, id: string) => Promise<void>;
|
|
902
|
-
/**
|
|
903
|
-
* Client→server upload chunk. Wired by `RpcStreamingHost`; do not
|
|
904
|
-
* register manually.
|
|
905
|
-
*
|
|
906
|
-
* @internal
|
|
907
|
-
*/
|
|
908
|
-
'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
909
|
-
/**
|
|
910
|
-
* Client→server upload terminator. Wired by `RpcStreamingHost`; do not
|
|
911
|
-
* register manually.
|
|
912
|
-
*
|
|
913
|
-
* @internal
|
|
914
|
-
*/
|
|
915
|
-
'devframe:streaming:upload-end': (channel: string, id: string, error?: {
|
|
916
|
-
name: string;
|
|
917
|
-
message: string;
|
|
918
|
-
}) => Promise<void>;
|
|
919
|
-
}
|
|
920
|
-
/**
|
|
921
|
-
* To be extended
|
|
922
|
-
*/
|
|
923
|
-
interface DevframeRpcSharedStates {
|
|
924
|
-
/**
|
|
925
|
-
* Wire-service advertisements: package name → `{ package, version,
|
|
926
|
-
* scope, meta }` for every installed {@link import('./services').DevframeServiceDefinition}.
|
|
927
|
-
* Written by the node services host at the `ready()` barrier; clients read
|
|
928
|
-
* it through `client.services` (or subscribe to it directly for
|
|
929
|
-
* reactivity). Read-only from the browser.
|
|
930
|
-
*/
|
|
931
|
-
'devframe:services': DevframeServicesState;
|
|
932
|
-
}
|
|
933
|
-
interface DevframeViewHost {
|
|
934
|
-
/**
|
|
935
|
-
* Static mounts registered through {@link DevframeViewHost.hostStatic}, each
|
|
936
|
-
* carrying the `resolveFrom` base it was mounted with so a build step that
|
|
937
|
-
* copies these itself (rather than serving them live) re-resolves a remote
|
|
938
|
-
* source to the same locally-installed copy it would serve live.
|
|
939
|
-
*
|
|
940
|
-
* @internal
|
|
941
|
-
*/
|
|
942
|
-
buildStaticDirs: {
|
|
943
|
-
baseUrl: string;
|
|
944
|
-
source: StaticAssetsSource;
|
|
945
|
-
resolveFrom?: string | null;
|
|
946
|
-
}[];
|
|
947
|
-
/**
|
|
948
|
-
* Helper to host static files
|
|
949
|
-
* - In `dev` mode, it will register middleware to `viteServer.middlewares` to host the static files
|
|
950
|
-
* - In `build` mode, it will copy the static files to the dist directory
|
|
951
|
-
*
|
|
952
|
-
* Accepts a local dist directory, or a {@link StaticAssetsSource} remote
|
|
953
|
-
* declaration served through devframe's caching CDN back-proxy.
|
|
954
|
-
*
|
|
955
|
-
* `defaultResolveFrom` overrides, for this call only, the context's own
|
|
956
|
-
* `importMetaUrl` as the default `resolveFrom` for a remote source that
|
|
957
|
-
* doesn't set one. A shared host that mounts assets on behalf of another
|
|
958
|
-
* devframe (a hub installing a plugin) passes that plugin's `importMetaUrl`
|
|
959
|
-
* so the assets resolve against the plugin's dependency graph rather than
|
|
960
|
-
* the host's.
|
|
961
|
-
*/
|
|
962
|
-
hostStatic: (baseUrl: string, source: StaticAssetsSource, defaultResolveFrom?: string | null) => void;
|
|
963
|
-
}
|
|
964
|
-
type AnyRpcFn = (...args: any[]) => any;
|
|
965
|
-
/**
|
|
966
|
-
* Resolve a scoped bare name `T` back to its fully-qualified entry in
|
|
967
|
-
* `Registry` (`<NS>:<T>`). Indexing the plain registry behind a `keyof`
|
|
968
|
-
* guard keeps TypeScript from rejecting a generic index into the
|
|
969
|
-
* key-remapped `Scoped*Functions<NS>` maps (TS2536) once a plugin augments
|
|
970
|
-
* 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.
|
|
971
632
|
*/
|
|
972
633
|
type ScopedRpcFn<Registry, NS extends string, T extends string> = `${NS}:${T}` extends keyof Registry ? Extract<Registry[`${NS}:${T}`], AnyRpcFn> : AnyRpcFn;
|
|
973
634
|
/**
|
|
@@ -1334,1085 +995,1424 @@ interface DevframeServicesHost {
|
|
|
1334
995
|
ready: () => Promise<void>;
|
|
1335
996
|
}
|
|
1336
997
|
/**
|
|
1337
|
-
*
|
|
1338
|
-
* (`vite`, `embedded`) share their origin with a host app and must
|
|
1339
|
-
* namespace their mount path under `/__<id>/`. Standalone adapters
|
|
1340
|
-
* (`cli`, `build`) own the origin and default to `/`.
|
|
1341
|
-
*/
|
|
1342
|
-
type DevframeDeploymentKind = 'standalone' | 'hosted';
|
|
1343
|
-
/**
|
|
1344
|
-
* How a hub deduplicates devframes that share an `id` when more than one
|
|
1345
|
-
* is mounted onto the same hub. See {@link DevframeDefinition.duplicationStrategy}.
|
|
1346
|
-
*
|
|
1347
|
-
* - `'warn'` (default): keep the first registration, drop later
|
|
1348
|
-
* duplicates, and emit a warning diagnostic (`DF8105`).
|
|
1349
|
-
* - `'silent'`: drop later duplicates without warning.
|
|
1350
|
-
* - `'throw'`: throw when a duplicate is mounted.
|
|
1351
|
-
* - `'duplicate'`: let every instance coexist under a disambiguated
|
|
1352
|
-
* dock id.
|
|
1353
|
-
*/
|
|
1354
|
-
type DevframeDuplicationStrategy = 'warn' | 'silent' | 'throw' | 'duplicate';
|
|
1355
|
-
/**
|
|
1356
|
-
* Controls where the browser opens the RPC WebSocket, advertised in
|
|
1357
|
-
* `__connection.json` and used to bind the dev server. The three shapes map
|
|
1358
|
-
* to the three connection scenarios; precedence is `url` > `port` > `route`:
|
|
1359
|
-
*
|
|
1360
|
-
* 1. **Same server, different route** (default): leave `port`/`url` unset.
|
|
1361
|
-
* The socket shares the HTTP server's port and binds to `route`
|
|
1362
|
-
* (`__ws`). The client connects to its own origin, so the link
|
|
1363
|
-
* survives a reverse proxy that rewrites the host/port/subpath.
|
|
1364
|
-
*
|
|
1365
|
-
* 2. **Different port**: set `port`. The socket binds on its own port on the
|
|
1366
|
-
* same host; the client targets `ws(s)://<page-host>:<port>/<route>`.
|
|
1367
|
-
*
|
|
1368
|
-
* 3. **Remote, different origin**: set `url` to a full `ws://`/`wss://`
|
|
1369
|
-
* endpoint (e.g. a public tunnel or relay). The client uses it verbatim.
|
|
998
|
+
* To be extended
|
|
1370
999
|
*/
|
|
1371
|
-
interface
|
|
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>;
|
|
1372
1005
|
/**
|
|
1373
|
-
*
|
|
1374
|
-
* to
|
|
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
|
|
1375
1011
|
*/
|
|
1376
|
-
|
|
1012
|
+
'devframe:auth:revoked': () => Promise<void>;
|
|
1377
1013
|
/**
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1014
|
+
* Streaming chunk pushed from server to subscribed clients. Wired by
|
|
1015
|
+
* `RpcStreamingHost`; do not register manually.
|
|
1016
|
+
*
|
|
1017
|
+
* @internal
|
|
1381
1018
|
*/
|
|
1382
|
-
|
|
1019
|
+
'devframe:streaming:chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
1383
1020
|
/**
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1021
|
+
* Streaming terminator pushed from server to subscribed clients. Wired by
|
|
1022
|
+
* `RpcStreamingHost`; do not register manually.
|
|
1023
|
+
*
|
|
1024
|
+
* @internal
|
|
1388
1025
|
*/
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1026
|
+
'devframe:streaming:end': (channel: string, id: string, error?: {
|
|
1027
|
+
name: string;
|
|
1028
|
+
message: string;
|
|
1029
|
+
}) => Promise<void>;
|
|
1030
|
+
/**
|
|
1031
|
+
* Server→client cancel for an in-flight upload. Wired by
|
|
1032
|
+
* `RpcStreamingHost`; do not register manually.
|
|
1033
|
+
*
|
|
1034
|
+
* @internal
|
|
1393
1035
|
*/
|
|
1394
|
-
|
|
1395
|
-
}
|
|
1396
|
-
/**
|
|
1397
|
-
* Controls the SSE RPC endpoint, the WebSocket-free transport for hosts
|
|
1398
|
-
* and proxies where the upgrade isn't available. It rides whatever HTTP
|
|
1399
|
-
* surface serves the instance (the same one serving `__connection.json`),
|
|
1400
|
-
* so a relative `route` always resolves; there is no port plumbing of its
|
|
1401
|
-
* own. Enabled by default alongside the WebSocket; pass `sse: false` to
|
|
1402
|
-
* disable, or `ws: false` to run SSE-only (`backend: 'sse'`).
|
|
1403
|
-
*/
|
|
1404
|
-
interface DevframeSseOptions {
|
|
1036
|
+
'devframe:streaming:upload-cancel': (channel: string, id: string) => Promise<void>;
|
|
1405
1037
|
/**
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1038
|
+
* Full shared-state snapshot pushed from server to subscribed clients.
|
|
1039
|
+
* Wired by `RpcSharedStateHost`; do not register manually.
|
|
1040
|
+
*
|
|
1041
|
+
* @internal
|
|
1408
1042
|
*/
|
|
1409
|
-
|
|
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>;
|
|
1410
1051
|
}
|
|
1411
1052
|
/**
|
|
1412
|
-
*
|
|
1413
|
-
* route-based MCP endpoint trusts same-machine callers by default (the
|
|
1414
|
-
* loopback origin gate is enough), so this is opt-in hardening for the cases
|
|
1415
|
-
* where a same-machine process is not a trust boundary, like a LAN/tunnel
|
|
1416
|
-
* origin, a shared/CI box, or a destructive tool surface:
|
|
1417
|
-
*
|
|
1418
|
-
* - a non-empty **bearer token string**: the request must carry
|
|
1419
|
-
* `Authorization: Bearer <token>`, compared in constant time. Back it with
|
|
1420
|
-
* an environment variable rather than a literal;
|
|
1421
|
-
* - a **callback** `(request) => boolean | Promise<boolean>`: a custom
|
|
1422
|
-
* identity check (validate a signed header, an mTLS-derived claim, …). It
|
|
1423
|
-
* only governs identity and cannot relax the origin gate;
|
|
1424
|
-
* - `false`, the default: **origin-only**, trusting same-machine callers.
|
|
1425
|
-
*/
|
|
1426
|
-
type McpAuthorization = string | ((request: Request) => boolean | Promise<boolean>) | false;
|
|
1427
|
-
/**
|
|
1428
|
-
* The route-based MCP setting accepted everywhere a host mounts a devframe
|
|
1429
|
-
* (`initDevframe` / `initHub` / `createDevServer` options, `createCac` /
|
|
1430
|
-
* `--mcp`, the framework kits):
|
|
1431
|
-
*
|
|
1432
|
-
* - `'auto'`, the default: mount the route when the devframe exposes an
|
|
1433
|
-
* agent surface (an `agent`-flagged RPC function, or a tool / resource /
|
|
1434
|
-
* provider registered on `ctx.agent`) AND the optional `@devframes/agentic`
|
|
1435
|
-
* peer (the MCP adapter and SDK) is installed. An empty agent surface
|
|
1436
|
-
* mounts nothing and loads no MCP code; a non-empty one without the peer
|
|
1437
|
-
* warns once (DF0078) and mounts nothing.
|
|
1438
|
-
* - `true`: always mount at the default `__mcp` route; a missing
|
|
1439
|
-
* `@devframes/agentic` throws DF0079.
|
|
1440
|
-
* - `false`: never mount, and never probe or warn.
|
|
1441
|
-
* - {@link McpRouteOptions}: always mount (like `true`), with a custom route
|
|
1442
|
-
* path, origin allow-list, or {@link McpAuthorization} identity check.
|
|
1443
|
-
*
|
|
1444
|
-
* A mounted route trusts same-machine callers by default (the loopback
|
|
1445
|
-
* origin gate), exactly like `mcp: true`.
|
|
1446
|
-
*/
|
|
1447
|
-
type McpSetting = boolean | 'auto' | McpRouteOptions;
|
|
1448
|
-
/**
|
|
1449
|
-
* Configuration for the route-based MCP server mounted alongside the dev
|
|
1450
|
-
* server (via {@link DevframeCliOptions.mcp}). The endpoint speaks
|
|
1451
|
-
* the MCP Streamable-HTTP transport over the same origin as the SPA,
|
|
1452
|
-
* exposing the definition's `ctx.agent` tools + shared-state resources to
|
|
1453
|
-
* external MCP clients connected to the *running* server.
|
|
1053
|
+
* To be extended
|
|
1454
1054
|
*/
|
|
1455
|
-
interface
|
|
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>;
|
|
1456
1060
|
/**
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
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
|
|
1459
1071
|
*/
|
|
1460
|
-
|
|
1072
|
+
'anonymous:devframe:auth': (params: {
|
|
1073
|
+
authToken: string;
|
|
1074
|
+
ua: string;
|
|
1075
|
+
origin: string;
|
|
1076
|
+
}) => Promise<{
|
|
1077
|
+
isTrusted: boolean;
|
|
1078
|
+
}>;
|
|
1461
1079
|
/**
|
|
1462
|
-
*
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
*
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
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
|
|
1469
1088
|
*/
|
|
1470
|
-
|
|
1089
|
+
'anonymous:devframe:auth:exchange': (params: {
|
|
1090
|
+
code: string;
|
|
1091
|
+
ua: string;
|
|
1092
|
+
origin: string;
|
|
1093
|
+
}) => Promise<{
|
|
1094
|
+
authToken: string | null;
|
|
1095
|
+
}>;
|
|
1471
1096
|
/**
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
1476
|
-
*
|
|
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.
|
|
1477
1102
|
*
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1481
|
-
*
|
|
1482
|
-
* widen it past loopback, layer on {@link McpRouteOptions.authorization} to
|
|
1483
|
-
* prove identity too.
|
|
1103
|
+
* Named with the `anonymous:` prefix (see `isAnonymousRpcMethod`) so it is
|
|
1104
|
+
* reachable before the connection is trusted.
|
|
1105
|
+
*
|
|
1106
|
+
* @internal
|
|
1484
1107
|
*/
|
|
1485
|
-
|
|
1108
|
+
'anonymous:devframe:auth:request-code': (params: {
|
|
1109
|
+
ua: string;
|
|
1110
|
+
origin: string;
|
|
1111
|
+
reissue?: boolean;
|
|
1112
|
+
}) => Promise<void>;
|
|
1486
1113
|
/**
|
|
1487
|
-
*
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1491
|
-
* - `(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`.
|
|
1492
1118
|
*
|
|
1493
|
-
*
|
|
1119
|
+
* @internal
|
|
1494
1120
|
*/
|
|
1495
|
-
|
|
1121
|
+
'devframe:auth:revoke': () => Promise<void>;
|
|
1496
1122
|
/**
|
|
1497
|
-
*
|
|
1498
|
-
* `
|
|
1499
|
-
*
|
|
1500
|
-
*
|
|
1123
|
+
* Subscribe a client to a shared-state key. Wired by
|
|
1124
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
1125
|
+
*
|
|
1126
|
+
* @internal
|
|
1501
1127
|
*/
|
|
1502
|
-
|
|
1128
|
+
'devframe:rpc:server-state:subscribe': (key: string) => Promise<void>;
|
|
1503
1129
|
/**
|
|
1504
|
-
*
|
|
1505
|
-
* `
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1508
|
-
* the first template whose `uriTemplate` matches the URI reads it. `resources/list` adds the
|
|
1509
|
-
* entries each template's `list` returns. Omitted or empty keeps the
|
|
1510
|
-
* resources unchanged.
|
|
1130
|
+
* Read the current value for a shared-state key. Wired by
|
|
1131
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
1132
|
+
*
|
|
1133
|
+
* @internal
|
|
1511
1134
|
*/
|
|
1512
|
-
|
|
1135
|
+
'devframe:rpc:server-state:get': (key: string) => Promise<any>;
|
|
1513
1136
|
/**
|
|
1514
|
-
*
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1137
|
+
* Replace a shared-state value (from the client). Wired by
|
|
1138
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
1139
|
+
*
|
|
1140
|
+
* @internal
|
|
1518
1141
|
*/
|
|
1519
|
-
|
|
1142
|
+
'devframe:rpc:server-state:set': (key: string, value: any, syncId: string) => Promise<void>;
|
|
1520
1143
|
/**
|
|
1521
|
-
*
|
|
1522
|
-
* `
|
|
1144
|
+
* Apply a patch to a shared-state value (from the client). Wired by
|
|
1145
|
+
* `RpcSharedStateHost`; do not register manually.
|
|
1146
|
+
*
|
|
1147
|
+
* @internal
|
|
1523
1148
|
*/
|
|
1524
|
-
|
|
1525
|
-
}
|
|
1526
|
-
interface DevframeCliOptions {
|
|
1527
|
-
/** Binary name; default: the devframe's `id`. */
|
|
1528
|
-
command?: string;
|
|
1529
|
-
/** Preferred port for the dev server (default 9999). */
|
|
1530
|
-
port?: number;
|
|
1531
|
-
/** Port scan range, forwarded to `get-port-please`. */
|
|
1532
|
-
portRange?: [number, number];
|
|
1533
|
-
/** Prefer a random open port. */
|
|
1534
|
-
random?: boolean;
|
|
1535
|
-
/** Default host to bind to; `--host` overrides. */
|
|
1536
|
-
host?: string;
|
|
1149
|
+
'devframe:rpc:server-state:patch': (key: string, patches: any[], syncId: string) => Promise<void>;
|
|
1537
1150
|
/**
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1151
|
+
* Client→server streaming subscription with optional replay cursor.
|
|
1152
|
+
* Wired by `RpcStreamingHost`; do not register manually.
|
|
1153
|
+
*
|
|
1154
|
+
* @internal
|
|
1541
1155
|
*/
|
|
1542
|
-
|
|
1156
|
+
'devframe:streaming:subscribe': (channel: string, id: string, opts?: {
|
|
1157
|
+
afterSeq?: number;
|
|
1158
|
+
}) => Promise<void>;
|
|
1543
1159
|
/**
|
|
1544
|
-
*
|
|
1545
|
-
*
|
|
1546
|
-
* - `undefined` / `true`: the standalone adapters (`cli` /
|
|
1547
|
-
* served `build`) auto-wire devframe's interactive OTP auth
|
|
1548
|
-
* (`createInteractiveAuth`): an untrusted client can only reach
|
|
1549
|
-
* `anonymous:` methods until it exchanges the printed one-time code.
|
|
1550
|
-
* The adapter prints the code + magic-link banner once the server is
|
|
1551
|
-
* listening.
|
|
1552
|
-
* - `false`: no gate, for trusted single-user localhost tools where an
|
|
1553
|
-
* auth round-trip only gets in the way (the built-in plugins set this).
|
|
1554
|
-
* The `--no-auth` CLI flag maps here for one-off runs.
|
|
1555
|
-
* - A {@link DevframeAuthHandler}: a custom handler (e.g. a tuned
|
|
1556
|
-
* `createInteractiveAuth`, or an entirely different scheme) passed
|
|
1557
|
-
* straight through to the RPC transport binding.
|
|
1558
|
-
*
|
|
1559
|
-
* Hosted adapters (`vite`, `embedded`) ignore this and defer to the host's
|
|
1560
|
-
* auth; `@vitejs/devtools` honors the equivalent `devtools.clientAuth`.
|
|
1160
|
+
* Client→server streaming unsubscribe. Wired by `RpcStreamingHost`;
|
|
1161
|
+
* do not register manually.
|
|
1561
1162
|
*
|
|
1562
|
-
* @
|
|
1163
|
+
* @internal
|
|
1563
1164
|
*/
|
|
1564
|
-
|
|
1165
|
+
'devframe:streaming:unsubscribe': (channel: string, id: string) => Promise<void>;
|
|
1565
1166
|
/**
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*/
|
|
1571
|
-
ws?: DevframeWsOptions | false;
|
|
1572
|
-
/**
|
|
1573
|
-
* How the browser reaches the SSE RPC endpoint, enabled by default
|
|
1574
|
-
* alongside the WebSocket as the more portable transport. Pass `false`
|
|
1575
|
-
* to disable, or a {@link DevframeSseOptions} to change its route.
|
|
1167
|
+
* Client→server streaming cancellation request. Wired by
|
|
1168
|
+
* `RpcStreamingHost`; do not register manually.
|
|
1169
|
+
*
|
|
1170
|
+
* @internal
|
|
1576
1171
|
*/
|
|
1577
|
-
|
|
1172
|
+
'devframe:streaming:cancel': (channel: string, id: string) => Promise<void>;
|
|
1578
1173
|
/**
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1583
|
-
* itself.
|
|
1174
|
+
* Client→server upload chunk. Wired by `RpcStreamingHost`; do not
|
|
1175
|
+
* register manually.
|
|
1176
|
+
*
|
|
1177
|
+
* @internal
|
|
1584
1178
|
*/
|
|
1585
|
-
|
|
1179
|
+
'devframe:streaming:upload-chunk': (channel: string, id: string, seq: number, chunk: any) => Promise<void>;
|
|
1586
1180
|
/**
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1589
|
-
* zod, arktype, or devframe's built-in `s`). The adapter registers
|
|
1590
|
-
* matching `--kebab-key` options on CAC, validates the parsed values,
|
|
1591
|
-
* and forwards the typed bag to `setup(ctx, { flags })`.
|
|
1592
|
-
*
|
|
1593
|
-
* Use {@link defineCliFlags} to preserve the literal schema-map
|
|
1594
|
-
* shape, and {@link InferCliFlags} to recover the typed output at the
|
|
1595
|
-
* call site:
|
|
1596
|
-
*
|
|
1597
|
-
* ```ts
|
|
1598
|
-
* const appFlags = defineCliFlags({
|
|
1599
|
-
* depth: v.pipe(v.number(), v.integer()),
|
|
1600
|
-
* config: v.optional(v.string()),
|
|
1601
|
-
* })
|
|
1181
|
+
* Client→server upload terminator. Wired by `RpcStreamingHost`; do not
|
|
1182
|
+
* register manually.
|
|
1602
1183
|
*
|
|
1603
|
-
*
|
|
1604
|
-
* cli: { flags: appFlags },
|
|
1605
|
-
* setup(ctx, info) {
|
|
1606
|
-
* const flags = info.flags as InferCliFlags<typeof appFlags>
|
|
1607
|
-
* },
|
|
1608
|
-
* })
|
|
1609
|
-
* ```
|
|
1184
|
+
* @internal
|
|
1610
1185
|
*/
|
|
1611
|
-
|
|
1186
|
+
'devframe:streaming:upload-end': (channel: string, id: string, error?: {
|
|
1187
|
+
name: string;
|
|
1188
|
+
message: string;
|
|
1189
|
+
}) => Promise<void>;
|
|
1612
1190
|
}
|
|
1613
1191
|
/**
|
|
1614
|
-
*
|
|
1615
|
-
* mounts this devframe. Framework-neutral metadata only; the hub layer
|
|
1616
|
-
* (`ctx.install`) merges these beneath its per-mount `dock` overrides,
|
|
1617
|
-
* which in turn sit beneath the locked, derived `id` / `type` / `url`.
|
|
1618
|
-
*
|
|
1619
|
-
* Every field is optional. `title` / `icon` default to the definition's
|
|
1620
|
-
* `name` / `icon` when omitted here; the rest are unset by default.
|
|
1621
|
-
* Standalone adapters (`cli` / `build`) ignore this entirely.
|
|
1192
|
+
* To be extended
|
|
1622
1193
|
*/
|
|
1623
|
-
interface
|
|
1624
|
-
/** Dock entry title. Defaults to the definition's `name`. */
|
|
1625
|
-
title?: string;
|
|
1626
|
-
/** Translations of `title` by BCP 47 tag; see `@devframes/hub`'s `resolveTitle()`. */
|
|
1627
|
-
titleLocales?: Record<string, string>;
|
|
1628
|
-
/** Dock entry icon. Defaults to the definition's `icon`. */
|
|
1629
|
-
icon?: string | {
|
|
1630
|
-
light: string;
|
|
1631
|
-
dark: string;
|
|
1632
|
-
};
|
|
1194
|
+
interface DevframeRpcSharedStates {
|
|
1633
1195
|
/**
|
|
1634
|
-
*
|
|
1635
|
-
* @
|
|
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.
|
|
1636
1201
|
*/
|
|
1637
|
-
|
|
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> & {
|
|
1638
1216
|
/**
|
|
1639
|
-
*
|
|
1640
|
-
*
|
|
1217
|
+
* Invoke a locally registered server RPC function directly.
|
|
1218
|
+
*
|
|
1219
|
+
* This bypasses transport and is useful for server-side cross-function calls.
|
|
1641
1220
|
*/
|
|
1642
|
-
|
|
1221
|
+
invokeLocal: <T extends keyof DevframeRpcServerFunctions, Args extends Parameters<DevframeRpcServerFunctions[T]>>(method: T, ...args: Args) => Promise<Awaited<ReturnType<DevframeRpcServerFunctions[T]>>>;
|
|
1643
1222
|
/**
|
|
1644
|
-
*
|
|
1645
|
-
* clauses). Set to `'false'` to hide the entry unconditionally.
|
|
1223
|
+
* Broadcast a message to all connected clients
|
|
1646
1224
|
*/
|
|
1647
|
-
|
|
1225
|
+
broadcast: <T extends keyof DevframeRpcClientFunctions, Args extends Parameters<DevframeRpcClientFunctions[T]>>(options: RpcBroadcastOptions<T, Args>) => Promise<void>;
|
|
1648
1226
|
/**
|
|
1649
|
-
*
|
|
1650
|
-
*
|
|
1651
|
-
*
|
|
1652
|
-
* 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
|
|
1653
1230
|
*/
|
|
1654
|
-
|
|
1655
|
-
/** Badge text rendered on the dock icon (e.g. an unread count). */
|
|
1656
|
-
badge?: string;
|
|
1657
|
-
/** Id of the dock group this entry collapses under, if any. */
|
|
1658
|
-
groupId?: string;
|
|
1231
|
+
getCurrentRpcSession: () => DevframeNodeRpcSession | undefined;
|
|
1659
1232
|
/**
|
|
1660
|
-
*
|
|
1661
|
-
* script**). An absolute-path `importFrom` is served by the hub under the
|
|
1662
|
-
* mount base and rewritten to that URL, so mounting by package name needs no
|
|
1663
|
-
* host wiring; a URL or bare specifier passes through untouched.
|
|
1233
|
+
* The shared state host
|
|
1664
1234
|
*/
|
|
1665
|
-
|
|
1666
|
-
/**
|
|
1667
|
-
* Initialize after RPC trust without waiting for dock activation.
|
|
1668
|
-
* @default false
|
|
1669
|
-
*/
|
|
1670
|
-
eager?: boolean;
|
|
1671
|
-
/** An absolute filesystem path, a served URL, or a bare npm specifier. */
|
|
1672
|
-
importFrom: string;
|
|
1673
|
-
/**
|
|
1674
|
-
* The name to import the module as.
|
|
1675
|
-
* @default 'default'
|
|
1676
|
-
*/
|
|
1677
|
-
importName?: string;
|
|
1678
|
-
};
|
|
1679
|
-
}
|
|
1680
|
-
/**
|
|
1681
|
-
* Runtime information threaded into `setup(ctx, info)`. Adapters
|
|
1682
|
-
* populate the fields that make sense for their deployment. In
|
|
1683
|
-
* particular, `createCac` fills `flags` with the parsed CAC bag.
|
|
1684
|
-
*/
|
|
1685
|
-
interface DevframeSetupInfo {
|
|
1686
|
-
/** Parsed CLI flags, populated by the CLI adapter. */
|
|
1687
|
-
flags?: Record<string, unknown>;
|
|
1688
|
-
}
|
|
1689
|
-
interface DevframeDefinition {
|
|
1690
|
-
id: string;
|
|
1691
|
-
name: string;
|
|
1692
|
-
/** Semver of the tool, surfaced in hub UIs and diagnostics. */
|
|
1693
|
-
version: string;
|
|
1694
|
-
/** npm package name the devframe ships in (e.g. `@scope/my-tool`). */
|
|
1695
|
-
packageName: string;
|
|
1235
|
+
sharedState: RpcSharedStateHost;
|
|
1696
1236
|
/**
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1699
|
-
* for the tool's own dependency graph, which lets the host resolve
|
|
1700
|
-
* everything against the plugin's own installed packages rather than the
|
|
1701
|
-
* consuming app's.
|
|
1702
|
-
*
|
|
1703
|
-
* - **Remote assets**: becomes the default `resolveFrom` for any remote
|
|
1704
|
-
* {@link StaticAssetsSource} the devframe hosts (its `clientAssets`, and
|
|
1705
|
-
* every `ctx.views.hostStatic` call) that doesn't set one explicitly, so a
|
|
1706
|
-
* locally installed copy of the assets package is served with zero
|
|
1707
|
-
* network. A per-asset `resolveFrom` still wins, and an explicit
|
|
1708
|
-
* `resolveFrom: null` still opts out.
|
|
1709
|
-
* - **Service dependencies**: becomes the base the host resolves declared
|
|
1710
|
-
* {@link DevframeServiceInput | services} from, so a plugin can ship a
|
|
1711
|
-
* service package as its own dependency instead of asking users to install
|
|
1712
|
-
* it.
|
|
1237
|
+
* The streaming channel host. Provides per-channel `start()` /
|
|
1238
|
+
* `pipeFrom()` producers; clients consume via `rpc.streaming.subscribe()`.
|
|
1713
1239
|
*
|
|
1714
|
-
*
|
|
1715
|
-
* runtime-directory resolution and disables the zero-network installed-copy
|
|
1716
|
-
* fast paths above.
|
|
1240
|
+
* @see RpcStreamingHost
|
|
1717
1241
|
*/
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
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[];
|
|
1727
1251
|
/**
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
*
|
|
1731
|
-
*
|
|
1732
|
-
*
|
|
1733
|
-
* @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.
|
|
1734
1256
|
*/
|
|
1735
|
-
|
|
1257
|
+
onKeyAdded: (fn: (key: string) => void) => () => void;
|
|
1736
1258
|
/**
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1739
|
-
* (
|
|
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.
|
|
1740
1263
|
*/
|
|
1741
|
-
|
|
1264
|
+
delete: (key: string) => boolean;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Options for `RpcStreamingHost.create()`.
|
|
1268
|
+
*/
|
|
1269
|
+
interface RpcStreamingChannelOptions {
|
|
1742
1270
|
/**
|
|
1743
|
-
*
|
|
1744
|
-
*
|
|
1745
|
-
*
|
|
1746
|
-
* 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.
|
|
1747
1274
|
*
|
|
1748
|
-
*
|
|
1275
|
+
* The buffer is per stream id, not per channel; each `channel.start()`
|
|
1276
|
+
* gets its own.
|
|
1749
1277
|
*/
|
|
1750
|
-
|
|
1278
|
+
replayWindow?: number;
|
|
1751
1279
|
/**
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
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.
|
|
1754
1284
|
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
* no SPA copy, no RPCs in the dump. Useful for a devframe whose value is
|
|
1759
|
-
* inherently live (e.g. it manages real files on disk), so a static
|
|
1760
|
-
* export would only ever produce a broken, write-less shell of the tool.
|
|
1761
|
-
* - `dev: false`: `createDevServer` refuses (throws `DF0058`) unless
|
|
1762
|
-
* `{ force: true }`. Useful for a devframe that only makes sense as a
|
|
1763
|
-
* 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.
|
|
1764
1288
|
*/
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
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;
|
|
1769
1301
|
/**
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
*
|
|
1773
|
-
*
|
|
1774
|
-
* own dependencies**) or a ready `DevframeServiceDefinition` (the factory
|
|
1775
|
-
* was already called). The adapter queues these before `setup(ctx)` runs
|
|
1776
|
-
* and constructs each service once at the `ctx.services.ready()` barrier,
|
|
1777
|
-
* merging option sets across every declarer. Missing services are skipped
|
|
1778
|
-
* unless marked `required`; clients feature-detect via
|
|
1779
|
-
* `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.
|
|
1780
1306
|
*/
|
|
1781
|
-
|
|
1307
|
+
start: (opts?: {
|
|
1308
|
+
id?: string;
|
|
1309
|
+
}) => StreamSink<T>;
|
|
1782
1310
|
/**
|
|
1783
|
-
*
|
|
1784
|
-
*
|
|
1785
|
-
* served through devframe's caching CDN back-proxy so the assets need not
|
|
1786
|
-
* 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.
|
|
1787
1313
|
*
|
|
1788
|
-
*
|
|
1789
|
-
*
|
|
1314
|
+
* Node-stream interop: convert a `Readable` with `Readable.toWeb(node)`
|
|
1315
|
+
* before passing it here.
|
|
1790
1316
|
*/
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
/**
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
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[];
|
|
1799
1324
|
/**
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
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:
|
|
1804
1330
|
*
|
|
1805
|
-
*
|
|
1806
|
-
*
|
|
1807
|
-
*
|
|
1808
|
-
* (
|
|
1809
|
-
*
|
|
1810
|
-
*
|
|
1811
|
-
*
|
|
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.
|
|
1812
1343
|
*/
|
|
1813
|
-
|
|
1344
|
+
openInbound: (opts?: {
|
|
1345
|
+
id?: string;
|
|
1346
|
+
}) => StreamReader<T>;
|
|
1814
1347
|
}
|
|
1815
|
-
/** Argument-tuples to bake for a {@link DevframeSnapshotRpcEntry}, or a provider that computes them at build time. */
|
|
1816
|
-
type DevframeSnapshotRpcInputs = readonly (readonly unknown[])[] | ((ctx: DevframeNodeContext) => readonly (readonly unknown[])[] | Promise<readonly (readonly unknown[])[]>);
|
|
1817
|
-
/**
|
|
1818
|
-
* One {@link DevframeRpcOptions.snapshot} entry: a bare method id (bakes
|
|
1819
|
-
* the no-argument call) or a method plus the argument-tuples to bake.
|
|
1820
|
-
*/
|
|
1821
|
-
type DevframeSnapshotRpcEntry = string | {
|
|
1822
|
-
method: string;
|
|
1823
|
-
inputs: DevframeSnapshotRpcInputs;
|
|
1824
|
-
};
|
|
1825
|
-
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
|
|
1826
|
-
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>;
|
|
1827
|
-
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
1828
|
-
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
1829
|
-
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
1830
|
-
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]>; };
|
|
1831
1348
|
/**
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
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.
|
|
1834
1352
|
*/
|
|
1835
|
-
interface
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
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;
|
|
1839
1372
|
}
|
|
1840
1373
|
/**
|
|
1841
|
-
*
|
|
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.
|
|
1842
1381
|
*/
|
|
1843
|
-
interface
|
|
1382
|
+
interface DevframeNodeContext {
|
|
1383
|
+
readonly workspaceRoot: string;
|
|
1384
|
+
readonly cwd: string;
|
|
1844
1385
|
/**
|
|
1845
|
-
*
|
|
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.
|
|
1846
1398
|
*/
|
|
1847
|
-
|
|
1399
|
+
readonly mode: 'dev' | 'build';
|
|
1848
1400
|
/**
|
|
1849
|
-
*
|
|
1401
|
+
* Host runtime abstraction, exposing `mountStatic` / `resolveOrigin` /
|
|
1402
|
+
* `getStorageDir`.
|
|
1850
1403
|
*/
|
|
1851
|
-
|
|
1404
|
+
host: DevframeHost;
|
|
1405
|
+
rpc: RpcFunctionsHost;
|
|
1406
|
+
views: DevframeViewHost;
|
|
1852
1407
|
/**
|
|
1853
|
-
*
|
|
1408
|
+
* Structured diagnostics host; wraps `nostics` and lets integrations
|
|
1409
|
+
* register their own coded errors/warnings into the shared lookup.
|
|
1854
1410
|
*/
|
|
1855
|
-
|
|
1411
|
+
diagnostics: DevframeDiagnosticsHost;
|
|
1856
1412
|
/**
|
|
1857
|
-
*
|
|
1413
|
+
* Agent host; aggregates the agent-exposed surface of this devtool.
|
|
1858
1414
|
*/
|
|
1859
|
-
|
|
1415
|
+
agent: DevframeAgentHost;
|
|
1860
1416
|
/**
|
|
1861
|
-
*
|
|
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.
|
|
1862
1423
|
*/
|
|
1863
|
-
|
|
1864
|
-
}
|
|
1865
|
-
interface SharedStateEvents<T> {
|
|
1866
|
-
updated: (fullState: T, patches: SharedStatePatch[] | undefined, syncId: string) => void;
|
|
1867
|
-
}
|
|
1868
|
-
interface SharedStateOptions<T> {
|
|
1424
|
+
services: DevframeServicesHost;
|
|
1869
1425
|
/**
|
|
1870
|
-
*
|
|
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
|
+
* ```
|
|
1871
1439
|
*/
|
|
1872
|
-
|
|
1440
|
+
staticConfig: Partial<DevframeConnectionConfigsRegistry>;
|
|
1873
1441
|
/**
|
|
1874
|
-
*
|
|
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.
|
|
1875
1447
|
*
|
|
1876
|
-
*
|
|
1448
|
+
* Pass `null` or `''` to un-scope and get the base context.
|
|
1877
1449
|
*/
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
* Serialized error shape sent over the wire when a stream ends with a failure.
|
|
1883
|
-
* Stays JSON-safe so the strict-JSON encoder can carry it without coercion.
|
|
1884
|
-
*/
|
|
1885
|
-
interface StreamErrorPayload {
|
|
1886
|
-
name: string;
|
|
1887
|
-
message: string;
|
|
1450
|
+
scope: {
|
|
1451
|
+
<NS extends string>(namespace: NS): DevframeScopedNodeContext<NS, SettingsForNamespace<NS>>;
|
|
1452
|
+
(namespace?: null | ''): DevframeNodeContext;
|
|
1453
|
+
};
|
|
1888
1454
|
}
|
|
1889
1455
|
/**
|
|
1890
|
-
*
|
|
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.
|
|
1891
1462
|
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
* replay any chunks the client missed during a brief disconnect.
|
|
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.
|
|
1895
1465
|
*/
|
|
1896
|
-
interface
|
|
1897
|
-
seq: number;
|
|
1898
|
-
chunk: T;
|
|
1899
|
-
}
|
|
1900
|
-
interface StreamSinkEvents<T> {
|
|
1901
|
-
/** Fired for each `write()`. The RPC layer subscribes and broadcasts. */
|
|
1902
|
-
chunk: (seq: number, chunk: T) => void;
|
|
1903
|
-
/** Terminal; fired exactly once per sink lifetime. */
|
|
1904
|
-
end: (error?: StreamErrorPayload) => void;
|
|
1905
|
-
}
|
|
1906
|
-
interface CreateStreamSinkOptions {
|
|
1907
|
-
id?: string;
|
|
1466
|
+
interface ConnectionMetaWebsocket {
|
|
1908
1467
|
/**
|
|
1909
|
-
*
|
|
1910
|
-
* `
|
|
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.
|
|
1911
1471
|
*/
|
|
1912
|
-
|
|
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;
|
|
1913
1477
|
}
|
|
1914
1478
|
/**
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
1917
|
-
*
|
|
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.
|
|
1918
1484
|
*/
|
|
1919
|
-
interface
|
|
1920
|
-
/** Stable id used by clients to subscribe. */
|
|
1921
|
-
readonly id: string;
|
|
1922
|
-
/**
|
|
1923
|
-
* Aborts when the consumer cancels (server-side) or when the transport
|
|
1924
|
-
* loses every subscriber. Producers should poll `signal.aborted` and exit
|
|
1925
|
-
* cleanly.
|
|
1926
|
-
*/
|
|
1927
|
-
readonly signal: AbortSignal;
|
|
1928
|
-
/** `true` after `close()` / `error()`. Further writes throw. */
|
|
1929
|
-
readonly closed: boolean;
|
|
1930
|
-
/** Last allocated sequence number. `0` until the first write. */
|
|
1931
|
-
readonly lastSeq: number;
|
|
1932
|
-
write: (chunk: T) => void;
|
|
1933
|
-
error: (reason: unknown) => void;
|
|
1934
|
-
close: () => void;
|
|
1935
|
-
/** External-cancel path. Aborts the signal so handlers can short-circuit. */
|
|
1936
|
-
abort: (reason?: unknown) => void;
|
|
1937
|
-
/** `WritableStream<T>` adapter; same in-memory state as the imperative API. */
|
|
1938
|
-
readonly writable: WritableStream<T>;
|
|
1939
|
-
/**
|
|
1940
|
-
* Internal: RPC layer subscribes to receive chunk/end notifications.
|
|
1941
|
-
* Not part of the public contract; do not call directly.
|
|
1942
|
-
*
|
|
1943
|
-
* @internal
|
|
1944
|
-
*/
|
|
1945
|
-
readonly events: EventEmitter<StreamSinkEvents<T>>;
|
|
1485
|
+
interface ConnectionMetaSse {
|
|
1946
1486
|
/**
|
|
1947
|
-
*
|
|
1948
|
-
*
|
|
1949
|
-
*
|
|
1950
|
-
* @internal
|
|
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.
|
|
1951
1490
|
*/
|
|
1952
|
-
|
|
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;
|
|
1953
1496
|
}
|
|
1954
|
-
interface
|
|
1955
|
-
id?: string;
|
|
1497
|
+
interface ConnectionMeta {
|
|
1956
1498
|
/**
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
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).
|
|
1959
1502
|
*/
|
|
1960
|
-
|
|
1503
|
+
backend: 'websocket' | 'sse' | 'static' | 'none';
|
|
1961
1504
|
/**
|
|
1962
|
-
*
|
|
1963
|
-
*
|
|
1964
|
-
*
|
|
1505
|
+
* WebSocket endpoint, resolved by the client into a `ws(s)://` URL:
|
|
1506
|
+
*
|
|
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.
|
|
1965
1512
|
*/
|
|
1966
|
-
|
|
1967
|
-
/** Called when the consumer cancels; the RPC layer sends `:cancel` upstream. */
|
|
1968
|
-
onCancel?: () => void;
|
|
1969
|
-
}
|
|
1970
|
-
/**
|
|
1971
|
-
* Client-side consumer handle. Both an `AsyncIterable<T>` (for `for await`)
|
|
1972
|
-
* and exposes `readable: ReadableStream<T>` (for `pipeTo`). Pick one; they
|
|
1973
|
-
* share a single internal queue, so concurrent draining will race.
|
|
1974
|
-
*/
|
|
1975
|
-
interface StreamReader<T> extends AsyncIterable<T> {
|
|
1976
|
-
readonly id: string;
|
|
1977
|
-
readonly cancelled: boolean;
|
|
1978
|
-
readonly done: boolean;
|
|
1979
|
-
/** Highest `seq` observed. Used for replay on reconnect. */
|
|
1980
|
-
readonly lastSeenSeq: number;
|
|
1981
|
-
/** `ReadableStream<T>` adapter for `pipeTo`-style consumption. */
|
|
1982
|
-
readonly readable: ReadableStream<T>;
|
|
1983
|
-
cancel: () => void;
|
|
1984
|
-
/** @internal */
|
|
1985
|
-
_push: (seq: number, chunk: T) => void;
|
|
1986
|
-
/** @internal */
|
|
1987
|
-
_end: (error?: StreamErrorPayload) => void;
|
|
1988
|
-
}
|
|
1989
|
-
/**
|
|
1990
|
-
* Build a server-side stream sink. RPC-agnostic; the RPC host wires
|
|
1991
|
-
* `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
|
|
1992
|
-
* for late or reconnecting subscribers.
|
|
1993
|
-
*/
|
|
1994
|
-
declare function createStreamSink<T>(options?: CreateStreamSinkOptions): StreamSink<T>;
|
|
1995
|
-
/**
|
|
1996
|
-
* Build a client-side stream reader. RPC-agnostic; the RPC host calls
|
|
1997
|
-
* `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
|
|
1998
|
-
* terminal frame. Consumers iterate with `for await` or pipe `readable`.
|
|
1999
|
-
*/
|
|
2000
|
-
declare function createStreamReader<T>(options?: CreateStreamReaderOptions): StreamReader<T>;
|
|
2001
|
-
interface DevframeNodeRpcSession {
|
|
2002
|
-
meta: DevframeNodeRpcSessionMeta;
|
|
2003
|
-
rpc: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
|
|
2004
|
-
}
|
|
2005
|
-
interface RpcBroadcastOptions<METHOD, Args extends any[]> {
|
|
2006
|
-
method: METHOD;
|
|
2007
|
-
args: Args;
|
|
2008
|
-
optional?: boolean;
|
|
2009
|
-
event?: boolean;
|
|
2010
|
-
filter?: (client: BirpcReturn<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>) => boolean | void;
|
|
2011
|
-
}
|
|
2012
|
-
type RpcFunctionsHost = RpcFunctionsCollectorBase<DevframeRpcServerFunctions, DevframeNodeContext> & {
|
|
1513
|
+
websocket?: number | string | ConnectionMetaWebsocket;
|
|
2013
1514
|
/**
|
|
2014
|
-
*
|
|
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}:
|
|
2015
1518
|
*
|
|
2016
|
-
*
|
|
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.
|
|
2017
1523
|
*/
|
|
2018
|
-
|
|
1524
|
+
sse?: string | ConnectionMetaSse;
|
|
2019
1525
|
/**
|
|
2020
|
-
*
|
|
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}.
|
|
2021
1535
|
*/
|
|
2022
|
-
|
|
1536
|
+
mcp?: {
|
|
1537
|
+
path: string;
|
|
1538
|
+
port?: number;
|
|
1539
|
+
};
|
|
2023
1540
|
/**
|
|
2024
|
-
*
|
|
2025
|
-
*
|
|
2026
|
-
*
|
|
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.
|
|
2027
1547
|
*/
|
|
2028
|
-
|
|
1548
|
+
jsonSerializableMethods?: string[];
|
|
2029
1549
|
/**
|
|
2030
|
-
*
|
|
1550
|
+
* URL of the `__connection.json` that owns this meta's relative paths.
|
|
1551
|
+
* Two producers write it:
|
|
1552
|
+
*
|
|
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.
|
|
2031
1563
|
*/
|
|
2032
|
-
|
|
1564
|
+
baseUrl?: string;
|
|
2033
1565
|
/**
|
|
2034
|
-
*
|
|
2035
|
-
*
|
|
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.
|
|
2036
1573
|
*
|
|
2037
|
-
*
|
|
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`.
|
|
2038
1578
|
*/
|
|
2039
|
-
|
|
2040
|
-
};
|
|
2041
|
-
interface RpcSharedStateGetOptions<T> {
|
|
2042
|
-
sharedState?: SharedState<T>;
|
|
2043
|
-
initialValue?: T;
|
|
2044
|
-
}
|
|
2045
|
-
interface RpcSharedStateHost {
|
|
2046
|
-
get: <T extends object = any>(key: string, options?: RpcSharedStateGetOptions<T>) => Promise<SharedState<T>>;
|
|
2047
|
-
keys: () => string[];
|
|
1579
|
+
authToken?: string;
|
|
2048
1580
|
/**
|
|
2049
|
-
*
|
|
2050
|
-
*
|
|
2051
|
-
*
|
|
2052
|
-
*
|
|
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.
|
|
1584
|
+
*
|
|
1585
|
+
* Treat this as a bearer credential. Keep connection metadata containing the
|
|
1586
|
+
* token same-origin until the requesting origin has been verified.
|
|
2053
1587
|
*/
|
|
2054
|
-
|
|
1588
|
+
viewerOriginToken?: string;
|
|
2055
1589
|
/**
|
|
2056
|
-
*
|
|
2057
|
-
*
|
|
2058
|
-
*
|
|
2059
|
-
*
|
|
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
|
+
* ```
|
|
2060
1610
|
*/
|
|
2061
|
-
|
|
1611
|
+
configs?: Partial<DevframeConnectionConfigsRegistry>;
|
|
2062
1612
|
}
|
|
2063
1613
|
/**
|
|
2064
|
-
*
|
|
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`.
|
|
2065
1619
|
*/
|
|
2066
|
-
interface
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
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 {
|
|
1670
|
+
/**
|
|
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).
|
|
2074
1675
|
*/
|
|
2075
|
-
|
|
1676
|
+
rpcFunctions: RpcFunctionDefinitionAny[];
|
|
2076
1677
|
/**
|
|
2077
|
-
*
|
|
2078
|
-
*
|
|
2079
|
-
*
|
|
2080
|
-
*
|
|
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.
|
|
2081
1810
|
*
|
|
2082
|
-
*
|
|
2083
|
-
*
|
|
2084
|
-
*
|
|
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.
|
|
2085
1815
|
*/
|
|
2086
|
-
|
|
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;
|
|
2087
1851
|
}
|
|
2088
1852
|
/**
|
|
2089
|
-
*
|
|
2090
|
-
*
|
|
2091
|
-
|
|
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.
|
|
2092
1868
|
*
|
|
2093
|
-
*
|
|
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.
|
|
2094
1873
|
*/
|
|
2095
|
-
interface
|
|
2096
|
-
/**
|
|
2097
|
-
|
|
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 {
|
|
2098
1965
|
/**
|
|
2099
|
-
*
|
|
2100
|
-
*
|
|
2101
|
-
* `pipeTo` consumption. The sink's `signal` aborts when every subscriber
|
|
2102
|
-
* disconnects or cancels.
|
|
1966
|
+
* Upgrade route segment the socket binds to and is advertised at, relative
|
|
1967
|
+
* to the SPA base. Default: `__ws`.
|
|
2103
1968
|
*/
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
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[];
|
|
2107
2106
|
/**
|
|
2108
|
-
*
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
* 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.
|
|
2113
2111
|
*/
|
|
2114
|
-
|
|
2115
|
-
id?: string;
|
|
2116
|
-
}) => Promise<StreamSink<T>>;
|
|
2117
|
-
/** Look up an active stream by id. Returns `undefined` if none. */
|
|
2118
|
-
get: (id: string) => StreamSink<T> | undefined;
|
|
2119
|
-
/** All active outbound stream ids on this channel. */
|
|
2120
|
-
ids: () => string[];
|
|
2112
|
+
onResourcesChanged?: McpResourcesChangedHook;
|
|
2121
2113
|
/**
|
|
2122
|
-
*
|
|
2123
|
-
*
|
|
2124
|
-
* the client writes chunks. Typical pattern is to call this from an
|
|
2125
|
-
* action handler, kick off background processing, and return the id
|
|
2126
|
-
* so the caller can start uploading:
|
|
2127
|
-
*
|
|
2128
|
-
* ```ts
|
|
2129
|
-
* handler: async () => {
|
|
2130
|
-
* const reader = channel.openInbound()
|
|
2131
|
-
* ;(async () => {
|
|
2132
|
-
* for await (const chunk of reader) processChunk(chunk)
|
|
2133
|
-
* })()
|
|
2134
|
-
* return { uploadId: reader.id }
|
|
2135
|
-
* }
|
|
2136
|
-
* ```
|
|
2137
|
-
*
|
|
2138
|
-
* Calling `reader.cancel()` on the server sends an `upload-cancel` to
|
|
2139
|
-
* 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.
|
|
2140
2116
|
*/
|
|
2141
|
-
|
|
2142
|
-
id?: string;
|
|
2143
|
-
}) => StreamReader<T>;
|
|
2117
|
+
prompts?: McpPrompt[];
|
|
2144
2118
|
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
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;
|
|
2151
2130
|
/**
|
|
2152
|
-
*
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
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.
|
|
2155
2134
|
*/
|
|
2156
|
-
|
|
2135
|
+
open?: boolean | string;
|
|
2157
2136
|
/**
|
|
2158
|
-
*
|
|
2159
|
-
* subscribers and abort orphaned streams. Most users do not need this;
|
|
2160
|
-
* it's wired by the RPC server binding automatically.
|
|
2137
|
+
* Authentication for the standalone dev server.
|
|
2161
2138
|
*
|
|
2162
|
-
*
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
* `createKitContext` adds `docks`, `terminals`, `messages`, and
|
|
2175
|
-
* `commands` when mounted into Vite DevTools. JSON rendering is an opt-in
|
|
2176
|
-
* integration (`@devframes/json-render`) layered on top, not part of this
|
|
2177
|
-
* core surface.
|
|
2178
|
-
*/
|
|
2179
|
-
interface DevframeNodeContext {
|
|
2180
|
-
readonly workspaceRoot: string;
|
|
2181
|
-
readonly cwd: string;
|
|
2182
|
-
/**
|
|
2183
|
-
* 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.
|
|
2184
2151
|
*
|
|
2185
|
-
*
|
|
2186
|
-
*
|
|
2187
|
-
* to keep the UI responsive.
|
|
2188
|
-
* - `'build'`: one-shot batch run. The context is set up, the devtool
|
|
2189
|
-
* collects what it needs, and a snapshot is written. No
|
|
2190
|
-
* 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`.
|
|
2191
2154
|
*
|
|
2192
|
-
*
|
|
2193
|
-
* is general: the same distinction applies to any tool that runs in
|
|
2194
|
-
* either an interactive or a static-output mode.
|
|
2195
|
-
*/
|
|
2196
|
-
readonly mode: 'dev' | 'build';
|
|
2197
|
-
/**
|
|
2198
|
-
* Host runtime abstraction, exposing `mountStatic` / `resolveOrigin` /
|
|
2199
|
-
* `getStorageDir`.
|
|
2155
|
+
* @default true
|
|
2200
2156
|
*/
|
|
2201
|
-
|
|
2202
|
-
rpc: RpcFunctionsHost;
|
|
2203
|
-
views: DevframeViewHost;
|
|
2157
|
+
auth?: boolean | DevframeAuthHandler;
|
|
2204
2158
|
/**
|
|
2205
|
-
*
|
|
2206
|
-
*
|
|
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'`).
|
|
2207
2163
|
*/
|
|
2208
|
-
|
|
2164
|
+
ws?: DevframeWsOptions | false;
|
|
2209
2165
|
/**
|
|
2210
|
-
*
|
|
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.
|
|
2211
2169
|
*/
|
|
2212
|
-
|
|
2170
|
+
sse?: boolean | DevframeSseOptions;
|
|
2213
2171
|
/**
|
|
2214
|
-
*
|
|
2215
|
-
*
|
|
2216
|
-
*
|
|
2217
|
-
*
|
|
2218
|
-
*
|
|
2219
|
-
* 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.
|
|
2220
2177
|
*/
|
|
2221
|
-
|
|
2178
|
+
configure?: (cli: CAC) => void;
|
|
2222
2179
|
/**
|
|
2223
|
-
*
|
|
2224
|
-
*
|
|
2225
|
-
*
|
|
2226
|
-
*
|
|
2227
|
-
*
|
|
2228
|
-
*
|
|
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:
|
|
2229
2189
|
*
|
|
2230
2190
|
* ```ts
|
|
2231
|
-
*
|
|
2232
|
-
*
|
|
2233
|
-
*
|
|
2234
|
-
* }
|
|
2235
|
-
* ```
|
|
2236
|
-
*/
|
|
2237
|
-
staticConfig: Partial<DevframeConnectionConfigsRegistry>;
|
|
2238
|
-
/**
|
|
2239
|
-
* Create a namespace-scoped view of this context. The returned
|
|
2240
|
-
* `ctx.scope('my-plugin')` auto-namespaces every RPC id, shared-state
|
|
2241
|
-
* key, and streaming channel with `my-plugin:`, and exposes a typed
|
|
2242
|
-
* top-level `settings` store. This is the preferred way to consume the
|
|
2243
|
-
* 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
|
+
* })
|
|
2244
2195
|
*
|
|
2245
|
-
*
|
|
2196
|
+
* defineDevframe({
|
|
2197
|
+
* cli: { flags: appFlags },
|
|
2198
|
+
* setup(ctx, info) {
|
|
2199
|
+
* const flags = info.flags as InferCliFlags<typeof appFlags>
|
|
2200
|
+
* },
|
|
2201
|
+
* })
|
|
2202
|
+
* ```
|
|
2246
2203
|
*/
|
|
2247
|
-
|
|
2248
|
-
<NS extends string>(namespace: NS): DevframeScopedNodeContext<NS, SettingsForNamespace<NS>>;
|
|
2249
|
-
(namespace?: null | ''): DevframeNodeContext;
|
|
2250
|
-
};
|
|
2204
|
+
flags?: CliFlagsSchema;
|
|
2251
2205
|
}
|
|
2252
2206
|
/**
|
|
2253
|
-
*
|
|
2254
|
-
*
|
|
2255
|
-
*
|
|
2256
|
-
*
|
|
2257
|
-
* applied). This survives reverse proxies that change the host/port, because
|
|
2258
|
-
* 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`.
|
|
2259
2211
|
*
|
|
2260
|
-
*
|
|
2261
|
-
*
|
|
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.
|
|
2262
2215
|
*/
|
|
2263
|
-
interface
|
|
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
|
+
};
|
|
2264
2226
|
/**
|
|
2265
|
-
*
|
|
2266
|
-
*
|
|
2267
|
-
|
|
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.
|
|
2268
2257
|
*/
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
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
|
+
};
|
|
2274
2272
|
}
|
|
2275
2273
|
/**
|
|
2276
|
-
*
|
|
2277
|
-
*
|
|
2278
|
-
* `
|
|
2279
|
-
* `host`) only when the SSE endpoint genuinely lives on a different origin
|
|
2280
|
-
* 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.
|
|
2281
2277
|
*/
|
|
2282
|
-
interface
|
|
2283
|
-
/**
|
|
2284
|
-
|
|
2285
|
-
* are resolved against `__connection.json`'s location; absolute paths
|
|
2286
|
-
* (`/__sse`) resolve against the page origin.
|
|
2287
|
-
*/
|
|
2288
|
-
path?: string;
|
|
2289
|
-
/** Override the port. Combined with the page hostname unless `host` is set. */
|
|
2290
|
-
port?: number;
|
|
2291
|
-
/** Override the host (`hostname[:port]`). Use for a fully cross-origin endpoint. */
|
|
2292
|
-
host?: string;
|
|
2278
|
+
interface DevframeSetupInfo {
|
|
2279
|
+
/** Parsed CLI flags, populated by the CLI adapter. */
|
|
2280
|
+
flags?: Record<string, unknown>;
|
|
2293
2281
|
}
|
|
2294
|
-
interface
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
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;
|
|
2301
2289
|
/**
|
|
2302
|
-
*
|
|
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.
|
|
2303
2295
|
*
|
|
2304
|
-
*
|
|
2305
|
-
*
|
|
2306
|
-
*
|
|
2307
|
-
*
|
|
2308
|
-
*
|
|
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.
|
|
2309
2310
|
*/
|
|
2310
|
-
|
|
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
|
+
};
|
|
2311
2320
|
/**
|
|
2312
|
-
*
|
|
2313
|
-
*
|
|
2314
|
-
*
|
|
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.
|
|
2315
2325
|
*
|
|
2316
|
-
*
|
|
2317
|
-
* same-origin path relative to `__connection.json`.
|
|
2318
|
-
* - `string`: a full `http(s)://` URL used verbatim, or a path
|
|
2319
|
-
* resolved same-origin.
|
|
2326
|
+
* @see {@link DevframeDockDefaults}
|
|
2320
2327
|
*/
|
|
2321
|
-
|
|
2328
|
+
dock?: DevframeDockDefaults;
|
|
2322
2329
|
/**
|
|
2323
|
-
*
|
|
2324
|
-
* (
|
|
2325
|
-
*
|
|
2326
|
-
* path. `path` is relative to `__connection.json`'s location, like the
|
|
2327
|
-
* WebSocket `path`. `port` is set when the endpoint lives on a side-car
|
|
2328
|
-
* server on its own port (bridge mode: `devframeViteBridge`,
|
|
2329
|
-
* `@devframes/next`): the client combines the page hostname with `port`
|
|
2330
|
-
* and resolves `path` against that origin, mirroring
|
|
2331
|
-
* {@link ConnectionMetaWebsocket.port}.
|
|
2330
|
+
* Mount path override. Defaults depend on the adapter:
|
|
2331
|
+
* `/` for standalone (`cli` / `build`), `/__<id>/` for hosted
|
|
2332
|
+
* (`vite` / `embedded`).
|
|
2332
2333
|
*/
|
|
2333
|
-
|
|
2334
|
-
path: string;
|
|
2335
|
-
port?: number;
|
|
2336
|
-
};
|
|
2334
|
+
basePath?: string;
|
|
2337
2335
|
/**
|
|
2338
|
-
*
|
|
2339
|
-
*
|
|
2340
|
-
*
|
|
2341
|
-
*
|
|
2342
|
-
*
|
|
2343
|
-
*
|
|
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'
|
|
2344
2342
|
*/
|
|
2345
|
-
|
|
2343
|
+
duplicationStrategy?: DevframeDuplicationStrategy;
|
|
2346
2344
|
/**
|
|
2347
|
-
*
|
|
2348
|
-
*
|
|
2345
|
+
* Declares which runtimes meaningfully support this devframe. Adapters
|
|
2346
|
+
* act on a `false` before doing any work:
|
|
2349
2347
|
*
|
|
2350
|
-
*
|
|
2351
|
-
*
|
|
2352
|
-
*
|
|
2353
|
-
*
|
|
2354
|
-
*
|
|
2355
|
-
*
|
|
2356
|
-
*
|
|
2357
|
-
*
|
|
2358
|
-
*
|
|
2359
|
-
* 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).
|
|
2360
2357
|
*/
|
|
2361
|
-
|
|
2358
|
+
capabilities?: {
|
|
2359
|
+
dev?: boolean;
|
|
2360
|
+
build?: boolean;
|
|
2361
|
+
};
|
|
2362
2362
|
/**
|
|
2363
|
-
*
|
|
2364
|
-
*
|
|
2365
|
-
*
|
|
2366
|
-
*
|
|
2367
|
-
*
|
|
2368
|
-
*
|
|
2369
|
-
*
|
|
2370
|
-
*
|
|
2371
|
-
*
|
|
2372
|
-
*
|
|
2373
|
-
* > it. Emit it exclusively from hub-controlled, per-frame meta, never
|
|
2374
|
-
* > 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.
|
|
2375
2373
|
*/
|
|
2376
|
-
|
|
2374
|
+
services?: DevframeServiceInput[];
|
|
2377
2375
|
/**
|
|
2378
|
-
*
|
|
2379
|
-
*
|
|
2380
|
-
*
|
|
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.
|
|
2381
2380
|
*
|
|
2382
|
-
*
|
|
2383
|
-
*
|
|
2381
|
+
* Consumed by every adapter that serves the UI (`dev`, `build`, `vite`,
|
|
2382
|
+
* `next`, and the hub install path).
|
|
2384
2383
|
*/
|
|
2385
|
-
|
|
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 {
|
|
2386
2392
|
/**
|
|
2387
|
-
*
|
|
2388
|
-
*
|
|
2389
|
-
*
|
|
2390
|
-
*
|
|
2391
|
-
*
|
|
2392
|
-
* Contrast this with {@link DevframeSettingsRegistry} (`ctx.scope(ns).settings`)
|
|
2393
|
-
* and a hub's `devframe:user-settings` shared-state key; both are
|
|
2394
|
-
* mutable, user-editable, and synced bidirectionally over RPC for the
|
|
2395
|
-
* life of the session. `configs` is the opposite: one-way, immutable,
|
|
2396
|
-
* decided by whoever assembled the server.
|
|
2397
|
-
*
|
|
2398
|
-
* 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`.
|
|
2399
2397
|
*
|
|
2400
|
-
*
|
|
2401
|
-
*
|
|
2402
|
-
*
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
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.
|
|
2407
2405
|
*/
|
|
2408
|
-
|
|
2406
|
+
snapshot?: DevframeSnapshotRpcEntry[];
|
|
2409
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[])[]>);
|
|
2410
2410
|
/**
|
|
2411
|
-
*
|
|
2412
|
-
*
|
|
2413
|
-
* connection handshake augments this interface with its own key (see
|
|
2414
|
-
* {@link ConnectionMeta.configs} for the pattern). `@devframes/hub`
|
|
2415
|
-
* 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.
|
|
2416
2413
|
*/
|
|
2417
|
-
|
|
2418
|
-
|
|
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 };
|