@tutti-os/workspace-app-center 0.0.21 → 0.0.23

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.
@@ -21,6 +21,7 @@ function mergeWorkspaceAppCatalogFields(currentApp, snapshotApp) {
21
21
  availableVersion: snapshotApp.availableVersion,
22
22
  description: snapshotApp.description,
23
23
  iconUrl: snapshotApp.iconUrl,
24
+ localPackageDir: snapshotApp.localPackageDir,
24
25
  localizations: snapshotApp.localizations,
25
26
  minimizeBehavior: snapshotApp.minimizeBehavior,
26
27
  name: snapshotApp.name,
@@ -48,7 +49,7 @@ function areWorkspaceAppCenterAppsEqual(left, right) {
48
49
  return rightApp !== void 0 && leftApp.appId === rightApp.appId && leftApp.availableIconUrl === rightApp.availableIconUrl && leftApp.availableVersion === rightApp.availableVersion && leftApp.createdAtUnixMs === rightApp.createdAtUnixMs && leftApp.description === rightApp.description && leftApp.enabled === rightApp.enabled && leftApp.exportable === rightApp.exportable && leftApp.iconUrl === rightApp.iconUrl && leftApp.installed === rightApp.installed && areWorkspaceAppInstallProgressEqual(
49
50
  leftApp.installProgress,
50
51
  rightApp.installProgress
51
- ) && leftApp.installationId === rightApp.installationId && areWorkspaceAppCenterLocalizationsEqual(
52
+ ) && leftApp.installationId === rightApp.installationId && leftApp.localPackageDir === rightApp.localPackageDir && areWorkspaceAppCenterLocalizationsEqual(
52
53
  leftApp.localizations ?? [],
53
54
  rightApp.localizations ?? []
54
55
  ) && leftApp.minimizeBehavior === rightApp.minimizeBehavior && leftApp.name === rightApp.name && leftApp.references.listSupported === rightApp.references.listSupported && leftApp.runtimeId === rightApp.runtimeId && leftApp.runtimeStatus === rightApp.runtimeStatus && leftApp.source === rightApp.source && leftApp.stateRevision === rightApp.stateRevision && areStringArraysEqual(leftApp.tags ?? [], rightApp.tags ?? []) && (leftApp.updateAvailable ?? false) === (rightApp.updateAvailable ?? false) && leftApp.launchUrl === rightApp.launchUrl && leftApp.version === rightApp.version && leftApp.windowMinHeight === rightApp.windowMinHeight && leftApp.windowMinWidth === rightApp.windowMinWidth;
@@ -118,6 +119,8 @@ function noop() {
118
119
  var defaultCatalogLoadingRefreshDelayMs = 750;
119
120
  var defaultAppOpenLaunchWaitTimeoutMs = 35e3;
120
121
  var defaultInstallRefreshDelayMs = 750;
122
+ var defaultTransientRuntimeRefreshDelayMs = 750;
123
+ var defaultTransientRuntimeRefreshMaxAttempts = 40;
121
124
  function createWorkspaceAppCenterStoreState() {
122
125
  return {
123
126
  apps: [],
@@ -141,6 +144,9 @@ var WorkspaceAppCenterControllerBase = class {
141
144
  listeners = /* @__PURE__ */ new Set();
142
145
  catalogRefreshTimer = null;
143
146
  installRefreshTimers = /* @__PURE__ */ new Map();
147
+ activeInstallRefreshTimers = /* @__PURE__ */ new Map();
148
+ transientRuntimeRefreshAttempts = 0;
149
+ transientRuntimeRefreshTimer = null;
144
150
  pendingFactoryPublishKeys = /* @__PURE__ */ new Set();
145
151
  pendingInstallKeys = /* @__PURE__ */ new Set();
146
152
  pendingInstallReportKeys = /* @__PURE__ */ new Set();
@@ -179,6 +185,8 @@ var WorkspaceAppCenterControllerBase = class {
179
185
  }
180
186
  this.clearCatalogRefreshTimer();
181
187
  this.clearInstallRefreshTimers();
188
+ this.clearActiveInstallRefreshTimers();
189
+ this.clearTransientRuntimeRefreshTimer();
182
190
  this.pollingWorkspaceId = normalizedWorkspaceId;
183
191
  }
184
192
  endWorkspacePolling(workspaceId) {
@@ -188,6 +196,8 @@ var WorkspaceAppCenterControllerBase = class {
188
196
  this.pollingWorkspaceId = null;
189
197
  this.clearCatalogRefreshTimer();
190
198
  this.clearInstallRefreshTimers();
199
+ this.clearActiveInstallRefreshTimers();
200
+ this.clearTransientRuntimeRefreshTimer();
191
201
  }
192
202
  getViewState(workspaceId, restoredState) {
193
203
  const normalizedWorkspaceId = workspaceId.trim();
@@ -261,6 +271,30 @@ var WorkspaceAppCenterControllerBase = class {
261
271
  this.pendingInstallKeys.clear();
262
272
  this.pendingInstallReportKeys.clear();
263
273
  }
274
+ clearActiveInstallRefreshTimer(workspaceId, appId) {
275
+ const key = appRuntimeKey(workspaceId, appId);
276
+ const timer = this.activeInstallRefreshTimers.get(key);
277
+ if (!timer) {
278
+ return;
279
+ }
280
+ clearTimeout(timer);
281
+ this.activeInstallRefreshTimers.delete(key);
282
+ }
283
+ clearActiveInstallRefreshTimers() {
284
+ for (const timer of this.activeInstallRefreshTimers.values()) {
285
+ clearTimeout(timer);
286
+ }
287
+ this.activeInstallRefreshTimers.clear();
288
+ }
289
+ clearTransientRuntimeRefreshTimer() {
290
+ if (!this.transientRuntimeRefreshTimer) {
291
+ this.transientRuntimeRefreshAttempts = 0;
292
+ return;
293
+ }
294
+ clearTimeout(this.transientRuntimeRefreshTimer);
295
+ this.transientRuntimeRefreshTimer = null;
296
+ this.transientRuntimeRefreshAttempts = 0;
297
+ }
264
298
  recordOperationFailure(error, toastMessage, details) {
265
299
  this.dependencies.hooks?.onOperationFailure?.({
266
300
  details,
@@ -387,10 +421,16 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
387
421
  workspaceId
388
422
  });
389
423
  }
424
+ this.reconcileActiveInstallRefreshes(workspaceId, nextApps);
390
425
  const appIdsToClose = this.store.workspaceId === workspaceId ? removedOrUninstalledAppIds(this.store.apps, nextApps) : [];
391
426
  this.scheduleCatalogLoadingRefresh(workspaceId, snapshot);
427
+ const hasTransientRuntimeApps = this.hasTransientRuntimeApps(
428
+ workspaceId,
429
+ nextApps
430
+ );
392
431
  const changed = this.store.workspaceId !== workspaceId || this.store.catalogLastError !== (snapshot.catalogLastError ?? null) || this.store.catalogStatus !== snapshot.catalogStatus || this.store.catalogUpdatedAtUnixMs !== (snapshot.catalogUpdatedAtUnixMs ?? null) || this.store.error !== null || this.store.loadStatus !== "ready" || !areWorkspaceAppCenterAppsEqual(this.store.apps, nextApps);
393
432
  if (!changed) {
433
+ this.syncTransientRuntimeRefresh(workspaceId, hasTransientRuntimeApps);
394
434
  return;
395
435
  }
396
436
  this.store.apps = nextApps;
@@ -402,6 +442,7 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
402
442
  this.store.workspaceId = workspaceId;
403
443
  this.bumpRevision();
404
444
  this.closeWorkspaceAppViews(workspaceId, appIdsToClose);
445
+ this.syncTransientRuntimeRefresh(workspaceId, hasTransientRuntimeApps);
405
446
  }
406
447
  applyFactorySnapshot(workspaceId, snapshot) {
407
448
  if (this.store.workspaceId !== workspaceId) {
@@ -457,13 +498,14 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
457
498
  const currentApp = this.store.apps.find(
458
499
  (candidate) => candidate.appId === nextApp.appId
459
500
  );
460
- if (currentApp && nextApp.stateRevision <= currentApp.stateRevision) {
501
+ if (currentApp && nextApp.stateRevision <= currentApp.stateRevision && !this.shouldAcceptRuntimeSnapshot(currentApp, nextApp)) {
461
502
  this.settlePendingInstallReport({
462
503
  app: currentApp,
463
504
  failureReason: options.installFailureReason ?? currentApp.failureReason ?? currentApp.lastError ?? null,
464
505
  previousApp: currentApp,
465
506
  workspaceId
466
507
  });
508
+ this.reconcileActiveInstallRefreshes(workspaceId, [currentApp]);
467
509
  return;
468
510
  }
469
511
  const mergedApp = this.mergeActiveInstallAppState(
@@ -478,6 +520,7 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
478
520
  ),
479
521
  mergedApp
480
522
  ]);
523
+ this.reconcileActiveInstallRefreshes(workspaceId, [mergedApp]);
481
524
  if (areWorkspaceAppCenterAppsEqual(this.store.apps, nextApps)) {
482
525
  return;
483
526
  }
@@ -531,6 +574,7 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
531
574
  this.installRefreshTimers.set(key, timer);
532
575
  }
533
576
  markAppStarting(appId) {
577
+ this.transientRuntimeRefreshAttempts = 0;
534
578
  this.store.apps = this.store.apps.map(
535
579
  (app) => app.appId === appId ? {
536
580
  ...app,
@@ -557,6 +601,7 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
557
601
  };
558
602
  });
559
603
  if (changed) {
604
+ this.transientRuntimeRefreshAttempts = 0;
560
605
  this.bumpRevision();
561
606
  }
562
607
  }
@@ -595,6 +640,7 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
595
640
  };
596
641
  });
597
642
  if (changed) {
643
+ this.transientRuntimeRefreshAttempts = 0;
598
644
  this.bumpRevision();
599
645
  }
600
646
  }
@@ -635,13 +681,28 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
635
681
  snapshotApp,
636
682
  currentApp
637
683
  );
