devframe 0.9.0 → 0.9.2

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 (79) hide show
  1. package/dist/{_shared-BPmX9fqK.d.mts → _shared-BAGBgTUO.d.mts} +1 -1
  2. package/dist/{_shared-CbZ_yhbz.mjs → _shared-BM3PdYli.mjs} +1 -1
  3. package/dist/adapters/build.d.mts +1 -1
  4. package/dist/adapters/build.mjs +12 -9
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +4 -3
  7. package/dist/adapters/dev.d.mts +3 -3
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/embedded.mjs +2 -0
  11. package/dist/adapters/initiate.d.mts +2 -2
  12. package/dist/adapters/initiate.mjs +1 -1
  13. package/dist/adapters/mcp.d.mts +1 -1
  14. package/dist/adapters/mcp.mjs +1 -1
  15. package/dist/cli/main.mjs +2 -2
  16. package/dist/client/index.d.mts +48 -2
  17. package/dist/client/index.mjs +133 -23
  18. package/dist/constants.d.mts +74 -2
  19. package/dist/constants.mjs +9 -2
  20. package/dist/{context-xdynGSLP.mjs → context-af2w_F0_.mjs} +2 -2
  21. package/dist/{context-B3YUu_95.d.mts → context-al99DDJ8.d.mts} +1 -1
  22. package/dist/{context-CEe4PBz1.mjs → context-riPPHGiu.mjs} +339 -26
  23. package/dist/{dev--rAPzbvO.mjs → dev-BKm_HisC.mjs} +15 -11
  24. package/dist/{devframe-DJMg0WLe.d.mts → devframe-mbfgpQQC.d.mts} +774 -587
  25. package/dist/{diagnostics-DOQPnQmX.mjs → diagnostics-DI1HGj2I.mjs} +20 -0
  26. package/dist/{dist-eNI9qM_I.mjs → dist-CZXfGEkd.mjs} +1 -1
  27. package/dist/events-DKfSmoTj.mjs +65 -0
  28. package/dist/hash-DdPnc4k3.mjs +360 -0
  29. package/dist/{host-agent-xLCSe65D.mjs → host-agent-DVDLqjvj.mjs} +14 -13
  30. package/dist/{http-DYpK0cUi.mjs → http-POepPnaQ.mjs} +10 -6
  31. package/dist/{index-CB-fSUyn.d.mts → index-Dipgo9ji.d.mts} +2 -2
  32. package/dist/index.d.mts +2 -2
  33. package/dist/{instance-registry-WvQkt42E.mjs → instance-registry-Dy28drtm.mjs} +4 -4
  34. package/dist/{instance-shell-CMFyszWJ.mjs → instance-shell-GqTY93gC.mjs} +5 -5
  35. package/dist/{instance-shell-CljiKq1c.d.mts → instance-shell-mU5j5xWn.d.mts} +2 -2
  36. package/dist/internal/index.d.mts +40 -4
  37. package/dist/internal/index.mjs +7 -7
  38. package/dist/node/auth.d.mts +2 -2
  39. package/dist/node/auth.mjs +2 -2
  40. package/dist/node/hub-internals.d.mts +2 -2
  41. package/dist/node/hub-internals.mjs +2 -2
  42. package/dist/node/index.d.mts +10 -1
  43. package/dist/node/index.mjs +2 -2
  44. package/dist/recipes/common-rpc-functions.d.mts +11 -1
  45. package/dist/recipes/common-rpc-functions.mjs +10 -0
  46. package/dist/recipes/interactive-auth.d.mts +2 -2
  47. package/dist/recipes/interactive-auth.mjs +2 -2
  48. package/dist/{remote-assets-nTTxY2MU.mjs → remote-assets-CqxiyltC.mjs} +18 -7
  49. package/dist/{revoke-DZbJ7Cl0.mjs → revoke-3Q6aDKWk.mjs} +2 -1
  50. package/dist/{rolldown-runtime-JspESFgx.mjs → rolldown-runtime-B4iAMlE-.mjs} +1 -1
  51. package/dist/rpc/dump.mjs +1 -1
  52. package/dist/rpc/index.mjs +1 -1
  53. package/dist/rpc/transports/ws-bun.d.mts +1 -1
  54. package/dist/rpc/transports/ws-deno.d.mts +26 -0
  55. package/dist/rpc/transports/ws-deno.mjs +30 -0
  56. package/dist/{rpc-core-DMTe-hLo.mjs → rpc-core-DRYnbxdD.mjs} +2 -2
  57. package/dist/{rpc-core-Cc4lucI0.d.mts → rpc-core-dV69u4lY.d.mts} +2 -2
  58. package/dist/{shared-state-fRRbRUtD.mjs → shared-state-DoXutg_U.mjs} +1 -1
  59. package/dist/{state-Cc1IeSmQ.mjs → state-CK9LjrnT.mjs} +1 -1
  60. package/dist/{static-BPZ1_OPA.mjs → static-DV_qUSRS.mjs} +1 -1
  61. package/dist/{storage-CYye7P7L.mjs → storage-BNqSAOxA.mjs} +3 -3
  62. package/dist/types/index.d.mts +2 -2
  63. package/dist/utils/events.d.mts +1 -1
  64. package/dist/utils/get-port.mjs +1 -1
  65. package/dist/utils/hash.mjs +1 -1
  66. package/dist/utils/launch-editor.mjs +12 -11
  67. package/dist/utils/open.mjs +1 -1
  68. package/dist/utils/remote-assets.d.mts +7 -1
  69. package/dist/utils/remote-assets.mjs +1 -1
  70. package/dist/utils/serve-static.mjs +6 -5
  71. package/dist/utils/shared-state.d.mts +1 -1
  72. package/dist/utils/shared-state.mjs +1 -1
  73. package/dist/utils/streaming-channel.d.mts +1 -1
  74. package/dist/validation-YrZH6atx.mjs +311 -0
  75. package/package.json +4 -3
  76. package/skills/devframe/SKILL.md +1 -1
  77. package/dist/hash-KtDZYXDN.mjs +0 -266
  78. package/dist/validation-CpXFB6Dz.mjs +0 -266
  79. package/dist/{host-h3-Crkzm42q.mjs → host-h3-fRbF9yor.mjs} +1 -1
