@ethisyscore/extension-runtime 1.33.0 → 1.35.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.
Files changed (33) hide show
  1. package/dist/{bridge-client-UK3qcGoi.d.ts → bridge-client-DD99netz.d.ts} +2 -33
  2. package/dist/{bridge-client-CIThO7jZ.d.cts → bridge-client-DzRcKIJT.d.cts} +2 -33
  3. package/dist/bridge-envelopes-DA6vxbyb.d.cts +151 -0
  4. package/dist/bridge-envelopes-DA6vxbyb.d.ts +151 -0
  5. package/dist/host/index.cjs +76 -2
  6. package/dist/host/index.cjs.map +1 -1
  7. package/dist/host/index.d.cts +3 -3
  8. package/dist/host/index.d.ts +3 -3
  9. package/dist/host/index.js +76 -2
  10. package/dist/host/index.js.map +1 -1
  11. package/dist/mock-host/cli.cjs +87 -2
  12. package/dist/mock-host/cli.cjs.map +1 -1
  13. package/dist/mock-host/cli.d.cts +2 -2
  14. package/dist/mock-host/cli.d.ts +2 -2
  15. package/dist/mock-host/cli.js +87 -2
  16. package/dist/mock-host/cli.js.map +1 -1
  17. package/dist/mock-host/index.cjs +41 -1
  18. package/dist/mock-host/index.cjs.map +1 -1
  19. package/dist/mock-host/index.d.cts +19 -7
  20. package/dist/mock-host/index.d.ts +19 -7
  21. package/dist/mock-host/index.js +41 -1
  22. package/dist/mock-host/index.js.map +1 -1
  23. package/dist/plugin/index.cjs +15 -3
  24. package/dist/plugin/index.cjs.map +1 -1
  25. package/dist/plugin/index.d.cts +13 -5
  26. package/dist/plugin/index.d.ts +13 -5
  27. package/dist/plugin/index.js +15 -4
  28. package/dist/plugin/index.js.map +1 -1
  29. package/dist/{transport-BN9Mzn_m.d.cts → transport-CHa8ESUx.d.ts} +39 -4
  30. package/dist/{transport-Jfd9KXAh.d.ts → transport-DJE6ljme.d.cts} +39 -4
  31. package/package.json +1 -1
  32. package/dist/bridge-envelopes-BRKGSiSC.d.cts +0 -63
  33. package/dist/bridge-envelopes-BRKGSiSC.d.ts +0 -63
@@ -1,35 +1,4 @@
1
- import { d as BridgePushThemeEnvelope, c as BridgePushLocaleEnvelope, b as BridgePushDensityEnvelope, a as BridgePushA11yEnvelope, B as BridgeNavPushEnvelope, e as BridgeSessionTokenPushEnvelope } from './bridge-envelopes-BRKGSiSC.js';
2
-
3
- /**
4
- * Transport contract used by the plugin-side React hooks
5
- * (`useMcpResource`, `useMcpTool`) to talk to the host.
6
- *
7
- * The hooks are deliberately transport-agnostic: the concrete implementation
8
- * (postMessage bridge, in-process direct call, fetch-based, …) is injected
9
- * via {@link ExtensionRuntimeProvider} or the per-hook `transport` option.
10
- * This keeps the React surface stable across Contract A (host-rendered) and
11
- * Contract B (worker remote-runtime) execution modes.
12
- *
13
- * Implementations must honour the supplied {@link AbortSignal} for
14
- * cancellation — hooks rely on it to tear down in-flight calls on unmount
15
- * or argument changes.
16
- */
17
- interface McpTransport {
18
- /**
19
- * Fetch a resource by URI. Implementations should reject with an `Error`
20
- * when the host returns a failure, and should observe `signal` to abort
21
- * any in-flight work.
22
- */
23
- getResource<T>(uri: string, signal?: AbortSignal): Promise<{
24
- uri: string;
25
- data: T;
26
- }>;
27
- /**
28
- * Invoke a tool by name. The request object is opaque to the transport.
29
- * Implementations should observe `signal` to abort in-flight work.
30
- */
31
- invokeTool<TReq, TRes>(name: string, args: TReq, signal?: AbortSignal): Promise<TRes>;
32
- }
1
+ import { d as BridgePushThemeEnvelope, c as BridgePushLocaleEnvelope, b as BridgePushDensityEnvelope, a as BridgePushA11yEnvelope, B as BridgeNavPushEnvelope, e as BridgeSessionTokenPushEnvelope } from './bridge-envelopes-DA6vxbyb.js';
33
2
 