638
- if (pendingSettled && currentApp.stateRevision <= snapshotApp.stateRevision) {
684
+ if (pendingSettled && currentApp.stateRevision < snapshotApp.stateRevision) {
639
685
  return snapshotApp;
640
686
  }
641
687
  }
688
+ if (this.shouldAcceptRuntimeSnapshot(currentApp, snapshotApp)) {
689
+ return snapshotApp;
690
+ }
642
691
  return mergeWorkspaceAppCatalogFields(currentApp, snapshotApp);
643
692
  });
644
693
  }
694
+ shouldAcceptRuntimeSnapshot(currentApp, snapshotApp) {
695
+ if (snapshotApp.runtimeStatus === "running") {
696
+ return currentApp.runtimeStatus !== "running" || currentApp.launchUrl !== snapshotApp.launchUrl || currentApp.runtimeId !== snapshotApp.runtimeId || currentApp.installationId !== snapshotApp.installationId || currentApp.installProgress != null || currentApp.failureReason != null || currentApp.lastError != null;
697
+ }
698
+ if (snapshotApp.runtimeStatus === "failed") {
699
+ return currentApp.runtimeStatus !== "failed" && (snapshotApp.failureReason != null || snapshotApp.lastError != null);
700
+ }
701
+ if (snapshotApp.runtimeStatus === "stopping" || snapshotApp.runtimeStatus === "unavailable") {
702
+ return currentApp.runtimeStatus !== snapshotApp.runtimeStatus;
703
+ }
704
+ return false;
705
+ }
645
706
  scheduleCatalogLoadingRefresh(workspaceId, snapshot) {
646
707
  if (snapshot.catalogStatus !== "loading") {
647
708
  this.clearCatalogRefreshTimer();
@@ -659,6 +720,52 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
659
720
  }, this.dependencies.catalogLoadingRefreshDelayMs ?? defaultCatalogLoadingRefreshDelayMs);
