@ethisyscore/extension-runtime 1.59.0 → 1.61.0

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.
@@ -3,8 +3,8 @@ import { C as ComponentRegistry } from '../registry-DpCx_LxF.cjs';
3
3
  export { P as PrimitiveProps } from '../registry-DpCx_LxF.cjs';
4
4
  import { SduiNode } from '@ethisyscore/protocol';
5
5
  export { PrimitiveName, SduiNode } from '@ethisyscore/protocol';
6
- import { M as McpHttpClient } from '../transport-DJE6ljme.cjs';
7
- export { D as DEFAULT_MAX_CONCURRENT_MCP_REQUESTS, I as InputEventPayload, a as McpHttpRequest, b as McpUploadRequest, W as WORKER_TRANSPORT_PROTOCOL, c as WorkerCtor, d as WorkerHandshakePayload, e as WorkerLike, f as WorkerRemoteDomTransport, g as WorkerRemoteDomTransportOptions } from '../transport-DJE6ljme.cjs';
6
+ import { M as McpHttpClient, f as VersionSkew } from '../transport-BbSPAS_N.cjs';
7
+ export { C as CAPABILITY_REJECTION_REASON_HEADER, D as DEFAULT_MAX_CONCURRENT_MCP_REQUESTS, E as EXTENSION_ACTIVE_VERSION_HEADER, a as EXTENSION_VERSION_HEADER, I as InputEventPayload, b as McpHttpRequest, c as McpHttpResponse, d as McpResponseMetadata, e as McpUploadRequest, V as VERSION_MISMATCH_REASON, g as VersionSkewSource, W as WORKER_TRANSPORT_PROTOCOL, h as WorkerCtor, i as WorkerHandshakePayload, j as WorkerLike, k as WorkerRemoteDomTransport, l as WorkerRemoteDomTransportOptions, m as detectVersionSkew } from '../transport-BbSPAS_N.cjs';
8
8
  export { RemoteReceiver, RemoteReceiverComment, RemoteReceiverElement, RemoteReceiverNode, RemoteReceiverParent, RemoteReceiverRoot, RemoteReceiverText } from '@remote-dom/core/receivers';
9
9
  export { RemoteComponentRendererMap, RemoteComponentRendererProps, RemoteRootRenderer, RemoteRootRendererProps, createRemoteComponentRenderer } from '@remote-dom/react/host';
10
10
  import '../bridge-envelopes-DA6vxbyb.cjs';
