@tutti-os/workspace-app-center 0.0.11 → 0.0.13

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.
@@ -0,0 +1,110 @@
1
+ // src/core/appCenterAppOrdering.ts
2
+ var installableRecommendedAppIds = [
3
+ ["ai-media-canvas", "media-canvas"],
4
+ ["vibe-design"],
5
+ ["group-chat"],
6
+ ["automation"],
7
+ ["daily-product-radar", "daily-tech-radar", "radar"]
8
+ ];
9
+ var comingSoonRecommendedAppIds = [
10
+ "ai-ppt",
11
+ "ai-document",
12
+ "ai-sheet",
13
+ "open-cut",
14
+ "product-competition",
15
+ "design-review",
16
+ "calendar",
17
+ "document-summarizer"
18
+ ];
19
+ var recommendedAppDisplayRankById = buildRecommendedAppDisplayRankById();
20
+ function sortMyAppsByCreatedDesc(apps) {
21
+ return [...apps].sort((left, right) => {
22
+ const createdOrder = (right.createdAtUnixMs ?? 0) - (left.createdAtUnixMs ?? 0);
23
+ if (createdOrder !== 0) {
24
+ return createdOrder;
25
+ }
26
+ const nameOrder = left.name.localeCompare(right.name);
27
+ if (nameOrder !== 0) {
28
+ return nameOrder;
29
+ }
30
+ return left.id.localeCompare(right.id);
31
+ });
32
+ }
33
+ function sortRecommendedApps(apps) {
34
+ return [...apps].sort(compareRecommendedApps);
35
+ }
36
+ function sortRecommendedAppsForAllTab(apps) {
37
+ return [...apps].sort(compareRecommendedApps);
38
+ }
39
+ function compareRecommendedApps(left, right) {
40
+ const sectionOrder = getRecommendedAppSectionRank(left) - getRecommendedAppSectionRank(right);
41
+ if (sectionOrder !== 0) {
42
+ return sectionOrder;
43
+ }
44
+ const displayOrder = getRecommendedAppDisplayRank(left.id) - getRecommendedAppDisplayRank(right.id);
45
+ if (displayOrder !== 0) {
46
+ return displayOrder;
47
+ }
48
+ const comingSoonOrder = getRecommendedAppComingSoonRank(left) - getRecommendedAppComingSoonRank(right);
49
+ if (comingSoonOrder !== 0) {
50
+ return comingSoonOrder;
51
+ }
52
+ return left.name.localeCompare(right.name);
53
+ }
54
+ function buildRecommendedAppDisplayRankById() {
55
+ const rankById = /* @__PURE__ */ new Map();
56
+ let rank = 0;
57
+ for (const aliases of installableRecommendedAppIds) {
58
+ for (const appId of aliases) {
59
+ rankById.set(appId, rank);
60
+ }
61
+ rank += 1;
62
+ }
63
+ for (const appId of comingSoonRecommendedAppIds) {
64
+ rankById.set(appId, rank);
65
+ rank += 1;
66
+ }
67
+ return rankById;
68
+ }
69
+ function getRecommendedAppDisplayRank(appId) {
70
+ return recommendedAppDisplayRankById.get(appId.trim().toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
71
+ }
72
+ function getRecommendedAppSectionRank(app) {
73
+ const normalizedAppId = app.id.trim().toLowerCase();
74
+ if (recommendedAppDisplayRankById.has(normalizedAppId)) {
75
+ return isConfiguredComingSoonApp(normalizedAppId) ? 1 : 0;
76
+ }
77
+ return getRecommendedAppComingSoonRank(app);
78
+ }
79
+ function isConfiguredComingSoonApp(appId) {
80
+ return comingSoonRecommendedAppIds.some(
81
+ (configuredAppId) => configuredAppId === appId
82
+ );
83
+ }
84
+ function getRecommendedAppComingSoonRank(app) {
85
+ return app.statusLabelKey === "status.comingSoon" || app.tags.some((tag) => tag.trim().toLowerCase() === "coming-soon") ? 1 : 0;
86
+ }
87
+
88
+ // src/core/appFactoryProviderDefaults.ts
89
+ var DEFAULT_APP_FACTORY_PROVIDER = "codex";
90
+ function resolveDefaultAppFactoryProvider(options, preferredProvider) {
91
+ const normalizedPreferredProvider = preferredProvider?.trim() ?? "";
92
+ return options.find(
93
+ (option) => normalizedPreferredProvider !== "" && option.disabled !== true && option.provider === normalizedPreferredProvider
94
+ )?.provider ?? options.find(
95
+ (option) => option.disabled !== true && option.provider === DEFAULT_APP_FACTORY_PROVIDER
96
+ )?.provider ?? options.find((option) => option.disabled !== true)?.provider ?? options.find((option) => option.provider === DEFAULT_APP_FACTORY_PROVIDER)?.provider ?? options[0]?.provider ?? "";
97
+ }
98
+ function resolveSelectedAppFactoryProvider(currentProvider, options, preferredProvider) {
99
+ return options.some((option) => option.provider === currentProvider) ? currentProvider : resolveDefaultAppFactoryProvider(options, preferredProvider);
100
+ }
101
+
102
+ export {
103
+ sortMyAppsByCreatedDesc,
104
+ sortRecommendedApps,
105
+ sortRecommendedAppsForAllTab,
106
+ DEFAULT_APP_FACTORY_PROVIDER,
107
+ resolveDefaultAppFactoryProvider,
108
+ resolveSelectedAppFactoryProvider
109
+ };
110
+ //# sourceMappingURL=chunk-2HXFGEJS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/appCenterAppOrdering.ts","../src/core/appFactoryProviderDefaults.ts"],"sourcesContent":["import type { AppCenterViewModel } from \"../contracts/viewModel.ts\";\n\nconst installableRecommendedAppIds = [\n [\"ai-media-canvas\", \"media-canvas\"],\n [\"vibe-design\"],\n [\"group-chat\"],\n [\"automation\"],\n [\"daily-product-radar\", \"daily-tech-radar\", \"radar\"]\n] as const;\n\nconst comingSoonRecommendedAppIds = [\n \"ai-ppt\",\n \"ai-document\",\n \"ai-sheet\",\n \"open-cut\",\n \"product-competition\",\n \"design-review\",\n \"calendar\",\n \"document-summarizer\"\n] as const;\n\nconst recommendedAppDisplayRankById = buildRecommendedAppDisplayRankById();\n\nexport function sortMyAppsByCreatedDesc(\n apps: readonly AppCenterViewModel[\"apps\"][number][]\n): AppCenterViewModel[\"apps\"] {\n return [...apps].sort((left, right) => {\n const createdOrder =\n (right.createdAtUnixMs ?? 0) - (left.createdAtUnixMs ?? 0);\n if (createdOrder !== 0) {\n return createdOrder;\n }\n const nameOrder = left.name.localeCompare(right.name);\n if (nameOrder !== 0) {\n return nameOrder;\n }\n return left.id.localeCompare(right.id);\n });\n}\n\nexport function sortRecommendedApps(\n apps: readonly AppCenterViewModel[\"apps\"][number][]\n): AppCenterViewModel[\"apps\"] {\n return [...apps].sort(compareRecommendedApps);\n}\n\nexport function sortRecommendedAppsForAllTab(\n apps: readonly AppCenterViewModel[\"apps\"][number][]\n): AppCenterViewModel[\"apps\"] {\n return [...apps].sort(compareRecommendedApps);\n}\n\nfunction compareRecommendedApps(\n left: AppCenterViewModel[\"apps\"][number],\n right: AppCenterViewModel[\"apps\"][number]\n): number {\n const sectionOrder =\n getRecommendedAppSectionRank(left) - getRecommendedAppSectionRank(right);\n if (sectionOrder !== 0) {\n return sectionOrder;\n }\n\n const displayOrder =\n getRecommendedAppDisplayRank(left.id) -\n getRecommendedAppDisplayRank(right.id);\n if (displayOrder !== 0) {\n return displayOrder;\n }\n\n const comingSoonOrder =\n getRecommendedAppComingSoonRank(left) -\n getRecommendedAppComingSoonRank(right);\n if (comingSoonOrder !== 0) {\n return comingSoonOrder;\n }\n\n return left.name.localeCompare(right.name);\n}\n\nfunction buildRecommendedAppDisplayRankById(): Map<string, number> {\n const rankById = new Map<string, number>();\n let rank = 0;\n\n for (const aliases of installableRecommendedAppIds) {\n for (const appId of aliases) {\n rankById.set(appId, rank);\n }\n rank += 1;\n }\n\n for (const appId of comingSoonRecommendedAppIds) {\n rankById.set(appId, rank);\n rank += 1;\n }\n\n return rankById;\n}\n\nfunction getRecommendedAppDisplayRank(appId: string): number {\n return (\n recommendedAppDisplayRankById.get(appId.trim().toLowerCase()) ??\n Number.MAX_SAFE_INTEGER\n );\n}\n\nfunction getRecommendedAppSectionRank(\n app: AppCenterViewModel[\"apps\"][number]\n): number {\n const normalizedAppId = app.id.trim().toLowerCase();\n if (recommendedAppDisplayRankById.has(normalizedAppId)) {\n return isConfiguredComingSoonApp(normalizedAppId) ? 1 : 0;\n }\n\n return getRecommendedAppComingSoonRank(app);\n}\n\nfunction isConfiguredComingSoonApp(appId: string): boolean {\n return comingSoonRecommendedAppIds.some(\n (configuredAppId) => configuredAppId === appId\n );\n}\n\nfunction getRecommendedAppComingSoonRank(\n app: AppCenterViewModel[\"apps\"][number]\n): number {\n return app.statusLabelKey === \"status.comingSoon\" ||\n app.tags.some((tag) => tag.trim().toLowerCase() === \"coming-soon\")\n ? 1\n : 0;\n}\n","export interface AppFactoryProviderDefaultOption {\n readonly disabled?: boolean;\n readonly provider: string;\n}\n\nexport const DEFAULT_APP_FACTORY_PROVIDER = \"codex\";\n\nexport function resolveDefaultAppFactoryProvider(\n options: readonly AppFactoryProviderDefaultOption[],\n preferredProvider?: string | null\n): string {\n const normalizedPreferredProvider = preferredProvider?.trim() ?? \"\";\n return (\n options.find(\n (option) =>\n normalizedPreferredProvider !== \"\" &&\n option.disabled !== true &&\n option.provider === normalizedPreferredProvider\n )?.provider ??\n options.find(\n (option) =>\n option.disabled !== true &&\n option.provider === DEFAULT_APP_FACTORY_PROVIDER\n )?.provider ??\n options.find((option) => option.disabled !== true)?.provider ??\n options.find((option) => option.provider === DEFAULT_APP_FACTORY_PROVIDER)\n ?.provider ??\n options[0]?.provider ??\n \"\"\n );\n}\n\nexport function resolveSelectedAppFactoryProvider(\n currentProvider: string,\n options: readonly AppFactoryProviderDefaultOption[],\n preferredProvider?: string | null\n): string {\n return options.some((option) => option.provider === currentProvider)\n ? currentProvider\n : resolveDefaultAppFactoryProvider(options, preferredProvider);\n}\n"],"mappings":"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","names":[]}
@@ -13,4 +13,4 @@ var workspaceAppRuntimeStatuses = [
13
13
  export {
14
14
  workspaceAppRuntimeStatuses
15
15
  };
16
- //# sourceMappingURL=chunk-PD2WLRHE.js.map
16
+ //# sourceMappingURL=chunk-7BJAJ7F3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contracts/runtime.ts"],"sourcesContent":["export const workspaceAppRuntimeStatuses = [\n \"idle\",\n \"installing\",\n \"preparing\",\n \"starting\",\n \"running\",\n \"failed\",\n \"stopping\",\n \"unavailable\"\n] as const;\n\nexport type WorkspaceAppRuntimeStatus =\n (typeof workspaceAppRuntimeStatuses)[number];\n\nexport interface WorkspaceAppRuntimeError {\n readonly code?: string;\n readonly message: string;\n}\n\nexport type WorkspaceAppInstallUserPhase =\n | \"downloading\"\n | \"installing\"\n | \"starting\";\n\nexport interface WorkspaceAppInstallProgress {\n readonly userPhase: WorkspaceAppInstallUserPhase;\n readonly overallPercent: number;\n readonly downloadedBytes?: number | null;\n readonly totalBytes?: number | null;\n readonly indeterminate: boolean;\n}\n\nexport interface WorkspaceAppRuntimeState {\n readonly runtimeId?: string;\n readonly installationId?: string;\n readonly appId: string;\n readonly status: WorkspaceAppRuntimeStatus;\n readonly launchUrl?: string | null;\n readonly installProgress?: WorkspaceAppInstallProgress | null;\n readonly error?: WorkspaceAppRuntimeError | null;\n readonly startedAt?: string | null;\n readonly updatedAt?: string | null;\n}\n"],"mappings":";AAAO,IAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  workspaceAppManifestSchemaVersion
3
- } from "./chunk-UBERYRMA.js";
3
+ } from "./chunk-Y63TZ5JT.js";
4
4
 
