devframe 1.2.6 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/{_shared-B6K2DKhb.mjs → _shared-C61zPwIz.mjs} +4 -1
  2. package/dist/{_shared-BvDsc83f.d.mts → _shared-Cm4iD0L3.d.mts} +8 -2
  3. package/dist/adapters/build.d.mts +1 -1
  4. package/dist/adapters/build.mjs +3 -3
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +4 -7
  7. package/dist/adapters/dev.d.mts +10 -4
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +2 -2
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +3 -3
  13. package/dist/{agentic-JXlb0uiD.d.mts → agentic-C6SCkdtj.d.mts} +1 -1
  14. package/dist/{browser-agent-rpc-BXhoSh1z.mjs → browser-agent-rpc-KH4HHvHf.mjs} +24 -2
  15. package/dist/cli/main.mjs +1 -1
  16. package/dist/client/index.d.mts +1 -1
  17. package/dist/client/index.mjs +2 -2
  18. package/dist/{host-agent-DB1yECaG.mjs → client-agent-iBkMYxqn.mjs} +148 -2
  19. package/dist/{context-O8NJzl93.d.mts → context-BGJi0ITA.d.mts} +1 -1
  20. package/dist/{context-dOWXVHc6.d.mts → context-BNxFOPbX.d.mts} +255 -117
  21. package/dist/{context-CeD8-IZA.mjs → context-CAJ5V017.mjs} +6 -46
  22. package/dist/{context-B-2gsJ6Z.mjs → context-D_jSSdME.mjs} +1 -1
  23. package/dist/{dev-pIWWAGYd.mjs → dev-BKBjIpX3.mjs} +43 -23
  24. package/dist/{diagnostics-Dngy7cNp.mjs → diagnostics-E_PvUPsA.mjs} +12 -0
  25. package/dist/in-page-channel/index.d.mts +1 -1
  26. package/dist/in-page-channel/index.mjs +1 -1
  27. package/dist/{index-DVGcpYm7.d.mts → index-CxJatyA1.d.mts} +2 -2
  28. package/dist/index.d.mts +3 -3
  29. package/dist/{instance-registry-B--XFmLS.mjs → instance-registry-C_4PpB6N.mjs} +1 -1
  30. package/dist/{instance-shell-DrvOWxqh.d.mts → instance-shell-GihbmXoF.d.mts} +2 -2
  31. package/dist/{instance-shell-DyVmJA6B.mjs → instance-shell-m4sY2oTG.mjs} +56 -15
  32. package/dist/{interactive-auth-CCZdQDy_.mjs → interactive-auth-DdDxs3WF.mjs} +1 -1
  33. package/dist/internal/index.d.mts +39 -7
  34. package/dist/internal/index.mjs +7 -7
  35. package/dist/{mcp-content-9_Pdtziq.d.mts → mcp-content-QImBQO8e.d.mts} +1 -1
  36. package/dist/node/auth.d.mts +2 -2
  37. package/dist/node/hub-internals.d.mts +2 -2
  38. package/dist/node/hub-internals.mjs +2 -2
  39. package/dist/node/index.d.mts +1 -1
  40. package/dist/node/index.mjs +2 -2
  41. package/dist/recipes/interactive-auth.d.mts +2 -2
  42. package/dist/recipes/interactive-auth.mjs +1 -1
  43. package/dist/{remote-assets-DoZ1AMpq.mjs → remote-assets-B-n83kUY.mjs} +1 -1
  44. package/dist/rpc/transports/ws-bun.d.mts +1 -1
  45. package/dist/rpc/transports/ws-deno.d.mts +1 -1
  46. package/dist/{rpc-core-Dl50hXG3.mjs → rpc-core-BLc5hm9X.mjs} +1 -1
  47. package/dist/{rpc-core-D6NWQW-B.d.mts → rpc-core-CGTCla-R.d.mts} +2 -2
  48. package/dist/{shared-state-Cow3u-_m.mjs → shared-state-Cc-Jrhma.mjs} +5 -2
  49. package/dist/{storage-BQy9865B.mjs → storage-CWI9PZ-5.mjs} +1 -1
  50. package/dist/types/index.d.mts +2 -2
  51. package/dist/utils/events.d.mts +1 -1
  52. package/dist/utils/remote-assets.mjs +1 -1
  53. package/dist/utils/shared-state.d.mts +1 -1
  54. package/dist/utils/shared-state.mjs +1 -1
  55. package/dist/utils/streaming-channel.d.mts +1 -1
  56. package/package.json +8 -8
@@ -373,6 +373,228 @@ interface DevframeAuthHandler {
373
373
  buildOpenUrl?: (url: string) => string;
374
374
  }
375
375
  //#endregion