34
3
  /**
35
4
  * Plugin-side bridge client for the host ↔ plugin platform contract
@@ -99,4 +68,4 @@ interface PortBridgeClient {
99
68
  */
100
69
  declare function createPortBridgeClient(port: BridgePortShim): PortBridgeClient;
101
70
 
102
- export { type A11yPayload as A, type BridgePortShim as B, type DensityPayload as D, type LocalePayload as L, type McpTransport as M, type NavPayload as N, type PortBridgeClient as P, type SessionTokenPayload as S, type ThemePayload as T, createPortBridgeClient as c };
71
+ export { type A11yPayload as A, type BridgePortShim as B, type DensityPayload as D, type LocalePayload as L, type NavPayload as N, type PortBridgeClient as P, type SessionTokenPayload as S, type ThemePayload as T, createPortBridgeClient as c };
@@ -1,35 +1,4 @@
1
- import { d as BridgePushThemeEnvelope, c as BridgePushLocaleEnvelope, b as BridgePushDensityEnvelope, a as BridgePushA11yEnvelope, B as BridgeNavPushEnvelope, e as BridgeSessionTokenPushEnvelope } from './bridge-envelopes-BRKGSiSC.cjs';
2
-
3
- /**
4
- * Transport contract used by the plugin-side React hooks
5
- * (`useMcpResource`, `useMcpTool`) to talk to the host.
6
- *
7
- * The hooks are deliberately transport-agnostic: the concrete implementation
8
- * (postMessage bridge, in-process direct call, fetch-based, …) is injected
9
- * via {@link ExtensionRuntimeProvider} or the per-hook `transport` option.
10
- * This keeps the React surface stable across Contract A (host-rendered) and
11
- * Contract B (worker remote-runtime) execution modes.
12
- *
13
- * Implementations must honour the supplied {@link AbortSignal} for
14
- * cancellation — hooks rely on it to tear down in-flight calls on unmount
15
- * or argument changes.
16
- */
17
- interface McpTransport {
18
- /**
19
- * Fetch a resource by URI. Implementations should reject with an `Error`
20
- * when the host returns a failure, and should observe `signal` to abort
21
- * any in-flight work.
22
- */
23
- getResource<T>(uri: string, signal?: AbortSignal): Promise<{
24
- uri: string;
25
- data: T;
26
- }>;
27
- /**
28
- * Invoke a tool by name. The request object is opaque to the transport.
29
- * Implementations should observe `signal` to abort in-flight work.
30
- */
31
- invokeTool<TReq, TRes>(name: string, args: TReq, signal?: AbortSignal): Promise<TRes>;
32
- }
1
+ import { d as BridgePushThemeEnvelope, c as BridgePushLocaleEnvelope, b as BridgePushDensityEnvelope, a as BridgePushA11yEnvelope, B as BridgeNavPushEnvelope, e as BridgeSessionTokenPushEnvelope } from './bridge-envelopes-DA6vxbyb.cjs';
33
2
 
34
3
  /**
35
4
  * Plugin-side bridge client for the host ↔ plugin platform contract
@@ -99,4 +68,4 @@ interface PortBridgeClient {
99
68
  */
100
69
  declare function createPortBridgeClient(port: BridgePortShim): PortBridgeClient;