5
5
  // src/core/appCenterControllerHelpers.ts
6
6
  function resolveAppRunDurationMs(startedAtUnixMs, now) {
@@ -30,16 +30,28 @@ function mergeWorkspaceAppCatalogFields(currentApp, snapshotApp) {
30
30
  updateAvailable: snapshotApp.updateAvailable
31
31
  };
32
32
  }
33
+ function areWorkspaceAppInstallProgressEqual(left, right) {
34
+ if (left == null && right == null) {
35
+ return true;
36
+ }
37
+ if (left == null || right == null) {
38
+ return false;
39
+ }
40
+ return left.userPhase === right.userPhase && left.overallPercent === right.overallPercent && left.downloadedBytes === right.downloadedBytes && left.totalBytes === right.totalBytes && left.indeterminate === right.indeterminate;
41
+ }
33
42
  function areWorkspaceAppCenterAppsEqual(left, right) {
34
43
  if (left.length !== right.length) {
35
44
  return false;
36
45
  }
37
46
  return left.every((leftApp, index) => {
38
47
  const rightApp = right[index];
39
- 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 && leftApp.installationId === rightApp.installationId && areWorkspaceAppCenterLocalizationsEqual(
48
+ 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
+ leftApp.installProgress,
50
+ rightApp.installProgress
51
+ ) && leftApp.installationId === rightApp.installationId && areWorkspaceAppCenterLocalizationsEqual(
40
52
  leftApp.localizations ?? [],
41
53
  rightApp.localizations ?? []
42
- ) && leftApp.minimizeBehavior === rightApp.minimizeBehavior && leftApp.name === rightApp.name && leftApp.references.searchSupported === rightApp.references.searchSupported && 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;
54
+ ) && 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;
43
55
  });