376
+ //#region src/types/mcp.d.ts
377
+ /**
378
+ * The MCP adapter surface, typed here so `devframe` (which probes for and
379
+ * lazily loads `@devframes/agentic`) and `@devframes/agentic/mcp` (which
380
+ * implements it) share one contract without a type-level dependency cycle.
381
+ * None of these shapes reference the MCP SDK: the SDK is an implementation
382
+ * detail of `@devframes/agentic`, freely swappable behind this surface.
383
+ * The h3-bound `mountMcpHttp` shapes live in `node/agentic.ts` (exported via
384
+ * `devframe/internal`) instead: `devframe/types` must stay lib-neutral, and
385
+ * h3's declarations are not.
386
+ */
387
+ interface CreateMcpServerOptions {
388
+ /**
389
+ * Transport to use. `createMcpServer` itself runs `'stdio'` (a standalone
390
+ * process with its own host context); the Streamable-HTTP transport is
391
+ * served route-based by the dev server instead; see `mountMcpHttp` and
392
+ * the `mcp` option on `createDevServer` / `createCac`'s `--mcp` flag.
393
+ */
394
+ transport?: 'stdio';
395
+ /**
396
+ * Expose shared-state keys as MCP resources.
397
+ * - `true` (default): every key the host publishes
398
+ * - `false`: none
399
+ * - `(key) => boolean`: filter
400
+ */
401
+ exposeSharedState?: boolean | ((key: string) => boolean);
402
+ /**
403
+ * Usage guidance sent as `instructions` in the MCP `initialize` (and
404
+ * `server/discover`) result. Omitted or empty sends no instructions.
405
+ */
406
+ instructions?: string;
407
+ /**
408
+ * Resource templates served through `resources/templates/list`, and read
409
+ * through `resources/read`. Omitted or empty keeps the resources unchanged.
410
+ */
411
+ resourceTemplates?: McpResourceTemplate[];
412
+ /**
413
+ * Host subscription for resource-list changes; each `notify()` sends
414
+ * `notifications/resources/list_changed`. See {@link McpResourcesChangedHook}.
415
+ */
416
+ onResourcesChanged?: McpResourcesChangedHook;
417
+ /**
418
+ * Prompt templates served through `prompts/list` and `prompts/get`.
419
+ * Omitted or empty declares no `prompts` capability.
420
+ */
421
+ prompts?: McpPrompt[];
422
+ /** Override the name reported in the MCP handshake. */
423
+ serverName?: string;
424
+ /** Override the version reported in the MCP handshake. Defaults to `definition.version ?? '0.0.0'`. */
425
+ serverVersion?: string;
426
+ /** Called once the transport is connected. */
427
+ onReady?: (info: {
428
+ transport: 'stdio';
429
+ }) => void;
430
+ }
431
+ /**
432
+ * One MCP content block in a tool result. Attach blocks to a tool's return
433
+ * value with `withMcpContent` (from `devframe`) to send them in place of the
434
+ * default JSON text block. `data` is base64.
435
+ */
436
+ type McpContentBlock = {
437
+ type: 'text';
438
+ text: string;
439
+ } | {
440
+ type: 'image';
441
+ data: string;
442
+ mimeType: string;
443
+ } | {
444
+ type: 'audio';
445
+ data: string;
446
+ mimeType: string;
447
+ };
448
+ interface McpServerHandle {
449
+ stop: () => Promise<void>;
450
+ }
451
+ interface CreateMcpFetchHandlerOptions {
452
+ /** Name reported in the MCP handshake. */
453
+ serverName: string;
454
+ /** Version reported in the MCP handshake. */
455
+ serverVersion: string;
456
+ /** Expose shared-state keys as MCP resources; see {@link CreateMcpServerOptions.exposeSharedState}. */
457
+ exposeSharedState: boolean | ((key: string) => boolean);
458
+ /** Usage guidance for the handshake; see {@link CreateMcpServerOptions.instructions}. */
459
+ instructions?: string;
460
+ /** Resource templates; see {@link CreateMcpServerOptions.resourceTemplates}. */
461
+ resourceTemplates?: McpResourceTemplate[];
462
+ /** Host subscription for resource-list changes; see {@link CreateMcpServerOptions.onResourcesChanged}. */
463
+ onResourcesChanged?: McpResourcesChangedHook;
464
+ /** Prompt templates; see {@link CreateMcpServerOptions.prompts}. */
465
+ prompts?: McpPrompt[];
466
+ /**
467
+ * Optional identity check, layered on top of the origin gate and checked
468
+ * **after** it: a bearer token string (matched in constant time against
469
+ * `Authorization: Bearer <token>`), a `(request) => boolean` callback, or
470
+ * `false` (the default) for origin-only, trusting same-machine callers. A
471
+ * callback governs identity only and cannot relax the origin gate. See
472
+ * {@link McpAuthorization}.
473
+ */
474
+ authorization?: McpAuthorization;
475
+ /**
476
+ * Origin allow-list beyond the loopback default. `false` disables the
477
+ * origin gate entirely. Default: loopback-only.
478
+ *
479
+ * Unlike the WS transport, the MCP route does **not** allow `Origin`-less
480
+ * requests: a route-based endpoint is reachable by any local process, so a
481
+ * request must carry an `Origin` that passes the gate. Native clients
482
+ * (e.g. `devframe connect`) send their loopback origin explicitly.
483
+ */
484
+ allowedOrigins?: readonly string[] | false;
485
+ }
486
+ /** Connection facts a host knows about a request beyond the `Request` itself. */
487
+ interface McpConnectionInfo {
488
+ /**
489
+ * The connecting peer's remote address (a node socket's `remoteAddress`),
490
+ * used to prove a same-machine caller when the endpoint relies on the
491
+ * loopback origin default with no identity check. A host that can resolve a
492
+ * trustworthy peer address (the h3/node mount) supplies it; when it's
493
+ * omitted the origin gate stays the only locality signal.
494
+ */
495
+ remoteAddress?: string;
496
+ }
497
+ interface McpFetchHandler {
498
+ /**
499
+ * WHATWG-`fetch` handler for the MCP endpoint. Hand every method
500
+ * (POST/GET/DELETE) on the endpoint's path to it; routing by path is the
501
+ * host's job. Pass {@link McpConnectionInfo} when the host can resolve the
502
+ * peer address so the default trust boundary can enforce same-machine
503
+ * locality.
504
+ */
505
+ fetch: (request: Request, connection?: McpConnectionInfo) => Promise<Response>;
506
+ /** Tear down the handler (aborts in-flight exchanges, drops the change bridge). */
507
+ dispose: () => Promise<void>;
508
+ }
509
+ /** One concrete resource that an {@link McpResourceTemplate} lists in `resources/list`. */
510
+ interface McpResourceTemplateEntry {
511
+ /** The resource URI. It SHOULD match the `uriTemplate` of its template, so `resources/read` finds it. */
512
+ uri: string;
513
+ name: string;
514
+ /** Human-readable name for display. */
515
+ title?: string;
516
+ description?: string;
517
+ /** Defaults to the `mimeType` of the template. */
518
+ mimeType?: string;
519
+ }
520
+ /**
521
+ * What an {@link McpResourceTemplate} returns from `read`, sent as the
522
+ * `resources/read` result: `text` as is, or else `json` as JSON text.
523
+ */
524
+ interface McpResourceTemplateContent {
525
+ text?: string;
526
+ json?: unknown;
527
+ /** Defaults to the `mimeType` of the template, then `application/json`. */
528
+ mimeType?: string;
529
+ }
530
+ /**
531
+ * A resource template the MCP server lists in `resources/templates/list` and
532
+ * reads in `resources/read`. The `uriTemplate` uses RFC 6570 level 1 only:
533
+ * plain `{name}` expressions, with text between any two of them. Each
534
+ * expression matches a non-empty value with no `/`, and its value is
535
+ * percent-decoded.
536
+ *
537
+ * `list` and `read` get the node context of the devframe (or of the hub), so
538
+ * they can read live state such as `ctx.rpc.sharedState` or `ctx.agent`. The
539
+ * route options are resolved before `setup()` runs, so a template cannot
540
+ * capture that context in a closure.
541
+ */
542
+ interface McpResourceTemplate {
543
+ /** RFC 6570 level 1 URI template, e.g. `my-tool://file/{path}`. Anything else throws `DF0084`. */
544
+ uriTemplate: string;
545
+ name: string;
546
+ /** Human-readable name for display. */
547
+ title?: string;
548
+ description?: string;
549
+ /** MIME type of every resource the template stands for. */
550
+ mimeType?: string;
551
+ /** The concrete resources the template stands for right now. They are added to `resources/list`. */
552
+ list?: (ctx: DevframeNodeContext) => McpResourceTemplateEntry[] | Promise<McpResourceTemplateEntry[]>;
553
+ /** Read one resource: `params` holds the decoded value of each `{name}`, `uri` the requested URI. */
554
+ read: (params: Record<string, string>, uri: string, ctx: DevframeNodeContext) => McpResourceTemplateContent | Promise<McpResourceTemplateContent>;
555
+ }
556
+ /**
557
+ * A host subscription for resource-list changes. Call `notify()` each time
558
+ * the resources change; the MCP server then sends
559
+ * `notifications/resources/list_changed`. Return a function that removes the
560
+ * subscription; the MCP server calls it on teardown.
561
+ */
562
+ type McpResourcesChangedHook = (notify: () => void, ctx: DevframeNodeContext) => () => void;
563
+ /** One argument of an {@link McpPrompt}. */
564
+ interface McpPromptArgument {
565
+ name: string;
566
+ description?: string;
567
+ /** `prompts/get` answers invalid params when a required argument is missing or empty. */
568
+ required?: boolean;
569
+ }
570
+ /** One message of the answer to `prompts/get`. */
571
+ interface McpPromptMessage {
572
+ role: 'user' | 'assistant';
573
+ content: {
574
+ type: 'text';
575
+ text: string;
576
+ };
577
+ }
578
+ /** What an {@link McpPrompt} returns from `get`, sent as the `prompts/get` result. */
579
+ interface McpPromptResult {
580
+ description?: string;
581
+ messages: McpPromptMessage[];
582
+ }
583
+ /**
584
+ * A prompt template the MCP server lists in `prompts/list` and builds in
585
+ * `prompts/get`. A coding agent usually offers it to the user as a slash
586
+ * command. Each `name` is used once; a repeated name throws `DF0085`.
587
+ */
588
+ interface McpPrompt {
589
+ name: string;
590
+ /** Human-readable name for display. */
591
+ title?: string;
592
+ description?: string;
593
+ arguments?: McpPromptArgument[];
594
+ /** Build the messages from the arguments the coding agent sent. */
595
+ get: (args: Record<string, string>) => McpPromptResult | Promise<McpPromptResult>;
596
+ }
597
+ //#endregion
376
598
  //#region src/types/diagnostics.d.ts
