@ai4b-team/fsaos-gateway-sdk 3.34.0 → 3.34.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -76,6 +76,7 @@ __export(src_exports, {
76
76
  initSession: () => initSession,
77
77
  initVfsRealtimeWs: () => initVfsRealtimeWs,
78
78
  initializeFsaos: () => initializeFsaos,
79
+ initializeWorkspace: () => initializeWorkspace,
79
80
  interpretSchema: () => interpretSchema,
80
81
  invalidateAllVfs: () => invalidateAllVfs,
81
82
  invalidateChannelMessages: () => invalidateChannelMessages,
@@ -91,6 +92,7 @@ __export(src_exports, {
91
92
  isScopeReady: () => isScopeReady,
92
93
  itemsFilterFromQueryFilter: () => itemsFilterFromQueryFilter,
93
94
  listChildren: () => listChildren,
95
+ loadMounts: () => loadMounts,
94
96
  mount: () => mount2,
95
97
  mountRealtimeScope: () => mountRealtimeScope,
96
98
  normalizeItem: () => normalizeItem,
@@ -102,9 +104,11 @@ __export(src_exports, {
102
104
  readItem: () => readItem,
103
105
  refreshQuery: () => refreshQuery,
104
106
  registerCleanup: () => registerCleanup,
107
+ replaceMounts: () => replaceMounts,
105
108
  resetAllSdkState: () => resetAllSdkState,
106
109
  resolvePrincipalId: () => resolvePrincipalId,
107
110
  resubscribePath: () => resubscribePath,
111
+ selectMount: () => selectMount,
108
112
  serverDeadlinePassed: () => serverDeadlinePassed,
109
113
  serverMsUntil: () => serverMsUntil,
110
114
  serverNow: () => serverNow,
@@ -310,7 +314,7 @@ function notifyAnchorListeners() {
310
314
  }
311
315
  function setAnchor(ref) {
312
316
  const path = typeof ref === "string" ? ref : ref.path;
313
- const changed = currentAnchorPath !== path;
317
+ const changed = currentAnchorPath !== path || currentAnchorRef?.id !== (typeof ref === "string" ? void 0 : ref.id);
314
318
  currentAnchorPath = path;
315
319
  currentAnchorRef = typeof ref === "string" ? null : ref;
316
320
  if (typeof ref !== "string" && !mountSet.some((m) => m.id === ref.id)) {
@@ -347,13 +351,13 @@ function isAnchorReady() {
347
351
  return anchorResolved;
348
352
  }
349
353
  var homeAnchor = null;
350
- function electHomeAnchor(ref) {
354
+ function electHomeAnchor(ref, options = {}) {
351
355
  if (homeAnchor) {
352
356
  console.warn("[SDK] electHomeAnchor called twice; ignoring (logout to re-elect).");
353
357
  return;
354
358
  }
355
359
  homeAnchor = ref;
356
- setAnchor(ref);
360
+ if (options.activate !== false) setAnchor(ref);
357
361
  }
358
362
  function getHomeAnchor() {
359
363
  return homeAnchor;
@@ -383,32 +387,33 @@ function subscribeMounts(listener) {
383
387
  mountListeners.delete(listener);
384
388
  };
385
389
  }
386
- function setMountsState(mounts) {
390
+ function commitMountSelection(mounts, anchor) {
391
+ const mountsChanged = JSON.stringify(mountSet) !== JSON.stringify(mounts);
392
+ const anchorChanged = currentAnchorPath !== anchor.path || currentAnchorRef?.id !== anchor.id;
387
393
  mountSet = mounts;
388
- notifyMountListeners();
389
- }
390
- function addMountState(ref) {
391
- if (mountSet.some((m) => m.id === ref.id)) return;
392
- mountSet = [...mountSet, ref];
393
- notifyMountListeners();
394
- }
395
- function removeMountState(id) {
396
- const removed = mountSet.find((m) => m.id === id);
397
- if (!removed) return;
398
- mountSet = mountSet.filter((m) => m.id !== id);
399
- notifyMountListeners();
400
- if (currentAnchorRef?.id === id && homeAnchor) {
401
- setAnchor(homeAnchor);
394
+ currentAnchorPath = anchor.path;
395
+ currentAnchorRef = anchor;
396
+ if (!anchorResolved) {
397
+ anchorResolved = true;
398
+ resolveAnchorReady();
402
399
  }
400
+ if (mountsChanged) notifyMountListeners();
401
+ if (anchorChanged) notifyAnchorListeners();
403
402
  }
404
403
  var sessionEntry = null;
405
404
  var sessionPromise = null;
405
+ var sessionGeneration = 0;
406
+ function getSessionGeneration() {
407
+ return sessionGeneration;
408
+ }
406
409
  async function initSession() {
407
410
  if (sessionEntry) return sessionEntry;
408
411
  if (sessionPromise) return sessionPromise;
412
+ const generation = sessionGeneration;
409
413
  sessionPromise = (async () => {
410
414
  const hostname = getHostname();
411
415
  const token = await getAccessToken();
416
+ if (generation !== sessionGeneration) throw new Error("Session changed during initialization");
412
417
  const headers = {};
413
418
  if (token) {
414
419
  headers["Authorization"] = `Bearer ${token}`;
@@ -421,6 +426,7 @@ async function initSession() {
421
426
  throw new Error(`Session init failed: ${response.status} ${response.statusText}`);
422
427
  }
423
428
  const json = await response.json();
429
+ if (generation !== sessionGeneration) throw new Error("Session changed during initialization");
424
430
  const entry = json.entry || json;
425
431
  sessionEntry = {
426
432
  scope_id: entry.scope_id,
@@ -434,13 +440,17 @@ async function initSession() {
434
440
  principal_id: entry.principal_id || null
435
441
  };
436
442
  return sessionEntry;
437
- })();
443
+ })().catch((error) => {
444
+ if (generation === sessionGeneration) sessionPromise = null;
445
+ throw error;
446
+ });
438
447
  return sessionPromise;
439
448
  }
440
449
  function getSessionEntry() {
441
450
  return sessionEntry;
442
451
  }
443
452
  function clearSession() {
453
+ sessionGeneration++;
444
454
  sessionEntry = null;
445
455
  sessionPromise = null;
446
456
  currentAnchorPath = null;
@@ -455,6 +465,7 @@ function registerCleanup(fn) {
455
465
  function resetAllSdkState() {
456
466
  clearSession();
457
467
  clearCachedToken();
468
+ resolveAnchorReady();
458
469
  anchorResolved = false;
459
470
  anchorReady = new Promise((resolve) => {
460
471
  resolveAnchorReady = resolve;
@@ -547,24 +558,28 @@ var GatewayRequestError = class extends Error {
547
558
  }
548
559
  };
549
560
  async function gatewayCall(method, params = {}) {
561
+ const generation = getSessionGeneration();
562
+ const requestedAnchor = getAnchor();
550
563
  const hostname = getHostname();
551
564
  const url = `${GATEWAY_URL}/d/${hostname}`;
552
565
  const headers = {
553
566
  "Content-Type": "application/json"
554
567
  };
555
568
  const token = await getAccessToken();
569
+ if (generation !== getSessionGeneration()) throw new Error("Session changed before gateway request");
556
570
  if (token) {
557
571
  headers["Authorization"] = `Bearer ${token}`;
558
572
  }
559
573
  if (!ANCHOR_INDEPENDENT_METHODS.has(method) && !params._scope_path) {
560
574
  await awaitAnchorReady();
561
575
  }
576
+ if (generation !== getSessionGeneration()) throw new Error("Session changed before gateway request");
562
577
  const enrichedParams = { ...params };
563
578
  const session = getSessionEntry();
564
579
  if (!enrichedParams.domain_scope_id && session?.scope_id) {
565
580
  enrichedParams.domain_scope_id = session.scope_id;
566
581
  }
567
- const anchorPath = getAnchor();
582
+ const anchorPath = requestedAnchor ?? getAnchor();
568
583
  if (!enrichedParams._scope_path && anchorPath) {
569
584
  enrichedParams._scope_path = anchorPath;
570
585
  }
@@ -574,6 +589,7 @@ async function gatewayCall(method, params = {}) {
574
589
  body: JSON.stringify({ method, params: enrichedParams })
575
590
  });
576
591
  const responseBody = await response.text();
592
+ if (generation !== getSessionGeneration()) throw new Error("Session changed during gateway request");
577
593
  let json = null;
578
594
  if (responseBody) {
579
595
  try {
@@ -680,6 +696,11 @@ function clearArrangementRefreshes(client2) {
680
696
  pending.delete(client2);
681
697
  }
682
698
  var client = null;
699
+ registerCleanup(() => {
700
+ if (!client) return;
701
+ void client.cancelQueries({ queryKey: ["vfs"] });
702
+ client.removeQueries({ queryKey: ["vfs"] });
703
+ });
683
704
  function setQueryClient(custom) {
684
705
  if (client && client !== custom) {
685
706
  throw new Error(
@@ -1957,60 +1978,40 @@ function invalidateOpenEnvelope(path, strategy) {
1957
1978
  }
1958
1979
  }
1959
1980
 
1960
- // src/mounts.ts
1961
- function declPath(memberSpacePath) {
1962
- return `${memberSpacePath}/settings/mounts`;
1963
- }
1964
- var declItemPath = null;
1965
- async function loadMounts(memberSpacePath, resolveRef) {
1966
- declItemPath = declPath(memberSpacePath);
1967
- try {
1968
- const res = await gatewayCall("read", { path: declItemPath });
1969
- const ids = res?.item?.type_data?.mounts ?? [];
1970
- const refs = ids.map(resolveRef).filter((r) => r !== null);
1971
- const home = getHomeAnchor();
1972
- if (home && !refs.some((r) => r.id === home.id)) {
1973
- refs.unshift(home);
1974
- }
1975
- setMountsState(refs);
1976
- return refs;
1977
- } catch {
1978
- const home = getHomeAnchor();
1979
- const refs = home ? [home] : [];
1980
- setMountsState(refs);
1981
- return refs;
1982
- }
1983
- }
1984
- async function persist() {
1985
- if (!declItemPath) return;
1986
- const ids = getMounts().map((m) => m.id);
1987
- const lastAnchor = getAnchorRef()?.id ?? null;
1988
- try {
1989
- await gatewayCall("update", {
1990
- path: declItemPath,
1991
- type_data: { mounts: ids, last_anchor: lastAnchor }
1992
- });
1993
- } catch (e) {
1994
- try {
1995
- const parent = declItemPath.split("/").slice(0, -1).join("/");
1996
- await gatewayCall("create", {
1997
- parent_path: parent,
1998
- name: "mounts",
1999
- item_type: "file",
2000
- type_data: { mounts: ids, last_anchor: lastAnchor }
2001
- });
2002
- } catch (e2) {
2003
- console.warn("[SDK] mount declaration persist failed:", e2);
2004
- }
2005
- }
2006
- }
2007
- async function mount(ref) {
2008
- addMountState(ref);
2009
- await persist();
1981
+ // src/memberships.ts
1982
+ function membershipToMountRef(m) {
1983
+ return {
1984
+ id: m.accountId,
1985
+ path: m.path,
1986
+ kind: m.isSpace ? "space" : "account",
1987
+ name: m.name,
1988
+ grantedUnderAccountId: m.grantedUnderAccountId ?? void 0
1989
+ };
2010
1990
  }
2011
- async function unmount(id) {
2012
- removeMountState(id);
2013
- await persist();
1991
+ async function fetchMemberships() {
1992
+ const result = await gatewayCall("list-memberships", {});
1993
+ const memberships = result.memberships || [];
1994
+ return memberships.map((m) => ({
1995
+ accountId: m.account_id,
1996
+ path: m.account_path,
1997
+ name: m.account_name,
1998
+ role: m.role || "member",
1999
+ typeData: m.account_type_data || {},
2000
+ joinedAt: m.joined_at,
2001
+ via: m.via,
2002
+ placement: m.placement,
2003
+ subordinateTo: m.subordinate_to ?? null,
2004
+ cascadeAnchorId: m.cascade_anchor_id ?? null,
2005
+ isAccount: m.is_account ?? void 0,
2006
+ isSpace: m.is_space ?? void 0,
2007
+ isOwner: m.is_owner ?? void 0,
2008
+ billingResponsible: m.billing_responsible ?? void 0,
2009
+ grantedVia: m.granted_via ?? null,
2010
+ grantedUnderAccountId: m.granted_under_account_id ?? null,
2011
+ membershipPath: m.membership_path ?? null,
2012
+ personalSpaceId: m.personal_space_id ?? null,
2013
+ personalSpacePath: m.personal_space_path ?? null
2014
+ }));
2014
2015
  }
2015
2016
 
2016
2017
  // src/vfs-realtime-ws.ts
@@ -2020,7 +2021,10 @@ var unsubCcmEvents = null;
2020
2021
  var debounceTimers = /* @__PURE__ */ new Map();
2021
2022
  var DEBOUNCE_MS = 250;
2022
2023
  function initVfsRealtimeWs(accountId) {
2023
- disposeVfsRealtimeWs();
2024
+ if (unsubEvents) {
2025
+ mountRealtimeScope(accountId);
2026
+ return;
2027
+ }
2024
2028
  mountRealtimeScope(accountId);
2025
2029
  unsubEvents = subscribeToEvents(
2026
2030
  "vfs_change",
@@ -2137,6 +2141,147 @@ function applyInvalidation(action, path, data, parentPath) {
2137
2141
  }
2138
2142
  }
2139
2143
 
2144
+ // src/mounts.ts
2145
+ var declaration = null;
2146
+ registerCleanup(() => {
2147
+ declaration = null;
2148
+ });
2149
+ function assertCurrent(owner) {
2150
+ if (owner !== declaration || owner.generation !== getSessionGeneration()) throw new Error("Workspace session changed");
2151
+ }
2152
+ function missing(error) {
2153
+ return error instanceof GatewayRequestError && error.payload?.error === "item_not_found";
2154
+ }
2155
+ function canonical(owner, ref) {
2156
+ const current = owner.resolve(ref.id);
2157
+ if (!current || current.path !== ref.path || current.kind !== ref.kind) {
2158
+ throw new Error("Mount is not an available membership");
2159
+ }
2160
+ return current;
2161
+ }
2162
+ function withHome(refs) {
2163
+ const home = getHomeAnchor();
2164
+ if (!home) throw new Error("Elect a home account before loading mounts");
2165
+ return [home, ...refs.filter((ref, index) => ref.id !== home.id && refs.findIndex((r) => r.id === ref.id) === index)];
2166
+ }
2167
+ function signature(refs, anchor) {
2168
+ return JSON.stringify({ mounts: refs.map((ref) => ref.id), last_anchor: anchor.id });
2169
+ }
2170
+ async function save(owner, refs, anchor) {
2171
+ assertCurrent(owner);
2172
+ const next = signature(refs, anchor);
2173
+ if (owner.saved === next) return;
2174
+ const type_data = JSON.parse(next);
2175
+ await gatewayCall(owner.exists ? "update" : "create", owner.exists ? {
2176
+ _scope_path: owner.scope,
2177
+ path: owner.path,
2178
+ type_data
2179
+ } : {
2180
+ _scope_path: owner.scope,
2181
+ parent_path: owner.path.slice(0, owner.path.lastIndexOf("/")),
2182
+ name: "mounts",
2183
+ item_type: "file",
2184
+ type_data
2185
+ });
2186
+ assertCurrent(owner);
2187
+ owner.exists = true;
2188
+ owner.saved = next;
2189
+ }
2190
+ function publish(refs, anchor) {
2191
+ const previous = getMounts();
2192
+ initVfsRealtimeWs(getHomeAnchor().id);
2193
+ for (const ref of previous) if (!refs.some((r) => r.id === ref.id)) unmountRealtimeScope(ref.id);
2194
+ for (const ref of refs) mountRealtimeScope(ref.id);
2195
+ commitMountSelection(refs, anchor);
2196
+ }
2197
+ async function loadMounts(memberSpacePath, resolveRef, options = {}) {
2198
+ const home = getHomeAnchor();
2199
+ if (!home) throw new Error("Elect a home account before loading mounts");
2200
+ const path = `${memberSpacePath}/settings/mounts`;
2201
+ if (declaration && declaration.path !== path) throw new Error("Reset the session before changing the mount declaration owner");
2202
+ const owner = declaration ??= {
2203
+ generation: getSessionGeneration(),
2204
+ path,
2205
+ scope: home.path,
2206
+ resolve: resolveRef,
2207
+ exists: false,
2208
+ ready: false,
2209
+ saved: null,
2210
+ queue: Promise.resolve()
2211
+ };
2212
+ assertCurrent(owner);
2213
+ if (owner.loading) return owner.loading;
2214
+ if (owner.ready) return getMounts();
2215
+ owner.resolve = resolveRef;
2216
+ owner.loading = (async () => {
2217
+ let data = null;
2218
+ try {
2219
+ const response = await gatewayCall("read", { path, _scope_path: owner.scope });
2220
+ assertCurrent(owner);
2221
+ data = response.type_data ?? response.item?.type_data;
2222
+ if (!data || !Array.isArray(data.mounts) || !data.mounts.every((id) => typeof id === "string")) {
2223
+ throw new Error("Invalid saved mount declaration");
2224
+ }
2225
+ owner.exists = true;
2226
+ } catch (error) {
2227
+ assertCurrent(owner);
2228
+ if (!missing(error)) throw error;
2229
+ owner.exists = false;
2230
+ }
2231
+ const initial = options.initialAnchor ? canonical(owner, options.initialAnchor) : home;
2232
+ const refs = withHome(data ? data.mounts.map(resolveRef).filter((ref) => ref !== null) : [initial]);
2233
+ const anchor = data ? refs.find((ref) => ref.id === data.last_anchor) ?? refs.find((ref) => ref.kind === "space") ?? home : initial;
2234
+ if (data) owner.saved = signature(refs, anchor);
2235
+ else if (options.initialAnchor) await save(owner, refs, anchor);
2236
+ assertCurrent(owner);
2237
+ owner.ready = true;
2238
+ publish(refs, anchor);
2239
+ return refs;
2240
+ })().finally(() => {
2241
+ owner.loading = void 0;
2242
+ });
2243
+ return owner.loading;
2244
+ }
2245
+ function mutate(change) {
2246
+ const owner = declaration;
2247
+ if (!owner?.ready) return Promise.reject(new Error("Workspace mounts are not loaded"));
2248
+ const task = owner.queue.then(async () => {
2249
+ assertCurrent(owner);
2250
+ const { refs, anchor } = change(owner);
2251
+ await save(owner, refs, anchor);
2252
+ assertCurrent(owner);
2253
+ publish(refs, anchor);
2254
+ });
2255
+ owner.queue = task.catch(() => {
2256
+ });
2257
+ return task;
2258
+ }
2259
+ function mount(ref) {
2260
+ return mutate((owner) => ({ refs: withHome([...getMounts(), canonical(owner, ref)]), anchor: getAnchorRef() }));
2261
+ }
2262
+ function unmount(id) {
2263
+ return mutate(() => {
2264
+ if (id === getHomeAnchor().id) throw new Error("The home account cannot be unmounted");
2265
+ const refs = withHome(getMounts().filter((ref) => ref.id !== id));
2266
+ const anchor = refs.find((ref) => ref.id === getAnchorRef()?.id) ?? refs.find((ref) => ref.kind === "space") ?? getHomeAnchor();
2267
+ return { refs, anchor };
2268
+ });
2269
+ }
2270
+ function selectMount(ref) {
2271
+ return mutate((owner) => {
2272
+ const anchor = canonical(owner, ref);
2273
+ return { refs: withHome([...getMounts(), anchor]), anchor };
2274
+ });
2275
+ }
2276
+ function replaceMounts(refs, anchor) {
2277
+ return mutate((owner) => {
2278
+ const next = withHome(refs.map((ref) => canonical(owner, ref)));
2279
+ const selected = canonical(owner, anchor);
2280
+ if (!next.some((ref) => ref.id === selected.id)) throw new Error("Selected mount must be in the working set");
2281
+ return { refs: next, anchor: selected };
2282
+ });
2283
+ }
2284
+
2140
2285
  // src/hooks.ts
2141
2286
  function useScopeKey() {
2142
2287
  return useSyncExternalStore(subscribeScope, getScopeVersion, getScopeVersion);
@@ -2503,44 +2648,13 @@ function useScope() {
2503
2648
  return { data, loading, error };
2504
2649
  }
2505
2650
  var EMPTY_ACCOUNTS = [];
2506
- function membershipToMountRef(m) {
2507
- return {
2508
- id: m.accountId,
2509
- path: m.path,
2510
- kind: m.isSpace ? "space" : "account",
2511
- name: m.name,
2512
- grantedUnderAccountId: m.grantedUnderAccountId ?? void 0
2513
- };
2514
- }
2515
2651
  function useAccounts() {
2516
2652
  const { user } = useAuth();
2517
- const autoSelectedRef = useRef(false);
2653
+ const autoSelectedRef = useRef(null);
2654
+ const [mountError, setMountError] = useState(null);
2518
2655
  const query = useQuery({
2519
2656
  queryKey: vfsKeys.memberships(user?.id ?? "anonymous"),
2520
- queryFn: async () => {
2521
- const result = await gatewayCall("list-memberships", {});
2522
- const memberships = result.memberships || [];
2523
- return memberships.map((m) => ({
2524
- accountId: m.account_id,
2525
- path: m.account_path,
2526
- name: m.account_name,
2527
- role: m.role || "member",
2528
- typeData: m.account_type_data || {},
2529
- joinedAt: m.joined_at,
2530
- via: m.via,
2531
- placement: m.placement,
2532
- subordinateTo: m.subordinate_to ?? null,
2533
- cascadeAnchorId: m.cascade_anchor_id ?? null,
2534
- isAccount: m.is_account ?? void 0,
2535
- isSpace: m.is_space ?? void 0,
2536
- isOwner: m.is_owner ?? void 0,
2537
- billingResponsible: m.billing_responsible ?? void 0,
2538
- grantedVia: m.granted_via ?? null,
2539
- grantedUnderAccountId: m.granted_under_account_id ?? null,
2540
- personalSpaceId: m.personal_space_id ?? null,
2541
- personalSpacePath: m.personal_space_path ?? null
2542
- }));
2543
- },
2657
+ queryFn: fetchMemberships,
2544
2658
  enabled: !!user?.id,
2545
2659
  staleTime: 1e3 * 60 * 2
2546
2660
  });
@@ -2562,50 +2676,47 @@ function useAccounts() {
2562
2676
  }, [anchorVersion2, allMemberships]);
2563
2677
  useEffect(() => {
2564
2678
  if (!user?.id) {
2565
- autoSelectedRef.current = false;
2679
+ autoSelectedRef.current = null;
2566
2680
  return;
2567
2681
  }
2568
- if (autoSelectedRef.current) return;
2682
+ if (autoSelectedRef.current === user.id) return;
2683
+ setMountError(null);
2569
2684
  if (electionCandidates.length === 1) {
2570
2685
  const only = electionCandidates[0];
2571
- electHomeAnchor(membershipToMountRef(only));
2686
+ if (!getHomeAnchor()) electHomeAnchor(membershipToMountRef(only));
2572
2687
  initVfsRealtimeWs(only.accountId);
2573
2688
  if (only.personalSpacePath) {
2574
2689
  loadMounts(only.personalSpacePath, (id) => {
2575
2690
  const m = allMemberships.find((x) => x.accountId === id);
2576
2691
  return m ? membershipToMountRef(m) : null;
2577
- }).catch(() => {
2578
- });
2692
+ }).catch(setMountError);
2579
2693
  }
2580
- autoSelectedRef.current = true;
2694
+ autoSelectedRef.current = user.id;
2581
2695
  } else if (electionCandidates.length > 1) {
2582
- autoSelectedRef.current = true;
2696
+ autoSelectedRef.current = user.id;
2583
2697
  }
2584
2698
  }, [user?.id, electionCandidates, allMemberships]);
2585
- const switchAccount = useCallback((accountPath) => {
2699
+ const switchAccount = useCallback(async (accountPath) => {
2586
2700
  const m = allMemberships.find((a) => a.path === accountPath);
2587
- if (!m) return;
2701
+ if (!m) throw new Error("Account membership is unavailable");
2588
2702
  const ref = membershipToMountRef(m);
2589
2703
  if (!getHomeAnchor()) {
2590
2704
  electHomeAnchor(ref);
2591
- if (m.personalSpacePath) {
2592
- loadMounts(m.personalSpacePath, (id) => {
2593
- const mm = allMemberships.find((x) => x.accountId === id);
2594
- return mm ? membershipToMountRef(mm) : null;
2595
- }).catch(() => {
2596
- });
2597
- }
2598
- } else {
2599
- setAnchor(ref);
2600
2705
  }
2706
+ const home = allMemberships.find((a) => a.accountId === getHomeAnchor()?.id);
2707
+ if (!home?.personalSpacePath) throw new Error("The home membership has no mount declaration location");
2708
+ await loadMounts(home.personalSpacePath, (id) => {
2709
+ const member = allMemberships.find((a) => a.accountId === id);
2710
+ return member ? membershipToMountRef(member) : null;
2711
+ });
2712
+ await selectMount(ref);
2713
+ setMountError(null);
2601
2714
  initVfsRealtimeWs(m.accountId);
2602
2715
  }, [allMemberships]);
2603
- const mountScope = useCallback((scopePath) => {
2716
+ const mountScope = useCallback(async (scopePath) => {
2604
2717
  const m = allMemberships.find((a) => a.path === scopePath);
2605
- if (!m) return;
2606
- mount(membershipToMountRef(m)).catch(() => {
2607
- });
2608
- mountRealtimeScope(scopePath);
2718
+ if (!m) throw new Error("Scope membership is unavailable");
2719
+ await mount(membershipToMountRef(m));
2609
2720
  }, [allMemberships]);
2610
2721
  return {
2611
2722
  accounts: electionCandidates,
@@ -2615,17 +2726,19 @@ function useAccounts() {
2615
2726
  switchAccount,
2616
2727
  mountScope,
2617
2728
  loading: query.isLoading,
2618
- error: query.error
2729
+ error: query.error ?? mountError
2619
2730
  };
2620
2731
  }
2621
2732
  function useMounts() {
2733
+ useScopeKey();
2622
2734
  const version = useSyncExternalStore(subscribeMounts, getMountVersion, getMountVersion);
2623
2735
  const mounts = useMemo(() => getMounts(), [version]);
2624
2736
  return {
2625
2737
  mounts,
2626
2738
  homeAnchor: getHomeAnchor(),
2627
2739
  anchor: getAnchorRef(),
2628
- setAnchor,
2740
+ setAnchor: selectMount,
2741
+ replaceMounts,
2629
2742
  mount,
2630
2743
  unmount
2631
2744
  };
@@ -3372,6 +3485,7 @@ function normalizeChannel(raw) {
3372
3485
  isPrivate: td.is_private || false,
3373
3486
  isDm: td.is_dm || false,
3374
3487
  messageCount: td.message_count || 0,
3488
+ createdAt: raw.created_at || "",
3375
3489
  updatedAt: raw.updated_at || "",
3376
3490
  chatScopePath: lineage.context_path || "",
3377
3491
  contextId: raw.scope_item_id || "",
@@ -3978,6 +4092,15 @@ function usePersonalItemOrder(key, items) {
3978
4092
  const scopePath = getScope();
3979
4093
  const supported = anchor?.kind === "space";
3980
4094
  const query = useArrangement(supported && scopePath ? { key, scope_id: anchor.id } : null);
4095
+ const orderedItems = useMemo(() => {
4096
+ const arranged = query.applyTo([...items]);
4097
+ if (key !== "channel-order") return arranged;
4098
+ const savedIds = new Set(query.order ?? []);
4099
+ const unplaced = arranged.filter((item) => !savedIds.has(item.id));
4100
+ const created = (item) => Date.parse(item.createdAt ?? "") || 0;
4101
+ unplaced.sort((a, b) => created(b) - created(a));
4102
+ return [...unplaced, ...arranged.filter((item) => savedIds.has(item.id))];
4103
+ }, [key, items, query.applyTo, query.order]);
3981
4104
  const client2 = useQueryClient();
3982
4105
  const pending3 = useRef(false);
3983
4106
  const ready = Boolean(supported && !query.error && query.arrangement?.item_id && query.resolutionLevel === "member" && query.arrangement.write_target_id === query.arrangement.item_id);
@@ -4004,7 +4127,7 @@ function usePersonalItemOrder(key, items) {
4004
4127
  if (cached?.item_id !== query.arrangement.item_id || JSON.stringify(cached?.type_data?.order ?? []) !== JSON.stringify(query.order ?? [])) {
4005
4128
  throw new Error("The saved order changed. Try dragging again");
4006
4129
  }
4007
- const current = query.applyTo([...items]).map((item) => item.id);
4130
+ const current = orderedItems.map((item) => item.id);
4008
4131
  if (ids.every((id, index2) => current[index2] === id)) return;
4009
4132
  const visible = new Set(visibleIds);
4010
4133
  let index = 0;
@@ -4023,7 +4146,7 @@ function usePersonalItemOrder(key, items) {
4023
4146
  }
4024
4147
  }
4025
4148
  return {
4026
- items: query.applyTo([...items]),
4149
+ items: orderedItems,
4027
4150
  ready,
4028
4151
  supported,
4029
4152
  contextKey: `${generation}:${scopePath}:${key}`,
@@ -4311,34 +4434,109 @@ function startTracking(options = {}) {
4311
4434
  return trackingCleanup;
4312
4435
  }
4313
4436
 
4314
- // src/initialize.ts
4437
+ // src/workspace.ts
4315
4438
  var initialization;
4439
+ registerCleanup(() => {
4440
+ initialization = void 0;
4441
+ });
4442
+ function initializeWorkspace(options) {
4443
+ if (initialization) {
4444
+ if (initialization.accountPath !== options.accountPath) return Promise.reject(new Error("Reset the session before changing accounts"));
4445
+ return initialization.promise;
4446
+ }
4447
+ const generation = getSessionGeneration();
4448
+ const check = () => {
4449
+ if (generation !== getSessionGeneration()) throw new Error("Workspace session changed");
4450
+ };
4451
+ const promise = (async () => {
4452
+ const entry = await initSession();
4453
+ check();
4454
+ if (!entry.principal_id) throw new Error("Workspace initialization requires an authenticated principal");
4455
+ const memberships = await getQueryClient().fetchQuery({
4456
+ queryKey: vfsKeys.memberships(entry.principal_id),
4457
+ queryFn: fetchMemberships,
4458
+ staleTime: 12e4,
4459
+ retry: false
4460
+ });
4461
+ check();
4462
+ const account = memberships.find((m) => m.path === options.accountPath && m.isAccount && !m.isSpace);
4463
+ if (!account) throw new Error("Authenticated account membership is unavailable");
4464
+ const personal = memberships.find((m) => m.isSpace && m.accountId === account.personalSpaceId && m.path === account.personalSpacePath);
4465
+ if (!personal || !account.personalSpacePath) throw new Error("A provisioned default Space membership is required");
4466
+ const home = membershipToMountRef(account);
4467
+ const defaultSpace = membershipToMountRef(personal);
4468
+ const elected = getHomeAnchor();
4469
+ if (elected && elected.id !== home.id) throw new Error("Reset the session before changing accounts");
4470
+ if (!elected) electHomeAnchor(home, { activate: false });
4471
+ await loadMounts(account.personalSpacePath, (id) => {
4472
+ const member = memberships.find((m) => m.accountId === id && (m.isAccount || m.isSpace));
4473
+ return member ? membershipToMountRef(member) : null;
4474
+ }, { initialAnchor: defaultSpace });
4475
+ check();
4476
+ return { home, defaultSpace, memberships };
4477
+ })().catch((error) => {
4478
+ if (generation === getSessionGeneration()) initialization = void 0;
4479
+ throw error;
4480
+ });
4481
+ initialization = { accountPath: options.accountPath, promise };
4482
+ return promise;
4483
+ }
4484
+
4485
+ // src/initialize.ts
4486
+ var initialization2;
4487
+ var stopActiveTracking;
4488
+ var workspaceAccount;
4489
+ registerCleanup(() => {
4490
+ stopActiveTracking?.();
4491
+ stopActiveTracking = void 0;
4492
+ initialization2 = void 0;
4493
+ workspaceAccount = void 0;
4494
+ });
4316
4495
  function initializeFsaos(options = {}) {
4317
- if (initialization) return initialization;
4318
- initialization = (async () => {
4496
+ if (initialization2) {
4497
+ if (options.workspace && options.workspace.accountPath !== workspaceAccount) {
4498
+ return Promise.reject(new Error("Initialize workspace through initializeWorkspace, or dispose the existing runtime first"));
4499
+ }
4500
+ return initialization2;
4501
+ }
4502
+ workspaceAccount = options.workspace?.accountPath;
4503
+ const generation = getSessionGeneration();
4504
+ initialization2 = (async () => {
4319
4505
  const tracking = options.tracking ?? true;
4320
4506
  const stopTracking = tracking === false ? () => {
4321
4507
  } : startTracking(typeof tracking === "object" ? tracking : {});
4508
+ stopActiveTracking = stopTracking;
4322
4509
  try {
4323
4510
  const session = options.session === false ? null : await initSession();
4511
+ const workspace = options.workspace ? await initializeWorkspace(options.workspace) : void 0;
4512
+ if (generation !== getSessionGeneration()) throw new Error("Session changed during initialization");
4324
4513
  let disposed = false;
4325
4514
  const runtime = {
4326
4515
  session,
4516
+ workspace,
4327
4517
  dispose() {
4328
4518
  if (disposed) return;
4329
4519
  disposed = true;
4330
4520
  stopTracking();
4331
- initialization = void 0;
4521
+ if (generation === getSessionGeneration()) {
4522
+ initialization2 = void 0;
4523
+ workspaceAccount = void 0;
4524
+ stopActiveTracking = void 0;
4525
+ }
4332
4526
  }
4333
4527
  };
4334
4528
  return runtime;
4335
4529
  } catch (error) {
4336
4530
  stopTracking();
4337
- initialization = void 0;
4531
+ if (generation === getSessionGeneration()) {
4532
+ initialization2 = void 0;
4533
+ workspaceAccount = void 0;
4534
+ stopActiveTracking = void 0;
4535
+ }
4338
4536
  throw error;
4339
4537
  }
4340
4538
  })();
4341
- return initialization;
4539
+ return initialization2;
4342
4540
  }
4343
4541
 
4344
4542
  // src/execution-control.ts
@@ -5369,6 +5567,6 @@ function usePinnedItems() {
5369
5567
  // src/index.ts
5370
5568
  __registerSdkExports(src_exports);
5371
5569
 
5372
- export { ComponentRenderer, EnforcementDeniedError, FormSubmissionError, GatewayRequestError, InteractionTrackingError, KernelProvider, auth, awaitAnchorReady, awaitScopeReady, callTool, cancelQueuedTurn, cancelTurn, clearCachedToken, clearSession, createItem, disconnectSSE, disposeVfsRealtimeWs, electHomeAnchor, emitEvent, fetchChannelMessages, fetchChannelMessagesPage, fetchChannelMessagesWithCards, fetchEdgesForItem, fetchItemHistory, fetchItemVersions, fetchItems, fetchMemberFocus, fetchOpenEnvelope, fetchRecentActivity, fetchResolvedItems, fetchTimelineHistory, fetchTurnQueueEntries, fetchTypeDefinitions, fetchVfsChildren, fetchVfsChildrenPage, fetchVfsItem, fetchVfsItemById, fetchVfsTree, gatewayCall, getAccessToken, getAnchor, getAnchorRef, getAnchorVersion, getAssetUrl, getDefaultSort, getHomeAnchor, getMountVersion, getMounts, getPathSubscriptionState, getQueryClient, getRequiredFormFields, getScope, getScopeVersion, getServerClockOffsetMs, getServerClockSyncedAt, getSessionEntry, hasServerClock, initSession, initVfsRealtimeWs, initializeFsaos, interpretSchema, invalidateAllVfs, invalidateChannelMessages, invalidateChildren, invalidateItem, invalidateOpenEnvelope, invalidatePathAndParent, invalidateSubtree, invalidateTypes, isAnchorReady, isChannelPath, isListableItemsFilter, isScopeReady, itemsFilterFromQueryFilter, listChildren, mount2 as mount, mountRealtimeScope, normalizeItem, onConnectionStateChange, pathSubscriptionMatches, ping, pushChanges, queryClient, readItem, refreshQuery, registerCleanup, resetAllSdkState, resolvePrincipalId, resubscribePath, serverDeadlinePassed, serverMsUntil, serverNow, setAnchor, setCachedToken, setQueryClient, setScope, setupRequireShim, signal, startTracking, steerTurn, submitForm, subscribeAnchor, subscribeArtifactStream, subscribeMounts, subscribeScope, subscribeToEvents, subscribeToPath, subscribeToScope, trackAction, trackEvent, trackPageView, unmountRealtimeScope, updateItem, uploadFile, useAccounts, useAgentTimeline, useAllChannels, useArrangement, useArtifactStream, useAsset, useAuth, useChannelMessages, useChannels, useChildren, useComponent, useCreate, useDelete, useDmChannels, useEdges, useEventStream, useFileUpload, useInfiniteChannelMessages, useInfiniteChildren, useInfiniteItems, useItem, useItemById, useItemHistory, useItemVersions, useItems, useLink, useList, useMemberFocus, useMintedAsset, useMounts, useMove, useMutation, useNotifications, useOpen, usePermission, usePermissions, usePersonalItemOrder, usePinnedItems, usePrincipal, useRealtimeQuery, useRecentActivity, useResolve, useResolvedItems, useScope, useScopeReady, useSearch, useTheme, useTimelineHistory, useTool, useTree, useType, useTypeHelpers, useTypes, useUnreadCounts, useUpdate, useUpdatePrincipal, vfsKeys, waitForPathReady };
5570
+ export { ComponentRenderer, EnforcementDeniedError, FormSubmissionError, GatewayRequestError, InteractionTrackingError, KernelProvider, auth, awaitAnchorReady, awaitScopeReady, callTool, cancelQueuedTurn, cancelTurn, clearCachedToken, clearSession, createItem, disconnectSSE, disposeVfsRealtimeWs, electHomeAnchor, emitEvent, fetchChannelMessages, fetchChannelMessagesPage, fetchChannelMessagesWithCards, fetchEdgesForItem, fetchItemHistory, fetchItemVersions, fetchItems, fetchMemberFocus, fetchOpenEnvelope, fetchRecentActivity, fetchResolvedItems, fetchTimelineHistory, fetchTurnQueueEntries, fetchTypeDefinitions, fetchVfsChildren, fetchVfsChildrenPage, fetchVfsItem, fetchVfsItemById, fetchVfsTree, gatewayCall, getAccessToken, getAnchor, getAnchorRef, getAnchorVersion, getAssetUrl, getDefaultSort, getHomeAnchor, getMountVersion, getMounts, getPathSubscriptionState, getQueryClient, getRequiredFormFields, getScope, getScopeVersion, getServerClockOffsetMs, getServerClockSyncedAt, getSessionEntry, hasServerClock, initSession, initVfsRealtimeWs, initializeFsaos, initializeWorkspace, interpretSchema, invalidateAllVfs, invalidateChannelMessages, invalidateChildren, invalidateItem, invalidateOpenEnvelope, invalidatePathAndParent, invalidateSubtree, invalidateTypes, isAnchorReady, isChannelPath, isListableItemsFilter, isScopeReady, itemsFilterFromQueryFilter, listChildren, loadMounts, mount2 as mount, mountRealtimeScope, normalizeItem, onConnectionStateChange, pathSubscriptionMatches, ping, pushChanges, queryClient, readItem, refreshQuery, registerCleanup, replaceMounts, resetAllSdkState, resolvePrincipalId, resubscribePath, selectMount, serverDeadlinePassed, serverMsUntil, serverNow, setAnchor, setCachedToken, setQueryClient, setScope, setupRequireShim, signal, startTracking, steerTurn, submitForm, subscribeAnchor, subscribeArtifactStream, subscribeMounts, subscribeScope, subscribeToEvents, subscribeToPath, subscribeToScope, trackAction, trackEvent, trackPageView, unmountRealtimeScope, updateItem, uploadFile, useAccounts, useAgentTimeline, useAllChannels, useArrangement, useArtifactStream, useAsset, useAuth, useChannelMessages, useChannels, useChildren, useComponent, useCreate, useDelete, useDmChannels, useEdges, useEventStream, useFileUpload, useInfiniteChannelMessages, useInfiniteChildren, useInfiniteItems, useItem, useItemById, useItemHistory, useItemVersions, useItems, useLink, useList, useMemberFocus, useMintedAsset, useMounts, useMove, useMutation, useNotifications, useOpen, usePermission, usePermissions, usePersonalItemOrder, usePinnedItems, usePrincipal, useRealtimeQuery, useRecentActivity, useResolve, useResolvedItems, useScope, useScopeReady, useSearch, useTheme, useTimelineHistory, useTool, useTree, useType, useTypeHelpers, useTypes, useUnreadCounts, useUpdate, useUpdatePrincipal, vfsKeys, waitForPathReady };
5373
5571
  //# sourceMappingURL=index.js.map
5374
5572
  //# sourceMappingURL=index.js.map