@tutti-os/workspace-app-center 0.0.10 → 0.0.12

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":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  workspaceAppManifestSchemaVersion
3
- } from "./chunk-QSAEUGFC.js";
3
+ } from "./chunk-Y63TZ5JT.js";
4
4
 
5
5
  // src/core/appCenterControllerHelpers.ts
6
6
  function resolveAppRunDurationMs(startedAtUnixMs, now) {
@@ -24,6 +24,7 @@ function mergeWorkspaceAppCatalogFields(currentApp, snapshotApp) {
24
24
  localizations: snapshotApp.localizations,
25
25
  minimizeBehavior: snapshotApp.minimizeBehavior,
26
26
  name: snapshotApp.name,
27
+ references: snapshotApp.references,
27
28
  source: snapshotApp.source,
28
29
  tags: snapshotApp.tags,
29
30
  updateAvailable: snapshotApp.updateAvailable
@@ -35,10 +36,10 @@ function areWorkspaceAppCenterAppsEqual(left, right) {
35
36
  }
36
37
  return left.every((leftApp, index) => {
37
38
  const rightApp = right[index];
38
- 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 && areWorkspaceAppCenterLocalizationsEqual(
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(
39
40
  leftApp.localizations ?? [],
40
41
  rightApp.localizations ?? []
41
- ) && leftApp.minimizeBehavior === rightApp.minimizeBehavior && leftApp.name === rightApp.name && leftApp.runtimeStatus === rightApp.runtimeStatus && leftApp.source === rightApp.source && leftApp.stateRevision === rightApp.stateRevision && areStringArraysEqual(leftApp.tags ?? [], rightApp.tags ?? []) && (leftApp.updateAvailable ?? false) === (rightApp.updateAvailable ?? false) && leftApp.url === rightApp.url && leftApp.version === rightApp.version;
42
+ ) && 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;
42
43
  });
43
44
  }
44
45
  function normalizeWorkspaceAppCenterViewState(value) {
@@ -463,7 +464,7 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
463
464
  const app = this.store.apps.find(
464
465
  (candidate) => candidate.appId === input.appId
465
466
  );
466
- return app?.installed && app.runtimeStatus === "running" && app.url ? app : null;
467
+ return app?.installed && app.runtimeStatus === "running" && app.launchUrl ? app : null;
467
468
  }
468
469
  markAppInstalling(appId, options = {}) {
469
470
  this.store.apps = this.store.apps.map(
@@ -481,7 +482,7 @@ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControll
481
482
  markEnabledAppsStarting() {
482
483
  let changed = false;
483
484
  this.store.apps = this.store.apps.map((app) => {
484
- if (!app.enabled || !app.installed || app.runtimeStatus === "running" || app.runtimeStatus === "preparing" || app.runtimeStatus === "starting") {
485
+ if (!app.enabled || !app.installed || app.runtimeStatus !== "idle") {
485
486
  return app;
486
487
  }
487
488
  changed = true;
@@ -655,14 +656,18 @@ var WorkspaceAppCenterController = class extends WorkspaceAppCenterControllerSta
655
656
  if (currentLaunchableApp) {
656
657
  return currentLaunchableApp;
657
658
  }
659
+ const previousApps = this.store.apps;
658
660
  this.markAppStarting(input.appId);
659
661
  try {
660
- const snapshot = await this.dependencies.gateway.retryWorkspaceApp(
662
+ const snapshot = await this.dependencies.gateway.launchWorkspaceApp(
661
663
  input.workspaceId,
662
664
  input.appId
663
665
  );
664
666
  this.applySnapshot(input.workspaceId, snapshot);
665
667
  } catch (error) {
668
+ if (this.store.workspaceId === input.workspaceId) {
669
+ this.store.apps = previousApps;
670
+ }
666
671
  this.setOperationError(error, {
667
672
  appId: input.appId,
668
673
  operation: "workspace_app.prepare_launch",
@@ -974,12 +979,31 @@ var WorkspaceAppCenterController = class extends WorkspaceAppCenterControllerSta
974
979
  }
975
980
  }
976
981
  async retryApp(input) {
977
- this.markAppStarting(input.appId);
978
- const snapshot = await this.dependencies.gateway.retryWorkspaceApp(
979
- input.workspaceId,
980
- input.appId
982
+ const app = this.store.apps.find(
983
+ (candidate) => candidate.appId === input.appId
981
984
  );
982
- this.applySnapshot(input.workspaceId, snapshot);
985
+ if (this.store.workspaceId !== input.workspaceId || !app?.installed || app.runtimeStatus !== "failed") {
986
+ return;
987
+ }
988
+ const previousApps = this.store.apps;
989
+ this.markAppStarting(input.appId);
990
+ try {
991
+ const snapshot = await this.dependencies.gateway.retryWorkspaceApp(
992
+ input.workspaceId,
993
+ input.appId
994
+ );
995
+ this.applySnapshot(input.workspaceId, snapshot);
996
+ } catch (error) {
997
+ if (this.store.workspaceId === input.workspaceId) {
998
+ this.store.apps = previousApps;
999
+ }
1000
+ this.setOperationError(error, {
1001
+ appId: input.appId,
1002
+ operation: "workspace_app.retry",
1003
+ uiAction: "retry_app",
1004
+ workspaceId: input.workspaceId
1005
+ });
1006
+ }
983
1007
  }
984
1008
  waitForLaunchableApp(input) {
985
1009
  const current = this.resolveLaunchableApp(input);
@@ -1122,6 +1146,7 @@ function validateWorkspaceAppManifest(value) {
1122
1146
  }
1123
1147
  const icon = validateIcon(value.icon, issues);
1124
1148
  const runtime = validateRuntime(value.runtime, issues);
1149
+ const references = validateReferences(value.references, issues);
1125
1150
  const window = validateWindow(value.window, issues);
1126
1151
  const author = validateAuthor(value.author, issues);
1127
1152
  const tags = validateTags(value.tags, issues);
@@ -1145,6 +1170,7 @@ function validateWorkspaceAppManifest(value) {
1145
1170
  description,
1146
1171
  ...icon ? { icon } : {},
1147
1172
  runtime,
1173
+ ...references ? { references } : {},
1148
1174
  ...window ? { window } : {},
1149
1175
  ...author ? { author } : {},
1150
1176
  ...tags ? { tags } : {},
@@ -1213,6 +1239,40 @@ function validateRuntime(value, issues) {
1213
1239
  healthcheckPath
1214
1240
  };
1215
1241
  }
1242
+ function validateReferences(value, issues) {
1243
+ if (value === void 0) {
1244
+ return void 0;
1245
+ }
1246
+ if (!isRecord(value)) {
1247
+ issues.push({
1248
+ code: "manifest.references",
1249
+ message: "references must be an object when provided.",
1250
+ path: "$.references"
1251
+ });
1252
+ return void 0;
1253
+ }
1254
+ const unsupportedKey = Object.keys(value).find(
1255
+ (key) => key !== "listEndpoint"
1256
+ );
1257
+ if (unsupportedKey) {
1258
+ issues.push({
1259
+ code: "manifest.references",
1260
+ message: `references.${unsupportedKey} is unsupported.`,
1261
+ path: `$.references.${unsupportedKey}`
1262
+ });
1263
+ return void 0;
1264
+ }
1265
+ const listEndpoint = readOptionalString(value.listEndpoint);
1266
+ if (!listEndpoint || !isRelativeUrlPath(listEndpoint)) {
1267
+ issues.push({
1268
+ code: "manifest.references",
1269
+ message: "references.listEndpoint must be a relative URL path without query or fragment.",
1270
+ path: "$.references.listEndpoint"
1271
+ });
1272
+ return void 0;
1273
+ }
1274
+ return { listEndpoint };
1275
+ }
1216
1276
  function validateWindow(value, issues) {
1217
1277
  if (value === void 0) {
1218
1278
  return void 0;
@@ -1402,6 +1462,17 @@ function readOptionalString(value) {
1402
1462
  function isRelativePackagePath(value) {
1403
1463
  return !value.startsWith("/") && !value.split(/[\\/]/u).includes("..");
1404
1464
  }
1465
+ function isRelativeUrlPath(value) {
1466
+ if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\0")) {
1467
+ return false;
1468
+ }
1469
+ try {
1470
+ const parsed = new URL(value, "http://tutti.local");
1471
+ return parsed.origin === "http://tutti.local" && parsed.pathname === value && parsed.search === "" && parsed.hash === "";
1472
+ } catch {
1473
+ return false;
1474
+ }
1475
+ }
1405
1476
  function isRecord(value) {
1406
1477
  return typeof value === "object" && value !== null && !Array.isArray(value);
1407
1478
  }
@@ -1422,12 +1493,18 @@ var statusAliases = /* @__PURE__ */ new Map([
1422
1493
  ["preparing", "preparing"],
1423
1494
  ["ready", "idle"],
1424
1495
  ["running", "running"],
1496
+ ["runner_unavailable", "unavailable"],
1497
+ ["runtime_unavailable", "unavailable"],
1498
+ ["sandbox_unavailable", "unavailable"],
1425
1499
  ["started", "running"],
1426
1500
  ["starting", "starting"],
1501
+ ["stale", "unavailable"],
1427
1502
  ["stopped", "idle"],
1428
1503
  ["stopping", "stopping"],
1429
1504
  ["terminated", "idle"],
1430
- ["terminating", "stopping"]
1505
+ ["terminating", "stopping"],
1506
+ ["unavailable", "unavailable"],
1507
+ ["unreachable", "unavailable"]
1431
1508
  ]);
1432
1509
  function mapWorkspaceAppRuntimeStatus(value) {
1433
1510
  if (typeof value !== "string") {
@@ -1479,6 +1556,12 @@ function resolveWorkspaceAppStatusPresentation(status) {
1479
1556
  pulse: true,
1480
1557
  tone: "amber"
1481
1558
  };
1559
+ case "unavailable":
1560
+ return {
1561
+ labelKey: "status.unavailable",
1562
+ pulse: false,
1563
+ tone: "amber"
1564
+ };
1482
1565
  case "idle":
1483
1566
  return {
1484
1567
  labelKey: "actions.openApp",
@@ -1496,21 +1579,20 @@ function createAppCenterViewModel({
1496
1579
  replaceableIconAppIds = [],
1497
1580
  runtimeStates = []
1498
1581
  }) {
1499
- const runtimeByAppId = new Map(
1500
- runtimeStates.map((state) => [
1501
- state.appId,
1502
- {
1503
- ...state,
1504
- status: mapWorkspaceAppRuntimeStatus(state.status)
1505
- }
1506
- ])
1507
- );
1582
+ const runtimeStateMaps = createRuntimeStateMaps(runtimeStates);
1508
1583
  const appFactoryJobByAppId = createAppFactoryJobByAppId(factoryJobs);
1509
1584
  const replaceableIconAppIdSet = new Set(
1510
1585
  replaceableIconAppIds.map((appId) => appId.trim()).filter(Boolean)
1511
1586
  );
1512
1587
  const appCards = apps.map((app) => {
1513
- const runtime = runtimeByAppId.get(app.manifest.appId);
1588
+ const installationId = normalizeOptionalString(
1589
+ app.install?.installationId
1590
+ );
1591
+ const runtime = findRuntimeStateForApp(
1592
+ runtimeStateMaps,
1593
+ app.manifest.appId,
1594
+ installationId
1595
+ );
1514
1596
  const factoryJob = appFactoryJobByAppId.get(app.manifest.appId);
1515
1597
  const metadata = resolveWorkspaceAppCatalogMetadata({
1516
1598
  catalog: app.catalog,
@@ -1523,9 +1605,15 @@ function createAppCenterViewModel({
1523
1605
  const installed = Boolean(app.install);
1524
1606
  const sourceKind = resolveCatalogSourceKind(app.catalog);
1525
1607
  const localApp = sourceKind === "local";
1608
+ const runtimeId = normalizeOptionalString(runtime?.runtimeId);
1609
+ const launchUrl = normalizeOptionalString(runtime?.launchUrl);
1610
+ const installedVersion = normalizeOptionalString(app.install?.version);
1611
+ const manifestVersion = normalizeOptionalString(app.manifest.version);
1612
+ const displayVersion = installedVersion ?? manifestVersion;
1526
1613
  const comingSoon = isComingSoonApp(metadata.tags) || isComingSoonApp(app.manifest.tags ?? []);
1527
1614
  const busy = isBusyRuntimeStatus(status);
1528
- const canUpdate = !comingSoon && !busy && installed && (app.updateAvailable ?? false);
1615
+ const unavailable = status === "unavailable";
1616
+ const canUpdate = !comingSoon && !busy && !unavailable && installed && (app.updateAvailable ?? false);
1529
1617
  const canOpen = !comingSoon && installed && canOpenInstalledApp(status);
1530
1618
  const canRetry = installed && status === "failed";
1531
1619
  const primaryAction = resolvePrimaryAction({
@@ -1538,10 +1626,13 @@ function createAppCenterViewModel({
1538
1626
  });
1539
1627
  return {
1540
1628
  id: app.manifest.appId,
1629
+ installationId,
1630
+ runtimeId,
1631
+ launchUrl,
1541
1632
  name: metadata.name,
1542
1633
  createdAtUnixMs: app.createdAtUnixMs ?? null,
1543
1634
  ...metadata.description ? { description: metadata.description } : {},
1544
- ...app.manifest.version && !comingSoon ? { version: app.manifest.version } : {},
1635
+ ...displayVersion && !comingSoon ? { version: displayVersion } : {},
1545
1636
  ...app.availableVersion && !comingSoon ? { availableVersion: app.availableVersion } : {},
1546
1637
  ...app.category?.trim() ? { category: app.category.trim() } : {},
1547
1638
  updateAvailable: app.updateAvailable ?? false,
@@ -1559,7 +1650,7 @@ function createAppCenterViewModel({
1559
1650
  canDelete: localApp,
1560
1651
  canReplaceIcon: replaceableIconAppIdSet.has(app.manifest.appId),
1561
1652
  canOpenFolder: installed,
1562
- canOpenPackageFolder: installed && localApp && Boolean(app.manifest.version?.trim()),
1653
+ canOpenPackageFolder: installed && localApp && Boolean(displayVersion),
1563
1654
  canOpenFactorySession: Boolean(factoryAgentSessionId),
1564
1655
  canPublishFactoryUpdate: installed && factoryJob?.status === "ready" && Boolean(factoryJob.publishedVersion?.trim()),
1565
1656
  canUninstall: installed,
@@ -1580,6 +1671,38 @@ function createAppCenterViewModel({
1580
1671
  runningCount: appCards.filter((app) => app.status === "running").length
1581
1672
  };
1582
1673
  }
1674
+ function createRuntimeStateMaps(runtimeStates) {
1675
+ const fallbackByAppId = /* @__PURE__ */ new Map();
1676
+ const byInstallationId = /* @__PURE__ */ new Map();
1677
+ for (const state of runtimeStates) {
1678
+ const runtime = {
1679
+ ...state,
1680
+ status: mapWorkspaceAppRuntimeStatus(state.status)
1681
+ };
1682
+ const installationId = normalizeOptionalString(runtime.installationId);
1683
+ if (installationId) {
1684
+ byInstallationId.set(installationId, runtime);
1685
+ continue;
1686
+ }
1687
+ const appId = normalizeOptionalString(runtime.appId);
1688
+ if (appId) {
1689
+ fallbackByAppId.set(appId, runtime);
1690
+ }
1691
+ }
1692
+ return {
1693
+ fallbackByAppId,
1694
+ byInstallationId
1695
+ };
1696
+ }
1697
+ function findRuntimeStateForApp(maps, appId, installationId) {
1698
+ if (installationId) {
1699
+ const runtime = maps.byInstallationId.get(installationId);
1700
+ if (runtime) {
1701
+ return runtime;
1702
+ }
1703
+ }
1704
+ return maps.fallbackByAppId.get(appId);
1705
+ }
1583
1706
  function resolveCatalogSourceKind(catalog) {
1584
1707
  return catalog?.source?.kind ?? "bundled";
1585
1708
  }
@@ -1622,6 +1745,10 @@ function normalizeLocale(value) {
1622
1745
  const normalized = value?.trim().replace(/_/gu, "-").toLowerCase() ?? "";
1623
1746
  return normalized.length > 0 ? normalized : null;
1624
1747
  }
1748
+ function normalizeOptionalString(value) {
1749
+ const normalized = value?.trim() ?? "";
1750
+ return normalized.length > 0 ? normalized : null;
1751
+ }
1625
1752
  function resolvePrimaryAction(input) {
1626
1753
  if (input.comingSoon) {
1627
1754
  return "none";
@@ -1629,6 +1756,9 @@ function resolvePrimaryAction(input) {
1629
1756
  if (isBusyRuntimeStatus(input.status)) {
1630
1757
  return "none";
1631
1758
  }
1759
+ if (input.status === "unavailable") {
1760
+ return "none";
1761
+ }
1632
1762
  if (!input.installed) {
1633
1763
  return "install";
1634
1764
  }
@@ -1704,7 +1834,7 @@ function createFactoryJobViewModel(job) {
1704
1834
  canFix: canFixValidationFailure,
1705
1835
  canOpenAgentSession: Boolean(agentSessionId),
1706
1836
  canPublish: job.status === "ready",
1707
- canRetryValidation: canFixValidationFailure,
1837
+ canRetryValidation: false,
1708
1838
  failureReason: job.failureReason,
1709
1839
  updatedAtUnixMs: job.updatedAtUnixMs
1710
1840
  };
@@ -1739,4 +1869,4 @@ export {
1739
1869
  resolveWorkspaceAppCatalogMetadata,
1740
1870
  createWorkspaceAppRecord
1741
1871
  };
1742
- //# sourceMappingURL=chunk-INI5PAWK.js.map
1872
+ //# sourceMappingURL=chunk-O3XCZVHE.js.map