377
599
  /**
378
600
  * The shared diagnostics lookup exposed by the host. A `Proxy` that resolves
@@ -509,6 +731,13 @@ interface BrowserAgentToolManifest {
509
731
  tags?: readonly string[];
510
732
  inputSchema?: unknown;
511
733
  }
734
+ /** What a connected document reports about itself alongside its tool manifest. */
735
+ interface BrowserAgentClientInfo {
736
+ url: string;
737
+ title: string;
738
+ visible: boolean;
739
+ focused: boolean;
740
+ }
512
741
  //#endregion
513
742
  //#region src/types/rpc-augments.d.ts
514
743
  /**
@@ -573,7 +802,7 @@ interface DevframeRpcServerFunctions {
573
802
  /** Methods eligible for automatic client caching. @internal */
574
803
  'devframe:rpc:cacheable-functions': () => Promise<string[]>;
575
804
  /** Replace this connection's browser-agent tool manifest, tagged with the calling tab's stable client id. @internal */
576
- 'devframe:agent:sync-client-tools': (clientId: string, tools: BrowserAgentToolManifest[]) => Promise<void>;
805
+ 'devframe:agent:sync-client-tools': (clientId: string, tools: BrowserAgentToolManifest[], info: BrowserAgentClientInfo) => Promise<void>;
577
806
  /**
578
807
  * Authenticate a connection with a previously-issued bearer token; resolves
579
808
  * whether the connection is now trusted. The interactive handler is provided
@@ -809,8 +1038,8 @@ interface DevframeSettingsStore<T extends Record<string, any> = Record<string, a
809
1038
  /**
810
1039
  * The two settings scopes available on a scoped context.
811
1040
  *
812
- * - `project`: per-workspace state, persisted under the host's
813
- * `workspace` storage dir. Project-local settings.
1041
+ * - `project`: per-checkout state, persisted under the
1042
+ * `project` storage dir. Project-local settings.
814
1043
  * - `global`: per-user state, persisted under the host's `global`
815
1044
  * storage dir. Machine-wide preferences.
816
1045
  */