101
70
 
102
- export { type A11yPayload as A, type BridgePortShim as B, type DensityPayload as D, type LocalePayload as L, type McpTransport as M, type NavPayload as N, type PortBridgeClient as P, type SessionTokenPayload as S, type ThemePayload as T, createPortBridgeClient as c };
71
+ export { type A11yPayload as A, type BridgePortShim as B, type DensityPayload as D, type LocalePayload as L, type NavPayload as N, type PortBridgeClient as P, type SessionTokenPayload as S, type ThemePayload as T, createPortBridgeClient as c };
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Transport contract used by the plugin-side React hooks
3
+ * (`useMcpResource`, `useMcpTool`) to talk to the host.
4
+ *
5
+ * The hooks are deliberately transport-agnostic: the concrete implementation
6
+ * (postMessage bridge, in-process direct call, fetch-based, …) is injected
7
+ * via {@link ExtensionRuntimeProvider} or the per-hook `transport` option.
8
+ * This keeps the React surface stable across Contract A (host-rendered) and
9
+ * Contract B (worker remote-runtime) execution modes.
10
+ *
11
+ * Implementations must honour the supplied {@link AbortSignal} for
12
+ * cancellation — hooks rely on it to tear down in-flight calls on unmount
13
+ * or argument changes.
14
+ */
15
+ interface McpTransport {
16
+ /**
17
+ * Fetch a resource by URI. Implementations should reject with an `Error`
18
+ * when the host returns a failure, and should observe `signal` to abort
19
+ * any in-flight work.
20
+ */
21
+ getResource<T>(uri: string, signal?: AbortSignal): Promise<{
22
+ uri: string;
23
+ data: T;
24
+ }>;
25
+ /**
26
+ * Invoke a tool by name. The request object is opaque to the transport.
27
+ * Implementations should observe `signal` to abort in-flight work.
28
+ */
29
+ invokeTool<TReq, TRes>(name: string, args: TReq, signal?: AbortSignal): Promise<TRes>;
30
+ /**
31
+ * Upload a binary document to CORE storage via the host shell and resolve
32
+ * the resulting {@link UploadDocumentResult} (a `StoredDocumentId` plus the
33
+ * server-validated file metadata).
34
+ *
35
+ * **Optional capability.** A transport that does not support binary upload
36
+ * simply omits this member; {@link "../../plugin-ui" useMcpUpload} throws a
37
+ * clear runtime error when invoked against such a transport. Keeping it
38
+ * optional makes adding the upload seam a **non-breaking, additive** change
39
+ * for existing custom transport implementations (which only provided
40
+ * `getResource` + `invokeTool`).
41
+ *
42
+ * The bytes are handed off as an {@link ArrayBuffer} — the transport is
43
+ * expected to move it as a **transferable** (zero-copy) across the
44
+ * worker↔host port. The plugin (worker) never sees the capability token or
45
+ * the kernel URL: the host shell mints the token, streams the octet-stream
46
+ * to the kernel document-upload route, and returns only the result.
47
+ *
48
+ * When provided, implementations MUST observe `signal` so an unmount /
49
+ * cancel aborts the in-flight upload rather than streaming bytes to
50
+ * completion.
51
+ */
52
+ uploadDocument?(meta: UploadDocumentMeta, buffer: ArrayBuffer, signal?: AbortSignal): Promise<UploadDocumentResult>;
53
+ }
54
+ /**
55
+ * Metadata accompanying an {@link McpTransport.uploadDocument} call. All fields
56
+ * are advisory hints to the host/kernel: the kernel derives the organisation
57
+ * from the authenticated capability token (never from this payload), binds the
58
+ * effective module key to the caller's plugin identity via a kernel allowlist,
59
+ * and re-validates / sanitises `fileName` + `contentType` server-side.
60
+ */
61
+ interface UploadDocumentMeta {
62
+ /** Requested storage module key (e.g. "hr"). Validated against the caller's kernel allowlist. */
63
+ moduleKey: string;
64
+ /** Owning entity name (e.g. "Candidates", "CompanyDocuments"). */
65
+ entityName: string;
66
+ /** Optional owning entity id. */
67
+ entityId?: string;
68
+ /** Logical sub-folder / category (e.g. "cvs"). */
69
+ category: string;
70
+ /** Client-supplied file name (server sanitises + caps length). */
71
+ fileName: string;
72
+ /** Client-supplied MIME type (server re-parses; falls back to application/octet-stream). */
73
+ contentType: string;
74
+ /** Byte length of the buffer (advisory; the kernel size cap is authoritative). */
75
+ sizeBytes: number;
76
+ }
77
+ /**
78
+ * Result of a successful {@link McpTransport.uploadDocument} call — the id of
79
+ * the registered `StoredDocument` plus the metadata the kernel actually stored.
80
+ * The plugin only ever holds the id; it never receives the bytes back.
81
+ */
82
+ interface UploadDocumentResult {
83
+ storedDocumentId: string;
84
+ fileName: string;
85
+ contentType: string;
86
+ sizeBytes: number;
87
+ }
88
+
89
+ /**
90
+ * Wire-shape constants and type guards for host→plugin bridge push messages
91
+ * (WI 4858 sub-plan 2). These types ride the existing MessagePort channel
92
+ * alongside the `ethisys:mcp:*` and `ethisys:remotedom` envelopes — the
93
+ * `type` discriminant keeps them fully separate at the dispatch site.
94
+ *
95
+ * The host posts bridge pushes through `WorkerRemoteDomTransport.hostPort`;
96
+ * the plugin side reads them in `createPortBridgeClient` (plugin-ui package).
97
+ * In-realm (Tier T) plugins use `InMemoryBridgeTransport` which calls the
98
+ * subscriber callbacks directly — no serialisation needed.
99
+ */
100
+ declare const BRIDGE_PUSH_THEME: "ethisys:bridge:theme";
101
+ declare const BRIDGE_PUSH_LOCALE: "ethisys:bridge:locale";
102
+ declare const BRIDGE_PUSH_DENSITY: "ethisys:bridge:density";
103
+ declare const BRIDGE_PUSH_A11Y: "ethisys:bridge:a11y";
104
+ declare const BRIDGE_NAV_PUSH: "ethisys:bridge:nav";
105
+ declare const BRIDGE_SESSION_TOKEN_PUSH: "ethisys:bridge:token";
106
+ /** Theme push: host → plugin whenever the host theme changes. */
107
+ interface BridgePushThemeEnvelope {
108
+ readonly type: typeof BRIDGE_PUSH_THEME;
109
+ /** "light" | "dark" | "high-contrast" */
110
+ readonly mode: string;
111
+ /** Flat design-token map (CSS-variable-name → value). May be empty. */
112
+ readonly tokens: Readonly<Record<string, string>>;
113
+ }
114
+ /** Locale push: host → plugin whenever the active locale changes. */
115
+ interface BridgePushLocaleEnvelope {
116
+ readonly type: typeof BRIDGE_PUSH_LOCALE;
117
+ /** BCP 47 tag, e.g. "en-GB". */
118
+ readonly locale: string;
119
+ /** "ltr" | "rtl" */
120
+ readonly dir: "ltr" | "rtl";
121
+ }
122
+ /** Density push: host → plugin when the UI density preference changes. */
123
+ interface BridgePushDensityEnvelope {
124
+ readonly type: typeof BRIDGE_PUSH_DENSITY;
125
+ /** "comfortable" | "compact" */
126
+ readonly density: string;
127
+ }
128
+ /** A11y push: host → plugin when accessibility prefs change. */
129
+ interface BridgePushA11yEnvelope {
130
+ readonly type: typeof BRIDGE_PUSH_A11Y;
131
+ readonly reducedMotion: boolean;
132
+ readonly highContrast: boolean;
133
+ }
134
+ /** Nav push: host → plugin when the SPA location changes. */
135
+ interface BridgeNavPushEnvelope {
136
+ readonly type: typeof BRIDGE_NAV_PUSH;
137
+ /** Current SPA path (pathname + search). */
138
+ readonly path: string;
139
+ /** `window.history.length` at the time of push. */
140
+ readonly historyLength: number;
141
+ }
142
+ /** Host → plugin: push a short-lived frontend-session token. */
143
+ interface BridgeSessionTokenPushEnvelope {
144
+ readonly type: typeof BRIDGE_SESSION_TOKEN_PUSH;
145
+ /** Opaque JWT string — audience-restricted to the plugin's own backend. */
146
+ readonly token: string;
147
+ /** Absolute epoch-ms at which the token expires. Refresh fires 30 s before. */
148
+ readonly expiresAtMs: number;
149
+ }
150
+
151
+ export type { BridgeNavPushEnvelope as B, McpTransport as M, UploadDocumentMeta as U, BridgePushA11yEnvelope as a, BridgePushDensityEnvelope as b, BridgePushLocaleEnvelope as c, BridgePushThemeEnvelope as d, BridgeSessionTokenPushEnvelope as e, UploadDocumentResult as f };
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Transport contract used by the plugin-side React hooks
3
+ * (`useMcpResource`, `useMcpTool`) to talk to the host.
4
+ *
5
+ * The hooks are deliberately transport-agnostic: the concrete implementation
6
+ * (postMessage bridge, in-process direct call, fetch-based, …) is injected
7
+ * via {@link ExtensionRuntimeProvider} or the per-hook `transport` option.
8
+ * This keeps the React surface stable across Contract A (host-rendered) and
9
+ * Contract B (worker remote-runtime) execution modes.
10
+ *
11
+ * Implementations must honour the supplied {@link AbortSignal} for
12
+ * cancellation — hooks rely on it to tear down in-flight calls on unmount
13
+ * or argument changes.
14
+ */
15
+ interface McpTransport {
16
+ /**
17
+ * Fetch a resource by URI. Implementations should reject with an `Error`
18
+ * when the host returns a failure, and should observe `signal` to abort
19
+ * any in-flight work.
20
+ */
21
+ getResource<T>(uri: string, signal?: AbortSignal): Promise<{
22
+ uri: string;
23
+ data: T;
24
+ }>;
25
+ /**
26
+ * Invoke a tool by name. The request object is opaque to the transport.
27
+ * Implementations should observe `signal` to abort in-flight work.
28
+ */
29
+ invokeTool<TReq, TRes>(name: string, args: TReq, signal?: AbortSignal): Promise<TRes>;
30
+ /**
31
+ * Upload a binary document to CORE storage via the host shell and resolve
32
+ * the resulting {@link UploadDocumentResult} (a `StoredDocumentId` plus the
33
+ * server-validated file metadata).
34
+ *
35
+ * **Optional capability.** A transport that does not support binary upload
36
+ * simply omits this member; {@link "../../plugin-ui" useMcpUpload} throws a
37
+ * clear runtime error when invoked against such a transport. Keeping it
38
+ * optional makes adding the upload seam a **non-breaking, additive** change
39
+ * for existing custom transport implementations (which only provided
40
+ * `getResource` + `invokeTool`).
41
+ *
42
+ * The bytes are handed off as an {@link ArrayBuffer} — the transport is
43
+ * expected to move it as a **transferable** (zero-copy) across the
44
+ * worker↔host port. The plugin (worker) never sees the capability token or
45
+ * the kernel URL: the host shell mints the token, streams the octet-stream
46
+ * to the kernel document-upload route, and returns only the result.
47
+ *
48
+ * When provided, implementations MUST observe `signal` so an unmount /
49
+ * cancel aborts the in-flight upload rather than streaming bytes to
50
+ * completion.
51
+ */
52
+ uploadDocument?(meta: UploadDocumentMeta, buffer: ArrayBuffer, signal?: AbortSignal): Promise<UploadDocumentResult>;
53
+ }
54
+ /**
55
+ * Metadata accompanying an {@link McpTransport.uploadDocument} call. All fields
56
+ * are advisory hints to the host/kernel: the kernel derives the organisation
57
+ * from the authenticated capability token (never from this payload), binds the
58
+ * effective module key to the caller's plugin identity via a kernel allowlist,
59
+ * and re-validates / sanitises `fileName` + `contentType` server-side.
60
+ */
61
+ interface UploadDocumentMeta {
62
+ /** Requested storage module key (e.g. "hr"). Validated against the caller's kernel allowlist. */
63
+ moduleKey: string;
64
+ /** Owning entity name (e.g. "Candidates", "CompanyDocuments"). */
65
+ entityName: string;
66
+ /** Optional owning entity id. */
67
+ entityId?: string;
68
+ /** Logical sub-folder / category (e.g. "cvs"). */
69
+ category: string;
70
+ /** Client-supplied file name (server sanitises + caps length). */
71
+ fileName: string;
72
+ /** Client-supplied MIME type (server re-parses; falls back to application/octet-stream). */
73
+ contentType: string;
74
+ /** Byte length of the buffer (advisory; the kernel size cap is authoritative). */
75
+ sizeBytes: number;
76
+ }
77
+ /**
78
+ * Result of a successful {@link McpTransport.uploadDocument} call — the id of
79
+ * the registered `StoredDocument` plus the metadata the kernel actually stored.
80
+ * The plugin only ever holds the id; it never receives the bytes back.
81
+ */
82
+ interface UploadDocumentResult {
83
+ storedDocumentId: string;
84
+ fileName: string;
85
+ contentType: string;
86
+ sizeBytes: number;
87
+ }
88
+
89
+ /**
90
+ * Wire-shape constants and type guards for host→plugin bridge push messages
91
+ * (WI 4858 sub-plan 2). These types ride the existing MessagePort channel
92
+ * alongside the `ethisys:mcp:*` and `ethisys:remotedom` envelopes — the
93
+ * `type` discriminant keeps them fully separate at the dispatch site.
94
+ *
95
+ * The host posts bridge pushes through `WorkerRemoteDomTransport.hostPort`;
96
+ * the plugin side reads them in `createPortBridgeClient` (plugin-ui package).
97
+ * In-realm (Tier T) plugins use `InMemoryBridgeTransport` which calls the
98
+ * subscriber callbacks directly — no serialisation needed.
99
+ */
100
+ declare const BRIDGE_PUSH_THEME: "ethisys:bridge:theme";
101
+ declare const BRIDGE_PUSH_LOCALE: "ethisys:bridge:locale";
102
+ declare const BRIDGE_PUSH_DENSITY: "ethisys:bridge:density";
103
+ declare const BRIDGE_PUSH_A11Y: "ethisys:bridge:a11y";
104
+ declare const BRIDGE_NAV_PUSH: "ethisys:bridge:nav";
105
+ declare const BRIDGE_SESSION_TOKEN_PUSH: "ethisys:bridge:token";
106
+ /** Theme push: host → plugin whenever the host theme changes. */
107
+ interface BridgePushThemeEnvelope {
108
+ readonly type: typeof BRIDGE_PUSH_THEME;
109
+ /** "light" | "dark" | "high-contrast" */
110
+ readonly mode: string;
111
+ /** Flat design-token map (CSS-variable-name → value). May be empty. */
112
+ readonly tokens: Readonly<Record<string, string>>;
113
+ }
114
+ /** Locale push: host → plugin whenever the active locale changes. */
115
+ interface BridgePushLocaleEnvelope {
116
+ readonly type: typeof BRIDGE_PUSH_LOCALE;
117
+ /** BCP 47 tag, e.g. "en-GB". */
118
+ readonly locale: string;
119
+ /** "ltr" | "rtl" */
120
+ readonly dir: "ltr" | "rtl";
121
+ }
122
+ /** Density push: host → plugin when the UI density preference changes. */
123
+ interface BridgePushDensityEnvelope {
124
+ readonly type: typeof BRIDGE_PUSH_DENSITY;
125
+ /** "comfortable" | "compact" */
126
+ readonly density: string;
127
+ }
128
+ /** A11y push: host → plugin when accessibility prefs change. */
129
+ interface BridgePushA11yEnvelope {
130
+ readonly type: typeof BRIDGE_PUSH_A11Y;
131
+ readonly reducedMotion: boolean;
132
+ readonly highContrast: boolean;
133
+ }
134
+ /** Nav push: host → plugin when the SPA location changes. */
135
+ interface BridgeNavPushEnvelope {
136
+ readonly type: typeof BRIDGE_NAV_PUSH;
137
+ /** Current SPA path (pathname + search). */
138
+ readonly path: string;
139
+ /** `window.history.length` at the time of push. */
140
+ readonly historyLength: number;
141
+ }
142
+ /** Host → plugin: push a short-lived frontend-session token. */
143
+ interface BridgeSessionTokenPushEnvelope {
144
+ readonly type: typeof BRIDGE_SESSION_TOKEN_PUSH;
145
+ /** Opaque JWT string — audience-restricted to the plugin's own backend. */
146
+ readonly token: string;
147
+ /** Absolute epoch-ms at which the token expires. Refresh fires 30 s before. */
148
+ readonly expiresAtMs: number;
149
+ }
150
+
151
+ export type { BridgeNavPushEnvelope as B, McpTransport as M, UploadDocumentMeta as U, BridgePushA11yEnvelope as a, BridgePushDensityEnvelope as b, BridgePushLocaleEnvelope as c, BridgePushThemeEnvelope as d, BridgeSessionTokenPushEnvelope as e, UploadDocumentResult as f };
@@ -309,6 +309,13 @@ var WorkerRemoteDomTransport = class {
309
309
  coalesceMs;
310
310
  maxConcurrentMcpRequests;
311
311
  abortController;
312
+ // Per-request AbortControllers keyed by request id. An inbound
313
+ // `ethisys:mcp:abort` envelope (the worker posts one when a hook's
314
+ // AbortSignal fires on unmount / arg change) aborts just that request;
315
+ // dispose() fans out to every registered controller. Without this a
316
+ // cancelled call — most costly for uploadDocument — keeps streaming to the
317
+ // kernel even though the plugin promise already rejected.
318
+ inFlightAborts = /* @__PURE__ */ new Map();
312
319
  inFlightMcpRequests = 0;
313
320
  remoteDomConsumer;
314
321
  bridgeMessageConsumer;
@@ -496,6 +503,10 @@ var WorkerRemoteDomTransport = class {
496
503
  this.disposed = true;
497
504
  try {
498
505
  this.abortController.abort();
506
+ for (const controller of this.inFlightAborts.values()) {
507
+ controller.abort();
508
+ }
509
+ this.inFlightAborts.clear();
499
510
  } catch {
500
511
  }
501
512
  try {
@@ -534,6 +545,12 @@ var WorkerRemoteDomTransport = class {
534
545
  case "ethisys:mcp:getResource":
535
546
  this.dispatchMcp(message, "ethisys:mcp:getResource:result", (m) => this.handleGetResource(m));
536
547
  return;
548
+ case "ethisys:mcp:uploadDocument":
549
+ this.dispatchMcp(message, "ethisys:mcp:uploadDocument:result", (m) => this.handleUploadDocument(m));
550
+ return;
551
+ case "ethisys:mcp:abort":
552
+ this.inFlightAborts.get(message.id ?? "")?.abort();
553
+ return;
537
554
  case "ethisys:remotedom":
538
555
  this.remoteDomConsumer?.(message.payload);
539
556
  return;
@@ -563,10 +580,37 @@ var WorkerRemoteDomTransport = class {
563
580
  return;
564
581
  }
565
582
  this.inFlightMcpRequests++;
583
+ this.registerRequestAbort(message.id);
566
584
  handler(message).finally(() => {
585
+ this.releaseRequestAbort(message.id);
567
586
  this.inFlightMcpRequests = Math.max(0, this.inFlightMcpRequests - 1);
568
587
  });
569
588
  }
589
+ /**
590
+ * Create a per-request AbortController and register it under the request id
591
+ * so an inbound `ethisys:mcp:abort` can cancel just this call. If the
592
+ * transport is already disposed the controller starts aborted so the
593
+ * handler's `mcpClient.fetch` short-circuits immediately.
594
+ */
595
+ registerRequestAbort(id) {
596
+ const controller = new AbortController();
597
+ if (this.abortController.signal.aborted) {
598
+ controller.abort();
599
+ }
600
+ this.inFlightAborts.set(id, controller);
601
+ }
602
+ /** Drop a settled request's AbortController. */
603
+ releaseRequestAbort(id) {
604
+ this.inFlightAborts.delete(id);
605
+ }
606
+ /**
607
+ * The signal to hand to `mcpClient.fetch` for a given request: the
608
+ * per-request controller when registered (client cancel path), falling back
609
+ * to the dispose-level controller.
610
+ */
611
+ requestSignal(id) {
612
+ return this.inFlightAborts.get(id)?.signal ?? this.abortController.signal;
613
+ }
570
614
  async handleInvokeTool(message) {
571
615
  let token;
572
616
  try {
@@ -584,7 +628,7 @@ var WorkerRemoteDomTransport = class {
584
628
  name: message.name,
585
629
  args: message.args,
586
630
  capabilityToken: token,
587
- signal: this.abortController.signal
631
+ signal: this.requestSignal(message.id)
588
632
  });
589
633
  this.safePostMessage({
590
634
  id: message.id,
@@ -613,7 +657,7 @@ var WorkerRemoteDomTransport = class {
613
657
  kind: "getResource",
614
658
  uri: message.uri,
615
659
  capabilityToken: token,
616
- signal: this.abortController.signal
660
+ signal: this.requestSignal(message.id)
617
661
  });
618
662
  this.safePostMessage({
619
663
  id: message.id,
@@ -626,6 +670,36 @@ var WorkerRemoteDomTransport = class {
626
670
  this.replyError(message.id, "ethisys:mcp:getResource:result", err);
627
671
  }
628
672
  }
673
+ async handleUploadDocument(message) {
674
+ let token;
675
+ try {
676
+ token = await this.capabilityTokenProvider();
677
+ } catch (err) {
678
+ this.replyError(message.id, "ethisys:mcp:uploadDocument:result", err);
679
+ return;
680
+ }
681
+ if (this.disposed) {
682
+ return;
683
+ }
684
+ try {
685
+ const result = await this.mcpClient.fetch({
686
+ kind: "uploadDocument",
687
+ meta: message.meta,
688
+ buffer: message.buffer,
689
+ capabilityToken: token,
690
+ signal: this.requestSignal(message.id)
691
+ });
692
+ this.safePostMessage({
693
+ id: message.id,
694
+ type: "ethisys:mcp:uploadDocument:result",
695
+ ok: result.ok,
696
+ data: result.data,
697
+ error: result.error
698
+ });
699
+ } catch (err) {
700
+ this.replyError(message.id, "ethisys:mcp:uploadDocument:result", err);
701
+ }
702
+ }
629
703
  replyError(id, type, err) {
630
704
  const message = err instanceof Error ? err.message : "MCP request failed";
631
705
  this.safePostMessage({ id, type, ok: false, error: message });