@@ -1,3 +1,69 @@
1
+ //#region src/events.d.ts
2
+ /**
3
+ * Centralized registry of the core devframe event names — the node-side host
4
+ * bus events, the client RPC connection events, and the server→client
5
+ * broadcast notifications — so these names live in one place instead of
6
+ * scattered string literals.
7
+ *
8
+ * **Keep this in sync with [`docs/guide/events.md`](../../../docs/guide/events.md)**
9
+ * (the "Core devframe events" section): every name here appears in that page's
10
+ * tables, and every name there resolves to an entry here. Add, rename, or
11
+ * remove a name in both places in the same change, and reference
12
+ * `DEVFRAME_EVENTS.*` from call sites instead of re-typing a literal.
13
+ *
14
+ * This map covers **notifications** (events, broadcasts). The request/response
15
+ * RPC endpoints of the shared-state, streaming, and auth-handshake protocols
16
+ * (`devframe:rpc:server-state:*`, `devframe:streaming:subscribe`,
17
+ * `anonymous:devframe:auth`, …) are defined at their handlers and typed in
18
+ * `types/rpc-augments.ts`; they aren't events and stay out of this map.
19
+ *
20
+ * The `EventEmitter` maps (`RpcClientEvents`, `DevframeAgentHostEvents`) and the
21
+ * `DevframeRpcClientFunctions` augmentation declare these names as type-level
22
+ * keys (a literal is unavoidable in a type position); those declarations mirror
23
+ * this map and move with it.
24
+ */
25
+ declare const DEVFRAME_EVENTS: {
26
+ /**
27
+ * Node-side host `EventEmitter` events. The agent host (`ctx.agent.events`)
28
+ * emits these as its tool/resource surface changes; protocol adapters (e.g.
29
+ * MCP) subscribe to re-publish their manifest.
30
+ */
31
+ readonly bus: {
32
+ readonly agentManifestChanged: "agent:manifest:changed";
33
+ readonly agentToolRegistered: "agent:tool:registered";
34
+ readonly agentToolUnregistered: "agent:tool:unregistered";
35
+ readonly agentResourceRegistered: "agent:resource:registered";
36
+ readonly agentResourceUnregistered: "agent:resource:unregistered";
37
+ };
38
+ /**
39
+ * Client-side RPC connection `EventEmitter` events (`rpc.events`) a UI
40
+ * subscribes to for connection lifecycle and error surfacing.
41
+ */
42
+ readonly client: {
43
+ readonly isTrustedUpdated: "rpc:is-trusted:updated";
44
+ readonly error: "rpc:error";
45
+ readonly connectionStatus: "connection:status";
46
+ readonly connectionError: "connection:error";
47
+ };
48
+ /**
49
+ * Broadcast notifications the server pushes to clients (server → client),
50
+ * `devframe:` prefix. The paired request methods (subscribe/get/set/…) are
51
+ * RPC endpoints, not events, and are omitted deliberately.
52
+ */
53
+ readonly broadcast: {
54
+ readonly authRevoked: "devframe:auth:revoked";
55
+ readonly clientStateUpdated: "devframe:rpc:client-state:updated";
56
+ readonly clientStatePatch: "devframe:rpc:client-state:patch";
57
+ readonly streamingChunk: "devframe:streaming:chunk";
58
+ readonly streamingEnd: "devframe:streaming:end";
59
+ readonly streamingUploadCancel: "devframe:streaming:upload-cancel";
60
+ };
61
+ /** `postMessage` channels the runtime posts across window boundaries. */
62
+ readonly postMessage: {
63
+ readonly remoteAssetsError: "devframe:remote-assets-error";
64
+ };
65
+ };
66
+ //#endregion
1
67
  //#region src/constants.d.ts
2
68
  declare const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