@@ -1296,6 +1525,28 @@ interface McpRouteOptions {
1296
1525
  * Omitted or empty sends no instructions.
1297
1526
  */
1298
1527
  instructions?: string;
1528
+ /**
1529
+ * Resource templates the route serves through MCP
1530
+ * `resources/templates/list`. `resources/read` tries them after the
1531
+ * registered resources and the shared-state resources (a
1532
+ * `devframe://resource/` or `devframe://state/` URI never reaches them);
1533
+ * the first template whose `uriTemplate` matches the URI reads it. `resources/list` adds the
1534
+ * entries each template's `list` returns. Omitted or empty keeps the
1535
+ * resources unchanged.
1536
+ */
1537
+ resourceTemplates?: McpResourceTemplate[];
1538
+ /**
1539
+ * Host subscription for resource-list changes. Call `notify()` each time
1540
+ * the resources change (for example, the entries of a resource template);
1541
+ * the route then sends `notifications/resources/list_changed`. Return a
1542
+ * function that removes the subscription; the route calls it on teardown.
1543
+ */
1544
+ onResourcesChanged?: McpResourcesChangedHook;
1545
+ /**
1546
+ * Prompt templates the route serves through MCP `prompts/list` and
1547
+ * `prompts/get`. Omitted or empty declares no `prompts` capability.
1548
+ */
1549
+ prompts?: McpPrompt[];
1299
1550
  }
