@ethisyscore/plugin-ui 1.34.0 → 1.37.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.
@@ -4,6 +4,7 @@ import * as _tanstack_react_query from '@tanstack/react-query';
4
4
  import { QueryClient, UseQueryResult, UseQueryOptions, QueryKey } from '@tanstack/react-query';
5
5
  import { PlatformReactPageProps } from '@ethisyscore/components-react';
6
6
  import * as _tanstack_query_core from '@tanstack/query-core';
7
+ import { McpTransport, UploadDocumentResult } from '@ethisyscore/extension-runtime/plugin';
7
8
 
8
9
  /** A view component resolved from the template manifest. Props are open. */
9
10
  type ViewComponent = ComponentType<Record<string, unknown>>;
@@ -800,6 +801,57 @@ declare function createReactRouterShim(options?: ReactRouterShimOptions): {
800
801
  */
801
802
  declare function usePluginRealtime(typeCode: string, handler: (payload: unknown) => void): void;
802
803
 
804
+ /**
805
+ * Advisory client-side upload ceiling (UX only). The kernel's streaming route
806
+ * applies the authoritative size cap mid-stream; this default lets the hook
807
+ * fail fast with a friendly message before reading a large file into memory.
808
+ * Roughly matches the host's default request-body ceiling.
809
+ */
810
+ declare const DEFAULT_MAX_UPLOAD_BYTES: number;
811
+ /** Where the uploaded document belongs. `moduleKey` is validated against the caller's kernel allowlist. */
812
+ interface UseMcpUploadTarget {
813
+ /** Storage module key (e.g. "hr"). Server rejects a key outside the plugin's allowlist. */
814
+ moduleKey: string;
815
+ /** Owning entity name (e.g. "Candidates", "CompanyDocuments"). */
816
+ entityName: string;
817
+ /** Optional owning entity id. */
818
+ entityId?: string;
819
+ /** Logical sub-folder / category (e.g. "cvs"). */
820
+ category: string;
821
+ }
822
+ interface UseMcpUploadOptions {
823
+ /** Override the transport resolved from the runtime provider (tests / advanced hosts). */
824
+ transport?: McpTransport;
825
+ /** Advisory client-side max byte size. Defaults to {@link DEFAULT_MAX_UPLOAD_BYTES}. */
826
+ maxSizeBytes?: number;
827
+ }
828
+ interface UseMcpUploadResult {
829
+ /**
830
+ * Read `file` into an ArrayBuffer and upload it to core storage via the host,
831
+ * resolving the resulting `StoredDocumentId` + server-validated metadata. The
832
+ * plugin then records the id with its own JSON command.
833
+ */
834
+ upload: (file: File | Blob, target: UseMcpUploadTarget) => Promise<UploadDocumentResult>;
835
+ /**
836
+ * Coarse progress in [0, 1]. NOTE: the FE→host leg is not streaming (the file
837
+ * is transferred whole), so this is phase-based, not byte-accurate: 0 idle,
838
+ * ~0.1 once the bytes are read + handed to the host, 1 on completion.
839
+ */
840
+ progress: number;
841
+ loading: boolean;
842
+ error: Error | undefined;
843
+ }
844
+ /**
845
+ * Upload a binary file from a platform-react plugin surface to CORE storage.
846
+ *
847
+ * The bytes never touch the plugin backend and the plugin never sees the
848
+ * capability token or kernel URL: the host shell mints the token and streams
849
+ * the octet-stream to the kernel document-upload route, returning only the
850
+ * `StoredDocumentId`. Filename/content-type are advisory — the kernel
851
+ * sanitises them and binds the effective module key + organisation itself.
852
+ */
853
+ declare function useMcpUpload(opts?: UseMcpUploadOptions): UseMcpUploadResult;
854
+
803
855
  declare const OVERLAY_HOST_CONTRACT_VERSION = "1.0.0";
804
856
  /** Canonical inset-lease identity — the SAME shape the web host layout store uses (Codex R2 M1:
805
857
  * one lease shape across SDK/web/store, no adapter). The store's handle extends this with `source`. */
@@ -969,4 +1021,42 @@ interface ManagedLifecycle {
969
1021
  */
970
1022
  declare function useManagedLifecycle(): ManagedLifecycle;
971
1023
 
972
- export { type AuthenticatedQueryResult, BaseMcpService, type DefinePlatformReactPluginOverlayOptions, type DefinePlatformReactPluginPageOptions, EHX_PLUGIN_ROOT_CLASS, type InsetLease, type ManagedLifecycle, MissingViewFallback, OVERLAY_HOST_CONTRACT_VERSION, type OverlayHostApi, type OverlayHostCapabilities, OverlayHostContext, type PlatformReactOverlayProps, type PluginOverlayDefinerConfig, type PluginPageDefinerConfig, PluginStyleScope, type ReactRouterShimOptions, TemplateContext, type TemplateManifest, type ToolInvoker, type ViewComponent, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, injectPluginStyles, isComponent, notViaMcp, useAuthenticatedQueries, useAuthenticatedQuery, useManagedLifecycle, useOverlayHost, usePluginRealtime, useToolInvoker, useToolInvokerMap, useView };
1024
+ /** The surface's mount context, provided by definePlatformReactPluginPage. */
1025
+ interface SurfaceBaseValue {
1026
+ /** Host-provided mount path `/extensions/<slug>/<pageId>` (authoritative). */
1027
+ basePath?: string;
1028
+ /** The page id (from PlatformReactPageProps), used for the derived fallback. */
1029
+ pageId?: string;
1030
+ }
1031
+ declare const SurfaceBaseContext: react.Context<SurfaceBaseValue | undefined>;
1032
+ /** Match the host's slug normalisation (useExtensionSurfaceShellPage `normaliseSlug`). */
1033
+ declare function normaliseSlug(value: string): string;
1034
+ /**
1035
+ * Resolve the surface base. Precedence: host-provided basePath → derived from
1036
+ * normaliseSlug(extensionGroupCode) + pageId → null (caller decides how to fail).
1037
+ * Pure — callers read context + identity and pass the pieces in.
1038
+ */
1039
+ declare function resolveSurfaceBase(input: {
1040
+ basePath?: string;
1041
+ pageId?: string;
1042
+ extensionGroupCode?: string | null;
1043
+ }): {
1044
+ base: string;
1045
+ groupRoot: string;
1046
+ } | null;
1047
+ /** Join base + sub (leading slash stripped) + optional ?query/#hash suffix. */
1048
+ declare function buildSurfaceUrl(base: string, sub?: string, suffix?: string): string;
1049
+ /**
1050
+ * Hook: build a URL relative to the CURRENT surface. Precedence per
1051
+ * resolveSurfaceBase; throws in dev when unresolved (never returns app-root).
1052
+ */
1053
+ declare function useSurfaceUrl(): (sub?: string, suffix?: string) => string;
1054
+ /** Pure: build a URL for ANOTHER surface (cross-surface, e.g. an overlay opening a page). */
1055
+ declare function surfacePathFor(opts: {
1056
+ slug: string;
1057
+ pageId: string;
1058
+ sub?: string;
1059
+ suffix?: string;
1060
+ }): string;
1061
+
1062
+ export { type AuthenticatedQueryResult, BaseMcpService, DEFAULT_MAX_UPLOAD_BYTES, type DefinePlatformReactPluginOverlayOptions, type DefinePlatformReactPluginPageOptions, EHX_PLUGIN_ROOT_CLASS, type InsetLease, type ManagedLifecycle, MissingViewFallback, OVERLAY_HOST_CONTRACT_VERSION, type OverlayHostApi, type OverlayHostCapabilities, OverlayHostContext, type PlatformReactOverlayProps, type PluginOverlayDefinerConfig, type PluginPageDefinerConfig, PluginStyleScope, type ReactRouterShimOptions, SurfaceBaseContext, type SurfaceBaseValue, TemplateContext, type TemplateManifest, type ToolInvoker, type UseMcpUploadOptions, type UseMcpUploadResult, type UseMcpUploadTarget, type ViewComponent, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, injectPluginStyles, isComponent, normaliseSlug, notViaMcp, resolveSurfaceBase, surfacePathFor, useAuthenticatedQueries, useAuthenticatedQuery, useManagedLifecycle, useMcpUpload, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useView };
@@ -4,6 +4,7 @@ import * as _tanstack_react_query from '@tanstack/react-query';
4
4
  import { QueryClient, UseQueryResult, UseQueryOptions, QueryKey } from '@tanstack/react-query';
5
5
  import { PlatformReactPageProps } from '@ethisyscore/components-react';
6
6
  import * as _tanstack_query_core from '@tanstack/query-core';
7
+ import { McpTransport, UploadDocumentResult } from '@ethisyscore/extension-runtime/plugin';
7
8
 
8
9
  /** A view component resolved from the template manifest. Props are open. */
9
10
  type ViewComponent = ComponentType<Record<string, unknown>>;
@@ -800,6 +801,57 @@ declare function createReactRouterShim(options?: ReactRouterShimOptions): {
800
801
  */
801
802
  declare function usePluginRealtime(typeCode: string, handler: (payload: unknown) => void): void;
802
803
 
804
+ /**
805
+ * Advisory client-side upload ceiling (UX only). The kernel's streaming route
806
+ * applies the authoritative size cap mid-stream; this default lets the hook
807
+ * fail fast with a friendly message before reading a large file into memory.
808
+ * Roughly matches the host's default request-body ceiling.
809
+ */
810
+ declare const DEFAULT_MAX_UPLOAD_BYTES: number;
811
+ /** Where the uploaded document belongs. `moduleKey` is validated against the caller's kernel allowlist. */
812
+ interface UseMcpUploadTarget {
813
+ /** Storage module key (e.g. "hr"). Server rejects a key outside the plugin's allowlist. */
814
+ moduleKey: string;
815
+ /** Owning entity name (e.g. "Candidates", "CompanyDocuments"). */
816
+ entityName: string;
817
+ /** Optional owning entity id. */
818
+ entityId?: string;
819
+ /** Logical sub-folder / category (e.g. "cvs"). */
820
+ category: string;
821
+ }
822
+ interface UseMcpUploadOptions {
823
+ /** Override the transport resolved from the runtime provider (tests / advanced hosts). */
824
+ transport?: McpTransport;
825
+ /** Advisory client-side max byte size. Defaults to {@link DEFAULT_MAX_UPLOAD_BYTES}. */
826
+ maxSizeBytes?: number;
827
+ }
828
+ interface UseMcpUploadResult {
829
+ /**
830
+ * Read `file` into an ArrayBuffer and upload it to core storage via the host,
831
+ * resolving the resulting `StoredDocumentId` + server-validated metadata. The
832
+ * plugin then records the id with its own JSON command.
833
+ */
834
+ upload: (file: File | Blob, target: UseMcpUploadTarget) => Promise<UploadDocumentResult>;
835
+ /**
836
+ * Coarse progress in [0, 1]. NOTE: the FE→host leg is not streaming (the file
837
+ * is transferred whole), so this is phase-based, not byte-accurate: 0 idle,
838
+ * ~0.1 once the bytes are read + handed to the host, 1 on completion.
839
+ */
840
+ progress: number;
841
+ loading: boolean;
842
+ error: Error | undefined;
843
+ }
844
+ /**
845
+ * Upload a binary file from a platform-react plugin surface to CORE storage.
846
+ *
847
+ * The bytes never touch the plugin backend and the plugin never sees the
848
+ * capability token or kernel URL: the host shell mints the token and streams
849
+ * the octet-stream to the kernel document-upload route, returning only the
850
+ * `StoredDocumentId`. Filename/content-type are advisory — the kernel
851
+ * sanitises them and binds the effective module key + organisation itself.
852
+ */
853
+ declare function useMcpUpload(opts?: UseMcpUploadOptions): UseMcpUploadResult;
854
+
803
855
  declare const OVERLAY_HOST_CONTRACT_VERSION = "1.0.0";
804
856
  /** Canonical inset-lease identity — the SAME shape the web host layout store uses (Codex R2 M1:
805
857
  * one lease shape across SDK/web/store, no adapter). The store's handle extends this with `source`. */
@@ -969,4 +1021,42 @@ interface ManagedLifecycle {
969
1021
  */
970
1022
  declare function useManagedLifecycle(): ManagedLifecycle;
971
1023
 
972
- export { type AuthenticatedQueryResult, BaseMcpService, type DefinePlatformReactPluginOverlayOptions, type DefinePlatformReactPluginPageOptions, EHX_PLUGIN_ROOT_CLASS, type InsetLease, type ManagedLifecycle, MissingViewFallback, OVERLAY_HOST_CONTRACT_VERSION, type OverlayHostApi, type OverlayHostCapabilities, OverlayHostContext, type PlatformReactOverlayProps, type PluginOverlayDefinerConfig, type PluginPageDefinerConfig, PluginStyleScope, type ReactRouterShimOptions, TemplateContext, type TemplateManifest, type ToolInvoker, type ViewComponent, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, injectPluginStyles, isComponent, notViaMcp, useAuthenticatedQueries, useAuthenticatedQuery, useManagedLifecycle, useOverlayHost, usePluginRealtime, useToolInvoker, useToolInvokerMap, useView };
1024
+ /** The surface's mount context, provided by definePlatformReactPluginPage. */
1025
+ interface SurfaceBaseValue {
1026
+ /** Host-provided mount path `/extensions/<slug>/<pageId>` (authoritative). */
1027
+ basePath?: string;
1028
+ /** The page id (from PlatformReactPageProps), used for the derived fallback. */
1029
+ pageId?: string;
1030
+ }
1031
+ declare const SurfaceBaseContext: react.Context<SurfaceBaseValue | undefined>;
1032
+ /** Match the host's slug normalisation (useExtensionSurfaceShellPage `normaliseSlug`). */
1033
+ declare function normaliseSlug(value: string): string;
1034
+ /**
1035
+ * Resolve the surface base. Precedence: host-provided basePath → derived from
1036
+ * normaliseSlug(extensionGroupCode) + pageId → null (caller decides how to fail).
1037
+ * Pure — callers read context + identity and pass the pieces in.
1038
+ */
1039
+ declare function resolveSurfaceBase(input: {
1040
+ basePath?: string;
1041
+ pageId?: string;
1042
+ extensionGroupCode?: string | null;
1043
+ }): {
1044
+ base: string;
1045
+ groupRoot: string;
1046
+ } | null;
1047
+ /** Join base + sub (leading slash stripped) + optional ?query/#hash suffix. */
1048
+ declare function buildSurfaceUrl(base: string, sub?: string, suffix?: string): string;
1049
+ /**
1050
+ * Hook: build a URL relative to the CURRENT surface. Precedence per
1051
+ * resolveSurfaceBase; throws in dev when unresolved (never returns app-root).
1052
+ */
1053
+ declare function useSurfaceUrl(): (sub?: string, suffix?: string) => string;
1054
+ /** Pure: build a URL for ANOTHER surface (cross-surface, e.g. an overlay opening a page). */
1055
+ declare function surfacePathFor(opts: {
1056
+ slug: string;
1057
+ pageId: string;
1058
+ sub?: string;
1059
+ suffix?: string;
1060
+ }): string;
1061
+
1062
+ export { type AuthenticatedQueryResult, BaseMcpService, DEFAULT_MAX_UPLOAD_BYTES, type DefinePlatformReactPluginOverlayOptions, type DefinePlatformReactPluginPageOptions, EHX_PLUGIN_ROOT_CLASS, type InsetLease, type ManagedLifecycle, MissingViewFallback, OVERLAY_HOST_CONTRACT_VERSION, type OverlayHostApi, type OverlayHostCapabilities, OverlayHostContext, type PlatformReactOverlayProps, type PluginOverlayDefinerConfig, type PluginPageDefinerConfig, PluginStyleScope, type ReactRouterShimOptions, SurfaceBaseContext, type SurfaceBaseValue, TemplateContext, type TemplateManifest, type ToolInvoker, type UseMcpUploadOptions, type UseMcpUploadResult, type UseMcpUploadTarget, type ViewComponent, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, injectPluginStyles, isComponent, normaliseSlug, notViaMcp, resolveSurfaceBase, surfacePathFor, useAuthenticatedQueries, useAuthenticatedQuery, useManagedLifecycle, useMcpUpload, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useView };
@@ -1,7 +1,7 @@
1
- import { createContext, useEffect, createElement, useContext, useMemo, useRef } from 'react';
1
+ import { createContext, useEffect, useContext, createElement, useMemo, useRef, useState, useCallback } from 'react';
2
2
  import { QueryClient, useQuery, useQueries, QueryClientProvider } from '@tanstack/react-query';
3
3
  import { definePlatformReactPage, emitNavigation } from '@ethisyscore/components-react';
4
- import { useMcpTool, usePluginRealtimeSource } from '@ethisyscore/extension-runtime/plugin';
4
+ import { useHostIdentity, useMcpTool, usePluginRealtimeSource, useExtensionRuntimeTransport } from '@ethisyscore/extension-runtime/plugin';
5
5
 
6
6
  // src/platform-react/definePlatformReactPluginPage.tsx
7
7
  var TemplateContext = createContext(null);
@@ -19,6 +19,57 @@ function PluginStyleScope({ id, css }) {
19
19
  }, [id, css]);
20
20
  return null;
21
21
  }
22
+ var SurfaceBaseContext = createContext(void 0);
23
+ function normaliseSlug(value) {
24
+ return value.trim().toLowerCase();
25
+ }
26
+ function stripTrailingSlash(p) {
27
+ return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
28
+ }
29
+ function resolveSurfaceBase(input) {
30
+ if (input.basePath) {
31
+ const base = stripTrailingSlash(input.basePath);
32
+ const cut = base.lastIndexOf("/");
33
+ const groupRoot = cut > 0 ? base.slice(0, cut) : base;
34
+ return { base, groupRoot };
35
+ }
36
+ const slug = input.extensionGroupCode ? normaliseSlug(input.extensionGroupCode) : "";
37
+ const pageId = input.pageId ?? "";
38
+ if (slug && pageId) {
39
+ const groupRoot = `/extensions/${slug}`;
40
+ return { base: `${groupRoot}/${pageId}`, groupRoot };
41
+ }
42
+ return null;
43
+ }
44
+ function buildSurfaceUrl(base, sub = "", suffix = "") {
45
+ const b = stripTrailingSlash(base);
46
+ const s = sub.replace(/^\/+/, "");
47
+ const path = s ? `${b}/${s}` : b;
48
+ return suffix ? `${path}${suffix}` : path;
49
+ }
50
+ function useSurfaceUrl() {
51
+ const ctx = useContext(SurfaceBaseContext);
52
+ const identity = useHostIdentity();
53
+ return (sub = "", suffix = "") => {
54
+ const resolved = resolveSurfaceBase({
55
+ basePath: ctx?.basePath,
56
+ pageId: ctx?.pageId,
57
+ extensionGroupCode: identity?.extensionGroupCode ?? null
58
+ });
59
+ if (!resolved) {
60
+ const msg = "useSurfaceUrl: no surface base \u2014 SurfaceBaseContext (basePath/pageId) and extensionGroupCode are both unavailable. Ensure the page is wrapped by definePlatformReactPluginPage inside the host runtime.";
61
+ const isDevEnv = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
62
+ if (isDevEnv) throw new Error(msg);
63
+ if (typeof console !== "undefined") console.error(msg);
64
+ return typeof window !== "undefined" ? window.location.pathname + window.location.search + window.location.hash : "";
65
+ }
66
+ return buildSurfaceUrl(resolved.base, sub, suffix);
67
+ };
68
+ }
69
+ function surfacePathFor(opts) {
70
+ const base = `/extensions/${normaliseSlug(opts.slug)}/${opts.pageId}`;
71
+ return buildSurfaceUrl(base, opts.sub ?? "", opts.suffix ?? "");
72
+ }
22
73
 
23
74
  // src/platform-react/definePlatformReactPluginPage.tsx
24
75
  function makeDefaultQueryClient() {
@@ -47,16 +98,20 @@ function definePlatformReactPluginPage(Page, options) {
47
98
  const page = createElement(Page, props);
48
99
  const body = wrapPage ? wrapPage(page) : page;
49
100
  return createElement(
50
- QueryClientProvider,
51
- { client: queryClient },
101
+ SurfaceBaseContext.Provider,
102
+ { value: { basePath: props.basePath, pageId: props.pageId } },
52
103
  createElement(
53
- TemplateContext.Provider,
54
- { value: manifest },
55
- createElement(PluginStyleScope, { id: styleId, css }),
104
+ QueryClientProvider,
105
+ { client: queryClient },
56
106
  createElement(
57
- "div",
58
- { className: `${EHX_PLUGIN_ROOT_CLASS} ${rootClassName}` },
59
- body
107
+ TemplateContext.Provider,
108
+ { value: manifest },
109
+ createElement(PluginStyleScope, { id: styleId, css }),
110
+ createElement(
111
+ "div",
112
+ { className: `${EHX_PLUGIN_ROOT_CLASS} ${rootClassName}` },
113
+ body
114
+ )
60
115
  )
61
116
  )
62
117
  );
@@ -219,20 +274,33 @@ function createReactRouterShim(options = {}) {
219
274
  };
220
275
  }
221
276
  function useParams() {
277
+ const ctx = useContext(SurfaceBaseContext);
278
+ const identity = useHostIdentity();
279
+ const resolved = resolveSurfaceBase({
280
+ basePath: ctx?.basePath,
281
+ pageId: ctx?.pageId,
282
+ extensionGroupCode: identity?.extensionGroupCode ?? null
283
+ });
222
284
  const path = typeof window !== "undefined" ? window.location.pathname : "";
223
285
  if (routes != null && routes.length > 0) {
224
286
  const matched = matchRouteParams(path, routes, basePath);
225
- if (matched != null) {
226
- return matched;
287
+ if (matched != null) return matched;
288
+ }
289
+ const pathNoQuery = path.split(/[?#]/)[0];
290
+ let remainder = pathNoQuery;
291
+ if (resolved) {
292
+ for (const prefix of [resolved.base, resolved.groupRoot].sort((a, b) => b.length - a.length)) {
293
+ if (pathNoQuery === prefix || pathNoQuery.startsWith(prefix + "/")) {
294
+ remainder = pathNoQuery.slice(prefix.length);
295
+ break;
296
+ }
227
297
  }
228
298
  }
229
- const segments = path.split("/").filter(Boolean);
299
+ const segments = remainder.split("/").filter(Boolean);
230
300
  const last = segments[segments.length - 1];
231
301
  const params = {};
232
302
  if (last) {
233
- for (const key of paramKeys) {
234
- params[key] = last;
235
- }
303
+ for (const key of paramKeys) params[key] = last;
236
304
  }
237
305
  return params;
238
306
  }
@@ -285,6 +353,84 @@ function usePluginRealtime(typeCode, handler) {
285
353
  return source.subscribe(typeCode, (payload) => ref.current(payload));
286
354
  }, [source, typeCode]);
287
355
  }
356
+ var DEFAULT_MAX_UPLOAD_BYTES = 30 * 1024 * 1024;
357
+ function useMcpUpload(opts) {
358
+ const transport = useExtensionRuntimeTransport(opts?.transport);
359
+ const maxSizeBytes = opts?.maxSizeBytes ?? DEFAULT_MAX_UPLOAD_BYTES;
360
+ const [progress, setProgress] = useState(0);
361
+ const [loading, setLoading] = useState(false);
362
+ const [error, setError] = useState(void 0);
363
+ const controllerRef = useRef(null);
364
+ const mountedRef = useRef(true);
365
+ useEffect(() => {
366
+ mountedRef.current = true;
367
+ return () => {
368
+ mountedRef.current = false;
369
+ controllerRef.current?.abort();
370
+ };
371
+ }, []);
372
+ const upload = useCallback(
373
+ async (file, target) => {
374
+ const controller = new AbortController();
375
+ controllerRef.current?.abort();
376
+ controllerRef.current = controller;
377
+ const applyState = (fn) => {
378
+ if (mountedRef.current && !controller.signal.aborted) {
379
+ fn();
380
+ }
381
+ };
382
+ applyState(() => {
383
+ setLoading(true);
384
+ setError(void 0);
385
+ setProgress(0);
386
+ });
387
+ try {
388
+ if (typeof transport.uploadDocument !== "function") {
389
+ throw new Error(
390
+ "The active McpTransport does not support binary uploads (no `uploadDocument`). The host runtime / transport must implement the upload capability for useMcpUpload to work."
391
+ );
392
+ }
393
+ const uploadDocument = transport.uploadDocument.bind(transport);
394
+ const size = file.size;
395
+ if (size > maxSizeBytes) {
396
+ throw new Error(
397
+ `File is ${size} bytes, which exceeds the ${maxSizeBytes}-byte upload limit.`
398
+ );
399
+ }
400
+ const buffer = await file.arrayBuffer();
401
+ applyState(() => setProgress(0.1));
402
+ const maybeName = file.name;
403
+ const fileName = typeof maybeName === "string" && maybeName.length > 0 ? maybeName : "upload.bin";
404
+ const contentType = file.type && file.type.length > 0 ? file.type : "application/octet-stream";
405
+ const meta = {
406
+ moduleKey: target.moduleKey,
407
+ entityName: target.entityName,
408
+ entityId: target.entityId,
409
+ category: target.category,
410
+ fileName,
411
+ contentType,
412
+ sizeBytes: buffer.byteLength
413
+ };
414
+ const result = await uploadDocument(meta, buffer, controller.signal);
415
+ applyState(() => {
416
+ setProgress(1);
417
+ setLoading(false);
418
+ });
419
+ return result;
420
+ } catch (err) {
421
+ const wrapped = err instanceof Error ? err : new Error(String(err));
422
+ applyState(() => {
423
+ setError(wrapped);
424
+ setLoading(false);
425
+ setProgress(0);
426
+ });
427
+ throw wrapped;
428
+ }
429
+ },
430
+ [transport, maxSizeBytes]
431
+ );
432
+ return { upload, progress, loading, error };
433
+ }
288
434
  var OVERLAY_HOST_CONTRACT_VERSION = "1.0.0";
289
435
  var OverlayHostContext = createContext(null);
290
436
  function useOverlayHost() {
@@ -367,6 +513,6 @@ function useManagedLifecycle() {
367
513
  return lifecycle;
368
514
  }
369
515
 
370
- export { BaseMcpService, EHX_PLUGIN_ROOT_CLASS, MissingViewFallback, OVERLAY_HOST_CONTRACT_VERSION, OverlayHostContext, PluginStyleScope, TemplateContext, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, injectPluginStyles, isComponent, notViaMcp, useAuthenticatedQueries, useAuthenticatedQuery, useManagedLifecycle, useOverlayHost, usePluginRealtime, useToolInvoker, useToolInvokerMap, useView };
516
+ export { BaseMcpService, DEFAULT_MAX_UPLOAD_BYTES, EHX_PLUGIN_ROOT_CLASS, MissingViewFallback, OVERLAY_HOST_CONTRACT_VERSION, OverlayHostContext, PluginStyleScope, SurfaceBaseContext, TemplateContext, buildSurfaceUrl, createPluginOverlayDefiner, createPluginPageDefiner, createReactRouterShim, createUseView, definePlatformReactPluginOverlay, definePlatformReactPluginPage, injectPluginStyles, isComponent, normaliseSlug, notViaMcp, resolveSurfaceBase, surfacePathFor, useAuthenticatedQueries, useAuthenticatedQuery, useManagedLifecycle, useMcpUpload, useOverlayHost, usePluginRealtime, useSurfaceUrl, useToolInvoker, useToolInvokerMap, useView };
371
517
  //# sourceMappingURL=index.js.map
372
518
  //# sourceMappingURL=index.js.map