44
56
  }
45
57
  function normalizeWorkspaceAppCenterViewState(value) {
@@ -276,6 +288,85 @@ var WorkspaceAppCenterControllerBase = class {
276
288
  }
277
289
  };
278
290
 
291
+ // src/core/installProgressMerge.ts
292
+ var installUserPhaseOrder = {
293
+ downloading: 0,
294
+ installing: 1,
295
+ starting: 2
296
+ };
297
+ var defaultDownloadCompletePercent = 70;
298
+ var defaultInstallingPartialPercent = 79;
299
+ var defaultStartingPartialPercent = 96;
300
+ function reconcilePendingInstallProgress(input) {
301
+ const runtimePhase = runtimeStatusToInstallUserPhase(input.runtimeStatus);
302
+ const progress = input.incoming ?? input.previous ?? (runtimePhase == null ? null : createProgressForRuntimePhase(runtimePhase));
303
+ if (progress == null) {
304
+ return null;
305
+ }
306
+ if (runtimePhase == null) {
307
+ return clearDownloadBytesUnlessDownloading(progress);
308
+ }
309
+ if (compareInstallUserPhase(progress.userPhase, runtimePhase) >= 0) {
310
+ return clearDownloadBytesUnlessDownloading(progress);
311
+ }
312
+ return advanceInstallProgressToPhase(progress, runtimePhase);
313
+ }
314
+ function runtimeStatusToInstallUserPhase(runtimeStatus) {
315
+ switch (runtimeStatus) {
316
+ case "preparing":
317
+ return "installing";
318
+ case "starting":
319
+ return "starting";
320
+ default:
321
+ return null;
322
+ }
323
+ }
324
+ function compareInstallUserPhase(left, right) {
325
+ return installUserPhaseOrder[left] - installUserPhaseOrder[right];
326
+ }
327
+ function advanceInstallProgressToPhase(progress, targetPhase) {
328
+ let overallPercent = progress.overallPercent;
329
+ let userPhase = progress.userPhase;
330
+ if (targetPhase === "installing" || targetPhase === "starting") {
331
+ overallPercent = Math.max(overallPercent, defaultDownloadCompletePercent);
332
+ userPhase = "installing";
333
+ }
334
+ if (targetPhase === "starting") {
335
+ overallPercent = Math.max(overallPercent, defaultStartingPartialPercent);
336
+ userPhase = "starting";
337
+ } else if (targetPhase === "installing") {
338
+ overallPercent = Math.max(overallPercent, defaultInstallingPartialPercent);
339
+ }
340
+ return clearDownloadBytesUnlessDownloading({
341
+ ...progress,
342
+ indeterminate: false,
343
+ overallPercent,
344
+ userPhase
345
+ });
346
+ }
347
+ function createProgressForRuntimePhase(userPhase) {
348
+ return advanceInstallProgressToPhase(
349
+ {
350
+ downloadedBytes: null,
351
+ indeterminate: false,
352
+ overallPercent: 0,
353
+ totalBytes: null,
354
+ userPhase: "downloading"
355
+ },
356
+ userPhase
357
+ );
358
+ }
359
+ function clearDownloadBytesUnlessDownloading(progress) {
360
+ if (progress.userPhase === "downloading") {
361
+ return progress;
362
+ }
363
+ return {
364
+ ...progress,
365
+ downloadedBytes: null,
366
+ totalBytes: null
367
+ };
368
+ }
369
+
279
370
  // src/core/appCenterControllerState.ts