1300
1551
  interface DevframeCliOptions {
1301
1552
  /** Binary name; default: the devframe's `id`. */
@@ -1597,119 +1848,6 @@ type DevframeSnapshotRpcEntry = string | {
1597
1848
  inputs: DevframeSnapshotRpcInputs;
1598
1849
  };
1599
1850
  //#endregion
1600
- //#region src/types/mcp.d.ts
1601
- /**
1602
- * The MCP adapter surface, typed here so `devframe` (which probes for and
1603
- * lazily loads `@devframes/agentic`) and `@devframes/agentic/mcp` (which
1604
- * implements it) share one contract without a type-level dependency cycle.
1605
- * None of these shapes reference the MCP SDK: the SDK is an implementation
1606
- * detail of `@devframes/agentic`, freely swappable behind this surface.
1607
- * The h3-bound `mountMcpHttp` shapes live in `node/agentic.ts` (exported via
1608
- * `devframe/internal`) instead: `devframe/types` must stay lib-neutral, and
1609
- * h3's declarations are not.
1610
- */
1611
- interface CreateMcpServerOptions {
1612
- /**
1613
- * Transport to use. `createMcpServer` itself runs `'stdio'` (a standalone
1614
- * process with its own host context); the Streamable-HTTP transport is
1615
- * served route-based by the dev server instead; see `mountMcpHttp` and
1616
- * the `mcp` option on `createDevServer` / `createCac`'s `--mcp` flag.
1617
- */
1618
- transport?: 'stdio';
1619
- /**
1620
- * Expose shared-state keys as MCP resources.
1621
- * - `true` (default): every key the host publishes
1622
- * - `false`: none
1623
- * - `(key) => boolean`: filter
1624
- */
1625
- exposeSharedState?: boolean | ((key: string) => boolean);
1626
- /**
1627
- * Usage guidance sent as `instructions` in the MCP `initialize` (and
1628
- * `server/discover`) result. Omitted or empty sends no instructions.
1629
- */
1630
- instructions?: string;
1631
- /** Override the name reported in the MCP handshake. */
1632
- serverName?: string;
1633
- /** Override the version reported in the MCP handshake. Defaults to `definition.version ?? '0.0.0'`. */
1634
- serverVersion?: string;
1635
- /** Called once the transport is connected. */
1636
- onReady?: (info: {
1637
- transport: 'stdio';
1638
- }) => void;
1639
- }
1640
- /**
1641
- * One MCP content block in a tool result. Attach blocks to a tool's return
1642
- * value with `withMcpContent` (from `devframe`) to send them in place of the
1643
- * default JSON text block. `data` is base64.
1644
- */
1645
- type McpContentBlock = {
1646
- type: 'text';
1647
- text: string;
1648
- } | {
1649
- type: 'image';
1650
- data: string;
1651
- mimeType: string;
1652
- } | {
1653
- type: 'audio';
1654
- data: string;
1655
- mimeType: string;
1656
- };
1657
- interface McpServerHandle {
1658
- stop: () => Promise<void>;
1659
- }
1660
- interface CreateMcpFetchHandlerOptions {
1661
- /** Name reported in the MCP handshake. */
1662
- serverName: string;
1663
- /** Version reported in the MCP handshake. */
1664
- serverVersion: string;
1665
- /** Expose shared-state keys as MCP resources; see {@link CreateMcpServerOptions.exposeSharedState}. */
1666
- exposeSharedState: boolean | ((key: string) => boolean);
1667
- /** Usage guidance for the handshake; see {@link CreateMcpServerOptions.instructions}. */
1668
- instructions?: string;
1669
- /**
1670
- * Optional identity check, layered on top of the origin gate and checked
1671
- * **after** it: a bearer token string (matched in constant time against
1672
- * `Authorization: Bearer <token>`), a `(request) => boolean` callback, or
1673
- * `false` (the default) for origin-only, trusting same-machine callers. A
1674
- * callback governs identity only and cannot relax the origin gate. See
1675
- * {@link McpAuthorization}.
1676
- */
1677
- authorization?: McpAuthorization;
1678
- /**
1679
- * Origin allow-list beyond the loopback default. `false` disables the
1680
- * origin gate entirely. Default: loopback-only.
1681
- *
1682
- * Unlike the WS transport, the MCP route does **not** allow `Origin`-less
1683
- * requests: a route-based endpoint is reachable by any local process, so a
1684
- * request must carry an `Origin` that passes the gate. Native clients
1685
- * (e.g. `devframe connect`) send their loopback origin explicitly.
1686
- */
1687
- allowedOrigins?: readonly string[] | false;
1688
- }
1689
- /** Connection facts a host knows about a request beyond the `Request` itself. */
1690
- interface McpConnectionInfo {
1691
- /**
1692
- * The connecting peer's remote address (a node socket's `remoteAddress`),
1693
- * used to prove a same-machine caller when the endpoint relies on the
1694
- * loopback origin default with no identity check. A host that can resolve a
1695
- * trustworthy peer address (the h3/node mount) supplies it; when it's
1696
- * omitted the origin gate stays the only locality signal.
1697
- */
1698
- remoteAddress?: string;
1699
- }
1700
- interface McpFetchHandler {
1701
- /**
1702
- * WHATWG-`fetch` handler for the MCP endpoint. Hand every method
1703
- * (POST/GET/DELETE) on the endpoint's path to it; routing by path is the
1704
- * host's job. Pass {@link McpConnectionInfo} when the host can resolve the
1705
- * peer address so the default trust boundary can enforce same-machine
1706
- * locality.
1707
- */
1708
- fetch: (request: Request, connection?: McpConnectionInfo) => Promise<Response>;
1709
- /** Tear down the handler (aborts in-flight exchanges, drops the change bridge). */
1710
- dispose: () => Promise<void>;
1711
- }
1712
- //#endregion
1713
1851
  //#region src/utils/shared-state.d.ts
1714
1852
  type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
1715
1853
  type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
@@ -2311,4 +2449,4 @@ interface ConnectionMeta {
2311
2449
  */
2312
2450
  interface DevframeConnectionConfigsRegistry {}
2313
2451
  //#endregion
2314
- export { McpSetting as $, SharedStateEvents as A, DevframeDiagnosticsLogger as At, DevframeCliOptions as B, AgentResourceInput as Bt, createStreamSink as C, DevframeViewHost as Ct, ImmutableObject as D, DevframeHost as Dt, ImmutableMap as E, DevframeRpcSharedStates as Et, CreateMcpServerOptions as F, parseCliFlags as Ft, DevframeRpcOptions as G, DevframeAgentHost as Gt, DevframeDeploymentKind as H, AgentToolInput as Ht, McpConnectionInfo as I, AgentHandle as It, DevframeSnapshotRpcInputs as J, EventUnsubscribe as Jt, DevframeSetupInfo as K, DevframeAgentHostEvents as Kt, McpContentBlock as L, AgentManifest as Lt, SharedStatePatch as M, CliFlagsSchema as Mt, createSharedState as N, InferCliFlags as Nt, ImmutableSet as O, DevframeStorageScope as Ot, CreateMcpFetchHandlerOptions as P, defineCliFlags as Pt, McpRouteOptions as Q, McpFetchHandler as R, AgentResource as Rt, createStreamReader as S, SettingsForNamespace as St, ImmutableArray as T, DevframeRpcServerFunctions as Tt, DevframeDockDefaults as U, AgentToolProvider as Ut, DevframeDefinition as V, AgentTool as Vt, DevframeDuplicationStrategy as W, AgentToolProviderHandle as Wt, DevframeWsOptions as X, DevframeSseOptions as Y, EventsMap as Yt, McpAuthorization as Z, CreateStreamSinkOptions as _, ScopedBroadcastOptions as _t, DevframeConnectionConfigsRegistry as a, DevframeServiceOf as at, StreamSink as b, ScopedServerFunctions as bt, RpcBroadcastOptions as c, DevframeServicesRegistry as ct, RpcSharedStateHost as d, DevframeScopedNodeContext as dt, DevframeServiceDefinition as et, RpcStreamingChannel as f, DevframeScopedNodeRpc as ft, CreateStreamReaderOptions as g, DevframeSettingsStore as gt, BufferedChunk as h, DevframeSettingsRegistry as ht, DevframeCapabilities as i, DevframeServiceMeta as it, SharedStateOptions as j, DevframeAuthHandler as jt, SharedState as k, DevframeDiagnosticsHost as kt, RpcFunctionsHost as l, DevframeServicesScopeRegistry as lt, RpcStreamingHost as m, DevframeSettings as mt, ConnectionMetaSse as n, DevframeServiceId as nt, DevframeNodeContext as o, DevframeServiceScopeOf as ot, RpcStreamingChannelOptions as p, DevframeScopedStreamingHost as pt, DevframeSnapshotRpcEntry as q, EventEmitter as qt, ConnectionMetaWebsocket as r, DevframeServiceInput as rt, DevframeNodeRpcSession as s, DevframeServicesHost as st, ConnectionMeta as t, DevframeServiceDescriptor as tt, RpcSharedStateGetOptions as u, DevframeServicesState as ut, StreamErrorPayload as v, ScopedClientFunctions as vt, Immutable as w, DevframeRpcClientFunctions as wt, StreamSinkEvents as x, ScopedSharedStates as xt, StreamReader as y, ScopedRpcFn as yt, McpServerHandle as z, AgentResourceContent as zt };
2452
+ export { DevframeServiceOf as $, AgentToolProviderHandle as $t, SharedStateEvents as A, McpFetchHandler as At, DevframeSetupInfo as B, DevframeAuthHandler as Bt, createStreamSink as C, DevframeStorageScope as Ct, ImmutableObject as D, CreateMcpServerOptions as Dt, ImmutableMap as E, CreateMcpFetchHandlerOptions as Et, DevframeDefinition as F, McpResourceTemplate as Ft, McpAuthorization as G, AgentHandle as Gt, DevframeSnapshotRpcInputs as H, InferCliFlags as Ht, DevframeDeploymentKind as I, McpResourceTemplateContent as It, DevframeServiceDefinition as J, AgentResourceContent as Jt, McpRouteOptions as K, AgentManifest as Kt, DevframeDockDefaults as L, McpResourceTemplateEntry as Lt, SharedStatePatch as M, McpPromptArgument as Mt, createSharedState as N, McpPromptMessage as Nt, ImmutableSet as O, McpConnectionInfo as Ot, DevframeCliOptions as P, McpPromptResult as Pt, DevframeServiceMeta as Q, AgentToolProvider as Qt, DevframeDuplicationStrategy as R, McpResourcesChangedHook as Rt, createStreamReader as S, DevframeHost as St, ImmutableArray as T, DevframeDiagnosticsLogger as Tt, DevframeSseOptions as U, defineCliFlags as Ut, DevframeSnapshotRpcEntry as V, CliFlagsSchema as Vt, DevframeWsOptions as W, parseCliFlags as Wt, DevframeServiceId as X, AgentTool as Xt, DevframeServiceDescriptor as Y, AgentResourceInput as Yt, DevframeServiceInput as Z, AgentToolInput as Zt, CreateStreamSinkOptions as _, DevframeViewHost as _t, DevframeConnectionConfigsRegistry as a, DevframeScopedNodeContext as at, StreamSink as b, DevframeRpcSharedStates as bt, RpcBroadcastOptions as c, DevframeSettings as ct, RpcSharedStateHost as d, ScopedBroadcastOptions as dt, DevframeAgentHost as en, DevframeServiceScopeOf as et, RpcStreamingChannel as f, ScopedClientFunctions as ft, CreateStreamReaderOptions as g, SettingsForNamespace as gt, BufferedChunk as h, ScopedSharedStates as ht, DevframeCapabilities as i, EventsMap as in, DevframeServicesState as it, SharedStateOptions as j, McpPrompt as jt, SharedState as k, McpContentBlock as kt, RpcFunctionsHost as l, DevframeSettingsRegistry as lt, RpcStreamingHost as m, ScopedServerFunctions as mt, ConnectionMetaSse as n, EventEmitter as nn, DevframeServicesRegistry as nt, DevframeNodeContext as o, DevframeScopedNodeRpc as ot, RpcStreamingChannelOptions as p, ScopedRpcFn as pt, McpSetting as q, AgentResource as qt, ConnectionMetaWebsocket as r, EventUnsubscribe as rn, DevframeServicesScopeRegistry as rt, DevframeNodeRpcSession as s, DevframeScopedStreamingHost as st, ConnectionMeta as t, DevframeAgentHostEvents as tn, DevframeServicesHost as tt, RpcSharedStateGetOptions as u, DevframeSettingsStore as ut, StreamErrorPayload as v, DevframeRpcClientFunctions as vt, Immutable as w, DevframeDiagnosticsHost as wt, StreamSinkEvents as x, BrowserAgentClientInfo as xt, StreamReader as y, DevframeRpcServerFunctions as yt, DevframeRpcOptions as z, McpServerHandle as zt };
@@ -4,10 +4,10 @@ import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
4
4
  import { defineRpcFunction } from "./index.mjs";
5
5
  import { t as DEVFRAME_EVENTS } from "./events-0wDwalmL.mjs";
6
6
  import { DEVFRAME_SERVICES_STATE_KEY } from "./constants.mjs";
7
- import { t as diagnostics$1 } from "./diagnostics-Dngy7cNp.mjs";
8
- import { r as createEventEmitter, t as DevframeAgentHost } from "./host-agent-DB1yECaG.mjs";
9
- import { n as createDebug, t as resolveStaticAssetsSource } from "./remote-assets-DoZ1AMpq.mjs";
10
- import { t as createStorage } from "./storage-BQy9865B.mjs";
7
+ import { t as diagnostics$1 } from "./diagnostics-E_PvUPsA.mjs";
8
+ import { i as DevframeAgentHost, n as removeClientAgentSession, o as createEventEmitter, r as syncClientAgentTools } from "./client-agent-iBkMYxqn.mjs";
9
+ import { n as createDebug, t as resolveStaticAssetsSource } from "./remote-assets-B-n83kUY.mjs";
10
+ import { t as createStorage } from "./storage-CWI9PZ-5.mjs";
11
11
  import { a as satisfiesVersionRange, n as expandResolveFrom, r as importServicePackage, t as deepMergeOptionSets } from "./services-install-D5YUwFXW.mjs";
12
12
  import { isAbsolute, join, relative } from "pathe";
13
13
  import { createSharedState } from "devframe/utils/shared-state";
@@ -32,46 +32,6 @@ var DevframeDiagnosticsHost = class {
32
32
  }
33
33
  };
34
34
  //#endregion
35
- //#region src/node/client-agent.ts
36
- const states = /* @__PURE__ */ new WeakMap();
37
- function getState(context) {
38
- let state = states.get(context);
39
- if (state) return state;
40
- const sessions = /* @__PURE__ */ new Map();
41
- state = {
42
- sessions,
43
- notifyChanged: context.agent.registerToolProvider(() => {
44
- const tools = /* @__PURE__ */ new Map();
45
- for (const { session, tools: manifests } of sessions.values()) for (const manifest of manifests) {
46
- if (tools.has(manifest.id)) continue;
47
- tools.set(manifest.id, {
48
- ...manifest,
49
- handler: (args) => session.rpc.$callRaw({
50
- method: "devframe:agent:invoke-client-tool",
51
- args: [manifest.id, args]
52
- })
53
- });
54
- }
55
- return [...tools.values()];
56
- }).notifyChanged
57
- };
58
- states.set(context, state);
59
- return state;
60
- }
61
- function syncClientAgentTools(context, session, clientId, tools) {
62
- const state = getState(context);
63
- state.sessions.set(session.meta, {
64
- session,
65
- clientId,
66
- tools
67
- });
68
- state.notifyChanged();
69
- }
70
- function removeClientAgentSession(context, meta) {
71
- const state = states.get(context);
72
- if (state?.sessions.delete(meta)) state.notifyChanged();
73
- }
74
- //#endregion
75
35
  //#region src/node/rpc-shared-state.ts
76
36
  const debug$2 = createDebug("devframe:rpc:state:changed");
77
37
  const debugSubscribe = createDebug("devframe:rpc:state:subscribe");
@@ -1261,9 +1221,9 @@ const BUILTIN_AGENT_RPC = [
1261
1221
  name: "devframe:agent:sync-client-tools",
1262
1222
  type: "action",
1263
1223
  jsonSerializable: true,
1264
- setup: (context) => ({ handler(clientId, tools) {
1224
+ setup: (context) => ({ handler(clientId, tools, info) {
1265
1225
  const session = context.rpc.getCurrentRpcSession();
1266
- if (session) syncClientAgentTools(context, session, clientId, tools);
1226
+ if (session) syncClientAgentTools(context, session, clientId, tools, info);
1267
1227
  } })
1268
1228
  })
1269
1229
  ];
@@ -1,5 +1,5 @@
1
1
  import { n as randomToken } from "./crypto-token-TVbGGFuj.mjs";
2
- import { t as createStorage } from "./storage-BQy9865B.mjs";
2
+ import { t as createStorage } from "./storage-CWI9PZ-5.mjs";
3
3
  import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-CeQUo6KI.mjs";
4
4
  import { join } from "pathe";
5
5
  //#region src/node/hub-internals/context.ts
@@ -1,10 +1,10 @@
1
1
  import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
2
- import { t as diagnostics } from "./diagnostics-Dngy7cNp.mjs";
3
- import { t as createHostContext } from "./context-CeD8-IZA.mjs";
4
- import { t as resolveStaticAssetsSource } from "./remote-assets-DoZ1AMpq.mjs";
5
- import { a as resolveMcpConfig, c as joinURL, d as withoutLeadingSlash, i as resolveDevServerPort, l as withBase, n as normalizeBasePath, r as resolveBasePath, s as importAgenticMcp, t as loadAutoMcpAdapter } from "./_shared-B6K2DKhb.mjs";
2
+ import { t as diagnostics } from "./diagnostics-E_PvUPsA.mjs";
3
+ import { t as createHostContext } from "./context-CAJ5V017.mjs";
4
+ import { t as resolveStaticAssetsSource } from "./remote-assets-B-n83kUY.mjs";
5
+ import { a as resolveMcpConfig, c as joinURL, d as withoutLeadingSlash, i as resolveDevServerPort, l as withBase, n as normalizeBasePath, r as resolveBasePath, s as importAgenticMcp, t as loadAutoMcpAdapter } from "./_shared-C61zPwIz.mjs";
6
6
  import { t as createH3DevframeHost } from "./host-h3-BROI6SQ4.mjs";
7
- import { i as normalizeHttpServerUrl, n as resolveInstanceRegister, t as createInstanceShell } from "./instance-shell-DyVmJA6B.mjs";
7
+ import { a as isAddressInUse, i as normalizeHttpServerUrl, n as resolveInstanceRegister, o as listenWithLoopbackTwin, t as createInstanceShell } from "./instance-shell-m4sY2oTG.mjs";
8
8
  import { open } from "./utils/open.mjs";
9
9
  import { mountStaticHandler } from "./utils/serve-static.mjs";
10
10
  import { resolve } from "pathe";
@@ -141,6 +141,9 @@ async function mountMcpRoute(app, context, def, base, setting) {
141
141
  serverVersion: def.version ?? "0.0.0",
142
142
  exposeSharedState: config.exposeSharedState ?? true,
143
143
  instructions: config.instructions,
144
+ resourceTemplates: config.resourceTemplates,
145
+ onResourcesChanged: config.onResourcesChanged,
146
+ prompts: config.prompts,
144
147
  authorization: config.authorization,
145
148
  allowedOrigins: config.allowedOrigins
146
149
  });
@@ -172,28 +175,11 @@ async function mountMcpRoute(app, context, def, base, setting) {
172
175
  async function createDevServer(def, options = {}) {
173
176
  if (def.capabilities?.dev === false && !options.force) throw diagnostics.DF0058({ id: def.id });
174
177
  const host = options.host ?? def.cli?.host ?? "localhost";
175
- const requestedPort = options.port ?? await resolveDevServerPort(def, { host });
176
178
  const flags = options.flags ?? {};
177
179
  const basePath = options.basePath ? normalizeBasePath(options.basePath) : resolveBasePath(def, "standalone");
178
180
  const app = options.app ?? new H3();
179
181
  const server = createServer(toNodeHandler(app));
180
- try {
181
- await new Promise((resolveListen, rejectListen) => {
182
- const onError = (error) => rejectListen(error);
183
- server.once("error", onError);
184
- server.listen(requestedPort, host, () => {
185
- server.removeListener("error", onError);
186
- resolveListen();
187
- });
188
- });
189
- } catch (error) {
190
- throw diagnostics.DF0052({
191
- host,
192
- port: requestedPort,
193
- reason: error instanceof Error ? error.message : String(error),
194
- cause: error
195
- });
196
- }
182
+ const { requestedPort, closeLoopbackTwin } = await listenDevServer(server, def, host, options);
197
183
  const address = server.address();
198
184
  const port = typeof address === "object" && address ? address.port : requestedPort;
199
185
  const origin = normalizeHttpServerUrl(host, port);
@@ -231,6 +217,7 @@ async function createDevServer(def, options = {}) {
231
217
  try {
232
218
  await devframe.ready;
233
219
  } catch (error) {
220
+ await closeLoopbackTwin();
234
221
  await new Promise((resolveClose) => server.close(() => resolveClose()));
235
222
  throw error;
236
223
  }
@@ -251,10 +238,43 @@ async function createDevServer(def, options = {}) {
251
238
  connectionMeta: transport.connectionMeta,
252
239
  async close() {
253
240
  await devframe.close();
241
+ await closeLoopbackTwin();
254
242
  await new Promise((resolveClose) => server.close(() => resolveClose()));
255
243
  }
256
244
  };
257
245
  }
246
+ /**
247
+ * Bind the dev server. `getPort` probes only `host`, so it can call a port
248
+ * free while another process holds it on the other loopback address of
249
+ * `localhost`. The twin bind catches that, and a port that was not pinned
250
+ * moves to the next free one.
251
+ */
252
+ async function listenDevServer(server, def, host, options) {
253
+ let requestedPort = options.port ?? await resolveDevServerPort(def, {
254
+ host,
255
+ defaultPort: options.defaultPort
256
+ });
257
+ for (let attempt = 1;; attempt++) try {
258
+ return {
259
+ requestedPort,
260
+ closeLoopbackTwin: await listenWithLoopbackTwin(server, requestedPort, host)
261
+ };
262
+ } catch (error) {
263
+ if (options.port == null && attempt < 3 && isAddressInUse(error)) {
264
+ requestedPort = await resolveDevServerPort(def, {
265
+ host,
266
+ defaultPort: requestedPort + 1
267
+ });
268
+ continue;
269
+ }
270
+ throw diagnostics.DF0052({
271
+ host,
272
+ port: requestedPort,
273
+ reason: error instanceof Error ? error.message : String(error),
274
+ cause: error
275
+ });
276
+ }
277
+ }
258
278
  async function maybeOpenBrowser(def, flags, origin, override, authHandler) {
259
279
  const flagsOpen = flags.open;
260
280
  const cliOpen = def.cli?.open;
@@ -186,6 +186,18 @@ const diagnostics = defineDiagnostics({
186
186
  DF0081: {
187
187
  why: (p) => `Tool "${p.name}" returned invalid MCP content blocks: ${p.reason}`,
188
188
  fix: "Pass `withMcpContent` a non-empty array of `{ type: 'text', text }`, `{ type: 'image', data, mimeType }` or `{ type: 'audio', data, mimeType }` blocks, with string fields and base64 `data`."
189
+ },
190
+ DF0082: {
191
+ why: (p) => `Tool "${p.tool}" was addressed to client "${p.clientId}", but no connected browser tab has that id and the tool.${p.live.length ? ` Connected clients: ${p.live.join(", ")}.` : " No browser tab is connected."}`,
192
+ fix: "Re-list the connected clients (`devframe:agent:list-clients`) and retry with a live `client_id`, or omit `client_id` to target the most recently focused tab."
193
+ },
194
+ DF0084: {
195
+ why: (p) => `MCP resource template "${p.name}" has an invalid \`uriTemplate\` "${p.uriTemplate}": ${p.reason}`,
196
+ fix: "Use RFC 6570 level 1 only: plain `{name}` expressions made of letters, digits, `_` and `.`, with no operator (`+`, `#`, `/`, `?`, …), modifier (`:n`, `*`) or list of names, and no `{` or `}` outside an expression."
197
+ },
198
+ DF0085: {
199
+ why: (p) => `MCP prompt "${p.name}" is listed more than once.`,
200
+ fix: "Give each entry in `prompts` its own `name`. `prompts/get` finds a prompt by its name, so two prompts cannot share one."
189
201
  }
190
202
  }
191
203
  });
@@ -1,5 +1,5 @@
1
1
  import { D as InferArgsType, E as Thenable, O as InferReturnType, T as RpcReturnSchema, m as RpcFunctionAgentOptions, n as RpcArgsSchema } from "../types-BmDbfHCx.mjs";
2
- import { k as SharedState, qt as EventEmitter } from "../context-dOWXVHc6.mjs";
2
+ import { k as SharedState, nn as EventEmitter } from "../context-BNxFOPbX.mjs";
3
3
  //#region src/in-page-channel/types.d.ts
4
4
  /**
5
5
  * The shared contract of one in-page channel, declared once (usually in a
@@ -1,6 +1,6 @@
1
1
  import { a as defineDiagnostics, i as createBirpc, n as argsToJsonSchema, o as DEVFRAME_EVENTS, r as resolveAgentSafety, t as toolInputToRpcArgs } from "../tool-input-CES3Pc4y.mjs";
2
2
  import { createEventEmitter } from "../utils/events.mjs";
3
- import { t as createSharedState } from "../shared-state-Cow3u-_m.mjs";
3
+ import { t as createSharedState } from "../shared-state-Cc-Jrhma.mjs";
4
4
  import { nanoid } from "../utils/nanoid.mjs";
5
5
  import { r as registerBrowserAgentTool } from "../browser-agent-Pu-FLk6I.mjs";
6
6
  //#region src/in-page-channel/diagnostics.ts
@@ -1,5 +1,5 @@
1
- import { k as SharedState, o as DevframeNodeContext, s as DevframeNodeRpcSession } from "./context-dOWXVHc6.mjs";
2
- import { n as InternalAnonymousAuthStorage } from "./context-O8NJzl93.mjs";
1
+ import { k as SharedState, o as DevframeNodeContext, s as DevframeNodeRpcSession } from "./context-BNxFOPbX.mjs";
2
+ import { n as InternalAnonymousAuthStorage } from "./context-BGJi0ITA.mjs";
3
3
  //#region src/node/auth/revoke.d.ts
4
4
  /**
5
5
  * Flip `isTrusted` to false on any live WS clients connected with `token`