@@ -273,6 +273,18 @@ interface IframeBridgeTransportOptions {
273
273
  capabilityToken: () => Promise<string>;
274
274
  /** Host-side MCP HTTP client; carries the capability token on the outbound call. */
275
275
  mcpClient: McpHttpClient;
276
+ /**
277
+ * The extension version this surface mounted at. Enables proactive skew detection — a successful
278
+ * response stamped with a different version means the install moved while the surface is still
279
+ * working, so it can remount before anything fails.
280
+ */
281
+ mountedExtensionVersion?: string;
282
+ /**
283
+ * Called at most once, when this surface is found to be running against a version that is no
284
+ * longer installed. The host remounts the surface; the transport does not retry, because the
285
+ * pending call belongs to a page about to be replaced.
286
+ */
287
+ onVersionSkew?: (skew: VersionSkew) => void;
276
288
  /** Bounded in-flight MCP requests before back-pressure. Default 8. */
277
289
  maxConcurrentMcpRequests?: number;
278
290
  /** Max inbound port messages per rolling second before drop. Default 64. */
@@ -308,6 +320,9 @@ declare class IframeBridgeTransport {
308
320
  private readonly maxConcurrentMcpRequests;
309
321
  private readonly maxMessagesPerSecond;
310
322
  private readonly onSecurityEvent;
323
+ private readonly mountedExtensionVersion;
324
+ private readonly onVersionSkew;
325
+ private versionSkewReported;
311
326
  private readonly abortController;
312
327
  private hostPort;
313
328
  private framePort;
@@ -343,10 +358,21 @@ declare class IframeBridgeTransport {
343
358
  private isRateLimited;
344
359
  private dispatchMcp;
345
360
  private handleInvokeTool;
361
+ /**
362
+ * Raises the skew callback the first time this surface is seen to be running against a version
363
+ * that is no longer installed. Latched: a page usually has several MCP calls in flight and after
364
+ * an upgrade every one of them reports the same skew, so firing per response would ask the host
365
+ * to remount the same surface repeatedly and each remount would tear down the last.
366
+ *
367
+ * The callback is host code and may throw. That must neither escape into the MCP handler's catch
368
+ * (replying `ok: false` for a call that succeeded) nor consume the latch (leaving the surface with
369
+ * no response and no further chance of being told to remount).
370
+ */
371
+ private reportVersionSkew;
346
372
  private handleGetResource;
347
373
  private replyError;
348
374
  /** Best-effort reply over the host port; tolerates post-dispose races. */
349
375
  private safePostMessage;
350
376
  }
351
377
 
352
- export { CONTRACT_B_PRIMITIVES, ComponentRegistry, DEFAULT_OFFSCREEN_COALESCE_MS, type EvaluatorContext, IFRAME_BRIDGE_PROTOCOL, type IframeBridgeSecurityEvent, IframeBridgeTransport, type IframeBridgeTransportOptions, type IframeHandshakePayload, type InputEventCoalescerOptions, McpHttpClient, type OffscreenCanvasTransferOptions, type OffscreenCanvasTransferResult, type OffscreenTransferMessage, SemanticComponentRegistry, type SemanticPrimitiveName, createInputEventCoalescer, createOffscreenCanvasTransfer, evaluate, interpret };
378
+ export { CONTRACT_B_PRIMITIVES, ComponentRegistry, DEFAULT_OFFSCREEN_COALESCE_MS, type EvaluatorContext, IFRAME_BRIDGE_PROTOCOL, type IframeBridgeSecurityEvent, IframeBridgeTransport, type IframeBridgeTransportOptions, type IframeHandshakePayload, type InputEventCoalescerOptions, McpHttpClient, type OffscreenCanvasTransferOptions, type OffscreenCanvasTransferResult, type OffscreenTransferMessage, SemanticComponentRegistry, type SemanticPrimitiveName, VersionSkew, createInputEventCoalescer, createOffscreenCanvasTransfer, evaluate, interpret };
@@ -3,8 +3,8 @@ import { C as ComponentRegistry } from '../registry-DpCx_LxF.js';
3
3
  export { P as PrimitiveProps } from '../registry-DpCx_LxF.js';
4
4
  import { SduiNode } from '@ethisyscore/protocol';
5
5
  export { PrimitiveName, SduiNode } from '@ethisyscore/protocol';
6
- import { M as McpHttpClient } from '../transport-CHa8ESUx.js';
7
- export { D as DEFAULT_MAX_CONCURRENT_MCP_REQUESTS, I as InputEventPayload, a as McpHttpRequest, b as McpUploadRequest, W as WORKER_TRANSPORT_PROTOCOL, c as WorkerCtor, d as WorkerHandshakePayload, e as WorkerLike, f as WorkerRemoteDomTransport, g as WorkerRemoteDomTransportOptions } from '../transport-CHa8ESUx.js';
6
+ import { M as McpHttpClient, f as VersionSkew } from '../transport-BPwasdNz.js';
7
+ export { C as CAPABILITY_REJECTION_REASON_HEADER, D as DEFAULT_MAX_CONCURRENT_MCP_REQUESTS, E as EXTENSION_ACTIVE_VERSION_HEADER, a as EXTENSION_VERSION_HEADER, I as InputEventPayload, b as McpHttpRequest, c as McpHttpResponse, d as McpResponseMetadata, e as McpUploadRequest, V as VERSION_MISMATCH_REASON, g as VersionSkewSource, W as WORKER_TRANSPORT_PROTOCOL, h as WorkerCtor, i as WorkerHandshakePayload, j as WorkerLike, k as WorkerRemoteDomTransport, l as WorkerRemoteDomTransportOptions, m as detectVersionSkew } from '../transport-BPwasdNz.js';
8
8
  export { RemoteReceiver, RemoteReceiverComment, RemoteReceiverElement, RemoteReceiverNode, RemoteReceiverParent, RemoteReceiverRoot, RemoteReceiverText } from '@remote-dom/core/receivers';
9
9
  export { RemoteComponentRendererMap, RemoteComponentRendererProps, RemoteRootRenderer, RemoteRootRendererProps, createRemoteComponentRenderer } from '@remote-dom/react/host';
10
10
  import '../bridge-envelopes-DA6vxbyb.js';
@@ -273,6 +273,18 @@ interface IframeBridgeTransportOptions {
273
273
  capabilityToken: () => Promise<string>;
274
274
  /** Host-side MCP HTTP client; carries the capability token on the outbound call. */
275
275
  mcpClient: McpHttpClient;
276
+ /**
277
+ * The extension version this surface mounted at. Enables proactive skew detection — a successful
278
+ * response stamped with a different version means the install moved while the surface is still
279
+ * working, so it can remount before anything fails.
280
+ */
281
+ mountedExtensionVersion?: string;
282
+ /**
283
+ * Called at most once, when this surface is found to be running against a version that is no
284
+ * longer installed. The host remounts the surface; the transport does not retry, because the
285
+ * pending call belongs to a page about to be replaced.
286
+ */
287
+ onVersionSkew?: (skew: VersionSkew) => void;
276
288
  /** Bounded in-flight MCP requests before back-pressure. Default 8. */
277
289
  maxConcurrentMcpRequests?: number;
278
290
  /** Max inbound port messages per rolling second before drop. Default 64. */
@@ -308,6 +320,9 @@ declare class IframeBridgeTransport {
308
320
  private readonly maxConcurrentMcpRequests;
309
321
  private readonly maxMessagesPerSecond;
310
322
  private readonly onSecurityEvent;
323
+ private readonly mountedExtensionVersion;
324
+ private readonly onVersionSkew;
325
+ private versionSkewReported;
311
326
  private readonly abortController;
312
327
  private hostPort;
313
328
  private framePort;
@@ -343,10 +358,21 @@ declare class IframeBridgeTransport {
343
358
  private isRateLimited;
344
359
  private dispatchMcp;
345
360
  private handleInvokeTool;
361
+ /**
362
+ * Raises the skew callback the first time this surface is seen to be running against a version
363
+ * that is no longer installed. Latched: a page usually has several MCP calls in flight and after
364
+ * an upgrade every one of them reports the same skew, so firing per response would ask the host
365
+ * to remount the same surface repeatedly and each remount would tear down the last.
366
+ *
367
+ * The callback is host code and may throw. That must neither escape into the MCP handler's catch
368
+ * (replying `ok: false` for a call that succeeded) nor consume the latch (leaving the surface with
369
+ * no response and no further chance of being told to remount).
370
+ */
371
+ private reportVersionSkew;
346
372
  private handleGetResource;
347
373
  private replyError;
348
374
  /** Best-effort reply over the host port; tolerates post-dispose races. */
349
375
  private safePostMessage;
350
376
  }
351
377
 
352
- export { CONTRACT_B_PRIMITIVES, ComponentRegistry, DEFAULT_OFFSCREEN_COALESCE_MS, type EvaluatorContext, IFRAME_BRIDGE_PROTOCOL, type IframeBridgeSecurityEvent, IframeBridgeTransport, type IframeBridgeTransportOptions, type IframeHandshakePayload, type InputEventCoalescerOptions, McpHttpClient, type OffscreenCanvasTransferOptions, type OffscreenCanvasTransferResult, type OffscreenTransferMessage, SemanticComponentRegistry, type SemanticPrimitiveName, createInputEventCoalescer, createOffscreenCanvasTransfer, evaluate, interpret };
378
+ export { CONTRACT_B_PRIMITIVES, ComponentRegistry, DEFAULT_OFFSCREEN_COALESCE_MS, type EvaluatorContext, IFRAME_BRIDGE_PROTOCOL, type IframeBridgeSecurityEvent, IframeBridgeTransport, type IframeBridgeTransportOptions, type IframeHandshakePayload, type InputEventCoalescerOptions, McpHttpClient, type OffscreenCanvasTransferOptions, type OffscreenCanvasTransferResult, type OffscreenTransferMessage, SemanticComponentRegistry, type SemanticPrimitiveName, VersionSkew, createInputEventCoalescer, createOffscreenCanvasTransfer, evaluate, interpret };
@@ -285,6 +285,50 @@ function createInputEventCoalescer(sink, options = {}) {
285
285
  };
286
286
  }
287
287
 
288
+ // src/host/version-skew.ts
289
+ var EXTENSION_VERSION_HEADER = "x-extension-version";
290
+ var EXTENSION_ACTIVE_VERSION_HEADER = "x-extension-active-version";
291
+ var CAPABILITY_REJECTION_REASON_HEADER = "x-capability-token-rejection-reason";
292
+ var VERSION_MISMATCH_REASON = "extensionversionmismatch";
293
+ function readHeader(headers, name) {
294
+ if (!headers) {
295
+ return void 0;
296
+ }
297
+ const direct = headers[name];
298
+ if (direct !== void 0) {
299
+ return direct;
300
+ }
301
+ const lowered = name.toLowerCase();
302
+ for (const key of Object.keys(headers)) {
303
+ if (key.toLowerCase() === lowered) {
304
+ return headers[key];
305
+ }
306
+ }
307
+ return void 0;
308
+ }
309
+ function trimmedOrUndefined(value) {
310
+ const trimmed = value?.trim();
311
+ return trimmed !== void 0 && trimmed.length > 0 ? trimmed : void 0;
312
+ }
313
+ function detectVersionSkew(response, mountedExtensionVersion) {
314
+ const reason = trimmedOrUndefined(readHeader(response.headers, CAPABILITY_REJECTION_REASON_HEADER));
315
+ if (reason !== void 0 && reason.toLowerCase() === VERSION_MISMATCH_REASON) {
316
+ return {
317
+ activeVersion: trimmedOrUndefined(readHeader(response.headers, EXTENSION_ACTIVE_VERSION_HEADER)),
318
+ detectedFrom: "rejection"
319
+ };
320
+ }
321
+ if (!response.ok) {
322
+ return null;
323
+ }
324
+ const mounted = trimmedOrUndefined(mountedExtensionVersion);
325
+ const served = trimmedOrUndefined(readHeader(response.headers, EXTENSION_VERSION_HEADER));
326
+ if (mounted === void 0 || served === void 0 || served === mounted) {
327
+ return null;
328
+ }
329
+ return { activeVersion: served, detectedFrom: "response-stamp" };
330
+ }
331
+
288
332
  // src/host/worker/bridge-envelopes.ts
289
333
  var BRIDGE_PUSH_THEME = "ethisys:bridge:theme";
290
334
  var BRIDGE_PUSH_LOCALE = "ethisys:bridge:locale";
@@ -304,6 +348,9 @@ var WorkerRemoteDomTransport = class {
304
348
  workerPort;
305
349
  mcpClient;
306
350
  capabilityTokenProvider;
351
+ mountedExtensionVersion;
352
+ onVersionSkew;
353
+ versionSkewReported = false;
307
354
  coalesceMs;
308
355
  maxConcurrentMcpRequests;
309
356
  abortController;
@@ -326,6 +373,8 @@ var WorkerRemoteDomTransport = class {
326
373
  }
327
374
  this.mcpClient = options.mcpClient;
328
375
  this.capabilityTokenProvider = options.capabilityToken;
376
+ this.mountedExtensionVersion = options.mountedExtensionVersion;
377
+ this.onVersionSkew = options.onVersionSkew;
329
378
  this.coalesceMs = options.coalesceMs ?? 16;
330
379
  this.maxConcurrentMcpRequests = Math.max(
331
380
  1,
@@ -609,6 +658,34 @@ var WorkerRemoteDomTransport = class {
609
658
  requestSignal(id) {
610
659
  return this.inFlightAborts.get(id)?.signal ?? this.abortController.signal;
611
660
  }
661
+ /**
662
+ * Raises the skew callback the first time this surface is seen to be running against a version
663
+ * that is no longer installed.
664
+ *
665
+ * Latched deliberately. A page typically has several MCP calls in flight, and after an upgrade
666
+ * every one of them reports the same skew — firing per response would ask the host to remount
667
+ * the same surface repeatedly, and the second remount would tear down the first. One signal per
668
+ * transport is all a remount needs, and the transport is discarded along with the surface.
669
+ *
670
+ * The callback is host code, so it is assumed to be able to throw. Two consequences are guarded
671
+ * here: the throw must not escape into the MCP handler's catch (which would reply `ok: false`
672
+ * for a call that actually succeeded), and it must not consume the latch (which would leave the
673
+ * surface with no response AND no further chance of being told to remount).
674
+ */
675
+ reportVersionSkew(response) {
676
+ if (this.versionSkewReported || this.onVersionSkew === void 0) {
677
+ return;
678
+ }
679
+ const skew = detectVersionSkew(response, this.mountedExtensionVersion);
680
+ if (skew === null) {
681
+ return;
682
+ }
683
+ try {
684
+ this.onVersionSkew(skew);
685
+ this.versionSkewReported = true;
686
+ } catch {
687
+ }
688
+ }
612
689
  async handleInvokeTool(message) {
613
690
  let token;
614
691
  try {
@@ -628,6 +705,7 @@ var WorkerRemoteDomTransport = class {
628
705
  capabilityToken: token,
629
706
  signal: this.requestSignal(message.id)
630
707
  });
708
+ this.reportVersionSkew(result);
631
709
  this.safePostMessage({
632
710
  id: message.id,
633
711
  type: "ethisys:mcp:invokeTool:result",
@@ -657,6 +735,7 @@ var WorkerRemoteDomTransport = class {
657
735
  capabilityToken: token,
658
736
  signal: this.requestSignal(message.id)
659
737
  });
738
+ this.reportVersionSkew(result);
660
739
  this.safePostMessage({
661
740
  id: message.id,
662
741
  type: "ethisys:mcp:getResource:result",
@@ -687,6 +766,7 @@ var WorkerRemoteDomTransport = class {
687
766
  capabilityToken: token,
688
767
  signal: this.requestSignal(message.id)
689
768
  });
769
+ this.reportVersionSkew(result);
690
770
  this.safePostMessage({
691
771
  id: message.id,
692
772
  type: "ethisys:mcp:uploadDocument:result",
@@ -729,6 +809,9 @@ var IframeBridgeTransport = class {
729
809
  maxConcurrentMcpRequests;
730
810
  maxMessagesPerSecond;
731
811
  onSecurityEvent;
812
+ mountedExtensionVersion;
813
+ onVersionSkew;
814
+ versionSkewReported = false;
732
815
  abortController = new AbortController();
733
816
  hostPort;
734
817
  framePort;
@@ -743,6 +826,8 @@ var IframeBridgeTransport = class {
743
826
  this.targetOrigin = options.targetOrigin;
744
827
  this.capabilityTokenProvider = options.capabilityToken;
745
828
  this.mcpClient = options.mcpClient;
829
+ this.mountedExtensionVersion = options.mountedExtensionVersion;
830
+ this.onVersionSkew = options.onVersionSkew;
746
831
  this.maxConcurrentMcpRequests = Math.max(1, options.maxConcurrentMcpRequests ?? DEFAULT_MAX_CONCURRENT_MCP_REQUESTS);
747
832
  this.maxMessagesPerSecond = Math.max(1, options.maxMessagesPerSecond ?? 64);
748
833
  this.onSecurityEvent = options.onSecurityEvent;
@@ -895,6 +980,7 @@ var IframeBridgeTransport = class {
895
980
  capabilityToken: token,
896
981
  signal: this.abortController.signal
897
982
  });
983
+ this.reportVersionSkew(result);
898
984
  this.safePostMessage({
899
985
  id: message.id,
900
986
  type: "ethisys:mcp:invokeTool:result",
@@ -906,6 +992,30 @@ var IframeBridgeTransport = class {
906
992
  this.replyError(message.id, "ethisys:mcp:invokeTool:result", err);
907
993
  }
908
994
  }
995
+ /**
996
+ * Raises the skew callback the first time this surface is seen to be running against a version
997
+ * that is no longer installed. Latched: a page usually has several MCP calls in flight and after
998
+ * an upgrade every one of them reports the same skew, so firing per response would ask the host
999
+ * to remount the same surface repeatedly and each remount would tear down the last.
1000
+ *
1001
+ * The callback is host code and may throw. That must neither escape into the MCP handler's catch
1002
+ * (replying `ok: false` for a call that succeeded) nor consume the latch (leaving the surface with
1003
+ * no response and no further chance of being told to remount).
1004
+ */
1005
+ reportVersionSkew(response) {
1006
+ if (this.versionSkewReported || this.onVersionSkew === void 0) {
1007
+ return;
1008
+ }
1009
+ const skew = detectVersionSkew(response, this.mountedExtensionVersion);
1010
+ if (skew === null) {
1011
+ return;
1012
+ }
1013
+ try {
1014
+ this.onVersionSkew(skew);
1015
+ this.versionSkewReported = true;
1016
+ } catch {
1017
+ }
1018
+ }
909
1019
  async handleGetResource(message) {
910
1020
  let token;
911
1021
  try {
@@ -924,6 +1034,7 @@ var IframeBridgeTransport = class {
924
1034
  capabilityToken: token,
925
1035
  signal: this.abortController.signal
926
1036
  });
1037
+ this.reportVersionSkew(result);
927
1038
  this.safePostMessage({
928
1039
  id: message.id,
929
1040
  type: "ethisys:mcp:getResource:result",
@@ -951,6 +1062,6 @@ var IframeBridgeTransport = class {
951
1062
  }
952
1063
  };
953
1064
 
954
- export { CONTRACT_B_PRIMITIVES, DEFAULT_MAX_CONCURRENT_MCP_REQUESTS, DEFAULT_OFFSCREEN_COALESCE_MS, IFRAME_BRIDGE_PROTOCOL, IframeBridgeTransport, SemanticComponentRegistry, WORKER_TRANSPORT_PROTOCOL, WorkerRemoteDomTransport, createInputEventCoalescer, createOffscreenCanvasTransfer, evaluate, interpret };
1065
+ export { CAPABILITY_REJECTION_REASON_HEADER, CONTRACT_B_PRIMITIVES, DEFAULT_MAX_CONCURRENT_MCP_REQUESTS, DEFAULT_OFFSCREEN_COALESCE_MS, EXTENSION_ACTIVE_VERSION_HEADER, EXTENSION_VERSION_HEADER, IFRAME_BRIDGE_PROTOCOL, IframeBridgeTransport, SemanticComponentRegistry, VERSION_MISMATCH_REASON, WORKER_TRANSPORT_PROTOCOL, WorkerRemoteDomTransport, createInputEventCoalescer, createOffscreenCanvasTransfer, detectVersionSkew, evaluate, interpret };
955
1066
  //# sourceMappingURL=index.js.map
956
1067
  //# sourceMappingURL=index.js.map