280
371
  var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControllerBase {
281
372
  applySnapshot(workspaceId, snapshot) {
@@ -371,11 +462,16 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
371
462
  return;
372
463
  }
373
464
  const appIdsToClose = currentApp?.installed === true && !nextApp.installed ? [nextApp.appId] : [];
465
+ const mergedApp = this.mergeActiveInstallAppState(
466
+ workspaceId,
467
+ nextApp,
468
+ currentApp
469
+ );
374
470
  const nextApps = sortWorkspaceAppCenterApps([
375
471
  ...this.store.apps.filter(
376
472
  (candidate) => candidate.appId !== nextApp.appId
377
473
  ),
378
- nextApp
474
+ mergedApp
379
475
  ]);
380
476
  if (areWorkspaceAppCenterAppsEqual(this.store.apps, nextApps)) {
381
477
  return;
@@ -385,8 +481,8 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
385
481
  this.store.loadStatus = "ready";
386
482
  this.bumpRevision();
387
483
  this.settlePendingInstallReport({
388
- app: nextApp,
389
- failureReason: options.installFailureReason ?? nextApp.failureReason ?? nextApp.lastError ?? null,
484
+ app: mergedApp,
485
+ failureReason: options.installFailureReason ?? mergedApp.failureReason ?? mergedApp.lastError ?? null,
390
486
  workspaceId
391
487
  });
392
488
  this.closeWorkspaceAppViews(workspaceId, appIdsToClose);
@@ -495,15 +591,24 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
495
591
  this.bumpRevision();
496
592
  }
497
593
  }