660
721
  this.catalogRefreshTimer.unref?.();
661
722
  }
723
+ syncTransientRuntimeRefresh(workspaceId, hasTransientRuntimeApps) {
724
+ if (this.pollingWorkspaceId !== workspaceId) {
725
+ return;
726
+ }
727
+ if (!hasTransientRuntimeApps) {
728
+ this.clearTransientRuntimeRefreshTimer();
729
+ return;
730
+ }
731
+ if (this.transientRuntimeRefreshTimer) {
732
+ return;
733
+ }
734
+ const maxAttempts = this.dependencies.transientRuntimeRefreshMaxAttempts ?? defaultTransientRuntimeRefreshMaxAttempts;
735
+ if (this.transientRuntimeRefreshAttempts >= maxAttempts) {
736
+ return;
737
+ }
738
+ this.transientRuntimeRefreshTimer = setTimeout(() => {
739
+ this.transientRuntimeRefreshTimer = null;
740
+ if (this.pollingWorkspaceId !== workspaceId || !this.hasTransientRuntimeApps(workspaceId, this.store.apps)) {
741
+ this.clearTransientRuntimeRefreshTimer();
742
+ return;
743
+ }
744
+ this.transientRuntimeRefreshAttempts += 1;
745
+ void this.refreshTransientRuntimeState(workspaceId);
746
+ }, this.dependencies.transientRuntimeRefreshDelayMs ?? defaultTransientRuntimeRefreshDelayMs);
747
+ this.transientRuntimeRefreshTimer.unref?.();
748
+ }
749
+ hasTransientRuntimeApps(workspaceId, apps) {
750
+ return apps.some(
751
+ (app) => app.enabled && app.installed && (app.runtimeStatus === "preparing" || app.runtimeStatus === "starting") && !this.pendingInstallKeys.has(appRuntimeKey(workspaceId, app.appId))
752
+ );
753
+ }
754
+ async refreshTransientRuntimeState(workspaceId) {
755
+ try {
756
+ const snapshot = await this.dependencies.gateway.listWorkspaceApps(workspaceId);
757
+ this.applySnapshot(workspaceId, snapshot);
758
+ } catch (error) {
759
+ this.setOperationError(error, {
760
+ operation: "app_center.refresh",
761
+ workspaceId
762
+ });
763
+ this.syncTransientRuntimeRefresh(
764
+ workspaceId,
765
+ this.hasTransientRuntimeApps(workspaceId, this.store.apps)
766
+ );
767
+ }
768
+ }
662
769
  mergeActiveInstallAppState(workspaceId, app, previousApp) {
663
770
  const installKey = appRuntimeKey(workspaceId, app.appId);
664
771
  if (!this.pendingInstallKeys.has(installKey)) {
@@ -720,6 +827,94 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
720
827
  this.scheduleInstallRefresh(workspaceId, appId);
721
828
  }
722
829
  }