3
69
  /**
@@ -40,6 +106,12 @@ declare const DEVFRAME_MCP_ROUTE = "__mcp";
40
106
  declare const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
41
107
  declare const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
42
108
  declare const DEVFRAME_RPC_DUMP_DIRNAME = "__rpc-dump";
109
+ /**
110
+ * Shared-state key carrying the wire-service advertisements (package name →
111
+ * `DevframeServiceMeta`). Written by the node services host; mirrored to
112
+ * clients for feature-detection via `client.services`.
113
+ */
114
+ declare const DEVFRAME_SERVICES_STATE_KEY = "devframe:services";
43
115
  /**
44
116
  * URL fragment / query parameter name carrying the remote dock
45
117
  * connection descriptor (defined as `RemoteConnectionInfo` in
@@ -77,7 +149,7 @@ declare const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = "devframe_viewer_origin
77
149
  * (`@devframes/hub-ui` does, in its iframe view). Payload shape:
78
150
  * `RemoteAssetsErrorMessage` (`devframe/types`).
79
151
  */
80
- declare const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE = "devframe:remote-assets-error";
152
+ declare const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE: string;
81
153
  /**
82
154
  * Prefix that marks an RPC method as callable before a connection is
83
155
  * trusted. This is the *only* rule the pre-trust gate applies — there is no
@@ -94,4 +166,4 @@ declare const ANONYMOUS_RPC_PREFIX = "anonymous:";
94
166
  */
95
167
  declare function isAnonymousRpcMethod(name: string): boolean;
96
168
  //#endregion
97
- export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE, DEVFRAME_OTP_URL_PARAM, DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_SSE_ROUTE, DEVFRAME_SSE_SESSION_HEADER, DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
169
+ export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_EVENTS, DEVFRAME_MCP_ROUTE, DEVFRAME_OTP_URL_PARAM, DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_SERVICES_STATE_KEY, DEVFRAME_SSE_ROUTE, DEVFRAME_SSE_SESSION_HEADER, DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
@@ -1,3 +1,4 @@
1
+ import { t as DEVFRAME_EVENTS } from "./events-DKfSmoTj.mjs";
1
2
  //#region src/constants.ts
2
3
  const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
3
4
  /**
@@ -41,6 +42,12 @@ const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
41
42
  const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
42
43
  const DEVFRAME_RPC_DUMP_DIRNAME = "__rpc-dump";
43
44
  /**
45
+ * Shared-state key carrying the wire-service advertisements (package name →
46
+ * `DevframeServiceMeta`). Written by the node services host; mirrored to
47
+ * clients for feature-detection via `client.services`.
48
+ */
49
+ const DEVFRAME_SERVICES_STATE_KEY = "devframe:services";
50
+ /**
44
51
  * URL fragment / query parameter name carrying the remote dock
45
52
  * connection descriptor (defined as `RemoteConnectionInfo` in
46
53
  * `@vitejs/devtools-kit`) injected into remote-UI iframe dock URLs.
@@ -77,7 +84,7 @@ const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = "devframe_viewer_origin_token";
77
84
  * (`@devframes/hub-ui` does, in its iframe view). Payload shape:
78
85
  * `RemoteAssetsErrorMessage` (`devframe/types`).
79
86
  */
80
- const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE = "devframe:remote-assets-error";
87
+ const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE = DEVFRAME_EVENTS.postMessage.remoteAssetsError;
81
88
  /**
82
89
  * Prefix that marks an RPC method as callable before a connection is
83
90
  * trusted. This is the *only* rule the pre-trust gate applies — there is no
@@ -96,4 +103,4 @@ function isAnonymousRpcMethod(name) {
96
103
  return name.startsWith(ANONYMOUS_RPC_PREFIX);
97
104
  }
98
105
  //#endregion
99
- export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE, DEVFRAME_OTP_URL_PARAM, DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_SSE_ROUTE, DEVFRAME_SSE_SESSION_HEADER, DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
106
+ export { ANONYMOUS_RPC_PREFIX, DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_CONNECTION_KEY, DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_EVENTS, DEVFRAME_MCP_ROUTE, DEVFRAME_OTP_URL_PARAM, DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_SERVICES_STATE_KEY, DEVFRAME_SSE_ROUTE, DEVFRAME_SSE_SESSION_HEADER, DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM, DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY, isAnonymousRpcMethod };
@@ -1,6 +1,6 @@
1
1
  import { n as randomToken } from "./crypto-token-XCqTSMg9.mjs";
2
- import { t as createStorage } from "./storage-CYye7P7L.mjs";
3
- import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-DZbJ7Cl0.mjs";
2
+ import { t as createStorage } from "./storage-BNqSAOxA.mjs";
3
+ import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-3Q6aDKWk.mjs";
4
4
  import { join } from "pathe";
5
5
  //#region src/node/hub-internals/context.ts
6
6
  const internalContextMap = /* @__PURE__ */ new WeakMap();
@@ -1,4 +1,4 @@
1
- import { g as DevframeNodeContext, lt as SharedState } from "./devframe-DJMg0WLe.mjs";
1
+ import { g as DevframeNodeContext, gt as SharedState } from "./devframe-mbfgpQQC.mjs";
2
2
  //#region src/node/hub-internals/context.d.ts