498
- isPendingInstallSettled(installKey, app) {
594
+ isPendingInstallSettled(installKey, app, previousApp) {
595
+ if (this.isPendingInstallAbandoned(installKey, app, previousApp)) {
596
+ return true;
597
+ }
499
598
  if (app.runtimeStatus === "failed") {
500
599
  return true;
501
600
  }
601
+ if (this.isRuntimeInstallBusy(app.runtimeStatus)) {
602
+ return false;
603
+ }
502
604
  if (this.pendingInstallReportKeys.has(installKey)) {
503
605
  return app.installed;
504
606
  }
505
607
  return app.installed && !app.updateAvailable && !app.availableVersion;
506
608
  }
609
+ isRuntimeInstallBusy(runtimeStatus) {
610
+ return runtimeStatus === "installing" || runtimeStatus === "preparing" || runtimeStatus === "starting";
611
+ }
507
612
  mergeSnapshotAppsByStateRevision(workspaceId, snapshotApps) {
508
613
  if (this.store.workspaceId !== workspaceId) {
509
614
  return [...snapshotApps];
@@ -520,7 +625,8 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
520
625
  if (this.pendingInstallKeys.has(installKey)) {
521
626
  const pendingSettled = this.isPendingInstallSettled(
522
627
  installKey,
523
- snapshotApp
628
+ snapshotApp,
629
+ currentApp
524
630
  );
525
631
  if (pendingSettled && currentApp.stateRevision <= snapshotApp.stateRevision) {
526
632
  return snapshotApp;
@@ -545,22 +651,50 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
545
651
  void this.refresh(workspaceId);
546
652
  }, this.dependencies.catalogLoadingRefreshDelayMs ?? defaultCatalogLoadingRefreshDelayMs);
547
653
  }
654
+ mergeActiveInstallAppState(workspaceId, app, previousApp) {
655
+ const installKey = appRuntimeKey(workspaceId, app.appId);
656
+ if (!this.pendingInstallKeys.has(installKey)) {
657
+ return app;
658
+ }
659
+ if (this.isPendingInstallSettled(installKey, app, previousApp)) {
660
+ return app;
661
+ }
662
+ const incomingRuntimeStatus = app.runtimeStatus;
663
+ const runtimeStatus = this.isRuntimeInstallLifecycleStatus(
664
+ incomingRuntimeStatus
665
+ ) ? incomingRuntimeStatus : "installing";
666
+ return {
667
+ ...app,
668
+ enabled: true,
669
+ installed: this.pendingInstallReportKeys.has(installKey) ? false : app.installed,
670
+ installProgress: reconcilePendingInstallProgress({
671
+ incoming: app.installProgress,
672
+ previous: previousApp?.installProgress ?? null,
673
+ runtimeStatus: incomingRuntimeStatus
674
+ }),
675
+ runtimeStatus
676
+ };
677
+ }
678
+ isRuntimeInstallLifecycleStatus(runtimeStatus) {
679
+ return runtimeStatus === "installing" || runtimeStatus === "preparing" || runtimeStatus === "starting";
680
+ }
681
+ isPendingInstallAbandoned(installKey, app, previousApp) {
682
+ return this.pendingInstallReportKeys.has(installKey) && !app.installed && app.runtimeStatus === "idle" && app.installProgress == null && this.hasObservedInstallActivity(previousApp);
683
+ }
684
+ hasObservedInstallActivity(app) {
685
+ return app?.installProgress != null || app?.runtimeStatus === "preparing" || app?.runtimeStatus === "starting";
686
+ }
548
687
  withPendingInstallState(workspaceId, apps) {
549
- return apps.map((app) => {
550
- const installKey = appRuntimeKey(workspaceId, app.appId);
551
- if (!this.pendingInstallKeys.has(installKey)) {
552
- return app;
553
- }
554
- if (this.isPendingInstallSettled(installKey, app)) {
555
- return app;
556
- }
557
- return {
558
- ...app,
559
- enabled: true,
560
- installed: this.pendingInstallReportKeys.has(installKey) ? false : app.installed,
561
- runtimeStatus: "installing"
562
- };
563
- });
688
+ const previousAppsById = new Map(
689
+ this.store.apps.map((app) => [app.appId, app])
690
+ );
691
+ return apps.map(
692
+ (app) => this.mergeActiveInstallAppState(
693
+ workspaceId,
694
+ app,
695
+ previousAppsById.get(app.appId)
696
+ )
697
+ );
564
698
  }
565
699
  async refreshInstallState(workspaceId, appId) {
566
700
  try {
@@ -595,6 +729,9 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
595
729
  this.dependencies.hooks?.onAppInstalled?.(input.app);
596
730
  return;
597
731
  }
732
+ if (input.app.runtimeStatus === "idle" && input.app.installProgress == null && input.failureReason == null && input.app.failureReason == null && input.app.lastError == null) {
733
+ return;
734
+ }
598
735
  this.dependencies.hooks?.onAppInstallFailed?.({
599
736
  app: input.app,
600
737
  appId: input.app.appId,
@@ -954,11 +1091,16 @@ var WorkspaceAppCenterController = class extends WorkspaceAppCenterControllerSta
954
1091
  this.pendingInstallKeys.add(installKey);
955
1092
  this.markAppInstalling(input.appId, { preserveInstalled: true });
956
1093
  try {
1094
+ const restartRunning = app?.installed === true && app.runtimeStatus === "running";
957
1095
  const snapshot = await this.dependencies.gateway.installWorkspaceApp(
958
1096
  input.workspaceId,
959
- input.appId
1097
+ input.appId,
1098
+ restartRunning ? { restartRunning: true } : void 0
960
1099
  );
961
1100
  this.applySnapshot(input.workspaceId, snapshot);
1101
+ if (restartRunning) {
1102
+ this.closeWorkspaceAppViews(input.workspaceId, [input.appId]);
1103
+ }
962
1104
  this.dependencies.hooks?.onAppUpdated?.({
963
1105
  app,
964
1106
  trigger: input.trigger
@@ -1146,9 +1288,9 @@ function validateWorkspaceAppManifest(value) {
1146
1288
  }
1147
1289
  const icon = validateIcon(value.icon, issues);
1148
1290
  const runtime = validateRuntime(value.runtime, issues);
1291
+ const references = validateReferences(value.references, issues);
1149
1292
  const window = validateWindow(value.window, issues);
1150
1293
  const author = validateAuthor(value.author, issues);
1151
- const references = validateReferences(value.references, issues);
1152
1294
  const tags = validateTags(value.tags, issues);
1153
1295
  const localizationInfo = validateLocalizationInfo(
1154
1296
  value.localizationInfo,
@@ -1170,9 +1312,9 @@ function validateWorkspaceAppManifest(value) {
1170
1312
  description,
1171
1313
  ...icon ? { icon } : {},
1172
1314
  runtime,
1315
+ ...references ? { references } : {},
1173
1316
  ...window ? { window } : {},
1174
1317
  ...author ? { author } : {},
1175
- ...references ? { references } : {},
1176
1318
  ...tags ? { tags } : {},
1177
1319
  ...localizationInfo ? { localizationInfo } : {}
1178
1320
  },
@@ -1239,6 +1381,40 @@ function validateRuntime(value, issues) {
1239
1381
  healthcheckPath
1240
1382
  };
1241
1383
  }
1384
+ function validateReferences(value, issues) {
1385
+ if (value === void 0) {
1386
+ return void 0;
1387
+ }
1388
+ if (!isRecord(value)) {
1389
+ issues.push({
1390
+ code: "manifest.references",
1391
+ message: "references must be an object when provided.",
1392
+ path: "$.references"
1393
+ });
1394
+ return void 0;
1395
+ }
1396
+ const unsupportedKey = Object.keys(value).find(
1397
+ (key) => key !== "listEndpoint"
1398
+ );
1399
+ if (unsupportedKey) {
1400
+ issues.push({
1401
+ code: "manifest.references",
1402
+ message: `references.${unsupportedKey} is unsupported.`,
1403
+ path: `$.references.${unsupportedKey}`
1404
+ });
1405
+ return void 0;
1406
+ }
1407
+ const listEndpoint = readOptionalString(value.listEndpoint);
1408
+ if (!listEndpoint || !isRelativeUrlPath(listEndpoint)) {
1409
+ issues.push({
1410
+ code: "manifest.references",
1411
+ message: "references.listEndpoint must be a relative URL path without query or fragment.",
1412
+ path: "$.references.listEndpoint"
1413
+ });
1414
+ return void 0;
1415
+ }
1416
+ return { listEndpoint };
1417
+ }
1242
1418
  function validateWindow(value, issues) {
1243
1419
  if (value === void 0) {
1244
1420
  return void 0;
@@ -1324,29 +1500,6 @@ function validateAuthor(value, issues) {
1324
1500
  ...url ? { url } : {}
1325
1501
  };
1326
1502
  }
1327
- function validateReferences(value, issues) {
1328
- if (value === void 0) {
1329
- return void 0;
1330
- }
1331
- if (!isRecord(value)) {
1332
- issues.push({
1333
- code: "manifest.references",
1334
- message: "references must be an object when provided.",
1335
- path: "$.references"
1336
- });
1337
- return void 0;
1338
- }
1339
- const searchEndpoint = readOptionalString(value.searchEndpoint);
1340
- if (!searchEndpoint || !isRelativeUrlPath(searchEndpoint)) {
1341
- issues.push({
1342
- code: "manifest.references",
1343
- message: "references.searchEndpoint must be a relative URL path without query or hash.",
1344
- path: "$.references.searchEndpoint"
1345
- });
1346
- return void 0;
1347
- }
1348
- return { searchEndpoint };
1349
- }
1350
1503
  function validateTags(value, issues) {
1351
1504
  if (value === void 0) {
1352
1505
  return void 0;
@@ -1452,11 +1605,15 @@ function isRelativePackagePath(value) {
1452
1605
  return !value.startsWith("/") && !value.split(/[\\/]/u).includes("..");
1453
1606
  }
1454
1607
  function isRelativeUrlPath(value) {
1455
- const trimmed = value.trim();
1456
- if (!trimmed || !trimmed.startsWith("/") || trimmed.startsWith("//") || trimmed.includes("\0") || trimmed.includes("?") || trimmed.includes("#") || trimmed.includes("%")) {
1608
+ if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\0")) {
1609
+ return false;
1610
+ }
1611
+ try {
1612
+ const parsed = new URL(value, "http://tutti.local");
1613
+ return parsed.origin === "http://tutti.local" && parsed.pathname === value && parsed.search === "" && parsed.hash === "";
1614
+ } catch {
1457
1615
  return false;
1458
1616
  }
1459
- return true;
1460
1617
  }
1461
1618
  function isRecord(value) {
1462
1619
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1585,9 +1742,11 @@ function createAppCenterViewModel({
1585
1742
  manifest: app.manifest
1586
1743
  });
1587
1744
  const factoryAgentSessionId = factoryJob?.agentSessionId?.trim() || null;
1588
- const status = runtime?.status ?? "idle";
1745
+ const factoryEditAction = factoryJob && factoryAgentSessionId ? factoryJob.status === "published" ? "prepare_modification" : "open_session" : null;
1746
+ const status = resolveRuntimeStatusForViewModel(runtime);
1589
1747
  const presentation = resolveWorkspaceAppStatusPresentation(status);
1590
1748
  const installed = Boolean(app.install);
1749
+ const installBusy = runtime?.installProgress != null || status === "installing";
1591
1750
  const sourceKind = resolveCatalogSourceKind(app.catalog);
1592
1751
  const localApp = sourceKind === "local";
1593
1752
  const runtimeId = normalizeOptionalString(runtime?.runtimeId);
@@ -1609,6 +1768,7 @@ function createAppCenterViewModel({
1609
1768
  installed,
1610
1769
  status
1611
1770
  });
1771
+ const iconUrl = resolveWorkspaceAppIconUrl(app);
1612
1772
  return {
1613
1773
  id: app.manifest.appId,
1614
1774
  installationId,
@@ -1621,30 +1781,37 @@ function createAppCenterViewModel({
1621
1781
  ...app.availableVersion && !comingSoon ? { availableVersion: app.availableVersion } : {},
1622
1782
  ...app.category?.trim() ? { category: app.category.trim() } : {},
1623
1783
  updateAvailable: app.updateAvailable ?? false,
1624
- ...app.manifest.icon ? { icon: app.manifest.icon } : {},
1784
+ ...iconUrl ? {
1785
+ icon: {
1786
+ type: "asset",
1787
+ src: iconUrl
1788
+ }
1789
+ } : {},
1625
1790
  tags: metadata.tags,
1626
1791
  installed,
1627
1792
  status,
1628
1793
  statusLabelKey: comingSoon ? "status.comingSoon" : resolvePrimaryActionLabelKey(primaryAction, presentation.labelKey),
1629
1794
  statusTone: presentation.tone,
1630
- statusPulse: presentation.pulse,
1795
+ statusPulse: runtime?.installProgress ? false : presentation.pulse,
1631
1796
  primaryAction,
1632
1797
  sourceKind,
1633
1798
  canOpen,
1634
1799
  canExport: localApp,
1635
1800
  canDelete: localApp,
1636
1801
  canReplaceIcon: replaceableIconAppIdSet.has(app.manifest.appId),
1637
- canOpenFolder: installed,
1638
- canOpenPackageFolder: installed && localApp && Boolean(displayVersion),
1802
+ canOpenFolder: installed && !installBusy,
1803
+ canOpenPackageFolder: installed && localApp && Boolean(displayVersion) && !installBusy,
1639
1804
  canOpenFactorySession: Boolean(factoryAgentSessionId),
1640
1805
  canPublishFactoryUpdate: installed && factoryJob?.status === "ready" && Boolean(factoryJob.publishedVersion?.trim()),
1641
- canUninstall: installed,
1806
+ canUninstall: installed && !installBusy,
1642
1807
  canRetry,
1643
1808
  canUpdate,
1644
1809
  ...factoryAgentSessionId ? { factoryAgentSessionId } : {},
1810
+ ...factoryEditAction ? { factoryEditAction } : {},
1645
1811
  ...factoryJob?.jobId ? { factoryJobId: factoryJob.jobId } : {},
1646
1812
  ...factoryJob?.provider ? { factoryProvider: factoryJob.provider } : {},
1647
- ...runtime?.error?.message ? { errorMessage: runtime.error.message } : {}
1813
+ ...runtime?.error?.message ? { errorMessage: runtime.error.message } : {},
1814
+ ...runtime?.installProgress ? { installProgress: runtime.installProgress } : {}
1648
1815
  };
1649
1816
  }).sort((left, right) => left.name.localeCompare(right.name));
1650
1817
  return {
@@ -1662,7 +1829,8 @@ function createRuntimeStateMaps(runtimeStates) {
1662
1829
  for (const state of runtimeStates) {
1663
1830
  const runtime = {
1664
1831
  ...state,
1665
- status: mapWorkspaceAppRuntimeStatus(state.status)
1832
+ status: mapWorkspaceAppRuntimeStatus(state.status),
1833
+ installProgress: state.installProgress ?? null
1666
1834
  };
1667
1835
  const installationId = normalizeOptionalString(runtime.installationId);
1668
1836
  if (installationId) {
@@ -1688,6 +1856,15 @@ function findRuntimeStateForApp(maps, appId, installationId) {
1688
1856
  }
1689
1857
  return maps.fallbackByAppId.get(appId);
1690
1858
  }
1859
+ function resolveRuntimeStatusForViewModel(runtime) {
1860
+ if (!runtime) {
1861
+ return "idle";
1862
+ }
1863
+ if (runtime.installProgress != null && runtime.status === "idle") {
1864
+ return "installing";
1865
+ }
1866
+ return runtime.status;
1867
+ }
1691
1868
  function resolveCatalogSourceKind(catalog) {
1692
1869
  return catalog?.source?.kind ?? "bundled";
1693
1870
  }
@@ -1734,6 +1911,16 @@ function normalizeOptionalString(value) {
1734
1911
  const normalized = value?.trim() ?? "";
1735
1912
  return normalized.length > 0 ? normalized : null;
1736
1913
  }
1914
+ function resolveWorkspaceAppIconUrl(app) {
1915
+ const manifestIconUrl = normalizeOptionalString(app.manifest.icon?.src);
1916
+ if (manifestIconUrl && isResolvedIconUrl(manifestIconUrl)) {
1917
+ return manifestIconUrl;
1918
+ }
1919
+ return normalizeOptionalString(app.availableIconUrl) ?? manifestIconUrl;
1920
+ }
1921
+ function isResolvedIconUrl(value) {
1922
+ return /^[a-z][a-z0-9+.-]*:/iu.test(value) || value.startsWith("/");
1923
+ }
1737
1924
  function resolvePrimaryAction(input) {
1738
1925
  if (input.comingSoon) {
1739
1926
  return "none";
@@ -1819,7 +2006,7 @@ function createFactoryJobViewModel(job) {
1819
2006
  canFix: canFixValidationFailure,
1820
2007
  canOpenAgentSession: Boolean(agentSessionId),
1821
2008
  canPublish: job.status === "ready",
1822
- canRetryValidation: canFixValidationFailure,
2009
+ canRetryValidation: false,
1823
2010
  failureReason: job.failureReason,
1824
2011
  updatedAtUnixMs: job.updatedAtUnixMs
1825
2012
  };
@@ -1854,4 +2041,4 @@ export {
1854
2041
  resolveWorkspaceAppCatalogMetadata,
1855
2042
  createWorkspaceAppRecord
1856
2043
  };
1857
- //# sourceMappingURL=chunk-5SANC3S6.js.map
2044
+ //# sourceMappingURL=chunk-DPJRK63W.js.map