830
+ reconcileActiveInstallRefreshes(workspaceId, apps) {
831
+ if (this.pollingWorkspaceId !== workspaceId) {
832
+ return;
833
+ }
834
+ for (const app of apps) {
835
+ const key = appRuntimeKey(workspaceId, app.appId);
836
+ if (this.pendingInstallKeys.has(key)) {
837
+ continue;
838
+ }
839
+ if (this.shouldRefreshActiveInstallApp(app)) {
840
+ this.scheduleActiveInstallRefresh(workspaceId, app.appId);
841
+ } else {
842
+ this.clearActiveInstallRefreshTimer(workspaceId, app.appId);
843
+ }
844
+ }
845
+ }
846
+ shouldRefreshActiveInstallApp(app) {
847
+ return app.installProgress != null || this.isRuntimeInstallBusy(app.runtimeStatus);
848
+ }
849
+ scheduleActiveInstallRefresh(workspaceId, appId) {
850
+ const key = appRuntimeKey(workspaceId, appId);
851
+ if (this.activeInstallRefreshTimers.has(key)) {
852
+ return;
853
+ }
854
+ const timer = setTimeout(() => {
855
+ this.activeInstallRefreshTimers.delete(key);
856
+ if (this.pollingWorkspaceId !== workspaceId) {
857
+ return;
858
+ }
859
+ void this.refreshActiveInstallState(workspaceId, appId);
860
+ }, this.dependencies.installRefreshDelayMs ?? defaultInstallRefreshDelayMs);
861
+ timer.unref?.();
862
+ this.activeInstallRefreshTimers.set(key, timer);
863
+ }
864
+ async refreshActiveInstallState(workspaceId, appId) {
865
+ try {
866
+ const snapshot = await this.dependencies.gateway.listWorkspaceApps(workspaceId);
867
+ this.applyActiveInstallRefreshSnapshot(workspaceId, appId, snapshot);
868
+ } catch (error) {
869
+ this.setOperationError(error, {
870
+ appId,
871
+ operation: "workspace_app.refresh_install_state",
872
+ uiAction: "refresh_install_state",
873
+ workspaceId
874
+ });
875
+ }
876
+ }
877
+ applyActiveInstallRefreshSnapshot(workspaceId, appId, snapshot) {
878
+ if (this.store.workspaceId !== workspaceId) {
879
+ this.applySnapshot(workspaceId, snapshot);
880
+ return;
881
+ }
882
+ const currentApp = this.store.apps.find(
883
+ (candidate) => candidate.appId === appId
884
+ );
885
+ const refreshedApp = snapshot.apps.find(
886
+ (candidate) => candidate.appId === appId
887
+ );
888
+ if (currentApp && refreshedApp && currentApp.stateRevision === refreshedApp.stateRevision && this.shouldRefreshActiveInstallApp(currentApp) && !this.shouldRefreshActiveInstallApp(refreshedApp)) {
889
+ this.applySnapshotWithForcedApp(workspaceId, snapshot, refreshedApp);
890
+ return;
891
+ }
892
+ this.applySnapshot(workspaceId, snapshot);
893
+ }
894
+ applySnapshotWithForcedApp(workspaceId, snapshot, forcedApp) {
895
+ const nextApps = sortWorkspaceAppCenterApps(
896
+ this.mergeSnapshotAppsByStateRevision(
897
+ workspaceId,
898
+ this.withPendingInstallState(workspaceId, snapshot.apps)
899
+ ).map((app) => app.appId === forcedApp.appId ? forcedApp : app)
900
+ );
901
+ const appIdsToClose = this.store.workspaceId === workspaceId ? removedOrUninstalledAppIds(this.store.apps, nextApps) : [];
902
+ this.reconcileActiveInstallRefreshes(workspaceId, nextApps);
903
+ this.scheduleCatalogLoadingRefresh(workspaceId, snapshot);
904
+ const changed = this.store.workspaceId !== workspaceId || this.store.catalogLastError !== (snapshot.catalogLastError ?? null) || this.store.catalogStatus !== snapshot.catalogStatus || this.store.catalogUpdatedAtUnixMs !== (snapshot.catalogUpdatedAtUnixMs ?? null) || this.store.error !== null || this.store.loadStatus !== "ready" || !areWorkspaceAppCenterAppsEqual(this.store.apps, nextApps);
905
+ if (!changed) {
906
+ return;
907
+ }
908
+ this.store.apps = nextApps;
909
+ this.store.catalogLastError = snapshot.catalogLastError ?? null;
910
+ this.store.catalogStatus = snapshot.catalogStatus;
911
+ this.store.catalogUpdatedAtUnixMs = snapshot.catalogUpdatedAtUnixMs ?? null;
912
+ this.store.error = null;
913
+ this.store.loadStatus = "ready";
914
+ this.store.workspaceId = workspaceId;
915
+ this.bumpRevision();
916
+ this.closeWorkspaceAppViews(workspaceId, appIdsToClose);
917
+ }
723
918
  settlePendingInstallReport(input) {
724
919
  const installKey = appRuntimeKey(input.workspaceId, input.app.appId);
725
920
  if (!this.pendingInstallKeys.has(installKey)) {
@@ -811,7 +1006,14 @@ var WorkspaceAppCenterController = class extends WorkspaceAppCenterControllerSta
811
1006
  this.applySnapshot(input.workspaceId, snapshot);
812
1007
  } catch (error) {
813
1008
  if (this.store.workspaceId === input.workspaceId) {
814
- this.store.apps = previousApps;
1009
+ try {
1010
+ const snapshot = await this.dependencies.gateway.listWorkspaceApps(
1011
+ input.workspaceId
1012
+ );
1013
+ this.applySnapshot(input.workspaceId, snapshot);
1014
+ } catch {
1015
+ this.store.apps = previousApps;
1016
+ }
815
1017
  }
816
1018
  this.setOperationError(error, {
817
1019
  appId: input.appId,
@@ -1775,6 +1977,7 @@ function createAppCenterViewModel({
1775
1977
  const installBusy = runtime?.installProgress != null || status === "installing";
1776
1978
  const sourceKind = resolveCatalogSourceKind(app.catalog);
1777
1979
  const localApp = sourceKind === "local";
1980
+ const localDevApp = app.catalog?.source?.kind === "local-dev";
1778
1981
  const runtimeId = normalizeOptionalString(runtime?.runtimeId);
1779
1982
  const launchUrl = normalizeOptionalString(runtime?.launchUrl);
1780
1983
  const installedVersion = normalizeOptionalString(app.install?.version);
@@ -1822,11 +2025,12 @@ function createAppCenterViewModel({
1822
2025
  primaryAction,
1823
2026
  sourceKind,
1824
2027
  canOpen,
1825
- canExport: localApp,
2028
+ canExport: localApp && !localDevApp,
1826
2029
  canDelete: localApp,
1827
- canReplaceIcon: replaceableIconAppIdSet.has(app.manifest.appId),
2030
+ canReloadLocal: localDevApp && installed && !installBusy,
2031
+ canReplaceIcon: !localDevApp && replaceableIconAppIdSet.has(app.manifest.appId),
1828
2032
  canOpenFolder: installed && !installBusy,
1829
- canOpenPackageFolder: installed && localApp && Boolean(displayVersion) && !installBusy,
2033
+ canOpenPackageFolder: installed && localApp && (localDevApp || Boolean(displayVersion)) && !installBusy,
1830
2034
  canOpenFactorySession: Boolean(factoryAgentSessionId),
1831
2035
  canPublishFactoryUpdate: installed && factoryJob?.status === "ready" && Boolean(factoryJob.publishedVersion?.trim()),
1832
2036
  canUninstall: installed && !installBusy,
@@ -1892,7 +2096,7 @@ function resolveRuntimeStatusForViewModel(runtime) {
1892
2096
  return runtime.status;
1893
2097
  }
1894
2098
  function resolveCatalogSourceKind(catalog) {
1895
- return catalog?.source?.kind ?? "bundled";
2099
+ return catalog?.source?.kind === "local-dev" ? "local" : catalog?.source?.kind ?? "bundled";
1896
2100
  }
1897
2101
  function resolveWorkspaceAppCatalogMetadata(input) {
1898
2102
  const manifest = input.manifest;
@@ -2067,4 +2271,4 @@ export {
2067
2271
  resolveWorkspaceAppCatalogMetadata,
2068
2272
  createWorkspaceAppRecord
2069
2273
  };
2070
- //# sourceMappingURL=chunk-ALPQ5IVQ.js.map
2274
+ //# sourceMappingURL=chunk-CRRKYYP7.js.map