@ai4b-team/fsaos-gateway-sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3079 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+ import { QueryClient, QueryClientProvider, useQuery, useInfiniteQuery, useQueryClient, useMutation as useMutation$1 } from '@tanstack/react-query';
3
+ export { QueryClientProvider } from '@tanstack/react-query';
4
+ import React, { createElement, useMemo, useState, useEffect, useRef, useCallback, useSyncExternalStore } from 'react';
5
+ import { createRoot } from 'react-dom/client';
6
+ import * as ReactDOM from 'react-dom';
7
+ import * as JsxRuntime from 'react/jsx-runtime';
8
+
9
+ var __defProp = Object.defineProperty;
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+
15
+ // src/index.ts
16
+ var src_exports = {};
17
+ __export(src_exports, {
18
+ ComponentRenderer: () => ComponentRenderer,
19
+ EnforcementDeniedError: () => EnforcementDeniedError,
20
+ KernelProvider: () => KernelProvider,
21
+ QueryClientProvider: () => QueryClientProvider,
22
+ auth: () => auth,
23
+ awaitScopeReady: () => awaitScopeReady,
24
+ callTool: () => callTool,
25
+ clearCachedToken: () => clearCachedToken,
26
+ clearSession: () => clearSession,
27
+ createItem: () => createItem,
28
+ disconnectSSE: () => disconnectSSE,
29
+ disposeVfsRealtimeWs: () => disposeVfsRealtimeWs,
30
+ emitEvent: () => emitEvent,
31
+ fetchChannelMessages: () => fetchChannelMessages,
32
+ fetchChannelMessagesPage: () => fetchChannelMessagesPage,
33
+ fetchEdgesForItem: () => fetchEdgesForItem,
34
+ fetchItemHistory: () => fetchItemHistory,
35
+ fetchItems: () => fetchItems,
36
+ fetchMemberFocus: () => fetchMemberFocus,
37
+ fetchOpenEnvelope: () => fetchOpenEnvelope,
38
+ fetchRecentActivity: () => fetchRecentActivity,
39
+ fetchTypeDefinitions: () => fetchTypeDefinitions,
40
+ fetchVfsChildren: () => fetchVfsChildren,
41
+ fetchVfsChildrenPage: () => fetchVfsChildrenPage,
42
+ fetchVfsItem: () => fetchVfsItem,
43
+ fetchVfsItemById: () => fetchVfsItemById,
44
+ fetchVfsTree: () => fetchVfsTree,
45
+ gatewayCall: () => gatewayCall,
46
+ getAccessToken: () => getAccessToken,
47
+ getAssetUrl: () => getAssetUrl,
48
+ getDefaultSort: () => getDefaultSort,
49
+ getRequiredFormFields: () => getRequiredFormFields,
50
+ getScope: () => getScope,
51
+ getScopeVersion: () => getScopeVersion,
52
+ getSessionEntry: () => getSessionEntry,
53
+ initSession: () => initSession,
54
+ initVfsRealtimeWs: () => initVfsRealtimeWs,
55
+ interpretSchema: () => interpretSchema,
56
+ invalidateAllVfs: () => invalidateAllVfs,
57
+ invalidateChannelMessages: () => invalidateChannelMessages,
58
+ invalidateChildren: () => invalidateChildren,
59
+ invalidateItem: () => invalidateItem,
60
+ invalidateOpenEnvelope: () => invalidateOpenEnvelope,
61
+ invalidatePathAndParent: () => invalidatePathAndParent,
62
+ invalidateSubtree: () => invalidateSubtree,
63
+ invalidateTypes: () => invalidateTypes,
64
+ isScopeReady: () => isScopeReady,
65
+ listChildren: () => listChildren,
66
+ mount: () => mount,
67
+ mountRealtimeScope: () => mountRealtimeScope,
68
+ normalizeItem: () => normalizeItem,
69
+ ping: () => ping,
70
+ pushChanges: () => pushChanges,
71
+ queryClient: () => queryClient,
72
+ readItem: () => readItem,
73
+ registerCleanup: () => registerCleanup,
74
+ resetAllSdkState: () => resetAllSdkState,
75
+ resolvePrincipalId: () => resolvePrincipalId,
76
+ setCachedToken: () => setCachedToken,
77
+ setScope: () => setScope,
78
+ setupRequireShim: () => setupRequireShim,
79
+ signal: () => signal,
80
+ subscribeScope: () => subscribeScope,
81
+ subscribeToEvents: () => subscribeToEvents,
82
+ subscribeToPath: () => subscribeToPath,
83
+ subscribeToScope: () => subscribeToScope,
84
+ unmountRealtimeScope: () => unmountRealtimeScope,
85
+ updateItem: () => updateItem,
86
+ uploadFile: () => uploadFile,
87
+ useAccounts: () => useAccounts,
88
+ useAllChannels: () => useAllChannels,
89
+ useAsset: () => useAsset,
90
+ useAuth: () => useAuth,
91
+ useChannelMessages: () => useChannelMessages,
92
+ useChannels: () => useChannels,
93
+ useChildren: () => useChildren,
94
+ useComponent: () => useComponent,
95
+ useCreate: () => useCreate,
96
+ useDelete: () => useDelete,
97
+ useDmChannels: () => useDmChannels,
98
+ useEdges: () => useEdges,
99
+ useEventStream: () => useEventStream,
100
+ useFileUpload: () => useFileUpload,
101
+ useInfiniteChannelMessages: () => useInfiniteChannelMessages,
102
+ useInfiniteChildren: () => useInfiniteChildren,
103
+ useInfiniteItems: () => useInfiniteItems,
104
+ useItem: () => useItem,
105
+ useItemById: () => useItemById,
106
+ useItemHistory: () => useItemHistory,
107
+ useItems: () => useItems,
108
+ useLink: () => useLink,
109
+ useList: () => useList,
110
+ useMemberFocus: () => useMemberFocus,
111
+ useMove: () => useMove,
112
+ useMutation: () => useMutation,
113
+ useNotifications: () => useNotifications,
114
+ useOpen: () => useOpen,
115
+ usePermission: () => usePermission,
116
+ usePermissions: () => usePermissions,
117
+ usePrincipal: () => usePrincipal,
118
+ useRealtimeQuery: () => useRealtimeQuery,
119
+ useRecentActivity: () => useRecentActivity,
120
+ useScope: () => useScope,
121
+ useScopeReady: () => useScopeReady,
122
+ useSearch: () => useSearch,
123
+ useTheme: () => useTheme,
124
+ useTool: () => useTool,
125
+ useTree: () => useTree,
126
+ useType: () => useType,
127
+ useTypeHelpers: () => useTypeHelpers,
128
+ useTypes: () => useTypes,
129
+ useUnreadCounts: () => useUnreadCounts,
130
+ useUpdate: () => useUpdate,
131
+ vfsKeys: () => vfsKeys
132
+ });
133
+
134
+ // src/enforcement.ts
135
+ var EnforcementDeniedError = class _EnforcementDeniedError extends Error {
136
+ name = "EnforcementDeniedError";
137
+ deniedBy;
138
+ ruleKey;
139
+ displayName;
140
+ constructor(payload) {
141
+ const message = payload.message || _EnforcementDeniedError.fallbackMessage(payload);
142
+ super(message);
143
+ this.deniedBy = payload.denied_by || payload.error_type || payload.error || "unknown";
144
+ const enforcement = payload.enforcement;
145
+ this.ruleKey = enforcement?.rule_key ?? void 0;
146
+ this.displayName = enforcement?.display_name ?? void 0;
147
+ }
148
+ static fallbackMessage(payload) {
149
+ const err = payload.error || "";
150
+ const deniedBy = payload.denied_by || "";
151
+ if (deniedBy === "rule" || err === "RULE_DENIED") {
152
+ return "This feature is not enabled for this workspace.";
153
+ }
154
+ if (deniedBy === "entitlement" || err === "ENTITLEMENT_REQUIRED") {
155
+ return "This feature requires a plan upgrade.";
156
+ }
157
+ if (deniedBy === "access" || err === "ACCESS_DENIED" || err === "PERMISSION_DENIED") {
158
+ return "You don't have permission to do this.";
159
+ }
160
+ return "This action is not allowed.";
161
+ }
162
+ get isRuleDenial() {
163
+ return this.deniedBy === "rule" || this.deniedBy === "RULE_DENIED";
164
+ }
165
+ get isAccessDenial() {
166
+ return this.deniedBy === "access" || this.deniedBy === "ACCESS_DENIED";
167
+ }
168
+ get isEntitlementDenial() {
169
+ return this.deniedBy === "entitlement" || this.deniedBy === "ENTITLEMENT_REQUIRED";
170
+ }
171
+ };
172
+ var CONFIG = typeof window !== "undefined" && window.__FSAOS_CONFIG__ || {};
173
+ var SUPABASE_URL = CONFIG.supabaseUrl || "https://vahbmsslxuustnlvsrkg.supabase.co";
174
+ var SUPABASE_ANON_KEY = CONFIG.supabaseAnonKey || "sb_publishable_ZjosozAjfpZ4InMNElTr6Q_hHB-f5nc";
175
+ var GATEWAY_URL = CONFIG.gatewayUrl || "https://fsaos-mcp-gw-rust.fly.dev";
176
+ var supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
177
+ auth: {
178
+ persistSession: true,
179
+ autoRefreshToken: true,
180
+ detectSessionInUrl: false
181
+ }
182
+ });
183
+
184
+ // src/session.ts
185
+ var cachedToken = null;
186
+ var tokenReady;
187
+ var resolveTokenReady;
188
+ var tokenResolved = false;
189
+ tokenReady = new Promise((resolve) => {
190
+ resolveTokenReady = resolve;
191
+ });
192
+ supabase.auth.onAuthStateChange((_event, session) => {
193
+ if (session?.access_token) {
194
+ cachedToken = session.access_token;
195
+ session.expires_at ?? Math.floor(Date.now() / 1e3) + 3600;
196
+ } else {
197
+ cachedToken = null;
198
+ }
199
+ if (!tokenResolved) {
200
+ tokenResolved = true;
201
+ resolveTokenReady();
202
+ }
203
+ });
204
+ setTimeout(() => {
205
+ if (!tokenResolved) {
206
+ tokenResolved = true;
207
+ resolveTokenReady();
208
+ }
209
+ }, 5e3);
210
+ function setCachedToken(token, expiresAt) {
211
+ cachedToken = token;
212
+ }
213
+ function clearCachedToken() {
214
+ cachedToken = null;
215
+ }
216
+ async function getAccessToken() {
217
+ await tokenReady;
218
+ return cachedToken;
219
+ }
220
+ var currentScopePath = null;
221
+ var scopeVersion = 0;
222
+ var scopeListeners = /* @__PURE__ */ new Set();
223
+ var scopeReady;
224
+ var resolveScopeReady;
225
+ var scopeResolved = false;
226
+ scopeReady = new Promise((resolve) => {
227
+ resolveScopeReady = resolve;
228
+ });
229
+ function setScope(scopePath) {
230
+ const changed = currentScopePath !== scopePath;
231
+ currentScopePath = scopePath;
232
+ if (!scopeResolved) {
233
+ scopeResolved = true;
234
+ resolveScopeReady();
235
+ }
236
+ if (changed) {
237
+ scopeVersion++;
238
+ scopeListeners.forEach((listener) => {
239
+ try {
240
+ listener();
241
+ } catch (e) {
242
+ console.warn("[SDK] Scope listener error:", e);
243
+ }
244
+ });
245
+ }
246
+ }
247
+ function getScope() {
248
+ return currentScopePath;
249
+ }
250
+ function getScopeVersion() {
251
+ return scopeVersion;
252
+ }
253
+ function subscribeScope(listener) {
254
+ scopeListeners.add(listener);
255
+ return () => {
256
+ scopeListeners.delete(listener);
257
+ };
258
+ }
259
+ function awaitScopeReady() {
260
+ return scopeReady;
261
+ }
262
+ function isScopeReady() {
263
+ return scopeResolved;
264
+ }
265
+ var sessionEntry = null;
266
+ var sessionPromise = null;
267
+ async function initSession() {
268
+ if (sessionEntry) return sessionEntry;
269
+ if (sessionPromise) return sessionPromise;
270
+ sessionPromise = (async () => {
271
+ const hostname = getHostname();
272
+ const token = await getAccessToken();
273
+ const headers = {};
274
+ if (token) {
275
+ headers["Authorization"] = `Bearer ${token}`;
276
+ }
277
+ const response = await fetch(`${GATEWAY_URL}/d/${hostname}/init`, {
278
+ method: "GET",
279
+ headers
280
+ });
281
+ if (!response.ok) {
282
+ throw new Error(`Session init failed: ${response.status} ${response.statusText}`);
283
+ }
284
+ const json = await response.json();
285
+ const entry = json.entry || json;
286
+ sessionEntry = {
287
+ scope_id: entry.scope_id,
288
+ scope_path: entry.scope_path,
289
+ instance_path: entry.instance_path || "/root",
290
+ fractal_id: entry.fractal_id || null,
291
+ instance_name: entry.instance_name || null,
292
+ display_name: entry.scope_display_name || entry.display_name || null
293
+ };
294
+ return sessionEntry;
295
+ })();
296
+ return sessionPromise;
297
+ }
298
+ function getSessionEntry() {
299
+ return sessionEntry;
300
+ }
301
+ function clearSession() {
302
+ sessionEntry = null;
303
+ sessionPromise = null;
304
+ currentScopePath = null;
305
+ }
306
+ var cleanupRegistry = [];
307
+ function registerCleanup(fn) {
308
+ cleanupRegistry.push(fn);
309
+ }
310
+ function resetAllSdkState() {
311
+ clearSession();
312
+ clearCachedToken();
313
+ scopeResolved = false;
314
+ scopeReady = new Promise((resolve) => {
315
+ resolveScopeReady = resolve;
316
+ });
317
+ scopeVersion++;
318
+ scopeListeners.forEach((listener) => {
319
+ try {
320
+ listener();
321
+ } catch (e) {
322
+ console.warn("[SDK] Scope listener error:", e);
323
+ }
324
+ });
325
+ for (const fn of cleanupRegistry) {
326
+ try {
327
+ fn();
328
+ } catch (e) {
329
+ console.warn("[SDK] Cleanup function failed:", e);
330
+ }
331
+ }
332
+ }
333
+ function getHostname() {
334
+ if (typeof window !== "undefined") {
335
+ const cfg = window.__FSAOS_CONFIG__;
336
+ if (cfg?.hostname) return cfg.hostname;
337
+ return window.location.hostname;
338
+ }
339
+ return "localhost";
340
+ }
341
+
342
+ // src/client.ts
343
+ var SCOPE_INDEPENDENT_METHODS = /* @__PURE__ */ new Set([
344
+ "list-memberships",
345
+ "init"
346
+ ]);
347
+ async function gatewayCall(method, params = {}) {
348
+ const hostname = getHostname();
349
+ const url = `${GATEWAY_URL}/d/${hostname}`;
350
+ const headers = {
351
+ "Content-Type": "application/json"
352
+ };
353
+ const token = await getAccessToken();
354
+ if (token) {
355
+ headers["Authorization"] = `Bearer ${token}`;
356
+ }
357
+ if (!SCOPE_INDEPENDENT_METHODS.has(method) && !params._scope_path) {
358
+ await awaitScopeReady();
359
+ }
360
+ const enrichedParams = { ...params };
361
+ const session = getSessionEntry();
362
+ if (!enrichedParams.domain_scope_id && session?.scope_id) {
363
+ enrichedParams.domain_scope_id = session.scope_id;
364
+ }
365
+ const scopePath = getScope();
366
+ if (!enrichedParams._scope_path && scopePath) {
367
+ enrichedParams._scope_path = scopePath;
368
+ }
369
+ const response = await fetch(url, {
370
+ method: "POST",
371
+ headers,
372
+ body: JSON.stringify({ method, params: enrichedParams })
373
+ });
374
+ if (!response.ok) {
375
+ throw new Error(`Gateway HTTP error: ${response.status} ${response.statusText}`);
376
+ }
377
+ const json = await response.json();
378
+ if (json.success === false) {
379
+ const err = json.error || "";
380
+ const deniedBy = json.denied_by || json.error_type || "";
381
+ if (deniedBy === "rule" || deniedBy === "access" || deniedBy === "entitlement" || err === "RULE_DENIED" || err === "ACCESS_DENIED" || err === "PERMISSION_DENIED" || err === "ENTITLEMENT_REQUIRED") {
382
+ throw new EnforcementDeniedError(json);
383
+ }
384
+ throw new Error(
385
+ json.message || json.error || `Gateway call ${method} failed`
386
+ );
387
+ }
388
+ return json;
389
+ }
390
+ var queryClient = new QueryClient({
391
+ defaultOptions: {
392
+ queries: {
393
+ staleTime: 1e3 * 60 * 2,
394
+ // 2 minutes
395
+ gcTime: 1e3 * 60 * 10,
396
+ // 10 minutes
397
+ refetchOnWindowFocus: false,
398
+ retry: 1
399
+ }
400
+ }
401
+ });
402
+
403
+ // src/vfs-keys.ts
404
+ function sk() {
405
+ return getScope() ?? "__unscoped__";
406
+ }
407
+ var vfsKeys = {
408
+ /** Root key for all VFS queries (scope-aware). */
409
+ all: () => ["vfs", sk()],
410
+ /** Single item by path (scope-aware — relative paths resolve per-scope). */
411
+ item: (path) => ["vfs", sk(), "item", path],
412
+ /** Single item by UUID (scope-independent — UUIDs are globally unique). */
413
+ itemById: (id) => ["vfs", "item-by-id", id],
414
+ /** Children of a path (scope-aware). */
415
+ children: (path) => ["vfs", sk(), "children", path],
416
+ /** All children queries for current scope (for broad invalidation). */
417
+ allChildren: () => ["vfs", sk(), "children"],
418
+ /** Type definitions for a scope (or default). */
419
+ types: (scopeId) => ["vfs", "types", scopeId ?? "default"],
420
+ /** Edges for an item (scope-independent — edges are by item UUID). */
421
+ edges: (itemId) => ["vfs", "edges", itemId],
422
+ /** Search results (scope-aware — search is scoped). */
423
+ search: (query, types) => ["vfs", sk(), "search", query, ...types ?? []],
424
+ /** Items query (type-based listing with filters, scope-aware). */
425
+ items: (type, filters) => ["vfs", sk(), "items", type, JSON.stringify(filters ?? {})],
426
+ /** All items queries for current scope (for broad invalidation). */
427
+ allItems: () => ["vfs", sk(), "items"],
428
+ /** Recursive tree at a path + depth (scope-aware). */
429
+ tree: (path, depth) => ["vfs", sk(), "tree", path, depth ?? 1],
430
+ /** Open envelope for a path (scope-aware). */
431
+ openEnvelope: (path, strategy) => ["vfs", sk(), "open", path, strategy ?? "__default__"],
432
+ /** All open-envelope queries for current scope (for broad invalidation). */
433
+ allOpenEnvelopes: () => ["vfs", sk(), "open"],
434
+ /** Member focus for a scope. */
435
+ memberFocus: (scopeId) => ["vfs", "member-focus", scopeId],
436
+ /** Fractal instances (scope-independent). */
437
+ fractalInstances: () => ["vfs", "fractal-instances"],
438
+ /** Recent activity (scope-aware). */
439
+ recentActivity: (a, b, c) => ["vfs", sk(), "recent-activity", a, b, c],
440
+ /** Item history (scope-independent — by item UUID). */
441
+ itemHistory: (a, b, c) => ["vfs", "item-history", a, b, c],
442
+ /** Channel messages (scope-aware — channels are scoped). */
443
+ channelMessages: (channelPath, parentMessageId) => ["vfs", sk(), "channel-messages", channelPath, parentMessageId ?? "__top__"],
444
+ /** All channel-messages queries for current scope (for broad invalidation). */
445
+ allChannelMessages: () => ["vfs", sk(), "channel-messages"],
446
+ /** Channels listing (scope-aware). */
447
+ channels: (filter) => ["vfs", sk(), "channels", filter ?? "all"],
448
+ /** All channels queries for current scope (for broad invalidation). */
449
+ allChannels: () => ["vfs", sk(), "channels"],
450
+ /** Notifications listing (scope-aware). */
451
+ notifications: (filter, limit) => ["vfs", sk(), "notifications", filter ?? "all", limit ?? 200],
452
+ /** All notification queries for current scope (for broad invalidation). */
453
+ allNotifications: () => ["vfs", sk(), "notifications"],
454
+ /** Unread counts (scope-aware). */
455
+ unreadCounts: () => ["vfs", sk(), "unread-counts"],
456
+ /** Membership graph for a principal (scope-independent). */
457
+ memberships: (principalId) => ["memberships", principalId]
458
+ };
459
+
460
+ // src/vfs.ts
461
+ function nowISO() {
462
+ return (/* @__PURE__ */ new Date()).toISOString();
463
+ }
464
+ function randomId() {
465
+ return crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
466
+ }
467
+ function normalizeItem(raw) {
468
+ const path = raw.path || raw.item_path || "";
469
+ const segments = path.split("/");
470
+ segments.pop();
471
+ const parentPath = segments.join("/");
472
+ const typeData = raw.type_data || {};
473
+ if (typeData.allowed_parent_types && !typeData.accepts) {
474
+ typeData.accepts = typeData.allowed_parent_types;
475
+ }
476
+ delete typeData.allowed_parent_types;
477
+ return {
478
+ id: raw.id || raw.item_id || "",
479
+ name: raw.name || raw.item_name || "",
480
+ item_type: raw.item_type || "unknown",
481
+ path,
482
+ parent_path: parentPath,
483
+ is_active: raw.is_active !== false,
484
+ has_children: raw.has_children ?? false,
485
+ created_at: raw.created_at || nowISO(),
486
+ updated_at: raw.updated_at || nowISO(),
487
+ visibility: raw.visibility,
488
+ type_data: typeData,
489
+ scope_item_id: raw.scope_item_id,
490
+ fractal_id: raw.fractal_id,
491
+ parent_instance_id: raw.parent_instance_id,
492
+ owner_principal_id: raw.owner_principal_id,
493
+ created_by_principal_id: raw.created_by_principal_id,
494
+ parent_id: raw.parent_id
495
+ };
496
+ }
497
+ function extractItems(response) {
498
+ return (response.content?.items || response.items || []).map(normalizeItem);
499
+ }
500
+ async function fetchVfsItemById(id) {
501
+ try {
502
+ const response = await gatewayCall("read", { id });
503
+ if (response.item_name || response.item_path) {
504
+ const item2 = normalizeItem(response);
505
+ if (item2.path) {
506
+ queryClient.setQueryData(vfsKeys.item(item2.path), item2);
507
+ }
508
+ return item2;
509
+ }
510
+ const item = response.item;
511
+ if (item) {
512
+ const normalized = normalizeItem(item);
513
+ if (normalized.path) {
514
+ queryClient.setQueryData(vfsKeys.item(normalized.path), normalized);
515
+ }
516
+ return normalized;
517
+ }
518
+ return null;
519
+ } catch {
520
+ return null;
521
+ }
522
+ }
523
+ async function fetchVfsItem(path) {
524
+ try {
525
+ const response = await gatewayCall("read", { path });
526
+ if (response.item_name || response.item_path) {
527
+ return normalizeItem(response);
528
+ }
529
+ const item = response.item;
530
+ return item ? normalizeItem(item) : null;
531
+ } catch {
532
+ return null;
533
+ }
534
+ }
535
+ async function fetchVfsChildren(path) {
536
+ try {
537
+ const response = await gatewayCall("list", { path, limit: 200 });
538
+ const items = extractItems(response);
539
+ for (const item of items) {
540
+ queryClient.setQueryData(vfsKeys.item(item.path), item);
541
+ }
542
+ return items;
543
+ } catch (err) {
544
+ console.error(`[gateway] Failed to fetch children of ${path}:`, err);
545
+ return [];
546
+ }
547
+ }
548
+ async function fetchVfsChildrenPage(path, limit, offset) {
549
+ try {
550
+ const response = await gatewayCall("list", { path, limit, offset });
551
+ const items = extractItems(response);
552
+ for (const item of items) {
553
+ queryClient.setQueryData(vfsKeys.item(item.path), item);
554
+ }
555
+ return { items, total_hint: response.total ?? response.content?.total ?? items.length };
556
+ } catch (err) {
557
+ console.error(`[gateway] Failed to fetch children page of ${path}:`, err);
558
+ return { items: [], total_hint: 0 };
559
+ }
560
+ }
561
+ async function fetchTypeDefinitions(scopeId) {
562
+ const params = scopeId ? { scope_id: scopeId } : {};
563
+ const response = await gatewayCall("types-list", params);
564
+ const rawItems = response.content?.items || response.items || [];
565
+ const types = /* @__PURE__ */ new Map();
566
+ for (const raw of rawItems) {
567
+ const name = raw.name;
568
+ const td = raw.type_data;
569
+ if (!name || !td) continue;
570
+ types.set(name, {
571
+ type_key: name,
572
+ display_name: td.display_name || name,
573
+ display_name_plural: td.display_name_plural || name + "s",
574
+ icon: td.icon || "File",
575
+ color: td.color || "gray",
576
+ description: td.description || "",
577
+ input_schema: td.input_schema || {},
578
+ system_schema: td.system_schema || {},
579
+ json_schema: td.input_schema || td.json_schema || {},
580
+ default_data: td.default_data || {},
581
+ field_defaults: td.field_defaults || {},
582
+ renderer_config: td.renderer_config || {},
583
+ is_system: td.is_system ?? false,
584
+ is_active: td.is_active ?? true,
585
+ is_container: td.is_container ?? false,
586
+ is_scope: td.is_scope ?? false,
587
+ render_mode: td.render_mode || "none",
588
+ placement_mode: td.placement_mode,
589
+ direct_parent_types: td.direct_parent_types,
590
+ allowed_parent_types: td.allowed_parent_types,
591
+ governed_create: td.governed_create,
592
+ create_method: td.create_method,
593
+ dedup: td.dedup,
594
+ edges: td.edges,
595
+ events: td.events
596
+ });
597
+ }
598
+ return types;
599
+ }
600
+ async function fetchEdgesForItem(itemId) {
601
+ try {
602
+ const response = await gatewayCall("edges", { item_id: itemId });
603
+ const outgoing = response.outgoing || [];
604
+ const incoming = response.incoming || [];
605
+ const edges = [];
606
+ for (const edge of outgoing) {
607
+ edges.push({
608
+ id: edge.edge_id || randomId(),
609
+ source_item_id: itemId,
610
+ target_item_id: edge.target_id || "",
611
+ edge_type: edge.edge_type || "",
612
+ weight: edge.weight ?? 1,
613
+ context: edge.context || {},
614
+ is_active: true,
615
+ is_bidirectional: edge.is_bidirectional
616
+ });
617
+ }
618
+ for (const edge of incoming) {
619
+ edges.push({
620
+ id: edge.edge_id || randomId(),
621
+ source_item_id: edge.source_id || "",
622
+ target_item_id: itemId,
623
+ edge_type: edge.edge_type || "",
624
+ weight: edge.weight ?? 1,
625
+ context: edge.context || {},
626
+ is_active: true,
627
+ is_bidirectional: edge.is_bidirectional
628
+ });
629
+ }
630
+ return edges;
631
+ } catch (err) {
632
+ console.warn(`[gateway] Failed to fetch edges for ${itemId}:`, err);
633
+ return [];
634
+ }
635
+ }
636
+ async function fetchVfsTree(path, maxDepth = 1, itemTypes, limit) {
637
+ try {
638
+ const params = {
639
+ path,
640
+ max_depth: maxDepth > 0 ? maxDepth : 999
641
+ };
642
+ if (itemTypes?.length) params.item_types = itemTypes;
643
+ if (limit) params.limit = limit;
644
+ const response = await gatewayCall("tree", params);
645
+ const items = extractItems(response);
646
+ for (const item of items) {
647
+ queryClient.setQueryData(vfsKeys.item(item.path), item);
648
+ }
649
+ return items;
650
+ } catch (err) {
651
+ console.error(`[gateway] Failed to fetch tree at ${path}:`, err);
652
+ return [];
653
+ }
654
+ }
655
+ async function fetchRecentActivity(scopePath, limit = 50, offset = 0) {
656
+ try {
657
+ const response = await gatewayCall("recent-activity", {
658
+ scope_path: scopePath,
659
+ limit,
660
+ offset
661
+ });
662
+ return response.content?.items || response.items || response.entries || [];
663
+ } catch (err) {
664
+ console.error(`[gateway] Failed to fetch recent activity for ${scopePath}:`, err);
665
+ return [];
666
+ }
667
+ }
668
+ async function fetchItemHistory(itemId, limit = 50, offset = 0) {
669
+ try {
670
+ const response = await gatewayCall("item-history", {
671
+ item_id: itemId,
672
+ limit,
673
+ offset
674
+ });
675
+ return {
676
+ versions: response.content?.items || response.versions || [],
677
+ total: response.total ?? 0
678
+ };
679
+ } catch (err) {
680
+ console.error(`[gateway] Failed to fetch item history for ${itemId}:`, err);
681
+ return { versions: [], total: 0 };
682
+ }
683
+ }
684
+ async function fetchMemberFocus(scopeId, itemTypes, limit) {
685
+ try {
686
+ const params = { scope_id: scopeId };
687
+ if (itemTypes?.length) params.item_types = itemTypes;
688
+ if (limit) params.limit = limit;
689
+ const response = await gatewayCall("member-focus", params);
690
+ const items = extractItems(response);
691
+ for (const item of items) {
692
+ queryClient.setQueryData(vfsKeys.item(item.path), item);
693
+ }
694
+ return items;
695
+ } catch (err) {
696
+ console.error(`[gateway] Failed to fetch member focus for scope ${scopeId}:`, err);
697
+ return [];
698
+ }
699
+ }
700
+ async function fetchOpenEnvelope(path, options) {
701
+ const params = { path };
702
+ if (options?.mode) params.mode = options.mode;
703
+ if (options?.strategy) params.strategy = options.strategy;
704
+ if (options?.arguments) params.arguments = options.arguments;
705
+ const response = await gatewayCall("open", params);
706
+ const r = response;
707
+ return {
708
+ item_id: r.item_id,
709
+ item_path: r.item_path,
710
+ item_name: r.item_name,
711
+ item_type: r.item_type,
712
+ render: r.render,
713
+ compatible_components: r.compatible_components ?? [],
714
+ content: r.content,
715
+ metadata: r.metadata,
716
+ instructions: r.instructions
717
+ };
718
+ }
719
+ async function fetchItems(filter) {
720
+ const params = {
721
+ item_type: filter.type
722
+ };
723
+ if (filter.scope) {
724
+ params.scope_path = filter.scope;
725
+ }
726
+ const filters = {};
727
+ if (filter.name) filters.name = filter.name;
728
+ if (filter.tag) filters.tag = filter.tag;
729
+ if (filter.parent_id) filters.parent_id = filter.parent_id;
730
+ if (filter.sort_by) filters.sort_by = filter.sort_by;
731
+ if (filter.sort_dir) filters.sort_dir = filter.sort_dir;
732
+ if (filter.offset != null) filters.offset = filter.offset;
733
+ if (filter.fields) {
734
+ for (const [key, value] of Object.entries(filter.fields)) {
735
+ filters[`field.${key}`] = String(value);
736
+ }
737
+ }
738
+ if (Object.keys(filters).length > 0) {
739
+ params.filters = filters;
740
+ }
741
+ if (filter.limit != null) {
742
+ params.limit = filter.limit;
743
+ }
744
+ try {
745
+ const response = await gatewayCall("search", params);
746
+ const items = extractItems(response);
747
+ for (const item of items) {
748
+ queryClient.setQueryData(vfsKeys.item(item.path), item);
749
+ }
750
+ return { items, total_hint: response.total ?? response.content?.total ?? items.length };
751
+ } catch (err) {
752
+ console.error(`[gateway] Failed to fetch items of type ${filter.type}:`, err);
753
+ return { items: [], total_hint: 0 };
754
+ }
755
+ }
756
+ function parseChannelMessages(rawItems) {
757
+ return rawItems.filter((i) => i.item_type === "message").map((i) => {
758
+ const td = i.type_data || {};
759
+ let role = "user";
760
+ if (td.role === "assistant") role = "assistant";
761
+ else if (td.role === "system") role = "system";
762
+ const metadata = td.metadata || {};
763
+ return {
764
+ id: i.id || "",
765
+ path: i.path || "",
766
+ content: td.message || td.content || "",
767
+ created_at: i.created_at || "",
768
+ role,
769
+ seq: td.seq || 0,
770
+ principal_id: td.principal_id,
771
+ reply_count: td.reply_count || 0,
772
+ parent_message_id: td.parent_message_id,
773
+ intent_card: metadata.intent_card,
774
+ thread_summary: Array.isArray(td.thread_summary) && td.thread_summary.length > 0 ? td.thread_summary : void 0
775
+ };
776
+ }).sort((a, b) => a.seq - b.seq);
777
+ }
778
+ async function fetchChannelMessages(channelPath, parentMessageId) {
779
+ try {
780
+ const params = { channel_path: channelPath };
781
+ if (parentMessageId) params.parent_message_id = parentMessageId;
782
+ const response = await gatewayCall("get-channel-messages", params);
783
+ const raw = response;
784
+ const items = raw.messages || raw.content?.items || raw.items || [];
785
+ return parseChannelMessages(items);
786
+ } catch (err) {
787
+ console.error(
788
+ `[gateway] Failed to fetch channel messages for ${channelPath}:`,
789
+ err
790
+ );
791
+ return [];
792
+ }
793
+ }
794
+ async function fetchChannelMessagesPage(channelPath, options) {
795
+ try {
796
+ const params = { channel_path: channelPath };
797
+ if (options?.parentMessageId) params.parent_message_id = options.parentMessageId;
798
+ if (options?.limit) params.limit = options.limit;
799
+ if (options?.before_seq != null) params.before_seq = options.before_seq;
800
+ if (options?.after_seq != null) params.after_seq = options.after_seq;
801
+ const response = await gatewayCall("get-channel-messages", params);
802
+ const raw = response;
803
+ const items = raw.messages || [];
804
+ return {
805
+ messages: parseChannelMessages(items),
806
+ mode: raw.mode || "top_level"
807
+ };
808
+ } catch (err) {
809
+ console.error(
810
+ `[gateway] Failed to fetch channel messages page for ${channelPath}:`,
811
+ err
812
+ );
813
+ return { messages: [], mode: "top_level" };
814
+ }
815
+ }
816
+ function invalidateChildren(path) {
817
+ queryClient.invalidateQueries({ queryKey: vfsKeys.children(path) });
818
+ }
819
+ function invalidateItem(path) {
820
+ queryClient.invalidateQueries({ queryKey: vfsKeys.item(path) });
821
+ }
822
+ function invalidatePathAndParent(path, fallbackParent) {
823
+ invalidateItem(path);
824
+ invalidateChildren(path);
825
+ const segments = path.split("/");
826
+ segments.pop();
827
+ const parentPath = segments.join("/") || fallbackParent;
828
+ if (parentPath) {
829
+ invalidateChildren(parentPath);
830
+ }
831
+ }
832
+ function invalidateSubtree(path) {
833
+ const prefix = path + "/";
834
+ queryClient.invalidateQueries({
835
+ predicate: (query) => {
836
+ const key = query.queryKey;
837
+ if (key[0] !== "vfs") return false;
838
+ return key.some(
839
+ (segment) => typeof segment === "string" && (segment === path || segment.startsWith(prefix))
840
+ );
841
+ }
842
+ });
843
+ }
844
+ function invalidateAllVfs() {
845
+ queryClient.invalidateQueries({ queryKey: vfsKeys.all() });
846
+ }
847
+ function invalidateTypes(scopeId) {
848
+ queryClient.invalidateQueries({ queryKey: vfsKeys.types(scopeId) });
849
+ }
850
+ function invalidateChannelMessages(channelPath, parentMessageId) {
851
+ if (channelPath) {
852
+ queryClient.invalidateQueries({ queryKey: vfsKeys.channelMessages(channelPath, parentMessageId) });
853
+ } else {
854
+ queryClient.invalidateQueries({ queryKey: vfsKeys.allChannelMessages() });
855
+ }
856
+ }
857
+ function invalidateOpenEnvelope(path, strategy) {
858
+ if (path && strategy) {
859
+ queryClient.invalidateQueries({ queryKey: vfsKeys.openEnvelope(path, strategy) });
860
+ } else if (path) {
861
+ queryClient.invalidateQueries({
862
+ predicate: (query) => {
863
+ const key = query.queryKey;
864
+ return key[0] === "vfs" && key[2] === "open" && key[3] === path;
865
+ }
866
+ });
867
+ } else {
868
+ queryClient.invalidateQueries({ queryKey: vfsKeys.allOpenEnvelopes() });
869
+ }
870
+ }
871
+
872
+ // src/ws.ts
873
+ var DEBUG = typeof window !== "undefined" && (window.location?.hostname === "localhost" || window.location?.hostname === "127.0.0.1" || window.__FSAOS_WS_DEBUG__);
874
+ function log(...args) {
875
+ if (DEBUG) console.log("[WS]", ...args);
876
+ }
877
+ var ws = null;
878
+ var connectionInFlight = null;
879
+ var reconnectTimer = null;
880
+ var reconnectAttempts = 0;
881
+ var MAX_RECONNECT_DELAY = 3e4;
882
+ var CONNECTION_TIMEOUT_MS = 1e4;
883
+ var intentionalClose = false;
884
+ var PING_INTERVAL_MS = 3e4;
885
+ var pingInterval = null;
886
+ var visibilityListenerAttached = false;
887
+ function startPing() {
888
+ stopPing();
889
+ pingInterval = setInterval(() => {
890
+ sendMessage({ type: "ping" });
891
+ }, PING_INTERVAL_MS);
892
+ }
893
+ function stopPing() {
894
+ if (pingInterval) {
895
+ clearInterval(pingInterval);
896
+ pingInterval = null;
897
+ }
898
+ }
899
+ function handleVisibilityChange() {
900
+ if (typeof document === "undefined") return;
901
+ if (document.hidden) {
902
+ stopPing();
903
+ } else {
904
+ if (hasActiveSubscriptions()) {
905
+ log("Tab visible, reconnecting...");
906
+ ensureConnection();
907
+ }
908
+ }
909
+ }
910
+ function attachVisibilityListener() {
911
+ if (visibilityListenerAttached) return;
912
+ if (typeof document === "undefined") return;
913
+ document.addEventListener("visibilitychange", handleVisibilityChange);
914
+ visibilityListenerAttached = true;
915
+ }
916
+ var pathListeners = {};
917
+ var typedListeners = [];
918
+ var activeScopes = /* @__PURE__ */ new Map();
919
+ var activePaths = /* @__PURE__ */ new Map();
920
+ function hasListeners() {
921
+ const hasPath = Object.keys(pathListeners).some(
922
+ (k) => pathListeners[k] && pathListeners[k].length > 0
923
+ );
924
+ return hasPath || typedListeners.length > 0;
925
+ }
926
+ function hasActiveSubscriptions() {
927
+ return activeScopes.size > 0 || activePaths.size > 0;
928
+ }
929
+ async function ensureConnection() {
930
+ if (!hasActiveSubscriptions()) {
931
+ log("ensureConnection: no active subscriptions, skipping");
932
+ return;
933
+ }
934
+ if (ws && ws.readyState === WebSocket.OPEN) {
935
+ log("ensureConnection: already open");
936
+ return;
937
+ }
938
+ if (connectionInFlight) {
939
+ log("ensureConnection: connection already in flight, waiting...");
940
+ return connectionInFlight;
941
+ }
942
+ if (ws) {
943
+ log("ensureConnection: killing stale WS in state", ws.readyState);
944
+ try {
945
+ ws.close();
946
+ } catch (_e) {
947
+ }
948
+ ws = null;
949
+ }
950
+ intentionalClose = false;
951
+ connectionInFlight = (async () => {
952
+ try {
953
+ const config = typeof window !== "undefined" && window.__FSAOS_CONFIG__ || {};
954
+ const gatewayUrl = GATEWAY_URL;
955
+ const embedToken = config.embedToken || null;
956
+ let token = embedToken;
957
+ if (!token) {
958
+ token = await getAccessToken();
959
+ }
960
+ if (!token) {
961
+ log("ensureConnection: no token available, will retry");
962
+ scheduleReconnect();
963
+ return;
964
+ }
965
+ const wsProtocol = gatewayUrl.startsWith("https") ? "wss" : "ws";
966
+ const wsHost = gatewayUrl.replace(/^https?:\/\//, "");
967
+ let wsUrl = `${wsProtocol}://${wsHost}/ws`;
968
+ wsUrl += `?token=${encodeURIComponent(token)}`;
969
+ log("ensureConnection: opening WS to", wsUrl.substring(0, 50) + "...");
970
+ ws = new WebSocket(wsUrl);
971
+ await new Promise((resolve, reject) => {
972
+ if (!ws) return reject(new Error("WebSocket not created"));
973
+ const timeout = setTimeout(() => {
974
+ log("ensureConnection: TIMEOUT after", CONNECTION_TIMEOUT_MS, "ms");
975
+ if (ws && ws.readyState !== WebSocket.OPEN) {
976
+ try {
977
+ ws.close();
978
+ } catch (_e) {
979
+ }
980
+ ws = null;
981
+ }
982
+ reject(new Error("WebSocket connection timeout"));
983
+ }, CONNECTION_TIMEOUT_MS);
984
+ ws.onopen = () => {
985
+ clearTimeout(timeout);
986
+ log("ensureConnection: CONNECTED");
987
+ reconnectAttempts = 0;
988
+ resubscribeAll();
989
+ if (typeof document === "undefined" || !document.hidden) {
990
+ startPing();
991
+ }
992
+ attachVisibilityListener();
993
+ resolve();
994
+ };
995
+ ws.onmessage = (event) => {
996
+ handleMessage(event.data);
997
+ };
998
+ ws.onerror = () => {
999
+ log("ensureConnection: WS error event");
1000
+ };
1001
+ ws.onclose = (event) => {
1002
+ clearTimeout(timeout);
1003
+ log("ensureConnection: WS closed, code:", event.code, "reason:", event.reason);
1004
+ ws = null;
1005
+ stopPing();
1006
+ if (!intentionalClose && hasActiveSubscriptions()) {
1007
+ scheduleReconnect();
1008
+ }
1009
+ reject(new Error(`WebSocket closed: ${event.code}`));
1010
+ };
1011
+ });
1012
+ } catch (e) {
1013
+ log("ensureConnection: failed -", e?.message || e);
1014
+ } finally {
1015
+ connectionInFlight = null;
1016
+ }
1017
+ })();
1018
+ return connectionInFlight;
1019
+ }
1020
+ function scheduleReconnect() {
1021
+ if (reconnectTimer) return;
1022
+ if (typeof document !== "undefined" && document.hidden) return;
1023
+ const delay = Math.min(
1024
+ 1e3 * Math.pow(2, reconnectAttempts) + Math.random() * 1e3,
1025
+ MAX_RECONNECT_DELAY
1026
+ );
1027
+ reconnectAttempts++;
1028
+ log("scheduleReconnect: attempt", reconnectAttempts, "in", Math.round(delay), "ms");
1029
+ reconnectTimer = setTimeout(() => {
1030
+ reconnectTimer = null;
1031
+ if (hasActiveSubscriptions()) {
1032
+ ensureConnection();
1033
+ }
1034
+ }, delay);
1035
+ }
1036
+ function resubscribeAll() {
1037
+ for (const sub of activeScopes.values()) {
1038
+ log("resubscribeAll: scope", sub.scope_id);
1039
+ sendMessage({ type: "subscribe", scope_id: sub.scope_id, event_types: sub.event_types });
1040
+ }
1041
+ for (const sub of activePaths.values()) {
1042
+ log("resubscribeAll: path", sub.path);
1043
+ sendMessage({ type: "subscribe_path", path: sub.path, event_types: sub.event_types });
1044
+ }
1045
+ }
1046
+ function sendMessage(msg) {
1047
+ if (ws && ws.readyState === WebSocket.OPEN) {
1048
+ ws.send(JSON.stringify(msg));
1049
+ }
1050
+ }
1051
+ function handleMessage(raw) {
1052
+ try {
1053
+ const msg = JSON.parse(raw);
1054
+ if (msg.type === "subscribed" || msg.type === "subscribed_path" || msg.type === "unsubscribed" || msg.type === "unsubscribed_path" || msg.type === "pong") {
1055
+ return;
1056
+ }
1057
+ if (msg.type === "error") {
1058
+ console.warn("[WS] Server error:", msg.message);
1059
+ return;
1060
+ }
1061
+ if (msg.type === "event" || msg.event_type) {
1062
+ const eventType = msg.event_type || "message";
1063
+ const data = msg.data || msg;
1064
+ if (data.path && pathListeners[data.path]) {
1065
+ pathListeners[data.path].forEach((cb) => cb(data));
1066
+ }
1067
+ if (pathListeners["*"]) {
1068
+ pathListeners["*"].forEach((cb) => cb(data));
1069
+ }
1070
+ for (const entry of typedListeners) {
1071
+ if (entry.eventType !== "*" && entry.eventType !== eventType) continue;
1072
+ if (entry.filter && !entry.filter(data)) continue;
1073
+ entry.callback(data);
1074
+ }
1075
+ }
1076
+ } catch (_e) {
1077
+ }
1078
+ }
1079
+ function maybeDisconnect() {
1080
+ if (!hasListeners() && activeScopes.size === 0 && activePaths.size === 0 && ws) {
1081
+ intentionalClose = true;
1082
+ stopPing();
1083
+ ws.close();
1084
+ ws = null;
1085
+ intentionalClose = false;
1086
+ }
1087
+ }
1088
+ function subscribeToPath(path, callback) {
1089
+ if (!pathListeners[path]) {
1090
+ pathListeners[path] = [];
1091
+ }
1092
+ pathListeners[path].push(callback);
1093
+ if (!activePaths.has(path)) {
1094
+ activePaths.set(path, { path, event_types: ["token_stream"] });
1095
+ if (ws && ws.readyState === WebSocket.OPEN) {
1096
+ sendMessage({ type: "subscribe_path", path, event_types: ["token_stream"] });
1097
+ }
1098
+ }
1099
+ ensureConnection();
1100
+ return () => {
1101
+ const listeners = pathListeners[path];
1102
+ if (listeners) {
1103
+ const idx = listeners.indexOf(callback);
1104
+ if (idx !== -1) listeners.splice(idx, 1);
1105
+ if (listeners.length === 0) {
1106
+ delete pathListeners[path];
1107
+ activePaths.delete(path);
1108
+ sendMessage({ type: "unsubscribe_path", path });
1109
+ }
1110
+ }
1111
+ maybeDisconnect();
1112
+ };
1113
+ }
1114
+ function subscribeToEvents(eventType, callback, filter) {
1115
+ const types = Array.isArray(eventType) ? eventType : [eventType];
1116
+ const entries = types.map((t) => ({ eventType: t, callback, filter }));
1117
+ typedListeners.push(...entries);
1118
+ if (hasActiveSubscriptions()) {
1119
+ ensureConnection();
1120
+ }
1121
+ return () => {
1122
+ for (const entry of entries) {
1123
+ const idx = typedListeners.indexOf(entry);
1124
+ if (idx !== -1) typedListeners.splice(idx, 1);
1125
+ }
1126
+ maybeDisconnect();
1127
+ };
1128
+ }
1129
+ function subscribeToScope(scopeId, eventTypes = ["vfs_change", "ccm_change"]) {
1130
+ log("subscribeToScope:", scopeId, eventTypes);
1131
+ activeScopes.set(scopeId, { scope_id: scopeId, event_types: eventTypes });
1132
+ if (ws && ws.readyState === WebSocket.OPEN) {
1133
+ sendMessage({ type: "subscribe", scope_id: scopeId, event_types: eventTypes });
1134
+ }
1135
+ ensureConnection();
1136
+ return () => {
1137
+ activeScopes.delete(scopeId);
1138
+ sendMessage({ type: "unsubscribe", scope_id: scopeId });
1139
+ maybeDisconnect();
1140
+ };
1141
+ }
1142
+ function emitEvent(scopeId, eventType, payload = {}) {
1143
+ sendMessage({ type: "emit", scope_id: scopeId, event_type: eventType, payload });
1144
+ }
1145
+ function disconnectSSE() {
1146
+ log("disconnectSSE: tearing down");
1147
+ intentionalClose = true;
1148
+ stopPing();
1149
+ if (ws) {
1150
+ ws.close();
1151
+ ws = null;
1152
+ }
1153
+ if (reconnectTimer) {
1154
+ clearTimeout(reconnectTimer);
1155
+ reconnectTimer = null;
1156
+ }
1157
+ connectionInFlight = null;
1158
+ intentionalClose = false;
1159
+ reconnectAttempts = 0;
1160
+ for (const key of Object.keys(pathListeners)) {
1161
+ delete pathListeners[key];
1162
+ }
1163
+ typedListeners.length = 0;
1164
+ activeScopes.clear();
1165
+ activePaths.clear();
1166
+ }
1167
+ function ping() {
1168
+ sendMessage({ type: "ping" });
1169
+ }
1170
+ registerCleanup(disconnectSSE);
1171
+
1172
+ // src/vfs-realtime-ws.ts
1173
+ var activeScopeSubs = /* @__PURE__ */ new Map();
1174
+ var unsubEvents = null;
1175
+ var debounceTimers = /* @__PURE__ */ new Map();
1176
+ var DEBOUNCE_MS = 250;
1177
+ function initVfsRealtimeWs(accountId) {
1178
+ disposeVfsRealtimeWs();
1179
+ mountRealtimeScope(accountId);
1180
+ unsubEvents = subscribeToEvents(
1181
+ "vfs_change",
1182
+ (data) => handleVfsChange(data)
1183
+ );
1184
+ }
1185
+ function mountRealtimeScope(scopeId) {
1186
+ if (activeScopeSubs.has(scopeId)) return;
1187
+ const unsub = subscribeToScope(scopeId, ["vfs_change", "ccm_change"]);
1188
+ activeScopeSubs.set(scopeId, unsub);
1189
+ }
1190
+ function unmountRealtimeScope(scopeId) {
1191
+ const unsub = activeScopeSubs.get(scopeId);
1192
+ if (unsub) {
1193
+ unsub();
1194
+ activeScopeSubs.delete(scopeId);
1195
+ }
1196
+ }
1197
+ function disposeVfsRealtimeWs() {
1198
+ for (const [, unsub] of activeScopeSubs) {
1199
+ unsub();
1200
+ }
1201
+ activeScopeSubs.clear();
1202
+ if (unsubEvents) {
1203
+ unsubEvents();
1204
+ unsubEvents = null;
1205
+ }
1206
+ debounceTimers.forEach((timer) => clearTimeout(timer));
1207
+ debounceTimers.clear();
1208
+ }
1209
+ registerCleanup(disposeVfsRealtimeWs);
1210
+ function handleVfsChange(data) {
1211
+ const path = data.path;
1212
+ const action = data.action;
1213
+ if (!path) return;
1214
+ const segments = path.split("/");
1215
+ segments.pop();
1216
+ const parentPath = segments.join("/") || "/root";
1217
+ const existing = debounceTimers.get(parentPath);
1218
+ if (existing) clearTimeout(existing);
1219
+ const timer = setTimeout(() => {
1220
+ debounceTimers.delete(parentPath);
1221
+ applyInvalidation(action, path, data, parentPath);
1222
+ }, DEBOUNCE_MS);
1223
+ debounceTimers.set(parentPath, timer);
1224
+ }
1225
+ function applyInvalidation(action, path, data, parentPath) {
1226
+ const upperAction = (action || "").toUpperCase();
1227
+ if (upperAction === "DELETE") {
1228
+ queryClient.removeQueries({ queryKey: vfsKeys.item(path) });
1229
+ queryClient.removeQueries({ queryKey: vfsKeys.children(path) });
1230
+ queryClient.removeQueries({ queryKey: vfsKeys.tree(path) });
1231
+ } else if (data.item_id) {
1232
+ try {
1233
+ const record = {
1234
+ id: data.item_id,
1235
+ path: data.path,
1236
+ name: data.name,
1237
+ item_type: data.item_type,
1238
+ parent_id: data.parent_id,
1239
+ scope_item_id: data.scope_item_id
1240
+ };
1241
+ const normalized = normalizeItem(record);
1242
+ if (normalized && normalized.id) {
1243
+ queryClient.setQueryData(vfsKeys.item(path), normalized);
1244
+ }
1245
+ } catch (_e) {
1246
+ }
1247
+ }
1248
+ queryClient.invalidateQueries({ queryKey: vfsKeys.children(parentPath) });
1249
+ queryClient.invalidateQueries({
1250
+ predicate: (query) => {
1251
+ const key = query.queryKey;
1252
+ if (key[0] !== "vfs" || key[1] !== "tree") return false;
1253
+ const treePath = key[2];
1254
+ return path.startsWith(treePath + "/") || path === treePath;
1255
+ }
1256
+ });
1257
+ if (data.item_id) {
1258
+ queryClient.invalidateQueries({ queryKey: vfsKeys.itemById(data.item_id) });
1259
+ }
1260
+ }
1261
+ function readItem(path) {
1262
+ return gatewayCall("read", { path });
1263
+ }
1264
+ function listChildren(path, options) {
1265
+ return gatewayCall("list", { path, ...options });
1266
+ }
1267
+ function createItem(params) {
1268
+ return gatewayCall("create", params);
1269
+ }
1270
+ function updateItem(params) {
1271
+ return gatewayCall("update", params);
1272
+ }
1273
+ function pushChanges(params) {
1274
+ return gatewayCall("create", params);
1275
+ }
1276
+ function callTool(toolName, params) {
1277
+ const config = typeof window !== "undefined" && window.__FSAOS_CONFIG__ || {};
1278
+ const scopePath = config.scopePath || "";
1279
+ return gatewayCall("tools/call", {
1280
+ name: toolName,
1281
+ instance_path: scopePath,
1282
+ ...params || {}
1283
+ });
1284
+ }
1285
+ function signal(targetId, eventName, payload) {
1286
+ return gatewayCall("signal", { target_id: targetId, event_name: eventName, payload: payload || {} });
1287
+ }
1288
+ function getAssetUrl(path) {
1289
+ const config = typeof window !== "undefined" && window.__FSAOS_CONFIG__ || {};
1290
+ return (config.edgeBaseUrl || "") + path;
1291
+ }
1292
+ var auth = {
1293
+ signIn: (opts) => {
1294
+ if (opts.provider) {
1295
+ return supabase.auth.signInWithOAuth({
1296
+ provider: opts.provider,
1297
+ options: { redirectTo: opts.redirectTo || window.location.href }
1298
+ });
1299
+ }
1300
+ return supabase.auth.signInWithPassword({
1301
+ email: opts.email,
1302
+ password: opts.password
1303
+ });
1304
+ },
1305
+ signUp: (opts) => {
1306
+ return supabase.auth.signUp({
1307
+ email: opts.email,
1308
+ password: opts.password,
1309
+ options: { data: opts.metadata || {} }
1310
+ });
1311
+ },
1312
+ signOut: async () => {
1313
+ await supabase.auth.signOut();
1314
+ resetAllSdkState();
1315
+ queryClient.clear();
1316
+ },
1317
+ getSession: () => supabase.auth.getSession().then((r) => r.data.session),
1318
+ getUser: () => supabase.auth.getUser().then((r) => r.data.user),
1319
+ onAuthStateChange: (callback) => {
1320
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(
1321
+ (event, session) => {
1322
+ callback({ event, session, user: session?.user ?? null });
1323
+ }
1324
+ );
1325
+ return () => subscription?.unsubscribe();
1326
+ },
1327
+ resetPassword: (email) => supabase.auth.resetPasswordForEmail(email),
1328
+ signInWithMagicLink: (email, redirectTo) => {
1329
+ return supabase.auth.signInWithOtp({
1330
+ email,
1331
+ options: { emailRedirectTo: redirectTo || window.location.href }
1332
+ });
1333
+ }
1334
+ };
1335
+ function mount() {
1336
+ const rootEl = document.getElementById("root");
1337
+ if (!rootEl) return;
1338
+ try {
1339
+ const comp = window.__FSAOS_COMPONENT__;
1340
+ if (!comp) {
1341
+ rootEl.innerHTML = '<div style="padding:2rem;color:#ef4444;font-family:system-ui"><h2>Component Error</h2><pre>No component found. The bundle may have failed to load.</pre></div>';
1342
+ return;
1343
+ }
1344
+ const RootComponent = comp.default || comp;
1345
+ const itemData = window.__FSAOS_ITEM_DATA__ || null;
1346
+ const props = itemData ? { item: itemData, ...itemData } : {};
1347
+ const root = createRoot(rootEl);
1348
+ root.render(
1349
+ createElement(
1350
+ QueryClientProvider,
1351
+ { client: queryClient },
1352
+ createElement(RootComponent, props)
1353
+ )
1354
+ );
1355
+ } catch (err) {
1356
+ console.error("[FSAOS] Failed to mount root component:", err);
1357
+ rootEl.innerHTML = '<div style="padding:2rem;color:#ef4444;font-family:system-ui"><h2>Component Error</h2><pre>' + (err.message || err) + "</pre></div>";
1358
+ }
1359
+ }
1360
+ function setupRequireShim() {
1361
+ const R = window.React;
1362
+ const RD = window.ReactDOM;
1363
+ const gateway = window.__FSAOS_GATEWAY__;
1364
+ const ui = window.__FSAOS_UI__;
1365
+ if (!gateway) {
1366
+ console.error("[FSAOS] Cannot set up require shim: __FSAOS_GATEWAY__ not found");
1367
+ return;
1368
+ }
1369
+ const modules = {
1370
+ "react": R,
1371
+ "react-dom": RD,
1372
+ "react-dom/client": RD,
1373
+ "react/jsx-runtime": {
1374
+ jsx: R?.createElement,
1375
+ jsxs: R?.createElement,
1376
+ Fragment: R?.Fragment
1377
+ },
1378
+ "@fsaos/react": gateway,
1379
+ "@fsaos/gateway": gateway,
1380
+ "@fsaos/ui": ui || {}
1381
+ };
1382
+ window.require = function fsaosRequire(name) {
1383
+ if (modules[name]) return modules[name];
1384
+ throw new Error("[FSAOS] Module not found: " + name);
1385
+ };
1386
+ window.__FSAOS_MOUNT__ = gateway.mount;
1387
+ }
1388
+ function useScopeKey() {
1389
+ return useSyncExternalStore(subscribeScope, getScopeVersion, getScopeVersion);
1390
+ }
1391
+ function useScopeReady() {
1392
+ useScopeKey();
1393
+ return isScopeReady();
1394
+ }
1395
+ function useItem(path) {
1396
+ useScopeKey();
1397
+ return useQuery({
1398
+ queryKey: vfsKeys.item(path),
1399
+ queryFn: () => fetchVfsItem(path),
1400
+ enabled: !!path
1401
+ });
1402
+ }
1403
+ function useItemById(id) {
1404
+ useScopeKey();
1405
+ return useQuery({
1406
+ queryKey: vfsKeys.itemById(id),
1407
+ queryFn: () => fetchVfsItemById(id),
1408
+ enabled: !!id
1409
+ });
1410
+ }
1411
+ function useList(path) {
1412
+ useScopeKey();
1413
+ return useQuery({
1414
+ queryKey: vfsKeys.children(path),
1415
+ queryFn: () => fetchVfsChildren(path),
1416
+ enabled: !!path
1417
+ });
1418
+ }
1419
+ function useChildren(path) {
1420
+ const query = useList(path);
1421
+ return {
1422
+ ...query,
1423
+ children: query.data ?? []
1424
+ };
1425
+ }
1426
+ function useTree(path, depth = 1) {
1427
+ useScopeKey();
1428
+ return useQuery({
1429
+ queryKey: vfsKeys.tree(path, depth),
1430
+ queryFn: () => fetchVfsTree(path, depth),
1431
+ enabled: !!path
1432
+ });
1433
+ }
1434
+ function useSearch(query, itemTypes) {
1435
+ useScopeKey();
1436
+ return useQuery({
1437
+ queryKey: vfsKeys.search(query, itemTypes),
1438
+ queryFn: async () => {
1439
+ const params = { query };
1440
+ if (itemTypes?.length) params.item_types = itemTypes;
1441
+ const response = await gatewayCall("search", params);
1442
+ return (response.content?.items || response.items || []).map(normalizeItem);
1443
+ },
1444
+ enabled: !!query && query.length > 0
1445
+ });
1446
+ }
1447
+ function useItems(filter) {
1448
+ useScopeKey();
1449
+ const stableFilter = useMemo(() => filter, [JSON.stringify(filter)]);
1450
+ return useQuery({
1451
+ queryKey: vfsKeys.items(
1452
+ stableFilter?.type ?? "",
1453
+ stableFilter
1454
+ ),
1455
+ queryFn: () => fetchItems(stableFilter),
1456
+ enabled: !!stableFilter?.type,
1457
+ select: (result) => result.items
1458
+ });
1459
+ }
1460
+ function useInfiniteItems(filter) {
1461
+ useScopeKey();
1462
+ const PAGE_SIZE = filter?.limit ?? 20;
1463
+ const stableFilter = useMemo(() => filter, [JSON.stringify(filter)]);
1464
+ const query = useInfiniteQuery({
1465
+ queryKey: ["vfs", getScope() ?? "__unscoped__", "infinite-items", stableFilter?.type ?? "", JSON.stringify(stableFilter ?? {})],
1466
+ queryFn: async ({ pageParam = 0 }) => {
1467
+ return fetchItems({
1468
+ ...stableFilter,
1469
+ limit: PAGE_SIZE,
1470
+ offset: pageParam
1471
+ });
1472
+ },
1473
+ initialPageParam: 0,
1474
+ getNextPageParam: (lastPage, _allPages, lastPageParam) => {
1475
+ if (lastPage.items.length < PAGE_SIZE) return void 0;
1476
+ return lastPageParam + lastPage.items.length;
1477
+ },
1478
+ enabled: !!stableFilter?.type
1479
+ });
1480
+ const allItems = useMemo(() => {
1481
+ if (!query.data?.pages) return [];
1482
+ const seen = /* @__PURE__ */ new Set();
1483
+ const flat = [];
1484
+ for (const page of query.data.pages) {
1485
+ for (const item of page.items) {
1486
+ if (item.id && !seen.has(item.id)) {
1487
+ seen.add(item.id);
1488
+ flat.push(item);
1489
+ }
1490
+ }
1491
+ }
1492
+ return flat;
1493
+ }, [query.data?.pages]);
1494
+ return {
1495
+ allItems,
1496
+ fetchNextPage: query.fetchNextPage,
1497
+ isFetchingNextPage: query.isFetchingNextPage,
1498
+ isExhausted: !query.hasNextPage,
1499
+ isLoading: query.isLoading,
1500
+ isFetching: query.isFetching,
1501
+ error: query.error,
1502
+ totalHint: query.data?.pages?.[0]?.total_hint
1503
+ };
1504
+ }
1505
+ function useInfiniteChildren(path) {
1506
+ useScopeKey();
1507
+ const PAGE_SIZE = 20;
1508
+ const query = useInfiniteQuery({
1509
+ queryKey: ["vfs", getScope() ?? "__unscoped__", "infinite-children", path ?? ""],
1510
+ queryFn: async ({ pageParam = 0 }) => {
1511
+ return fetchVfsChildrenPage(path, PAGE_SIZE, pageParam);
1512
+ },
1513
+ initialPageParam: 0,
1514
+ getNextPageParam: (lastPage, _allPages, lastPageParam) => {
1515
+ if (lastPage.items.length < PAGE_SIZE) return void 0;
1516
+ return lastPageParam + lastPage.items.length;
1517
+ },
1518
+ enabled: !!path
1519
+ });
1520
+ const allItems = useMemo(() => {
1521
+ if (!query.data?.pages) return [];
1522
+ const seen = /* @__PURE__ */ new Set();
1523
+ const flat = [];
1524
+ for (const page of query.data.pages) {
1525
+ for (const item of page.items) {
1526
+ if (item.id && !seen.has(item.id)) {
1527
+ seen.add(item.id);
1528
+ flat.push(item);
1529
+ }
1530
+ }
1531
+ }
1532
+ return flat;
1533
+ }, [query.data?.pages]);
1534
+ return {
1535
+ allItems,
1536
+ fetchNextPage: query.fetchNextPage,
1537
+ isFetchingNextPage: query.isFetchingNextPage,
1538
+ isExhausted: !query.hasNextPage,
1539
+ isLoading: query.isLoading,
1540
+ isFetching: query.isFetching,
1541
+ error: query.error,
1542
+ totalHint: query.data?.pages?.[0]?.total_hint
1543
+ };
1544
+ }
1545
+ function useEdges(itemId) {
1546
+ useScopeKey();
1547
+ return useQuery({
1548
+ queryKey: vfsKeys.edges(itemId),
1549
+ queryFn: () => fetchEdgesForItem(itemId),
1550
+ enabled: !!itemId
1551
+ });
1552
+ }
1553
+ function useTypes(scopeId) {
1554
+ useScopeKey();
1555
+ return useQuery({
1556
+ queryKey: vfsKeys.types(scopeId),
1557
+ queryFn: () => fetchTypeDefinitions(scopeId)
1558
+ });
1559
+ }
1560
+ function useType(typeKey, scopeId) {
1561
+ const typesQuery = useTypes(scopeId);
1562
+ return {
1563
+ ...typesQuery,
1564
+ data: typesQuery.data?.get(typeKey) ?? void 0
1565
+ };
1566
+ }
1567
+ function useOpen(path, options) {
1568
+ useScopeKey();
1569
+ const stableArgs = useMemo(
1570
+ () => options?.arguments,
1571
+ [JSON.stringify(options?.arguments)]
1572
+ );
1573
+ const stableMode = options?.mode;
1574
+ const stableStrategy = options?.strategy;
1575
+ return useQuery({
1576
+ queryKey: vfsKeys.openEnvelope(path, stableStrategy),
1577
+ queryFn: () => fetchOpenEnvelope(path, {
1578
+ mode: stableMode,
1579
+ strategy: stableStrategy,
1580
+ arguments: stableArgs
1581
+ }),
1582
+ enabled: !!path
1583
+ });
1584
+ }
1585
+ var EDGE_TYPE_LABELS = {
1586
+ related_to: "Related To",
1587
+ depends_on: "Depends On",
1588
+ blocks: "Blocks",
1589
+ parent_of: "Parent Of",
1590
+ child_of: "Child Of",
1591
+ references: "References",
1592
+ implements: "Implements",
1593
+ extends: "Extends",
1594
+ contains: "Contains",
1595
+ belongs_to: "Belongs To",
1596
+ created_by: "Created By",
1597
+ assigned_to: "Assigned To",
1598
+ tagged_with: "Tagged With",
1599
+ linked_to: "Linked To"
1600
+ };
1601
+ function useTypeHelpers(scopeId) {
1602
+ const typesQuery = useTypes(scopeId);
1603
+ const types = typesQuery.data ?? /* @__PURE__ */ new Map();
1604
+ return useMemo(() => {
1605
+ const getTypeDefinition = (typeKey) => types.get(typeKey);
1606
+ const canHaveChildren = (item) => {
1607
+ const td = types.get(item.item_type);
1608
+ return td?.is_container ?? false;
1609
+ };
1610
+ const isScope = (item) => {
1611
+ const td = types.get(item.item_type);
1612
+ return td?.is_scope ?? false;
1613
+ };
1614
+ const isScopeType = (typeKey) => {
1615
+ const td = types.get(typeKey);
1616
+ return td?.is_scope ?? false;
1617
+ };
1618
+ const isContainerType = (typeKey) => {
1619
+ const td = types.get(typeKey);
1620
+ return td?.is_container ?? false;
1621
+ };
1622
+ const getTypeColor = (typeKey) => {
1623
+ const td = types.get(typeKey);
1624
+ return td?.color ?? "gray";
1625
+ };
1626
+ const getTypeIcon = (typeKey) => {
1627
+ const td = types.get(typeKey);
1628
+ return td?.icon ?? "File";
1629
+ };
1630
+ const getTypeDisplayName = (typeKey) => {
1631
+ const td = types.get(typeKey);
1632
+ return td?.display_name ?? typeKey;
1633
+ };
1634
+ const getKnownEdgeTypes = () => Object.entries(EDGE_TYPE_LABELS).map(([key, label]) => ({ key, label }));
1635
+ const getEdgeTypeLabel = (edgeType) => EDGE_TYPE_LABELS[edgeType] ?? edgeType.replace(/_/g, " ");
1636
+ return {
1637
+ canHaveChildren,
1638
+ isScope,
1639
+ isScopeType,
1640
+ isContainerType,
1641
+ getTypeDefinition,
1642
+ getTypeColor,
1643
+ getTypeIcon,
1644
+ getTypeDisplayName,
1645
+ getKnownEdgeTypes,
1646
+ getEdgeTypeLabel,
1647
+ allTypes: types,
1648
+ loading: typesQuery.isLoading
1649
+ };
1650
+ }, [types, typesQuery.isLoading]);
1651
+ }
1652
+ function useMemberFocus(scopeId, itemTypes, limit) {
1653
+ useScopeKey();
1654
+ return useQuery({
1655
+ queryKey: vfsKeys.memberFocus(scopeId),
1656
+ queryFn: () => fetchMemberFocus(scopeId, itemTypes, limit),
1657
+ enabled: !!scopeId
1658
+ });
1659
+ }
1660
+ function useItemHistory(itemId, limit = 50, offset = 0) {
1661
+ return useQuery({
1662
+ queryKey: vfsKeys.itemHistory(itemId, String(limit), String(offset)),
1663
+ queryFn: () => fetchItemHistory(itemId, limit, offset),
1664
+ enabled: !!itemId
1665
+ });
1666
+ }
1667
+ function useRecentActivity(scopePath, limit = 50, offset = 0) {
1668
+ useScopeKey();
1669
+ return useQuery({
1670
+ queryKey: vfsKeys.recentActivity(scopePath, String(limit), String(offset)),
1671
+ queryFn: () => fetchRecentActivity(scopePath, limit, offset),
1672
+ enabled: !!scopePath
1673
+ });
1674
+ }
1675
+ function useScope() {
1676
+ const [data, setData] = useState(null);
1677
+ const [loading, setLoading] = useState(true);
1678
+ const [error, setError] = useState(null);
1679
+ useEffect(() => {
1680
+ let cancelled = false;
1681
+ initSession().then((entry) => {
1682
+ if (!cancelled) {
1683
+ const config = typeof window !== "undefined" && window.__FSAOS_CONFIG__ || {};
1684
+ setData({
1685
+ path: entry.scope_path,
1686
+ scope_id: entry.scope_id,
1687
+ fractal_id: entry.fractal_id,
1688
+ instance_name: entry.instance_name,
1689
+ display_name: entry.display_name,
1690
+ componentPath: config.componentPath || "",
1691
+ isEmbed: !!config.embedToken
1692
+ });
1693
+ setLoading(false);
1694
+ }
1695
+ }).catch((err) => {
1696
+ if (!cancelled) {
1697
+ setError(err instanceof Error ? err : new Error(String(err)));
1698
+ setLoading(false);
1699
+ }
1700
+ });
1701
+ return () => {
1702
+ cancelled = true;
1703
+ };
1704
+ }, []);
1705
+ return { data, loading, error };
1706
+ }
1707
+ function useAccounts() {
1708
+ const { user } = useAuth();
1709
+ const [currentAccount, setCurrentAccount] = useState(null);
1710
+ const [mountedPath, setMountedPath] = useState(null);
1711
+ const autoSelectedRef = useRef(false);
1712
+ const query = useQuery({
1713
+ queryKey: vfsKeys.memberships(user?.id ?? "anonymous"),
1714
+ queryFn: async () => {
1715
+ const result = await gatewayCall("list-memberships", {});
1716
+ const memberships = result.memberships || [];
1717
+ return memberships.map((m) => ({
1718
+ accountId: m.account_id,
1719
+ path: m.account_path,
1720
+ name: m.account_name,
1721
+ role: m.role || "member",
1722
+ typeData: m.account_type_data || {},
1723
+ joinedAt: m.joined_at,
1724
+ via: m.via,
1725
+ placement: m.placement,
1726
+ subordinateTo: m.subordinate_to ?? null,
1727
+ cascadeAnchorId: m.cascade_anchor_id ?? null,
1728
+ isAccount: m.is_account ?? void 0,
1729
+ isSpace: m.is_space ?? void 0,
1730
+ isOwner: m.is_owner ?? void 0,
1731
+ billingResponsible: m.billing_responsible ?? void 0,
1732
+ grantedVia: m.granted_via ?? null,
1733
+ grantedUnderAccountId: m.granted_under_account_id ?? null,
1734
+ personalSpaceId: m.personal_space_id ?? null,
1735
+ personalSpacePath: m.personal_space_path ?? null
1736
+ }));
1737
+ },
1738
+ enabled: !!user?.id,
1739
+ staleTime: 1e3 * 60 * 2
1740
+ });
1741
+ const allMemberships = query.data ?? [];
1742
+ const switcherAccounts = useMemo(() => {
1743
+ return allMemberships.filter((a) => {
1744
+ if (a.placement !== void 0) {
1745
+ return a.placement === "switcher_entry" && a.isAccount === true;
1746
+ }
1747
+ return true;
1748
+ });
1749
+ }, [allMemberships]);
1750
+ useEffect(() => {
1751
+ if (!user?.id) {
1752
+ autoSelectedRef.current = false;
1753
+ setCurrentAccount(null);
1754
+ setMountedPath(null);
1755
+ return;
1756
+ }
1757
+ if (autoSelectedRef.current) return;
1758
+ if (switcherAccounts.length === 0) return;
1759
+ if (switcherAccounts.length === 1) {
1760
+ setCurrentAccount(switcherAccounts[0]);
1761
+ setScope(switcherAccounts[0].path);
1762
+ initVfsRealtimeWs(switcherAccounts[0].accountId);
1763
+ autoSelectedRef.current = true;
1764
+ } else if (switcherAccounts.length > 1) {
1765
+ autoSelectedRef.current = true;
1766
+ }
1767
+ }, [user?.id, switcherAccounts]);
1768
+ const switchAccount = useCallback((accountPath) => {
1769
+ const account = switcherAccounts.find((a) => a.path === accountPath);
1770
+ if (account) {
1771
+ setCurrentAccount(account);
1772
+ setScope(account.path);
1773
+ initVfsRealtimeWs(account.accountId);
1774
+ setMountedPath(null);
1775
+ }
1776
+ }, [switcherAccounts]);
1777
+ const mountScope = useCallback((scopePath) => {
1778
+ if (mountedPath) {
1779
+ unmountRealtimeScope(mountedPath);
1780
+ }
1781
+ setMountedPath(scopePath);
1782
+ mountRealtimeScope(scopePath);
1783
+ }, [mountedPath]);
1784
+ return {
1785
+ accounts: switcherAccounts,
1786
+ allMemberships,
1787
+ currentAccount,
1788
+ mountedScope: mountedPath,
1789
+ switchAccount,
1790
+ mountScope,
1791
+ loading: query.isLoading,
1792
+ error: query.error
1793
+ };
1794
+ }
1795
+ function useAuth() {
1796
+ const [user, setUser] = useState(null);
1797
+ const [session, setSession] = useState(null);
1798
+ const [loading, setLoading] = useState(true);
1799
+ const subscriptionRef = useRef(null);
1800
+ useEffect(() => {
1801
+ supabase.auth.getSession().then(({ data, error }) => {
1802
+ if (error) {
1803
+ console.warn("[useAuth] getSession error, clearing stale session:", error.message);
1804
+ supabase.auth.signOut().catch(() => {
1805
+ });
1806
+ setSession(null);
1807
+ setUser(null);
1808
+ setLoading(false);
1809
+ return;
1810
+ }
1811
+ setSession(data?.session ?? null);
1812
+ setUser(data?.session?.user ?? null);
1813
+ setLoading(false);
1814
+ }).catch((err) => {
1815
+ console.warn("[useAuth] getSession threw, clearing stale session:", err?.message);
1816
+ supabase.auth.signOut().catch(() => {
1817
+ });
1818
+ setSession(null);
1819
+ setUser(null);
1820
+ setLoading(false);
1821
+ });
1822
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(
1823
+ (event, newSession) => {
1824
+ if (event === "TOKEN_REFRESHED" && !newSession) {
1825
+ console.warn("[useAuth] TOKEN_REFRESHED with null session \u2014 signing out");
1826
+ supabase.auth.signOut().catch(() => {
1827
+ });
1828
+ setSession(null);
1829
+ setUser(null);
1830
+ setLoading(false);
1831
+ return;
1832
+ }
1833
+ setSession(newSession);
1834
+ setUser(newSession?.user ?? null);
1835
+ setLoading(false);
1836
+ }
1837
+ );
1838
+ subscriptionRef.current = subscription;
1839
+ return () => {
1840
+ subscription?.unsubscribe();
1841
+ };
1842
+ }, []);
1843
+ const signIn = useCallback(async (params) => {
1844
+ if (params.provider) {
1845
+ return supabase.auth.signInWithOAuth({
1846
+ provider: params.provider,
1847
+ options: params.redirectTo ? { redirectTo: params.redirectTo } : { redirectTo: window.location.href }
1848
+ });
1849
+ }
1850
+ return supabase.auth.signInWithPassword({
1851
+ email: params.email,
1852
+ password: params.password
1853
+ });
1854
+ }, []);
1855
+ const signUp = useCallback(async (params) => {
1856
+ return supabase.auth.signUp({
1857
+ email: params.email,
1858
+ password: params.password,
1859
+ options: { data: params.metadata || {} }
1860
+ });
1861
+ }, []);
1862
+ const signOut = useCallback(async () => {
1863
+ await supabase.auth.signOut();
1864
+ resetAllSdkState();
1865
+ queryClient.clear();
1866
+ }, []);
1867
+ const resetPassword = useCallback(async (email) => {
1868
+ const { error } = await supabase.auth.resetPasswordForEmail(email);
1869
+ if (error) throw error;
1870
+ }, []);
1871
+ const signInWithMagicLink = useCallback(async (email, redirectTo) => {
1872
+ const { error } = await supabase.auth.signInWithOtp({
1873
+ email,
1874
+ options: redirectTo ? { emailRedirectTo: redirectTo } : { emailRedirectTo: window.location.href }
1875
+ });
1876
+ if (error) throw error;
1877
+ }, []);
1878
+ return { user, session, loading, signIn, signUp, signOut, resetPassword, signInWithMagicLink };
1879
+ }
1880
+ function useAsset(idOrPath) {
1881
+ const [data, setData] = useState(null);
1882
+ const [loading, setLoading] = useState(false);
1883
+ const [error, setError] = useState(null);
1884
+ useEffect(() => {
1885
+ if (!idOrPath) {
1886
+ setData(null);
1887
+ setLoading(false);
1888
+ return;
1889
+ }
1890
+ const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(idOrPath);
1891
+ if (isUUID) {
1892
+ setLoading(true);
1893
+ gatewayCall("resolve-asset", { asset_id: idOrPath }).then((result) => {
1894
+ if (result.success && result.public_url) {
1895
+ setData({
1896
+ url: result.public_url,
1897
+ id: result.asset_id,
1898
+ name: result.name,
1899
+ mimeType: result.mime_type,
1900
+ width: result.width,
1901
+ height: result.height,
1902
+ sizeBytes: result.size_bytes
1903
+ });
1904
+ } else {
1905
+ setError(new Error(result.error || "Asset not found"));
1906
+ }
1907
+ setLoading(false);
1908
+ }).catch((e) => {
1909
+ setError(e instanceof Error ? e : new Error(String(e)));
1910
+ setLoading(false);
1911
+ });
1912
+ } else {
1913
+ const config = typeof window !== "undefined" && window.__FSAOS_CONFIG__ || {};
1914
+ const edgeBaseUrl = config.edgeBaseUrl || "";
1915
+ setData({ url: edgeBaseUrl + idOrPath });
1916
+ setLoading(false);
1917
+ }
1918
+ }, [idOrPath]);
1919
+ return { data, loading, error };
1920
+ }
1921
+ var componentCache = {};
1922
+ var componentCssCache = {};
1923
+ registerCleanup(() => {
1924
+ for (const key of Object.keys(componentCache)) {
1925
+ delete componentCache[key];
1926
+ }
1927
+ });
1928
+ var _sdkExports = null;
1929
+ function __registerSdkExports(exports) {
1930
+ _sdkExports = exports;
1931
+ if (typeof window !== "undefined" && !window.__FSAOS_GATEWAY__) {
1932
+ window.__FSAOS_GATEWAY__ = exports;
1933
+ }
1934
+ }
1935
+ function sdkRequireShim(name) {
1936
+ switch (name) {
1937
+ case "react":
1938
+ return typeof window !== "undefined" && window.React || React;
1939
+ case "react-dom":
1940
+ case "react-dom/client":
1941
+ return typeof window !== "undefined" && window.ReactDOM || ReactDOM;
1942
+ case "react/jsx-runtime":
1943
+ case "react/jsx-dev-runtime":
1944
+ return typeof window !== "undefined" && window.React ? { jsx: window.React.createElement, jsxs: window.React.createElement, Fragment: window.React.Fragment } : JsxRuntime;
1945
+ case "@fsaos/gateway":
1946
+ case "@fsaos/react":
1947
+ return typeof window !== "undefined" && window.__FSAOS_GATEWAY__ || _sdkExports || {};
1948
+ case "@fsaos/ui":
1949
+ return typeof window !== "undefined" && window.__FSAOS_UI__ || {};
1950
+ case "@fsaos/theme":
1951
+ return {};
1952
+ default:
1953
+ console.warn(`[useComponent] Unknown module requested: ${name}`);
1954
+ return {};
1955
+ }
1956
+ }
1957
+ function scopeComponentCss(cssText, scopeClass) {
1958
+ return cssText.replace(
1959
+ /^(\s*)(:root|body)(\s*[{,])/gm,
1960
+ (_match, ws2, _sel, rest) => `${ws2}.${scopeClass}${rest}`
1961
+ );
1962
+ }
1963
+ async function loadComponentCss(cssUrl, scopeId) {
1964
+ if (componentCssCache[cssUrl]) return;
1965
+ try {
1966
+ const response = await fetch(cssUrl);
1967
+ if (!response.ok) return;
1968
+ let cssText = await response.text();
1969
+ if (!cssText.trim()) return;
1970
+ if (cssText.includes("No CSS bundle for this component")) return;
1971
+ if (scopeId && !cssText.includes(`.fsaos-c-${scopeId}`)) ;
1972
+ const style = document.createElement("style");
1973
+ style.setAttribute("data-fsaos-component-css", cssUrl);
1974
+ if (scopeId) ;
1975
+ style.textContent = cssText;
1976
+ document.head.appendChild(style);
1977
+ componentCssCache[cssUrl] = style;
1978
+ } catch {
1979
+ }
1980
+ }
1981
+ function evaluateBundle(jsCode) {
1982
+ let registered = null;
1983
+ const previousRegister = window.__FSAOS_REGISTER__;
1984
+ window.__FSAOS_REGISTER__ = (component) => {
1985
+ registered = component;
1986
+ };
1987
+ try {
1988
+ const wrappedCode = [
1989
+ "var require = arguments[0];",
1990
+ jsCode,
1991
+ "return window.__FSAOS_COMPONENT__;"
1992
+ ].join("\n");
1993
+ const fn = new Function(wrappedCode);
1994
+ const fromIife = fn(sdkRequireShim);
1995
+ const exported = registered ?? fromIife;
1996
+ if (!exported) return null;
1997
+ return exported.default || exported;
1998
+ } finally {
1999
+ if (previousRegister !== void 0) {
2000
+ window.__FSAOS_REGISTER__ = previousRegister;
2001
+ } else {
2002
+ delete window.__FSAOS_REGISTER__;
2003
+ }
2004
+ }
2005
+ }
2006
+ function useComponent(path) {
2007
+ const [Component, setComponent] = useState(null);
2008
+ const [loading, setLoading] = useState(true);
2009
+ const [error, setError] = useState(null);
2010
+ useEffect(() => {
2011
+ if (!path) return;
2012
+ let cancelled = false;
2013
+ setLoading(true);
2014
+ setError(null);
2015
+ const config = typeof window !== "undefined" && window.__FSAOS_CONFIG__ || {};
2016
+ const edgeBaseUrl = config.edgeBaseUrl || "";
2017
+ (async () => {
2018
+ try {
2019
+ let version = 1;
2020
+ let cacheKey = path + "@v1";
2021
+ let bundleUrl = edgeBaseUrl + path + "/__bundle.js";
2022
+ try {
2023
+ const metaResponse = await fetch(edgeBaseUrl + path + "/__meta", { cache: "no-store" });
2024
+ if (metaResponse.ok) {
2025
+ const meta = await metaResponse.json();
2026
+ version = meta.version || 1;
2027
+ cacheKey = path + "@v" + version;
2028
+ bundleUrl = edgeBaseUrl + path + "/__bundle.v" + version + ".js";
2029
+ }
2030
+ } catch {
2031
+ bundleUrl = edgeBaseUrl + path + "/__bundle.js?v=" + Date.now();
2032
+ }
2033
+ if (cancelled) return;
2034
+ if (componentCache[cacheKey]) {
2035
+ setComponent(() => componentCache[cacheKey]);
2036
+ setLoading(false);
2037
+ return;
2038
+ }
2039
+ const jsResponse = await fetch(bundleUrl);
2040
+ if (!jsResponse.ok) {
2041
+ throw new Error(
2042
+ `Failed to fetch bundle: ${jsResponse.status} ${jsResponse.statusText} (${bundleUrl})`
2043
+ );
2044
+ }
2045
+ const jsCode = await jsResponse.text();
2046
+ if (cancelled) return;
2047
+ const cssUrl = bundleUrl.replace(/__bundle(\.v\d+)?\.js(\?.*)?$/, "__bundle$1.css");
2048
+ loadComponentCss(cssUrl).catch(() => {
2049
+ });
2050
+ const resolved = evaluateBundle(jsCode);
2051
+ if (!resolved) {
2052
+ throw new Error(
2053
+ "Bundle did not export a component. Expected window.__FSAOS_COMPONENT__ (FSAOS build) or a __FSAOS_REGISTER__(Component) call."
2054
+ );
2055
+ }
2056
+ if (typeof resolved !== "function") {
2057
+ throw new Error(`Bundle export is not a React component (got ${typeof resolved})`);
2058
+ }
2059
+ componentCache[cacheKey] = resolved;
2060
+ if (!cancelled) {
2061
+ setComponent(() => resolved);
2062
+ setLoading(false);
2063
+ }
2064
+ } catch (err) {
2065
+ if (!cancelled) {
2066
+ setError(err instanceof Error ? err : new Error(String(err)));
2067
+ setLoading(false);
2068
+ }
2069
+ }
2070
+ })();
2071
+ return () => {
2072
+ cancelled = true;
2073
+ };
2074
+ }, [path]);
2075
+ return { Component, loading, error };
2076
+ }
2077
+ var THEME_PROPERTIES = [
2078
+ "--color-primary",
2079
+ "--color-secondary",
2080
+ "--color-background",
2081
+ "--color-surface",
2082
+ "--color-text",
2083
+ "--color-text-secondary",
2084
+ "--color-border",
2085
+ "--color-accent",
2086
+ "--color-success",
2087
+ "--color-warning",
2088
+ "--color-error",
2089
+ "--font-family-heading",
2090
+ "--font-family-body",
2091
+ "--border-radius",
2092
+ "--spacing-unit"
2093
+ ];
2094
+ function useTheme() {
2095
+ const themeData = useMemo(() => {
2096
+ if (typeof document === "undefined") return { tokens: {} };
2097
+ const vars = {};
2098
+ const style = getComputedStyle(document.documentElement);
2099
+ THEME_PROPERTIES.forEach((p) => {
2100
+ vars[p] = style.getPropertyValue(p).trim();
2101
+ });
2102
+ return { tokens: vars };
2103
+ }, []);
2104
+ return { data: themeData, loading: false, error: null };
2105
+ }
2106
+ function usePermission(_action, _path) {
2107
+ return { data: { allowed: true }, loading: false, error: null };
2108
+ }
2109
+ function usePermissions(_path, actions) {
2110
+ const perms = {};
2111
+ (actions || []).forEach((a) => {
2112
+ perms[a] = true;
2113
+ });
2114
+ return { data: perms, loading: false, error: null };
2115
+ }
2116
+ var _principalIdCache = null;
2117
+ var _principalIdPromise = null;
2118
+ registerCleanup(() => {
2119
+ _principalIdCache = null;
2120
+ _principalIdPromise = null;
2121
+ });
2122
+ async function resolvePrincipalId(authUserId) {
2123
+ let uid = authUserId;
2124
+ if (!uid) {
2125
+ const { data } = await supabase.auth.getSession();
2126
+ uid = data?.session?.user?.id;
2127
+ }
2128
+ if (!uid) return null;
2129
+ if (_principalIdCache && _principalIdCache.authUserId === uid) {
2130
+ return _principalIdCache.principalId;
2131
+ }
2132
+ if (_principalIdPromise) return _principalIdPromise;
2133
+ _principalIdPromise = (async () => {
2134
+ try {
2135
+ const { data, error } = await supabase.from("os_principals").select("id").eq("auth_user_id", uid).eq("principal_type", "user").maybeSingle();
2136
+ if (error) {
2137
+ console.warn("[SDK] Could not resolve principal_id:", error.message);
2138
+ return null;
2139
+ }
2140
+ const principalId = data?.id ?? null;
2141
+ _principalIdCache = { authUserId: uid, principalId };
2142
+ return principalId;
2143
+ } finally {
2144
+ _principalIdPromise = null;
2145
+ }
2146
+ })();
2147
+ return _principalIdPromise;
2148
+ }
2149
+ function usePrincipal() {
2150
+ const authResult = useAuth();
2151
+ const user = authResult.user;
2152
+ const [principalId, setPrincipalId] = useState(
2153
+ (_principalIdCache?.authUserId === user?.id ? _principalIdCache?.principalId : null) ?? null
2154
+ );
2155
+ const prevUserIdRef = useRef(null);
2156
+ useEffect(() => {
2157
+ if (!user?.id) {
2158
+ setPrincipalId(null);
2159
+ prevUserIdRef.current = null;
2160
+ return;
2161
+ }
2162
+ if (prevUserIdRef.current === user.id && principalId !== null) return;
2163
+ prevUserIdRef.current = user.id;
2164
+ resolvePrincipalId(user.id).then((id) => {
2165
+ setPrincipalId(id);
2166
+ });
2167
+ }, [user?.id]);
2168
+ if (authResult.loading) {
2169
+ return { data: null, loading: true, error: null };
2170
+ }
2171
+ if (!user) {
2172
+ return {
2173
+ data: { id: "anonymous", principalId: null, role: "viewer", authenticated: false },
2174
+ loading: false,
2175
+ error: null
2176
+ };
2177
+ }
2178
+ return {
2179
+ data: {
2180
+ id: user.id,
2181
+ principalId,
2182
+ email: user.email,
2183
+ role: user.role || user.app_metadata?.role || "user",
2184
+ authenticated: true,
2185
+ metadata: user.user_metadata || {}
2186
+ },
2187
+ loading: false,
2188
+ error: null
2189
+ };
2190
+ }
2191
+ function useTool() {
2192
+ return { data: null, loading: false, error: null };
2193
+ }
2194
+ function ComponentRenderer(props) {
2195
+ const { path, ...rest } = props;
2196
+ const result = useComponent(path);
2197
+ const Comp = result.Component;
2198
+ const { loading, error } = result;
2199
+ if (loading) return createElement("div", { className: "fsaos-loading" }, "");
2200
+ if (error) return createElement("div", { className: "fsaos-error" }, "Failed to load component");
2201
+ if (!Comp) return null;
2202
+ return createElement(Comp, rest);
2203
+ }
2204
+ function KernelProvider(props) {
2205
+ return props.children;
2206
+ }
2207
+ function useChannelMessages(options) {
2208
+ const scopeKey = useScopeKey();
2209
+ const {
2210
+ channelPath,
2211
+ parentMessageId,
2212
+ realtime = true,
2213
+ enabled: userEnabled
2214
+ } = options;
2215
+ const isEnabled = userEnabled !== void 0 ? userEnabled && !!channelPath : !!channelPath;
2216
+ const stableKey = useMemo(
2217
+ () => vfsKeys.channelMessages(channelPath ?? "", parentMessageId),
2218
+ [channelPath, parentMessageId, scopeKey]
2219
+ );
2220
+ const qc = useQueryClient();
2221
+ const debounceRef = useRef(null);
2222
+ const query = useQuery({
2223
+ queryKey: stableKey,
2224
+ queryFn: () => fetchChannelMessages(channelPath, parentMessageId),
2225
+ enabled: isEnabled
2226
+ });
2227
+ useEffect(() => {
2228
+ if (!realtime || !isEnabled) return;
2229
+ const unsub = subscribeToEvents(
2230
+ "vfs_change",
2231
+ () => {
2232
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2233
+ debounceRef.current = setTimeout(() => {
2234
+ qc.invalidateQueries({ queryKey: stableKey });
2235
+ }, 500);
2236
+ },
2237
+ (data) => {
2238
+ return data.item_type === "message" && typeof data.path === "string" && data.path.startsWith(channelPath);
2239
+ }
2240
+ );
2241
+ return () => {
2242
+ unsub();
2243
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2244
+ };
2245
+ }, [realtime, isEnabled, channelPath, stableKey, qc]);
2246
+ const messages = query.data ?? [];
2247
+ return {
2248
+ messages,
2249
+ threads: useMemo(() => messages.filter((m) => m.reply_count > 0), [messages]),
2250
+ isLoading: query.isLoading,
2251
+ isFetching: query.isFetching,
2252
+ error: query.error,
2253
+ refetch: query.refetch
2254
+ };
2255
+ }
2256
+ function useInfiniteChannelMessages(options) {
2257
+ useScopeKey();
2258
+ const { channelPath, parentMessageId, limit = 50, realtime = true, enabled = true } = options;
2259
+ const isThread = !!parentMessageId;
2260
+ const queryEnabled = enabled && !!channelPath;
2261
+ const query = useInfiniteQuery({
2262
+ queryKey: [...vfsKeys.channelMessages(channelPath || "", parentMessageId), "infinite", limit],
2263
+ queryFn: async ({ pageParam }) => {
2264
+ return fetchChannelMessagesPage(channelPath, {
2265
+ parentMessageId,
2266
+ limit,
2267
+ before_seq: isThread ? void 0 : pageParam,
2268
+ after_seq: isThread ? pageParam : void 0
2269
+ });
2270
+ },
2271
+ initialPageParam: void 0,
2272
+ getNextPageParam: (lastPage) => {
2273
+ if (!isThread) return void 0;
2274
+ if (lastPage.messages.length < limit) return void 0;
2275
+ const maxSeq = lastPage.messages[lastPage.messages.length - 1]?.seq;
2276
+ return maxSeq;
2277
+ },
2278
+ getPreviousPageParam: (firstPage) => {
2279
+ if (isThread) return void 0;
2280
+ if (firstPage.messages.length < limit) return void 0;
2281
+ const minSeq = firstPage.messages[0]?.seq;
2282
+ return minSeq;
2283
+ },
2284
+ enabled: queryEnabled
2285
+ });
2286
+ const messages = useMemo(() => {
2287
+ if (!query.data?.pages) return [];
2288
+ const seen = /* @__PURE__ */ new Set();
2289
+ const flat = [];
2290
+ for (const page of query.data.pages) {
2291
+ for (const msg of page.messages) {
2292
+ if (!seen.has(msg.id)) {
2293
+ seen.add(msg.id);
2294
+ flat.push(msg);
2295
+ }
2296
+ }
2297
+ }
2298
+ return flat.sort((a, b) => a.seq - b.seq);
2299
+ }, [query.data?.pages]);
2300
+ useEffect(() => {
2301
+ if (!realtime || !channelPath || !queryEnabled) return;
2302
+ const unsub = subscribeToEvents(channelPath, (event) => {
2303
+ if (event.event_type === "vfs_change") {
2304
+ query.refetch();
2305
+ }
2306
+ });
2307
+ return unsub;
2308
+ }, [realtime, channelPath, queryEnabled]);
2309
+ return {
2310
+ messages,
2311
+ threads: messages.filter((m) => m.reply_count > 0),
2312
+ isLoading: query.isLoading,
2313
+ isFetching: query.isFetching,
2314
+ error: query.error,
2315
+ refetch: query.refetch,
2316
+ // Backward pagination (top-level: load older)
2317
+ hasPreviousPage: query.hasPreviousPage ?? false,
2318
+ fetchPreviousPage: query.fetchPreviousPage,
2319
+ isFetchingPreviousPage: query.isFetchingPreviousPage ?? false,
2320
+ // Forward pagination (thread: load newer)
2321
+ hasNextPage: query.hasNextPage ?? false,
2322
+ fetchNextPage: query.fetchNextPage,
2323
+ isFetchingNextPage: query.isFetchingNextPage ?? false
2324
+ };
2325
+ }
2326
+ function normalizeAiMode(value) {
2327
+ if (value === "on" || value === "auto") return "on";
2328
+ return "off";
2329
+ }
2330
+ function normalizeChannel(raw) {
2331
+ const td = raw.type_data || {};
2332
+ return {
2333
+ id: raw.channel_id || "",
2334
+ path: raw.channel_path || "",
2335
+ name: raw.channel_name || "",
2336
+ displayName: raw.display_name || raw.channel_name || "",
2337
+ aiMode: normalizeAiMode(raw.ai_mode),
2338
+ isPrivate: raw.is_private || false,
2339
+ isDm: raw.is_dm || false,
2340
+ messageCount: raw.message_count || 0,
2341
+ updatedAt: raw.updated_at || "",
2342
+ chatScopePath: raw.chat_scope_path || "",
2343
+ contextId: raw.context_id || "",
2344
+ contextPath: raw.context_path || "",
2345
+ contextType: raw.context_type || "",
2346
+ contextName: raw.context_name || "",
2347
+ contextDisplayName: raw.context_display_name || "",
2348
+ contextPrincipalId: raw.context_principal_id,
2349
+ accountId: raw.account_id || "",
2350
+ accountPath: raw.account_path || "",
2351
+ accountDisplayName: raw.account_display_name || "",
2352
+ memberRole: raw.member_role || "member",
2353
+ memberPath: raw.member_path || "",
2354
+ modePath: raw.mode_path || "",
2355
+ autoPilot: raw.auto_pilot || td.auto_pilot || false
2356
+ };
2357
+ }
2358
+ function useAllChannels(options = {}) {
2359
+ const scopeKey = useScopeKey();
2360
+ const { realtime = true, enabled = true } = options;
2361
+ const qc = useQueryClient();
2362
+ const debounceRef = useRef(null);
2363
+ const stableKey = useMemo(() => vfsKeys.channels("all"), [scopeKey]);
2364
+ const query = useQuery({
2365
+ queryKey: stableKey,
2366
+ queryFn: async () => {
2367
+ const result = await gatewayCall("list-channels", {});
2368
+ const raw = result.channels || [];
2369
+ return raw.map(normalizeChannel);
2370
+ },
2371
+ enabled
2372
+ });
2373
+ useEffect(() => {
2374
+ if (!realtime || !enabled) return;
2375
+ const unsub = subscribeToEvents(
2376
+ "vfs_change",
2377
+ () => {
2378
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2379
+ debounceRef.current = setTimeout(() => {
2380
+ qc.invalidateQueries({ queryKey: stableKey });
2381
+ }, 1e3);
2382
+ },
2383
+ (data) => data.item_type === "channel" || data.item_type === "message"
2384
+ );
2385
+ const unsub2 = subscribeToEvents(
2386
+ "ccm_change",
2387
+ () => {
2388
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2389
+ debounceRef.current = setTimeout(() => {
2390
+ qc.invalidateQueries({ queryKey: stableKey });
2391
+ }, 1e3);
2392
+ },
2393
+ (data) => data.item_type === "channel" || data.item_type === "message"
2394
+ );
2395
+ return () => {
2396
+ unsub();
2397
+ unsub2();
2398
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2399
+ };
2400
+ }, [realtime, enabled, stableKey, qc]);
2401
+ const channels = query.data ?? [];
2402
+ return {
2403
+ channels,
2404
+ isLoading: query.isLoading,
2405
+ isFetching: query.isFetching,
2406
+ error: query.error,
2407
+ refetch: query.refetch
2408
+ };
2409
+ }
2410
+ function useChannels(options = {}) {
2411
+ const result = useAllChannels(options);
2412
+ const filtered = useMemo(
2413
+ () => result.channels.filter((c) => !c.isDm),
2414
+ [result.channels]
2415
+ );
2416
+ return { ...result, channels: filtered };
2417
+ }
2418
+ function useDmChannels(options = {}) {
2419
+ const result = useAllChannels(options);
2420
+ const filtered = useMemo(
2421
+ () => result.channels.filter((c) => c.isDm),
2422
+ [result.channels]
2423
+ );
2424
+ return { ...result, channels: filtered };
2425
+ }
2426
+ function useNotifications(options = {}) {
2427
+ const scopeKey = useScopeKey();
2428
+ const {
2429
+ filter = "all",
2430
+ limit = 200,
2431
+ realtime = true,
2432
+ enabled = true
2433
+ } = options;
2434
+ const qc = useQueryClient();
2435
+ const debounceRef = useRef(null);
2436
+ const stableKey = useMemo(() => vfsKeys.notifications(filter, limit), [filter, limit, scopeKey]);
2437
+ const query = useQuery({
2438
+ queryKey: stableKey,
2439
+ queryFn: async () => {
2440
+ const params = { limit };
2441
+ if (filter === "unread") params.filter = "unread";
2442
+ const result = await gatewayCall("get-notifications", params);
2443
+ return result.notifications || [];
2444
+ },
2445
+ enabled
2446
+ });
2447
+ useEffect(() => {
2448
+ if (!realtime || !enabled) return;
2449
+ const unsubs = ["vfs_change", "ccm_change"].map(
2450
+ (eventType) => subscribeToEvents(
2451
+ eventType,
2452
+ () => {
2453
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2454
+ debounceRef.current = setTimeout(() => {
2455
+ qc.invalidateQueries({ queryKey: stableKey });
2456
+ }, 2e3);
2457
+ },
2458
+ (data) => data.item_type === "notification" || data.item_type === "message"
2459
+ )
2460
+ );
2461
+ return () => {
2462
+ unsubs.forEach((u) => u());
2463
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2464
+ };
2465
+ }, [realtime, enabled, stableKey, qc]);
2466
+ const notifications = query.data ?? [];
2467
+ return {
2468
+ notifications,
2469
+ isLoading: query.isLoading,
2470
+ isFetching: query.isFetching,
2471
+ error: query.error,
2472
+ refetch: query.refetch
2473
+ };
2474
+ }
2475
+ function useUnreadCounts(options = {}) {
2476
+ const { channels: channelList, realtime = true, enabled = true } = options;
2477
+ const { notifications, isLoading, isFetching, error, refetch } = useNotifications({
2478
+ filter: "unread",
2479
+ limit: 200,
2480
+ realtime,
2481
+ enabled
2482
+ });
2483
+ const unreadByChannel = useMemo(() => {
2484
+ const counts = /* @__PURE__ */ new Map();
2485
+ for (const n of notifications) {
2486
+ if (n.is_unread && n.channel_id) {
2487
+ counts.set(n.channel_id, (counts.get(n.channel_id) || 0) + 1);
2488
+ }
2489
+ }
2490
+ return counts;
2491
+ }, [notifications]);
2492
+ const totalUnread = useMemo(() => {
2493
+ let total = 0;
2494
+ unreadByChannel.forEach((v) => {
2495
+ total += v;
2496
+ });
2497
+ return total;
2498
+ }, [unreadByChannel]);
2499
+ const { dmUnread, channelUnread } = useMemo(() => {
2500
+ if (!channelList) return { dmUnread: 0, channelUnread: 0 };
2501
+ const dmIds = new Set(channelList.filter((c) => c.isDm).map((c) => c.id));
2502
+ let dm = 0;
2503
+ let ch = 0;
2504
+ unreadByChannel.forEach((count, channelId) => {
2505
+ if (dmIds.has(channelId)) dm += count;
2506
+ else ch += count;
2507
+ });
2508
+ return { dmUnread: dm, channelUnread: ch };
2509
+ }, [channelList, unreadByChannel]);
2510
+ return {
2511
+ unreadByChannel,
2512
+ totalUnread,
2513
+ dmUnread,
2514
+ channelUnread,
2515
+ isLoading,
2516
+ isFetching,
2517
+ error,
2518
+ refetch
2519
+ };
2520
+ }
2521
+ function useCreate() {
2522
+ const qc = useQueryClient();
2523
+ return useMutation$1({
2524
+ mutationFn: (params) => gatewayCall("create", params),
2525
+ onSuccess: (_data, variables) => {
2526
+ const parentPath = variables.parent_path;
2527
+ if (parentPath) {
2528
+ qc.invalidateQueries({ queryKey: vfsKeys.children(parentPath) });
2529
+ }
2530
+ }
2531
+ });
2532
+ }
2533
+ function useUpdate() {
2534
+ const qc = useQueryClient();
2535
+ return useMutation$1({
2536
+ mutationFn: (params) => gatewayCall("update", params),
2537
+ onSuccess: (_data, variables) => {
2538
+ const path = variables.path;
2539
+ if (path) {
2540
+ qc.invalidateQueries({ queryKey: vfsKeys.item(path) });
2541
+ const segments = path.split("/");
2542
+ segments.pop();
2543
+ const parentPath = segments.join("/");
2544
+ if (parentPath) {
2545
+ qc.invalidateQueries({ queryKey: vfsKeys.children(parentPath) });
2546
+ }
2547
+ }
2548
+ }
2549
+ });
2550
+ }
2551
+ function useDelete() {
2552
+ const qc = useQueryClient();
2553
+ return useMutation$1({
2554
+ mutationFn: (params) => gatewayCall("delete", params),
2555
+ onSuccess: (_data, variables) => {
2556
+ const path = variables.path;
2557
+ if (path) {
2558
+ qc.removeQueries({ queryKey: vfsKeys.item(path) });
2559
+ const segments = path.split("/");
2560
+ segments.pop();
2561
+ const parentPath = segments.join("/");
2562
+ if (parentPath) {
2563
+ qc.invalidateQueries({ queryKey: vfsKeys.children(parentPath) });
2564
+ }
2565
+ }
2566
+ }
2567
+ });
2568
+ }
2569
+ function useMove() {
2570
+ const qc = useQueryClient();
2571
+ return useMutation$1({
2572
+ mutationFn: (params) => gatewayCall("move", params),
2573
+ onSuccess: (_data, variables) => {
2574
+ const path = variables.path;
2575
+ const newParentPath = variables.new_parent_path;
2576
+ if (path) {
2577
+ qc.invalidateQueries({ queryKey: vfsKeys.item(path) });
2578
+ const segments = path.split("/");
2579
+ segments.pop();
2580
+ const oldParentPath = segments.join("/");
2581
+ if (oldParentPath) {
2582
+ qc.invalidateQueries({ queryKey: vfsKeys.children(oldParentPath) });
2583
+ }
2584
+ }
2585
+ if (newParentPath) {
2586
+ qc.invalidateQueries({ queryKey: vfsKeys.children(newParentPath) });
2587
+ }
2588
+ }
2589
+ });
2590
+ }
2591
+ function useLink() {
2592
+ const qc = useQueryClient();
2593
+ return useMutation$1({
2594
+ mutationFn: (params) => gatewayCall("link", params),
2595
+ onSuccess: (_data, variables) => {
2596
+ const sourceId = variables.source_id;
2597
+ const targetId = variables.target_id;
2598
+ if (sourceId) {
2599
+ qc.invalidateQueries({ queryKey: vfsKeys.edges(sourceId) });
2600
+ }
2601
+ if (targetId) {
2602
+ qc.invalidateQueries({ queryKey: vfsKeys.edges(targetId) });
2603
+ }
2604
+ }
2605
+ });
2606
+ }
2607
+ function useMutation(method, options) {
2608
+ const qc = useQueryClient();
2609
+ const { optimistic, ...rest } = options ?? {};
2610
+ return useMutation$1({
2611
+ mutationFn: (params) => gatewayCall(method, params),
2612
+ onMutate: optimistic ? async (params) => {
2613
+ await qc.cancelQueries({ queryKey: optimistic.queryKey });
2614
+ const previous = qc.getQueryData(optimistic.queryKey);
2615
+ qc.setQueryData(optimistic.queryKey, (old) => optimistic.update(old, params));
2616
+ return { previous };
2617
+ } : void 0,
2618
+ onError: (err, _vars, context) => {
2619
+ if (optimistic && context?.previous !== void 0) {
2620
+ qc.setQueryData(optimistic.queryKey, context.previous);
2621
+ }
2622
+ rest.onError?.(err);
2623
+ },
2624
+ onSuccess: rest.onSuccess,
2625
+ onSettled: optimistic ? () => {
2626
+ qc.invalidateQueries({ queryKey: optimistic.queryKey });
2627
+ } : void 0
2628
+ });
2629
+ }
2630
+ function useEventStream(handlerOrEventType, handlerOrOptions, filter) {
2631
+ let eventType;
2632
+ let handler;
2633
+ let resolvedFilter;
2634
+ let enabled = true;
2635
+ if (typeof handlerOrEventType === "string") {
2636
+ eventType = handlerOrEventType;
2637
+ handler = handlerOrOptions;
2638
+ resolvedFilter = filter;
2639
+ } else {
2640
+ handler = handlerOrEventType;
2641
+ const opts = handlerOrOptions;
2642
+ eventType = opts.eventType;
2643
+ resolvedFilter = opts.filter;
2644
+ enabled = opts.enabled ?? true;
2645
+ }
2646
+ const handlerRef = useRef(handler);
2647
+ const filterRef = useRef(resolvedFilter);
2648
+ useEffect(() => {
2649
+ handlerRef.current = handler;
2650
+ filterRef.current = resolvedFilter;
2651
+ }, [handler, resolvedFilter]);
2652
+ useEffect(() => {
2653
+ if (!enabled) return;
2654
+ const unsub = subscribeToEvents(
2655
+ eventType,
2656
+ (data) => handlerRef.current(data),
2657
+ filterRef.current ? (data) => filterRef.current(data) : void 0
2658
+ );
2659
+ return unsub;
2660
+ }, [eventType, enabled]);
2661
+ }
2662
+ function useRealtimeQuery(options) {
2663
+ const { queryKey, queryFn, eventType, filter, enabled = true, debounceMs = 500 } = options;
2664
+ const qc = useQueryClient();
2665
+ const debounceRef = useRef(null);
2666
+ const query = useQuery({
2667
+ queryKey,
2668
+ queryFn,
2669
+ enabled
2670
+ });
2671
+ useEffect(() => {
2672
+ if (!enabled) return;
2673
+ const unsub = subscribeToEvents(
2674
+ eventType,
2675
+ () => {
2676
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2677
+ debounceRef.current = setTimeout(() => {
2678
+ qc.invalidateQueries({ queryKey });
2679
+ }, debounceMs);
2680
+ },
2681
+ filter
2682
+ );
2683
+ return () => {
2684
+ unsub();
2685
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2686
+ };
2687
+ }, [eventType, enabled, debounceMs, qc, JSON.stringify(queryKey)]);
2688
+ return query;
2689
+ }
2690
+
2691
+ // src/schema-utils.ts
2692
+ function keyToLabel(key) {
2693
+ return key.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
2694
+ }
2695
+ function inferFilterWidget(type, enumValues) {
2696
+ if (enumValues && enumValues.length > 0) return "select";
2697
+ if (type === "boolean") return "toggle";
2698
+ if (type === "integer" || type === "number") return "range";
2699
+ if (type === "string") return "text";
2700
+ return "none";
2701
+ }
2702
+ function isFilterable(type, enumValues) {
2703
+ if (type === "object" || type === "array") return false;
2704
+ return true;
2705
+ }
2706
+ function isSortable(type) {
2707
+ return ["string", "number", "integer", "boolean"].includes(type);
2708
+ }
2709
+ function isColumnSuitable(type) {
2710
+ return type !== "object" && type !== "array";
2711
+ }
2712
+ function extractFields(schema, editable, defaults, fieldDefaults) {
2713
+ const properties = schema?.properties;
2714
+ if (!properties || typeof properties !== "object") return [];
2715
+ const fields = [];
2716
+ for (const [key, prop] of Object.entries(properties)) {
2717
+ const type = prop.type || "string";
2718
+ const enumValues = prop.enum;
2719
+ const filterable = isFilterable(type);
2720
+ const sortable = isSortable(type);
2721
+ const filterWidget = inferFilterWidget(type, enumValues);
2722
+ fields.push({
2723
+ key,
2724
+ label: prop.title || keyToLabel(key),
2725
+ type,
2726
+ description: prop.description,
2727
+ enum_values: enumValues,
2728
+ default_value: defaults[key] ?? fieldDefaults[key] ?? prop.default,
2729
+ editable,
2730
+ filterable,
2731
+ sortable,
2732
+ filter_widget: filterWidget
2733
+ });
2734
+ }
2735
+ return fields;
2736
+ }
2737
+ function interpretSchema(typeDef) {
2738
+ const defaults = typeDef.default_data || {};
2739
+ const fieldDefaults = typeDef.field_defaults || {};
2740
+ const inputFields = extractFields(
2741
+ typeDef.input_schema,
2742
+ true,
2743
+ defaults,
2744
+ fieldDefaults
2745
+ );
2746
+ const systemFields = extractFields(
2747
+ typeDef.system_schema,
2748
+ false,
2749
+ defaults,
2750
+ fieldDefaults
2751
+ );
2752
+ const allFields = [...inputFields, ...systemFields];
2753
+ const columns = allFields.filter((f) => isColumnSuitable(f.type));
2754
+ const filterable = allFields.filter((f) => f.filterable);
2755
+ const sortable = allFields.filter((f) => f.sortable);
2756
+ return {
2757
+ fields: allFields,
2758
+ columns,
2759
+ filterable,
2760
+ sortable,
2761
+ form_fields: inputFields,
2762
+ system_fields: systemFields
2763
+ };
2764
+ }
2765
+ function getDefaultSort(_typeDef) {
2766
+ return { field: "updated_at", direction: "desc" };
2767
+ }
2768
+ function getRequiredFormFields(typeDef) {
2769
+ const schema = interpretSchema(typeDef);
2770
+ const required = new Set(typeDef.input_schema?.required || []);
2771
+ return schema.form_fields.filter(
2772
+ (f) => required.has(f.key) || f.default_value === void 0
2773
+ );
2774
+ }
2775
+ var _idCounter = 0;
2776
+ function generateUploadId() {
2777
+ return `upload_${Date.now()}_${++_idCounter}`;
2778
+ }
2779
+ async function uploadFile(file, options, onProgress, signal2) {
2780
+ const fileName = options.name || (file instanceof File ? file.name : `file-${Date.now()}`);
2781
+ const contentType = file.type || "application/octet-stream";
2782
+ const itemType = options.itemType || "file";
2783
+ const createResult = await gatewayCall("create", {
2784
+ parent_path: options.parentPath,
2785
+ item_type: itemType,
2786
+ name: fileName,
2787
+ type_data: {
2788
+ ...options.typeData || {},
2789
+ file_source: {
2790
+ type: "upload",
2791
+ content_type: contentType
2792
+ }
2793
+ }
2794
+ });
2795
+ const result = createResult;
2796
+ if (!result.id) {
2797
+ throw new Error(result.error || result.message || "Failed to create file item");
2798
+ }
2799
+ const itemId = result.id;
2800
+ const itemPath = result.path;
2801
+ if (result.storage_completed && result.file_ref) {
2802
+ return { itemId, itemPath, fileRef: result.file_ref };
2803
+ }
2804
+ const storageKey = result.storage_key;
2805
+ const uploadToken = result.upload_token;
2806
+ const storageWorkerUrl = result.storage_worker_url || "https://fsaos-storage.radns.workers.dev";
2807
+ if (!storageKey) {
2808
+ throw new Error("Gateway returned no storage_key \u2014 file_source may not be supported for this item type");
2809
+ }
2810
+ await uploadBinaryXHR(storageWorkerUrl, storageKey, uploadToken, file, contentType, onProgress, signal2);
2811
+ const fileRef = await confirmUpload(itemId, storageKey, contentType);
2812
+ return { itemId, itemPath, fileRef };
2813
+ }
2814
+ function uploadBinaryXHR(storageWorkerUrl, storageKey, uploadToken, body, contentType, onProgress, signal2) {
2815
+ return new Promise((resolve, reject) => {
2816
+ const xhr = new XMLHttpRequest();
2817
+ xhr.open("PUT", `${storageWorkerUrl}/upload`);
2818
+ xhr.setRequestHeader("X-Storage-Key", storageKey);
2819
+ xhr.setRequestHeader("Content-Type", contentType);
2820
+ if (uploadToken) {
2821
+ xhr.setRequestHeader("X-Upload-Token", uploadToken);
2822
+ }
2823
+ xhr.upload.onprogress = (e) => {
2824
+ if (e.lengthComputable && onProgress) {
2825
+ onProgress(Math.round(e.loaded / e.total * 100));
2826
+ }
2827
+ };
2828
+ xhr.onload = () => {
2829
+ if (xhr.status >= 200 && xhr.status < 300) {
2830
+ resolve();
2831
+ } else {
2832
+ reject(new Error(`Storage upload failed: HTTP ${xhr.status} \u2014 ${xhr.responseText?.slice(0, 300)}`));
2833
+ }
2834
+ };
2835
+ xhr.onerror = () => reject(new Error("Storage upload network error"));
2836
+ xhr.ontimeout = () => reject(new Error("Storage upload timed out"));
2837
+ if (signal2) {
2838
+ if (signal2.aborted) {
2839
+ reject(new Error("Upload cancelled"));
2840
+ return;
2841
+ }
2842
+ signal2.addEventListener("abort", () => {
2843
+ xhr.abort();
2844
+ reject(new Error("Upload cancelled"));
2845
+ });
2846
+ }
2847
+ xhr.send(body);
2848
+ });
2849
+ }
2850
+ async function confirmUpload(itemId, storageKey, contentType) {
2851
+ const token = await getAccessToken();
2852
+ const response = await fetch(`${GATEWAY_URL}/storage/upload-complete`, {
2853
+ method: "POST",
2854
+ headers: {
2855
+ "Content-Type": "application/json",
2856
+ ...token ? { "Authorization": `Bearer ${token}` } : {}
2857
+ },
2858
+ body: JSON.stringify({
2859
+ item_id: itemId,
2860
+ storage_key: storageKey,
2861
+ content_type: contentType
2862
+ })
2863
+ });
2864
+ if (!response.ok) {
2865
+ const text = await response.text().catch(() => "");
2866
+ throw new Error(`Upload confirmation failed: HTTP ${response.status} \u2014 ${text.slice(0, 300)}`);
2867
+ }
2868
+ const result = await response.json();
2869
+ if (!result.success) {
2870
+ throw new Error(result.error || result.message || "Upload confirmation rejected");
2871
+ }
2872
+ return result.file_ref;
2873
+ }
2874
+ function useFileUpload() {
2875
+ const [uploads, setUploads] = useState([]);
2876
+ const qc = useQueryClient();
2877
+ const uploadsRef = useRef(uploads);
2878
+ uploadsRef.current = uploads;
2879
+ const updateUpload = useCallback((id, patch) => {
2880
+ setUploads((prev) => prev.map((u) => u.id === id ? { ...u, ...patch } : u));
2881
+ }, []);
2882
+ const addUpload = useCallback((item) => {
2883
+ setUploads((prev) => [...prev, item]);
2884
+ }, []);
2885
+ const executeUpload = useCallback(async (uploadId, file, options, resumeFrom, existingState) => {
2886
+ const fileName = options.name || file.name;
2887
+ const contentType = file.type || "application/octet-stream";
2888
+ const itemType = options.itemType || "file";
2889
+ let itemId = existingState?.itemId || null;
2890
+ let itemPath = existingState?.itemPath || null;
2891
+ let storageKey = existingState?.storageKey || null;
2892
+ let uploadToken = existingState?.uploadToken || null;
2893
+ let storageWorkerUrl = existingState?.storageWorkerUrl || null;
2894
+ try {
2895
+ if (!resumeFrom || resumeFrom === "create") {
2896
+ updateUpload(uploadId, { status: "creating", error: null, _failedAt: null });
2897
+ const createResult = await gatewayCall("create", {
2898
+ parent_path: options.parentPath,
2899
+ item_type: itemType,
2900
+ name: fileName,
2901
+ type_data: {
2902
+ ...options.typeData || {},
2903
+ file_source: {
2904
+ type: "upload",
2905
+ content_type: contentType
2906
+ }
2907
+ }
2908
+ });
2909
+ const result = createResult;
2910
+ if (!result.id) {
2911
+ throw Object.assign(
2912
+ new Error(result.error || result.message || "Failed to create file item"),
2913
+ { _step: "create" }
2914
+ );
2915
+ }
2916
+ itemId = result.id;
2917
+ itemPath = result.path;
2918
+ updateUpload(uploadId, { itemId, itemPath });
2919
+ if (result.storage_completed && result.file_ref) {
2920
+ updateUpload(uploadId, {
2921
+ status: "complete",
2922
+ progress: 100,
2923
+ fileRef: result.file_ref
2924
+ });
2925
+ qc.invalidateQueries({ queryKey: vfsKeys.children(options.parentPath) });
2926
+ return;
2927
+ }
2928
+ storageKey = result.storage_key;
2929
+ uploadToken = result.upload_token || "";
2930
+ storageWorkerUrl = result.storage_worker_url || "https://fsaos-storage.radns.workers.dev";
2931
+ updateUpload(uploadId, {
2932
+ _storageKey: storageKey,
2933
+ _uploadToken: uploadToken,
2934
+ _storageWorkerUrl: storageWorkerUrl
2935
+ });
2936
+ if (!storageKey) {
2937
+ throw Object.assign(
2938
+ new Error("Gateway returned no storage_key"),
2939
+ { _step: "create" }
2940
+ );
2941
+ }
2942
+ }
2943
+ if (!resumeFrom || resumeFrom === "create" || resumeFrom === "upload") {
2944
+ updateUpload(uploadId, { status: "uploading", progress: 0, error: null, _failedAt: null });
2945
+ const xhrPromise = new Promise((resolve, reject) => {
2946
+ const xhr = new XMLHttpRequest();
2947
+ updateUpload(uploadId, { _xhr: xhr });
2948
+ xhr.open("PUT", `${storageWorkerUrl}/upload`);
2949
+ xhr.setRequestHeader("X-Storage-Key", storageKey);
2950
+ xhr.setRequestHeader("Content-Type", contentType);
2951
+ if (uploadToken) {
2952
+ xhr.setRequestHeader("X-Upload-Token", uploadToken);
2953
+ }
2954
+ xhr.upload.onprogress = (e) => {
2955
+ if (e.lengthComputable) {
2956
+ updateUpload(uploadId, { progress: Math.round(e.loaded / e.total * 100) });
2957
+ }
2958
+ };
2959
+ xhr.onload = () => {
2960
+ if (xhr.status >= 200 && xhr.status < 300) {
2961
+ resolve();
2962
+ } else {
2963
+ reject(Object.assign(
2964
+ new Error(`Storage upload failed: HTTP ${xhr.status}`),
2965
+ { _step: "upload" }
2966
+ ));
2967
+ }
2968
+ };
2969
+ xhr.onerror = () => reject(Object.assign(
2970
+ new Error("Storage upload network error"),
2971
+ { _step: "upload" }
2972
+ ));
2973
+ xhr.ontimeout = () => reject(Object.assign(
2974
+ new Error("Storage upload timed out"),
2975
+ { _step: "upload" }
2976
+ ));
2977
+ xhr.send(file);
2978
+ });
2979
+ await xhrPromise;
2980
+ updateUpload(uploadId, { progress: 100, _xhr: null });
2981
+ }
2982
+ updateUpload(uploadId, { status: "confirming", error: null, _failedAt: null });
2983
+ const fileRef = await confirmUpload(itemId, storageKey, contentType);
2984
+ updateUpload(uploadId, {
2985
+ status: "complete",
2986
+ fileRef
2987
+ });
2988
+ qc.invalidateQueries({ queryKey: vfsKeys.children(options.parentPath) });
2989
+ } catch (err) {
2990
+ const message = err?.message || String(err);
2991
+ const failedAt = err?._step || "create";
2992
+ updateUpload(uploadId, {
2993
+ status: "error",
2994
+ error: message,
2995
+ _failedAt: failedAt,
2996
+ _xhr: null
2997
+ });
2998
+ }
2999
+ }, [updateUpload, qc]);
3000
+ const upload = useCallback((file, options) => {
3001
+ const id = generateUploadId();
3002
+ const item = {
3003
+ id,
3004
+ file,
3005
+ status: "creating",
3006
+ progress: 0,
3007
+ error: null,
3008
+ itemId: null,
3009
+ itemPath: null,
3010
+ fileRef: null,
3011
+ options,
3012
+ _storageKey: null,
3013
+ _uploadToken: null,
3014
+ _storageWorkerUrl: null,
3015
+ _xhr: null,
3016
+ _failedAt: null
3017
+ };
3018
+ addUpload(item);
3019
+ executeUpload(id, file, options);
3020
+ }, [addUpload, executeUpload]);
3021
+ const uploadMultiple = useCallback((files, options) => {
3022
+ for (const file of files) {
3023
+ upload(file, options);
3024
+ }
3025
+ }, [upload]);
3026
+ const retryUpload = useCallback((uploadId) => {
3027
+ const item = uploadsRef.current.find((u) => u.id === uploadId);
3028
+ if (!item || item.status !== "error") return;
3029
+ const resumeFrom = item._failedAt || "create";
3030
+ const existingState = item._storageKey && item.itemId ? {
3031
+ storageKey: item._storageKey,
3032
+ uploadToken: item._uploadToken || "",
3033
+ storageWorkerUrl: item._storageWorkerUrl || "https://fsaos-storage.radns.workers.dev",
3034
+ itemId: item.itemId,
3035
+ itemPath: item.itemPath || ""
3036
+ } : void 0;
3037
+ executeUpload(uploadId, item.file, item.options, resumeFrom, existingState);
3038
+ }, [executeUpload]);
3039
+ const cancelUpload = useCallback((uploadId) => {
3040
+ const item = uploadsRef.current.find((u) => u.id === uploadId);
3041
+ if (!item) return;
3042
+ if (item._xhr) {
3043
+ item._xhr.abort();
3044
+ }
3045
+ updateUpload(uploadId, {
3046
+ status: "error",
3047
+ error: "Cancelled",
3048
+ _xhr: null,
3049
+ _failedAt: null
3050
+ });
3051
+ }, [updateUpload]);
3052
+ const removeUpload = useCallback((uploadId) => {
3053
+ setUploads((prev) => prev.filter((u) => u.id !== uploadId));
3054
+ }, []);
3055
+ const clearCompleted = useCallback(() => {
3056
+ setUploads((prev) => prev.filter((u) => u.status !== "complete"));
3057
+ }, []);
3058
+ const isUploading = uploads.some(
3059
+ (u) => u.status === "creating" || u.status === "uploading" || u.status === "confirming"
3060
+ );
3061
+ const publicUploads = uploads.map(({ _storageKey, _uploadToken, _storageWorkerUrl, _xhr, _failedAt, ...rest }) => rest);
3062
+ return {
3063
+ upload,
3064
+ uploadMultiple,
3065
+ uploads: publicUploads,
3066
+ retryUpload,
3067
+ cancelUpload,
3068
+ removeUpload,
3069
+ clearCompleted,
3070
+ isUploading
3071
+ };
3072
+ }
3073
+
3074
+ // src/index.ts
3075
+ __registerSdkExports(src_exports);
3076
+
3077
+ export { ComponentRenderer, EnforcementDeniedError, KernelProvider, auth, awaitScopeReady, callTool, clearCachedToken, clearSession, createItem, disconnectSSE, disposeVfsRealtimeWs, emitEvent, fetchChannelMessages, fetchChannelMessagesPage, fetchEdgesForItem, fetchItemHistory, fetchItems, fetchMemberFocus, fetchOpenEnvelope, fetchRecentActivity, fetchTypeDefinitions, fetchVfsChildren, fetchVfsChildrenPage, fetchVfsItem, fetchVfsItemById, fetchVfsTree, gatewayCall, getAccessToken, getAssetUrl, getDefaultSort, getRequiredFormFields, getScope, getScopeVersion, getSessionEntry, initSession, initVfsRealtimeWs, interpretSchema, invalidateAllVfs, invalidateChannelMessages, invalidateChildren, invalidateItem, invalidateOpenEnvelope, invalidatePathAndParent, invalidateSubtree, invalidateTypes, isScopeReady, listChildren, mount, mountRealtimeScope, normalizeItem, ping, pushChanges, queryClient, readItem, registerCleanup, resetAllSdkState, resolvePrincipalId, setCachedToken, setScope, setupRequireShim, signal, subscribeScope, subscribeToEvents, subscribeToPath, subscribeToScope, unmountRealtimeScope, updateItem, uploadFile, useAccounts, useAllChannels, useAsset, useAuth, useChannelMessages, useChannels, useChildren, useComponent, useCreate, useDelete, useDmChannels, useEdges, useEventStream, useFileUpload, useInfiniteChannelMessages, useInfiniteChildren, useInfiniteItems, useItem, useItemById, useItemHistory, useItems, useLink, useList, useMemberFocus, useMove, useMutation, useNotifications, useOpen, usePermission, usePermissions, usePrincipal, useRealtimeQuery, useRecentActivity, useScope, useScopeReady, useSearch, useTheme, useTool, useTree, useType, useTypeHelpers, useTypes, useUnreadCounts, useUpdate, vfsKeys };
3078
+ //# sourceMappingURL=index.js.map
3079
+ //# sourceMappingURL=index.js.map