3
3
  interface InternalAnonymousAuthStorage {
4
4
  trusted: Record<string, {
@@ -2,13 +2,18 @@ import { i as defineDiagnostics } from "./nostics-CzECRXpE.mjs";
2
2
  import { t as diagnostics } from "./diagnostics-CD8nlgll.mjs";
3
3
  import { RpcFunctionsCollectorBase } from "./rpc/index.mjs";
4
4
  import { defineRpcFunction } from "./index.mjs";
5
- import { t as diagnostics$1 } from "./diagnostics-DOQPnQmX.mjs";
6
- import { r as createEventEmitter, t as DevframeAgentHost } from "./host-agent-xLCSe65D.mjs";
7
- import { n as createDebug, t as resolveStaticAssetsSource } from "./remote-assets-nTTxY2MU.mjs";
8
- import { t as createStorage } from "./storage-CYye7P7L.mjs";
5
+ import { t as DEVFRAME_EVENTS } from "./events-DKfSmoTj.mjs";
6
+ import { DEVFRAME_SERVICES_STATE_KEY } from "./constants.mjs";
7
+ import { t as diagnostics$1 } from "./diagnostics-DI1HGj2I.mjs";
8
+ import { r as createEventEmitter, t as DevframeAgentHost } from "./host-agent-DVDLqjvj.mjs";
9
+ import { n as createDebug, t as resolveStaticAssetsSource } from "./remote-assets-CqxiyltC.mjs";
10
+ import { t as createStorage } from "./storage-BNqSAOxA.mjs";
11
+ import { createRequire } from "node:module";
9
12
  import { createSharedState } from "devframe/utils/shared-state";
13
+ import process from "node:process";
14
+ import { pathToFileURL } from "node:url";
15
+ import { isAbsolute, join } from "pathe";
10
16
  import { existsSync } from "node:fs";
11
- import { join } from "pathe";
12
17
  //#region src/node/host-diagnostics.ts
13
18
  var DevframeDiagnosticsHost = class {
14
19
  context;
@@ -25,7 +30,7 @@ var DevframeDiagnosticsHost = class {
25
30
  };
26
31
  //#endregion
27
32
  //#region src/node/rpc-shared-state.ts
28
- const debug$1 = createDebug("devframe:rpc:state:changed");
33
+ const debug$2 = createDebug("devframe:rpc:state:changed");
29
34
  const debugSubscribe = createDebug("devframe:rpc:state:subscribe");
30
35
  function createRpcSharedStateServerHost(rpc) {
31
36
  const sharedState = /* @__PURE__ */ new Map();
@@ -35,12 +40,12 @@ function createRpcSharedStateServerHost(rpc) {
35
40
  const offs = [];
36
41
  offs.push(state.on("updated", (fullState, patches, syncId) => {
37
42
  if (patches) {
38
- debug$1("patch", {
43
+ debug$2("patch", {
39
44
  key,
40
45
  syncId
41
46
  });
42
47
  rpc.broadcast({
43
- method: "devframe:rpc:client-state:patch",
48
+ method: DEVFRAME_EVENTS.broadcast.clientStatePatch,
44
49
  args: [
45
50
  key,
46
51
  patches,
@@ -49,12 +54,12 @@ function createRpcSharedStateServerHost(rpc) {
49
54
  filter: (client) => client.$meta.subscribedStates.has(key)
50
55
  });
51
56
  } else {
52
- debug$1("updated", {
57
+ debug$2("updated", {
53
58
  key,
54
59
  syncId
55
60
  });
56
61
  rpc.broadcast({
57
- method: "devframe:rpc:client-state:updated",
62
+ method: DEVFRAME_EVENTS.broadcast.clientStateUpdated,
58
63
  args: [
59
64
  key,
60
65
  fullState,
@@ -72,7 +77,7 @@ function createRpcSharedStateServerHost(rpc) {
72
77
  get: async (key, options) => {
73
78
  if (sharedState.has(key)) return sharedState.get(key);
74
79
  if (options?.initialValue === void 0 && options?.sharedState === void 0) throw diagnostics$1.DF0013({ key });
75
- debug$1("new-state", key);
80
+ debug$2("new-state", key);
76
81
  const state = options.sharedState ?? createSharedState({
77
82
  initialValue: options.initialValue,
78
83
  enablePatches: false
@@ -402,7 +407,7 @@ function toErrorPayload(reason) {
402
407
  }
403
408
  //#endregion
404
409
  //#region src/node/rpc-streaming.ts
405
- const debug = createDebug("devframe:rpc:streaming");
410
+ const debug$1 = createDebug("devframe:rpc:streaming");
406
411
  const STREAM_KEY_SEPARATOR = "";
407
412
  function streamKey(channel, id) {
408
413
  return `${channel}${STREAM_KEY_SEPARATOR}${id}`;
@@ -427,7 +432,7 @@ function createRpcStreamingServerHost(rpc) {
427
432
  }
428
433
  for (const off of record.unbinders) off();
429
434
  state.streams.delete(id);
430
- debug("freed", state.name, id);
435
+ debug$1("freed", state.name, id);
431
436
  }
432
437
  function maybeFreeStream(state, id) {
433
438
  const record = state.streams.get(id);
@@ -476,7 +481,7 @@ function createRpcStreamingServerHost(rpc) {
476
481
  cancelRetention(record);
477
482
  const afterSeq = opts?.afterSeq ?? 0;
478
483
  for (const buffered of record.sink.buffer) if (buffered.seq > afterSeq) rpc.broadcast({
479
- method: "devframe:streaming:chunk",
484
+ method: DEVFRAME_EVENTS.broadcast.streamingChunk,
480
485
  args: [
481
486
  channelName,
482
487
  id,
@@ -488,7 +493,7 @@ function createRpcStreamingServerHost(rpc) {
488
493
  filter: (client) => client.$meta === session.meta
489
494
  });
490
495
  if (record.sink.closed) rpc.broadcast({
491
- method: "devframe:streaming:end",
496
+ method: DEVFRAME_EVENTS.broadcast.streamingEnd,
492
497
  args: [
493
498
  channelName,
494
499
  id,
@@ -589,7 +594,7 @@ function createRpcStreamingServerHost(rpc) {
589
594
  state.streams.set(sink.id, record);
590
595
  record.unbinders.push(sink.events.on("chunk", (seq, chunk) => {
591
596
  rpc.broadcast({
592
- method: "devframe:streaming:chunk",
597
+ method: DEVFRAME_EVENTS.broadcast.streamingChunk,
593
598
  args: [
594
599
  name,
595
600
  sink.id,
@@ -603,7 +608,7 @@ function createRpcStreamingServerHost(rpc) {
603
608
  }));
604
609
  record.unbinders.push(sink.events.on("end", (error) => {
605
610
  rpc.broadcast({
606
- method: "devframe:streaming:end",
611
+ method: DEVFRAME_EVENTS.broadcast.streamingEnd,
607
612
  args: [
608
613
  name,
609
614
  sink.id,
@@ -636,7 +641,7 @@ function createRpcStreamingServerHost(rpc) {
636
641
  const targetMeta = inboundRecord?.uploaderMeta;
637
642
  if (!targetMeta) return;
638
643
  rpc.broadcast({
639
- method: "devframe:streaming:upload-cancel",
644
+ method: DEVFRAME_EVENTS.broadcast.streamingUploadCancel,
640
645
  args: [name, reader.id],
641
646
  event: true,
642
647
  optional: true,
@@ -646,7 +651,7 @@ function createRpcStreamingServerHost(rpc) {
646
651
  });
647
652
  inboundRecord = { reader };
648
653
  state.inbound.set(reader.id, inboundRecord);
649
- debug("opened-inbound", name, reader.id);
654
+ debug$1("opened-inbound", name, reader.id);
650
655
  return reader;
651
656
  }
652
657
  return {
@@ -758,15 +763,190 @@ var RpcFunctionsHostImpl = class extends RpcFunctionsCollectorBase {
758
763
  }
759
764
  };
760
765
  //#endregion
766
+ //#region src/node/services-install.ts
767
+ /**
768
+ * Turn a `resolveFrom` value (a file path, a file URL like `import.meta.url`,
769
+ * or a directory) into something `createRequire` accepts — a directory gets a
770
+ * synthetic filename appended so resolution starts inside it.
771
+ */
772
+ function toRequireBase(resolveFrom) {
773
+ if (resolveFrom.startsWith("file://")) return resolveFrom;
774
+ if ((resolveFrom.split(/[/\\]/).pop() ?? "").includes(".")) return resolveFrom;
775
+ return join(resolveFrom, "_devframe_resolve.js");
776
+ }
777
+ /**
778
+ * Normalize an `install()` `resolveFrom` into a resolution base. Paths and
779
+ * file URLs pass through (the common case: the declaring plugin's
780
+ * `importMetaUrl`, so a service it declares resolves against the plugin's own
781
+ * dependencies); a bare npm package name resolves to that package's location
782
+ * from `cwd`. An unresolvable package name reads as no base (the caller's
783
+ * workspace fallbacks apply).
784
+ */
785
+ function expandResolveFrom(resolveFrom, cwd) {
786
+ if (resolveFrom.startsWith("file://") || resolveFrom.startsWith(".") || isAbsolute(resolveFrom)) return resolveFrom;
787
+ const require = createRequire(join(cwd, "_devframe_resolve.js"));
788
+ try {
789
+ return require.resolve(`${resolveFrom}/package.json`);
790
+ } catch {}
791
+ try {
792
+ return require.resolve(resolveFrom);
793
+ } catch {}
794
+ }
795
+ /**
796
+ * Import a service package's module, trying each `resolveFrom` candidate in
797
+ * order (so a plugin-declared service resolves against the plugin's own
798
+ * dependency tree first, then the workspace fallback). Throws the last
799
+ * resolution error when no candidate succeeds.
800
+ */
801
+ async function importServicePackage(pkg, resolveFroms) {
802
+ const candidates = [...new Set(resolveFroms.filter((x) => typeof x === "string" && x.length > 0))];
803
+ let lastError = /* @__PURE__ */ new Error(`no resolution base available for "${pkg}"`);
804
+ for (const from of candidates) {
805
+ let resolved;
806
+ try {
807
+ resolved = createRequire(toRequireBase(from)).resolve(pkg);
808
+ } catch (error) {
809
+ lastError = error;
810
+ continue;
811
+ }
812
+ return await import(pathToFileURL(resolved).href);
813
+ }
814
+ throw lastError;
815
+ }
816
+ function parseVersion(input) {
817
+ const [core, ...prerelease] = input.trim().replace(/^v/, "").split("-");
818
+ if (!core) return void 0;
819
+ const parts = core.split(".").map((part) => Number.parseInt(part, 10));
820
+ if (parts.length === 0 || parts.some((part) => Number.isNaN(part) || part < 0)) return void 0;
821
+ while (parts.length < 3) parts.push(0);
822
+ return {
823
+ parts,
824
+ ...prerelease.length ? { prerelease: prerelease.join("-") } : {}
825
+ };
826
+ }
827
+ function compareVersions(a, b) {
828
+ for (let i = 0; i < 3; i++) {
829
+ const diff = (a.parts[i] ?? 0) - (b.parts[i] ?? 0);
830
+ if (diff !== 0) return diff;
831
+ }
832
+ if (a.prerelease && !b.prerelease) return -1;
833
+ if (!a.prerelease && b.prerelease) return 1;
834
+ if (a.prerelease && b.prerelease) return a.prerelease < b.prerelease ? -1 : a.prerelease > b.prerelease ? 1 : 0;
835
+ return 0;
836
+ }
837
+ function satisfiesComparator(version, comparator) {
838
+ const raw = comparator.trim();
839
+ if (!raw || raw === "*" || raw === "x") return true;
840
+ const operatorMatch = raw.match(/^([\^~]|>=|<=|[><=])?(.+)$/);
841
+ if (!operatorMatch) return false;
842
+ const operator = operatorMatch[1];
843
+ const rest = operatorMatch[2].trim();
844
+ const segments = rest.replace(/\.[x*]/gi, "").split(".").filter(Boolean);
845
+ const base = parseVersion(rest.replace(/[x*]/gi, "0"));
846
+ if (!base) return false;
847
+ switch (operator) {
848
+ case ">": return compareVersions(version, base) > 0;
849
+ case ">=": return compareVersions(version, base) >= 0;
850
+ case "<": return compareVersions(version, base) < 0;
851
+ case "<=": return compareVersions(version, base) <= 0;
852
+ case "^": {
853
+ if (compareVersions(version, base) < 0) return false;
854
+ const fixedIndex = base.parts.findIndex((part) => part !== 0);
855
+ const lockUpTo = fixedIndex === -1 ? base.parts.length - 1 : fixedIndex;
856
+ for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
857
+ return true;
858
+ }
859
+ case "~": {
860
+ if (compareVersions(version, base) < 0) return false;
861
+ const lockUpTo = segments.length >= 2 ? 1 : 0;
862
+ for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
863
+ return true;
864
+ }
865
+ default:
866
+ for (let i = 0; i < Math.max(segments.length, 3); i++) if (i < segments.length && (version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
867
+ return segments.length >= 3 ? compareVersions(version, base) === 0 : true;
868
+ }
869
+ }
870
+ /**
871
+ * Pragmatic semver range check for service version declarations — supports
872
+ * the common forms (`1.2.3`, `^1.2.3`, `~1.2`, `>=1 <3`, `1.x`, `*`, and
873
+ * `||`-joined alternatives) without pulling in a semver dependency. An
874
+ * unparseable version or range reads as **not satisfied**.
875
+ */
876
+ function satisfiesVersionRange(version, range) {
877
+ const parsed = parseVersion(version);
878
+ if (!parsed) return false;
879
+ const alternatives = range.split("||").map((alt) => alt.trim()).filter(Boolean);
880
+ if (alternatives.length === 0) return true;
881
+ return alternatives.some((alternative) => alternative.split(/\s+/).every((comparator) => satisfiesComparator(parsed, comparator)));
882
+ }
883
+ function isPlainObject(value) {
884
+ return typeof value === "object" && value !== null && !Array.isArray(value);
885
+ }
886
+ /** Deep-merge two values with the service option-set rules (see below). */
887
+ function deepMergeTwo(a, b) {
888
+ if (Array.isArray(a) && Array.isArray(b)) return [.../* @__PURE__ */ new Set([...a, ...b])];
889
+ if (isPlainObject(a) && isPlainObject(b)) {
890
+ const out = { ...a };
891
+ for (const key of Object.keys(b)) out[key] = key in a ? deepMergeTwo(a[key], b[key]) : b[key];
892
+ return out;
893
+ }
894
+ return b;
895
+ }
896
+ /**
897
+ * Default option-set merge when a service declares no `mergeOptions`:
898
+ * deep-merge in declaration order — objects recurse, arrays union-dedupe,
899
+ * scalars take the later value. Covers the built-in services (`roots` /
900
+ * `langs` union, `themes` per-key last-wins) without a custom hook.
901
+ */
902
+ function deepMergeOptionSets(sets) {
903
+ return sets.reduce((merged, set) => deepMergeTwo(merged, set));
904
+ }
905
+ //#endregion
761
906
  //#region src/node/host-services.ts
907
+ const debug = createDebug("devframe:services");
908
+ function isServiceDefinition(input) {
909
+ return typeof input.setup === "function";
910
+ }
911
+ function validateServiceInput(input) {
912
+ if (!input || typeof input.package !== "string" || input.package.length === 0) throw diagnostics$1.DF0070({
913
+ package: String(input?.package ?? input),
914
+ reason: "the input has no `package` name"
915
+ });
916
+ if (isServiceDefinition(input)) validateServiceDefinition(input);
917
+ }
918
+ function validateServiceDefinition(def) {
919
+ if (typeof def.version !== "string" || def.version.length === 0) throw diagnostics$1.DF0070({
920
+ package: def.package,
921
+ reason: "the definition has no `version`"
922
+ });
923
+ if (typeof def.scope !== "string" || def.scope.length === 0) throw diagnostics$1.DF0070({
924
+ package: def.package,
925
+ reason: "the definition has no RPC `scope` namespace"
926
+ });
927
+ }
762
928
  /**
763
929
  * Cross-plugin service registry (see `types/services.ts` for the contract).
764
930
  * Values are held per context instance; `whenAvailable` subscriptions make
765
931
  * the mechanism robust against setup ordering between provider and consumer.
932
+ *
933
+ * On top of the in-process `provide`/`get` tier, this host implements the
934
+ * **wire-service** lifecycle: `install()` queues definitions/descriptors,
935
+ * `ready()` fires the collect-then-setup barrier — importing descriptor
936
+ * packages, merging option sets per service, constructing each service once,
937
+ * providing its node API under the package name, and advertising it to
938
+ * clients through the `devframe:services` shared state.
766
939
  */
767
940
  var DevframeServicesHostImpl = class {
941
+ context;
768
942
  services = /* @__PURE__ */ new Map();
769
943
  listeners = /* @__PURE__ */ new Map();
944
+ pending = /* @__PURE__ */ new Map();
945
+ installed = /* @__PURE__ */ new Map();
946
+ readyPromise;
947
+ constructor(context) {
948
+ this.context = context;
949
+ }
770
950
  provide(id, service) {
771
951
  const key = id;
772
952
  if (this.services.has(key)) throw diagnostics$1.DF0037({ id: key });
@@ -799,20 +979,152 @@ var DevframeServicesHostImpl = class {
799
979
  keys() {
800
980
  return Array.from(this.services.keys());
801
981
  }
982
+ install(input, options) {
983
+ validateServiceInput(input);
984
+ const promise = new Promise((resolve, reject) => {
985
+ const entry = {
986
+ input,
987
+ resolveFrom: options?.resolveFrom,
988
+ resolve,
989
+ reject
990
+ };
991
+ if (this.readyPromise) this.flushPackage(input.package, [entry]).catch(() => {});
992
+ else {
993
+ let entries = this.pending.get(input.package);
994
+ if (!entries) {
995
+ entries = [];
996
+ this.pending.set(input.package, entries);
997
+ }
998
+ entries.push(entry);
999
+ }
1000
+ });
1001
+ promise.catch(() => {});
1002
+ return promise;
1003
+ }
1004
+ ready() {
1005
+ if (this.readyPromise) return this.readyPromise;
1006
+ this.readyPromise = this.flushAll();
1007
+ return this.readyPromise;
1008
+ }
1009
+ async flushAll() {
1010
+ if (this.context) await this.advertisementState();
1011
+ const groups = Array.from(this.pending.entries());
1012
+ this.pending.clear();
1013
+ for (const [pkg, entries] of groups) await this.flushPackage(pkg, entries);
1014
+ }
1015
+ async flushPackage(pkg, entries) {
1016
+ try {
1017
+ const api = await this.installPackage(pkg, entries);
1018
+ for (const entry of entries) entry.resolve(api);
1019
+ return api;
1020
+ } catch (error) {
1021
+ for (const entry of entries) entry.reject(error);
1022
+ throw error;
1023
+ }
1024
+ }
1025
+ async installPackage(pkg, entries) {
1026
+ if (this.installed.has(pkg)) {
1027
+ diagnostics$1.DF0066({ package: pkg });
1028
+ return this.installed.get(pkg);
1029
+ }
1030
+ let def = entries.filter((entry) => isServiceDefinition(entry.input))[0]?.input;
1031
+ if (!def) {
1032
+ const required = entries.map((entry) => entry.input).some((descriptor) => descriptor.required === true);
1033
+ const cwd = this.context?.cwd ?? process.cwd();
1034
+ const resolveFroms = [
1035
+ ...entries.map((entry) => entry.resolveFrom && expandResolveFrom(entry.resolveFrom, cwd)),
1036
+ this.context?.workspaceRoot,
1037
+ cwd
1038
+ ];
1039
+ let mod;
1040
+ try {
1041
+ mod = await importServicePackage(pkg, resolveFroms);
1042
+ } catch (error) {
1043
+ const reason = error instanceof Error ? error.message : String(error);
1044
+ if (required) throw diagnostics$1.DF0067({
1045
+ package: pkg,
1046
+ reason,
1047
+ cause: error
1048
+ });
1049
+ debug("optional service %s not importable, skipping: %s", pkg, reason);
1050
+ return;
1051
+ }
1052
+ const factory = mod.default;
1053
+ if (typeof factory !== "function") throw diagnostics$1.DF0070({
1054
+ package: pkg,
1055
+ reason: "its default export is not a factory function"
1056
+ });
1057
+ def = await factory();
1058
+ if (!def || typeof def.setup !== "function") throw diagnostics$1.DF0070({
1059
+ package: pkg,
1060
+ reason: "its factory did not return a definition with a `setup` function"
1061
+ });
1062
+ if (typeof def.package !== "string" || def.package.length === 0) def = {
1063
+ ...def,
1064
+ package: pkg
1065
+ };
1066
+ validateServiceDefinition(def);
1067
+ }
1068
+ for (const entry of entries) {
1069
+ const descriptor = entry.input;
1070
+ if (isServiceDefinition(entry.input) || typeof descriptor.version !== "string") continue;
1071
+ if (satisfiesVersionRange(def.version, descriptor.version)) continue;
1072
+ if (descriptor.required === true) throw diagnostics$1.DF0068({
1073
+ package: pkg,
1074
+ required: descriptor.version,
1075
+ installed: def.version
1076
+ });
1077
+ diagnostics$1.DF0069({
1078
+ package: pkg,
1079
+ required: descriptor.version,
1080
+ installed: def.version
1081
+ });
1082
+ }
1083
+ const sets = entries.map((entry) => entry.input.options).filter((options) => options !== void 0);
1084
+ const options = def.mergeOptions ? def.mergeOptions(sets) : sets.length > 0 ? deepMergeOptionSets(sets) : void 0;
1085
+ if (!this.context) throw diagnostics$1.DF0070({
1086
+ package: pkg,
1087
+ reason: "this services host has no node context to install into"
1088
+ });
1089
+ debug("installing service %s@%s (scope %s)", def.package, def.version, def.scope);
1090
+ const scoped = this.context.scope(def.scope);
1091
+ const api = await def.setup(scoped, options === void 0 ? {} : { options });
1092
+ this.installed.set(def.package, api);
1093
+ this.provide(def.package, api);
1094
+ await this.advertise(def);
1095
+ return api;
1096
+ }
1097
+ advertisementState() {
1098
+ return this.context.rpc.sharedState.get(DEVFRAME_SERVICES_STATE_KEY, { initialValue: {} });
1099
+ }
1100
+ async advertise(def) {
1101
+ const state = await this.advertisementState();
1102
+ const { package: pkg, version, scope, meta } = def;
1103
+ state.mutate((value) => {
1104
+ value[pkg] = {
1105
+ package: pkg,
1106
+ version,
1107
+ scope,
1108
+ ...meta ? { meta } : {}
1109
+ };
1110
+ });
1111
+ }
802
1112
  };
803
1113
  //#endregion
804
1114
  //#region src/node/host-views.ts
805
1115
  var DevframeViewHost = class {
806
1116
  context;
1117
+ importMetaUrl;
807
1118
  /**
808
1119
  * @internal
809
1120
  */
810
1121
  buildStaticDirs = [];
811
- constructor(context) {
1122
+ constructor(context, importMetaUrl) {
812
1123
  this.context = context;
1124
+ this.importMetaUrl = importMetaUrl;
813
1125
  }
814
- hostStatic(baseUrl, source) {
815
- const resolved = resolveStaticAssetsSource(source, this.context.host.getStorageDir("project"));
1126
+ hostStatic(baseUrl, source, defaultResolveFrom = this.importMetaUrl) {
1127
+ const resolved = resolveStaticAssetsSource(source, this.context.host.getStorageDir("project"), defaultResolveFrom);
816
1128
  if (typeof resolved === "string" && !existsSync(resolved)) throw diagnostics$1.DF0008({ distDir: resolved });
817
1129
  this.buildStaticDirs.push({
818
1130
  baseUrl,
@@ -900,7 +1212,8 @@ function createNodeSettingsStore(context, namespace, scope) {
900
1212
  let statePromise;
901
1213
  function store() {
902
1214
  if (!statePromise) {
903
- const filepath = join(context.host.getStorageDir(STORAGE_SCOPE[scope]), "settings", `${namespace}.json`);
1215
+ const dir = context.host.getStorageDir(STORAGE_SCOPE[scope]);
1216
+ const filepath = join(dir, "settings", `${namespace}.json`);
904
1217
  statePromise = context.rpc.sharedState.get(stateKey, { sharedState: createStorage({
905
1218
  filepath,
906
1219
  initialValue: {}
@@ -1003,7 +1316,7 @@ function createScopedNodeContext(context, namespace) {
1003
1316
  * `commands` when mounted into Vite DevTools.
1004
1317
  */
1005
1318
  async function createHostContext(options) {
1006
- const { cwd, workspaceRoot = cwd, mode, host, builtinRpcDeclarations = [] } = options;
1319
+ const { cwd, workspaceRoot = cwd, mode, host, importMetaUrl, builtinRpcDeclarations = [] } = options;
1007
1320
  const context = {
1008
1321
  cwd,
1009
1322
  workspaceRoot,
@@ -1018,12 +1331,12 @@ async function createHostContext(options) {
1018
1331
  scope: void 0
1019
1332
  };
1020
1333
  const rpcHost = new RpcFunctionsHostImpl(context);
1021
- const viewsHost = new DevframeViewHost(context);
1334
+ const viewsHost = new DevframeViewHost(context, importMetaUrl);
1022
1335
  const diagnosticsHost = new DevframeDiagnosticsHost(context, [diagnostics$1, diagnostics]);
1023
1336
  context.rpc = rpcHost;
1024
1337
  context.views = viewsHost;
1025
1338
  context.diagnostics = diagnosticsHost;
1026
- context.services = new DevframeServicesHostImpl();
1339
+ context.services = new DevframeServicesHostImpl(context);
1027
1340
  context.agent = new DevframeAgentHost(context);
1028
1341
  const scopedCache = /* @__PURE__ */ new Map();
1029
1342
  context.scope = ((namespace) => {