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

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,1857 @@
1
+ import {
2
+ workspaceAppManifestSchemaVersion
3
+ } from "./chunk-UBERYRMA.js";
4
+
5
+ // src/core/appCenterControllerHelpers.ts
6
+ function resolveAppRunDurationMs(startedAtUnixMs, now) {
7
+ if (typeof startedAtUnixMs !== "number" || !Number.isFinite(startedAtUnixMs) || startedAtUnixMs <= 0) {
8
+ return null;
9
+ }
10
+ return Math.max(0, now - startedAtUnixMs);
11
+ }
12
+ function sortWorkspaceAppCenterApps(apps) {
13
+ return [...apps].sort(
14
+ (left, right) => left.name.localeCompare(right.name, void 0, { sensitivity: "base" })
15
+ );
16
+ }
17
+ function mergeWorkspaceAppCatalogFields(currentApp, snapshotApp) {
18
+ return {
19
+ ...currentApp,
20
+ availableIconUrl: snapshotApp.availableIconUrl,
21
+ availableVersion: snapshotApp.availableVersion,
22
+ description: snapshotApp.description,
23
+ iconUrl: snapshotApp.iconUrl,
24
+ localizations: snapshotApp.localizations,
25
+ minimizeBehavior: snapshotApp.minimizeBehavior,
26
+ name: snapshotApp.name,
27
+ references: snapshotApp.references,
28
+ source: snapshotApp.source,
29
+ tags: snapshotApp.tags,
30
+ updateAvailable: snapshotApp.updateAvailable
31
+ };
32
+ }
33
+ function areWorkspaceAppCenterAppsEqual(left, right) {
34
+ if (left.length !== right.length) {
35
+ return false;
36
+ }
37
+ return left.every((leftApp, index) => {
38
+ 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(
40
+ leftApp.localizations ?? [],
41
+ 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;
43
+ });
44
+ }
45
+ function normalizeWorkspaceAppCenterViewState(value) {
46
+ return {
47
+ activeAppTab: value?.activeAppTab === "my" ? "my" : "recommended"
48
+ };
49
+ }
50
+ function areWorkspaceAppCenterViewStatesEqual(left, right) {
51
+ return left.activeAppTab === right.activeAppTab;
52
+ }
53
+ function areWorkspaceAppCenterLocalizationsEqual(left, right) {
54
+ if (left.length !== right.length) {
55
+ return false;
56
+ }
57
+ return left.every((leftLocalization, index) => {
58
+ const rightLocalization = right[index];
59
+ return rightLocalization !== void 0 && leftLocalization.locale === rightLocalization.locale && leftLocalization.name === rightLocalization.name && leftLocalization.description === rightLocalization.description && areStringArraysEqual(leftLocalization.tags, rightLocalization.tags);
60
+ });
61
+ }
62
+ function areStringArraysEqual(left, right) {
63
+ if (left.length !== right.length) {
64
+ return false;
65
+ }
66
+ return left.every((value, index) => value === right[index]);
67
+ }
68
+ function appRuntimeKey(workspaceId, appId) {
69
+ return `${workspaceId}\0${appId}`;
70
+ }
71
+ function factoryJobKey(workspaceId, jobId) {
72
+ return `${workspaceId}\0${jobId}`;
73
+ }
74
+ function removedOrUninstalledAppIds(previousApps, nextApps) {
75
+ const nextAppsById = new Map(nextApps.map((app) => [app.appId, app]));
76
+ const appIds = [];
77
+ for (const previousApp of previousApps) {
78
+ if (!previousApp.installed) {
79
+ continue;
80
+ }
81
+ const nextApp = nextAppsById.get(previousApp.appId);
82
+ if (!nextApp?.installed) {
83
+ appIds.push(previousApp.appId);
84
+ }
85
+ }
86
+ return appIds;
87
+ }
88
+ function sortWorkspaceAppFactoryJobs(jobs) {
89
+ return [...jobs].sort(
90
+ (left, right) => right.updatedAtUnixMs - left.updatedAtUnixMs
91
+ );
92
+ }
93
+ function areWorkspaceAppFactoryJobsEqual(left, right) {
94
+ if (left.length !== right.length) {
95
+ return false;
96
+ }
97
+ return left.every((leftJob, index) => {
98
+ const rightJob = right[index];
99
+ return rightJob !== void 0 && leftJob.agentSessionId === rightJob.agentSessionId && leftJob.appId === rightJob.appId && leftJob.createdAtUnixMs === rightJob.createdAtUnixMs && leftJob.description === rightJob.description && leftJob.displayName === rightJob.displayName && leftJob.failureReason === rightJob.failureReason && leftJob.jobId === rightJob.jobId && leftJob.model === rightJob.model && leftJob.prompt === rightJob.prompt && leftJob.provider === rightJob.provider && leftJob.publishedVersion === rightJob.publishedVersion && leftJob.reasoningEffort === rightJob.reasoningEffort && leftJob.status === rightJob.status && leftJob.updatedAtUnixMs === rightJob.updatedAtUnixMs && leftJob.workspaceId === rightJob.workspaceId;
100
+ });
101
+ }
102
+ function noop() {
103
+ }
104
+
105
+ // src/core/appCenterControllerTypes.ts
106
+ var defaultCatalogLoadingRefreshDelayMs = 750;
107
+ var defaultAppOpenLaunchWaitTimeoutMs = 35e3;
108
+ var defaultInstallRefreshDelayMs = 750;
109
+ function createWorkspaceAppCenterStoreState() {
110
+ return {
111
+ apps: [],
112
+ catalogLastError: null,
113
+ catalogStatus: "disabled",
114
+ catalogUpdatedAtUnixMs: null,
115
+ error: null,
116
+ factoryJobs: [],
117
+ loadStatus: "idle",
118
+ openingFolderAppId: null,
119
+ revision: 0,
120
+ viewStateByWorkspaceId: {},
121
+ workspaceId: null
122
+ };
123
+ }
124
+
125
+ // src/core/appCenterControllerBase.ts
126
+ var WorkspaceAppCenterControllerBase = class {
127
+ store;
128
+ dependencies;
129
+ listeners = /* @__PURE__ */ new Set();
130
+ catalogRefreshTimer = null;
131
+ installRefreshTimers = /* @__PURE__ */ new Map();
132
+ pendingFactoryPublishKeys = /* @__PURE__ */ new Set();
133
+ pendingInstallKeys = /* @__PURE__ */ new Set();
134
+ pendingInstallReportKeys = /* @__PURE__ */ new Set();
135
+ appLoadSequence = 0;
136
+ factoryLoadSequence = 0;
137
+ pollingWorkspaceId = null;
138
+ constructor(dependencies) {
139
+ this.dependencies = dependencies;
140
+ this.store = dependencies.store ?? createWorkspaceAppCenterStoreState();
141
+ }
142
+ get readableStore() {
143
+ return this.store;
144
+ }
145
+ consumeError() {
146
+ const error = this.store.error;
147
+ if (error === null) {
148
+ return null;
149
+ }
150
+ this.store.error = null;
151
+ this.bumpRevision();
152
+ return error;
153
+ }
154
+ subscribe(listener) {
155
+ this.listeners.add(listener);
156
+ return () => {
157
+ this.listeners.delete(listener);
158
+ };
159
+ }
160
+ beginWorkspacePolling(workspaceId) {
161
+ const normalizedWorkspaceId = workspaceId.trim();
162
+ if (!normalizedWorkspaceId) {
163
+ return;
164
+ }
165
+ if (this.pollingWorkspaceId === normalizedWorkspaceId) {
166
+ return;
167
+ }
168
+ this.clearCatalogRefreshTimer();
169
+ this.clearInstallRefreshTimers();
170
+ this.pollingWorkspaceId = normalizedWorkspaceId;
171
+ }
172
+ endWorkspacePolling(workspaceId) {
173
+ if (this.pollingWorkspaceId !== workspaceId.trim()) {
174
+ return;
175
+ }
176
+ this.pollingWorkspaceId = null;
177
+ this.clearCatalogRefreshTimer();
178
+ this.clearInstallRefreshTimers();
179
+ }
180
+ getViewState(workspaceId, restoredState) {
181
+ const normalizedWorkspaceId = workspaceId.trim();
182
+ if (!normalizedWorkspaceId) {
183
+ return normalizeWorkspaceAppCenterViewState(restoredState);
184
+ }
185
+ const existing = this.store.viewStateByWorkspaceId[normalizedWorkspaceId];
186
+ if (existing) {
187
+ return existing;
188
+ }
189
+ const nextState = normalizeWorkspaceAppCenterViewState(restoredState);
190
+ this.store.viewStateByWorkspaceId = {
191
+ ...this.store.viewStateByWorkspaceId,
192
+ [normalizedWorkspaceId]: nextState
193
+ };
194
+ return nextState;
195
+ }
196
+ setViewState(input) {
197
+ const normalizedWorkspaceId = input.workspaceId.trim();
198
+ if (!normalizedWorkspaceId) {
199
+ return;
200
+ }
201
+ const previous = this.getViewState(normalizedWorkspaceId);
202
+ const nextState = normalizeWorkspaceAppCenterViewState({
203
+ ...previous,
204
+ ...input.state
205
+ });
206
+ if (areWorkspaceAppCenterViewStatesEqual(previous, nextState)) {
207
+ return;
208
+ }
209
+ this.store.viewStateByWorkspaceId = {
210
+ ...this.store.viewStateByWorkspaceId,
211
+ [normalizedWorkspaceId]: nextState
212
+ };
213
+ this.bumpRevision();
214
+ }
215
+ setOperationError(error, details) {
216
+ const message = this.dependencies.formatError(error, details);
217
+ this.recordOperationFailure(error, message, details);
218
+ this.store.error = message;
219
+ this.bumpRevision();
220
+ }
221
+ setUnavailableError(error, details) {
222
+ const message = this.dependencies.formatError(error, details);
223
+ this.recordOperationFailure(error, message, details);
224
+ this.store.error = message;
225
+ this.store.loadStatus = "unavailable";
226
+ this.bumpRevision();
227
+ }
228
+ clearCatalogRefreshTimer() {
229
+ if (!this.catalogRefreshTimer) {
230
+ return;
231
+ }
232
+ clearTimeout(this.catalogRefreshTimer);
233
+ this.catalogRefreshTimer = null;
234
+ }
235
+ clearInstallRefreshTimer(workspaceId, appId) {
236
+ const key = appRuntimeKey(workspaceId, appId);
237
+ const timer = this.installRefreshTimers.get(key);
238
+ if (!timer) {
239
+ return;
240
+ }
241
+ clearTimeout(timer);
242
+ this.installRefreshTimers.delete(key);
243
+ }
244
+ clearInstallRefreshTimers() {
245
+ for (const timer of this.installRefreshTimers.values()) {
246
+ clearTimeout(timer);
247
+ }
248
+ this.installRefreshTimers.clear();
249
+ this.pendingInstallKeys.clear();
250
+ this.pendingInstallReportKeys.clear();
251
+ }
252
+ recordOperationFailure(error, toastMessage, details) {
253
+ this.dependencies.hooks?.onOperationFailure?.({
254
+ details,
255
+ error,
256
+ toastMessage
257
+ });
258
+ }
259
+ bumpRevision() {
260
+ this.store.revision += 1;
261
+ for (const listener of this.listeners) {
262
+ listener();
263
+ }
264
+ }
265
+ closeWorkspaceAppViews(workspaceId, appIds) {
266
+ if (appIds.length === 0) {
267
+ return;
268
+ }
269
+ this.dependencies.hooks?.onCloseWorkspaceAppViews?.({
270
+ appIds,
271
+ workspaceId
272
+ });
273
+ }
274
+ getErrorReason(error) {
275
+ return this.dependencies.getErrorReason?.(error) ?? null;
276
+ }
277
+ };
278
+
279
+ // src/core/appCenterControllerState.ts
280
+ var WorkspaceAppCenterControllerState = class extends WorkspaceAppCenterControllerBase {
281
+ applySnapshot(workspaceId, snapshot) {
282
+ const nextApps = sortWorkspaceAppCenterApps(
283
+ this.mergeSnapshotAppsByStateRevision(
284
+ workspaceId,
285
+ this.withPendingInstallState(workspaceId, snapshot.apps)
286
+ )
287
+ );
288
+ for (const app of nextApps) {
289
+ this.settlePendingInstallReport({
290
+ app,
291
+ failureReason: app.failureReason ?? app.lastError ?? null,
292
+ workspaceId
293
+ });
294
+ }
295
+ const appIdsToClose = this.store.workspaceId === workspaceId ? removedOrUninstalledAppIds(this.store.apps, nextApps) : [];
296
+ this.scheduleCatalogLoadingRefresh(workspaceId, snapshot);
297
+ 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);
298
+ if (!changed) {
299
+ return;
300
+ }
301
+ this.store.apps = nextApps;
302
+ this.store.catalogLastError = snapshot.catalogLastError ?? null;
303
+ this.store.catalogStatus = snapshot.catalogStatus;
304
+ this.store.catalogUpdatedAtUnixMs = snapshot.catalogUpdatedAtUnixMs ?? null;
305
+ this.store.error = null;
306
+ this.store.loadStatus = "ready";
307
+ this.store.workspaceId = workspaceId;
308
+ this.bumpRevision();
309
+ this.closeWorkspaceAppViews(workspaceId, appIdsToClose);
310
+ }
311
+ applyFactorySnapshot(workspaceId, snapshot) {
312
+ if (this.store.workspaceId !== workspaceId) {
313
+ this.store.workspaceId = workspaceId;
314
+ }
315
+ const nextJobs = sortWorkspaceAppFactoryJobs(snapshot.jobs);
316
+ if (areWorkspaceAppFactoryJobsEqual(this.store.factoryJobs, nextJobs)) {
317
+ return;
318
+ }
319
+ const previousJobs = this.store.factoryJobs;
320
+ this.store.factoryJobs = nextJobs;
321
+ this.dependencies.hooks?.onFactorySnapshotApplied?.({
322
+ nextJobs,
323
+ previousJobs,
324
+ workspaceId
325
+ });
326
+ this.bumpRevision();
327
+ }
328
+ applyAppUpdate(input) {
329
+ if (this.store.workspaceId !== input.workspaceId) {
330
+ return;
331
+ }
332
+ const currentApp = this.store.apps.find(
333
+ (candidate) => candidate.appId === input.app.appId
334
+ );
335
+ if (!currentApp) {
336
+ return;
337
+ }
338
+ const acceptedRuntimeTransition = input.app.stateRevision > currentApp.stateRevision;
339
+ if (acceptedRuntimeTransition && input.app.runtimeStatus === "failed" && currentApp.runtimeStatus !== "failed") {
340
+ this.dependencies.hooks?.onAppRuntimeFailed?.({
341
+ app: input.app,
342
+ failureReason: input.failureReason ?? null
343
+ });
344
+ }
345
+ if (acceptedRuntimeTransition && currentApp.runtimeStatus === "running" && input.app.runtimeStatus !== "running") {
346
+ this.dependencies.hooks?.onAppStopped?.({
347
+ app: currentApp,
348
+ runDurationMs: resolveAppRunDurationMs(
349
+ input.startedAtUnixMs,
350
+ this.dependencies.now?.() ?? Date.now()
351
+ )
352
+ });
353
+ }
354
+ this.applyAppSnapshot(input.workspaceId, input.app, {
355
+ installFailureReason: input.failureReason ?? null
356
+ });
357
+ }
358
+ applyAppSnapshot(workspaceId, nextApp, options = {}) {
359
+ if (this.store.workspaceId !== workspaceId) {
360
+ return;
361
+ }
362
+ const currentApp = this.store.apps.find(
363
+ (candidate) => candidate.appId === nextApp.appId
364
+ );
365
+ if (currentApp && nextApp.stateRevision <= currentApp.stateRevision) {
366
+ this.settlePendingInstallReport({
367
+ app: currentApp,
368
+ failureReason: options.installFailureReason ?? currentApp.failureReason ?? currentApp.lastError ?? null,
369
+ workspaceId
370
+ });
371
+ return;
372
+ }
373
+ const appIdsToClose = currentApp?.installed === true && !nextApp.installed ? [nextApp.appId] : [];
374
+ const nextApps = sortWorkspaceAppCenterApps([
375
+ ...this.store.apps.filter(
376
+ (candidate) => candidate.appId !== nextApp.appId
377
+ ),
378
+ nextApp
379
+ ]);
380
+ if (areWorkspaceAppCenterAppsEqual(this.store.apps, nextApps)) {
381
+ return;
382
+ }
383
+ this.store.apps = nextApps;
384
+ this.store.error = null;
385
+ this.store.loadStatus = "ready";
386
+ this.bumpRevision();
387
+ this.settlePendingInstallReport({
388
+ app: nextApp,
389
+ failureReason: options.installFailureReason ?? nextApp.failureReason ?? nextApp.lastError ?? null,
390
+ workspaceId
391
+ });
392
+ this.closeWorkspaceAppViews(workspaceId, appIdsToClose);
393
+ }
394
+ applyFactoryJobUpdate(workspaceId, job) {
395
+ if (this.store.workspaceId !== workspaceId) {
396
+ return;
397
+ }
398
+ const currentJob = this.store.factoryJobs.find(
399
+ (candidate) => candidate.jobId === job.jobId
400
+ );
401
+ if (currentJob && job.updatedAtUnixMs < currentJob.updatedAtUnixMs) {
402
+ return;
403
+ }
404
+ const nextJobs = sortWorkspaceAppFactoryJobs([
405
+ ...this.store.factoryJobs.filter(
406
+ (candidate) => candidate.jobId !== job.jobId
407
+ ),
408
+ job
409
+ ]);
410
+ if (areWorkspaceAppFactoryJobsEqual(this.store.factoryJobs, nextJobs)) {
411
+ return;
412
+ }
413
+ this.store.factoryJobs = nextJobs;
414
+ this.bumpRevision();
415
+ }
416
+ scheduleInstallRefresh(workspaceId, appId) {
417
+ const key = appRuntimeKey(workspaceId, appId);
418
+ if (this.installRefreshTimers.has(key)) {
419
+ return;
420
+ }
421
+ const timer = setTimeout(() => {
422
+ this.installRefreshTimers.delete(key);
423
+ if (!this.pendingInstallKeys.has(key)) {
424
+ return;
425
+ }
426
+ void this.refreshInstallState(workspaceId, appId);
427
+ }, this.dependencies.installRefreshDelayMs ?? defaultInstallRefreshDelayMs);
428
+ this.installRefreshTimers.set(key, timer);
429
+ }
430
+ markAppStarting(appId) {
431
+ this.store.apps = this.store.apps.map(
432
+ (app) => app.appId === appId ? {
433
+ ...app,
434
+ enabled: true,
435
+ installed: true,
436
+ runtimeStatus: "preparing"
437
+ } : app
438
+ );
439
+ this.bumpRevision();
440
+ }
441
+ markLaunchWaitTimedOut(input) {
442
+ if (this.store.workspaceId !== input.workspaceId) {
443
+ return;
444
+ }
445
+ let changed = false;
446
+ this.store.apps = this.store.apps.map((app) => {
447
+ if (app.appId !== input.appId || !app.installed || app.runtimeStatus !== "preparing" && app.runtimeStatus !== "starting") {
448
+ return app;
449
+ }
450
+ changed = true;
451
+ return {
452
+ ...app,
453
+ runtimeStatus: "failed"
454
+ };
455
+ });
456
+ if (changed) {
457
+ this.bumpRevision();
458
+ }
459
+ }
460
+ resolveLaunchableApp(input) {
461
+ if (this.store.workspaceId !== input.workspaceId) {
462
+ return null;
463
+ }
464
+ const app = this.store.apps.find(
465
+ (candidate) => candidate.appId === input.appId
466
+ );
467
+ return app?.installed && app.runtimeStatus === "running" && app.launchUrl ? app : null;
468
+ }
469
+ markAppInstalling(appId, options = {}) {
470
+ this.store.apps = this.store.apps.map(
471
+ (app) => app.appId === appId ? {
472
+ ...app,
473
+ availableVersion: null,
474
+ enabled: true,
475
+ installed: options.preserveInstalled === true ? app.installed : false,
476
+ runtimeStatus: "installing",
477
+ updateAvailable: false
478
+ } : app
479
+ );
480
+ this.bumpRevision();
481
+ }
482
+ markEnabledAppsStarting() {
483
+ let changed = false;
484
+ this.store.apps = this.store.apps.map((app) => {
485
+ if (!app.enabled || !app.installed || app.runtimeStatus !== "idle") {
486
+ return app;
487
+ }
488
+ changed = true;
489
+ return {
490
+ ...app,
491
+ runtimeStatus: "preparing"
492
+ };
493
+ });
494
+ if (changed) {
495
+ this.bumpRevision();
496
+ }
497
+ }
498
+ isPendingInstallSettled(installKey, app) {
499
+ if (app.runtimeStatus === "failed") {
500
+ return true;
501
+ }
502
+ if (this.pendingInstallReportKeys.has(installKey)) {
503
+ return app.installed;
504
+ }
505
+ return app.installed && !app.updateAvailable && !app.availableVersion;
506
+ }
507
+ mergeSnapshotAppsByStateRevision(workspaceId, snapshotApps) {
508
+ if (this.store.workspaceId !== workspaceId) {
509
+ return [...snapshotApps];
510
+ }
511
+ const currentAppsById = new Map(
512
+ this.store.apps.map((app) => [app.appId, app])
513
+ );
514
+ return snapshotApps.map((snapshotApp) => {
515
+ const currentApp = currentAppsById.get(snapshotApp.appId);
516
+ if (!currentApp || currentApp.stateRevision < snapshotApp.stateRevision) {
517
+ return snapshotApp;
518
+ }
519
+ const installKey = appRuntimeKey(workspaceId, snapshotApp.appId);
520
+ if (this.pendingInstallKeys.has(installKey)) {
521
+ const pendingSettled = this.isPendingInstallSettled(
522
+ installKey,
523
+ snapshotApp
524
+ );
525
+ if (pendingSettled && currentApp.stateRevision <= snapshotApp.stateRevision) {
526
+ return snapshotApp;
527
+ }
528
+ }
529
+ return mergeWorkspaceAppCatalogFields(currentApp, snapshotApp);
530
+ });
531
+ }
532
+ scheduleCatalogLoadingRefresh(workspaceId, snapshot) {
533
+ if (snapshot.catalogStatus !== "loading") {
534
+ this.clearCatalogRefreshTimer();
535
+ return;
536
+ }
537
+ if (this.pollingWorkspaceId !== workspaceId || this.catalogRefreshTimer !== null) {
538
+ return;
539
+ }
540
+ this.catalogRefreshTimer = setTimeout(() => {
541
+ this.catalogRefreshTimer = null;
542
+ if (this.pollingWorkspaceId !== workspaceId) {
543
+ return;
544
+ }
545
+ void this.refresh(workspaceId);
546
+ }, this.dependencies.catalogLoadingRefreshDelayMs ?? defaultCatalogLoadingRefreshDelayMs);
547
+ }
548
+ 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
+ });
564
+ }
565
+ async refreshInstallState(workspaceId, appId) {
566
+ try {
567
+ const snapshot = await this.dependencies.gateway.listWorkspaceApps(workspaceId);
568
+ this.applySnapshot(workspaceId, snapshot);
569
+ } catch (error) {
570
+ this.setOperationError(error, {
571
+ appId,
572
+ operation: "workspace_app.refresh_install_state",
573
+ uiAction: "refresh_install_state",
574
+ workspaceId
575
+ });
576
+ }
577
+ if (this.pendingInstallKeys.has(appRuntimeKey(workspaceId, appId))) {
578
+ this.scheduleInstallRefresh(workspaceId, appId);
579
+ }
580
+ }
581
+ settlePendingInstallReport(input) {
582
+ const installKey = appRuntimeKey(input.workspaceId, input.app.appId);
583
+ if (!this.pendingInstallKeys.has(installKey)) {
584
+ return;
585
+ }
586
+ if (!this.isPendingInstallSettled(installKey, input.app)) {
587
+ return;
588
+ }
589
+ this.pendingInstallKeys.delete(installKey);
590
+ this.clearInstallRefreshTimer(input.workspaceId, input.app.appId);
591
+ if (!this.pendingInstallReportKeys.delete(installKey)) {
592
+ return;
593
+ }
594
+ if (input.app.installed) {
595
+ this.dependencies.hooks?.onAppInstalled?.(input.app);
596
+ return;
597
+ }
598
+ this.dependencies.hooks?.onAppInstallFailed?.({
599
+ app: input.app,
600
+ appId: input.app.appId,
601
+ failureReason: input.failureReason ?? input.app.failureReason ?? input.app.lastError ?? null
602
+ });
603
+ }
604
+ };
605
+
606
+ // src/core/appCenterController.ts
607
+ function createWorkspaceAppCenterController(dependencies) {
608
+ return new WorkspaceAppCenterController(dependencies);
609
+ }
610
+ var WorkspaceAppCenterController = class extends WorkspaceAppCenterControllerState {
611
+ async installApp(input) {
612
+ const installKey = appRuntimeKey(input.workspaceId, input.appId);
613
+ if (this.pendingInstallKeys.has(installKey)) {
614
+ return;
615
+ }
616
+ const previousApps = this.store.apps;
617
+ const appBeforeInstall = previousApps.find((app) => app.appId === input.appId) ?? null;
618
+ this.pendingInstallKeys.add(installKey);
619
+ this.pendingInstallReportKeys.add(installKey);
620
+ this.markAppInstalling(input.appId);
621
+ try {
622
+ const snapshot = await this.dependencies.gateway.installWorkspaceApp(
623
+ input.workspaceId,
624
+ input.appId
625
+ );
626
+ this.applySnapshot(input.workspaceId, snapshot);
627
+ if (this.pendingInstallKeys.has(installKey)) {
628
+ this.scheduleInstallRefresh(input.workspaceId, input.appId);
629
+ }
630
+ } catch (error) {
631
+ this.pendingInstallKeys.delete(installKey);
632
+ this.pendingInstallReportKeys.delete(installKey);
633
+ this.clearInstallRefreshTimer(input.workspaceId, input.appId);
634
+ this.store.apps = previousApps;
635
+ this.dependencies.hooks?.onAppInstallFailed?.({
636
+ app: appBeforeInstall,
637
+ appId: input.appId,
638
+ failureReason: this.getErrorReason(error)
639
+ });
640
+ this.setOperationError(error, {
641
+ appId: input.appId,
642
+ operation: "workspace_app.install",
643
+ uiAction: "install_app",
644
+ workspaceId: input.workspaceId
645
+ });
646
+ }
647
+ }
648
+ async prepareAppLaunch(input) {
649
+ const app = this.store.apps.find(
650
+ (candidate) => candidate.appId === input.appId
651
+ );
652
+ if (this.store.workspaceId !== input.workspaceId || !app?.installed) {
653
+ return null;
654
+ }
655
+ const currentLaunchableApp = this.resolveLaunchableApp(input);
656
+ if (currentLaunchableApp) {
657
+ return currentLaunchableApp;
658
+ }
659
+ const previousApps = this.store.apps;
660
+ this.markAppStarting(input.appId);
661
+ try {
662
+ const snapshot = await this.dependencies.gateway.launchWorkspaceApp(
663
+ input.workspaceId,
664
+ input.appId
665
+ );
666
+ this.applySnapshot(input.workspaceId, snapshot);
667
+ } catch (error) {
668
+ if (this.store.workspaceId === input.workspaceId) {
669
+ this.store.apps = previousApps;
670
+ }
671
+ this.setOperationError(error, {
672
+ appId: input.appId,
673
+ operation: "workspace_app.prepare_launch",
674
+ uiAction: "open_app",
675
+ workspaceId: input.workspaceId
676
+ });
677
+ return null;
678
+ }
679
+ return await this.waitForLaunchableApp(input);
680
+ }
681
+ async createFactoryJob(input) {
682
+ const previousJobIds = new Set(
683
+ this.store.factoryJobs.map((job) => job.jobId)
684
+ );
685
+ const snapshot = await this.dependencies.gateway.createWorkspaceAppFactoryJob(
686
+ input.workspaceId,
687
+ {
688
+ displayName: input.displayName,
689
+ ...input.model?.trim() ? { model: input.model.trim() } : {},
690
+ ...input.permissionModeId?.trim() ? { permissionModeId: input.permissionModeId.trim() } : {},
691
+ ...input.provider?.trim() ? { provider: input.provider.trim() } : {},
692
+ prompt: input.prompt,
693
+ ...input.reasoningEffort?.trim() ? { reasoningEffort: input.reasoningEffort.trim() } : {}
694
+ }
695
+ );
696
+ this.applyFactorySnapshot(input.workspaceId, snapshot);
697
+ this.dependencies.hooks?.onFactoryJobCreated?.(
698
+ snapshot.jobs.find((job) => !previousJobIds.has(job.jobId)) ?? null
699
+ );
700
+ }
701
+ async cancelFactoryJob(input) {
702
+ const snapshot = await this.dependencies.gateway.cancelWorkspaceAppFactoryJob(
703
+ input.workspaceId,
704
+ input.jobId
705
+ );
706
+ this.applyFactorySnapshot(input.workspaceId, snapshot);
707
+ }
708
+ async deleteFactoryJob(input) {
709
+ const snapshot = await this.dependencies.gateway.deleteWorkspaceAppFactoryJob(
710
+ input.workspaceId,
711
+ input.jobId
712
+ );
713
+ this.applyFactorySnapshot(input.workspaceId, snapshot);
714
+ }
715
+ async deleteApp(input) {
716
+ const app = this.store.apps.find(
717
+ (candidate) => candidate.appId === input.appId
718
+ );
719
+ try {
720
+ const snapshot = await this.dependencies.gateway.deleteWorkspaceApp(
721
+ input.workspaceId,
722
+ input.appId
723
+ );
724
+ this.pendingInstallKeys.delete(
725
+ appRuntimeKey(input.workspaceId, input.appId)
726
+ );
727
+ this.pendingInstallReportKeys.delete(
728
+ appRuntimeKey(input.workspaceId, input.appId)
729
+ );
730
+ this.clearInstallRefreshTimer(input.workspaceId, input.appId);
731
+ this.applySnapshot(input.workspaceId, snapshot);
732
+ this.dependencies.hooks?.onAppDeleted?.(app ?? null);
733
+ } catch (error) {
734
+ this.setOperationError(error, {
735
+ appId: input.appId,
736
+ operation: "workspace_app.delete",
737
+ uiAction: "delete_app",
738
+ workspaceId: input.workspaceId
739
+ });
740
+ }
741
+ }
742
+ async retryFactoryValidation(input) {
743
+ const snapshot = await this.dependencies.gateway.retryWorkspaceAppFactoryJobValidation(
744
+ input.workspaceId,
745
+ input.jobId
746
+ );
747
+ this.applyFactorySnapshot(input.workspaceId, snapshot);
748
+ }
749
+ async fixFactoryJob(input) {
750
+ const snapshot = await this.dependencies.gateway.fixWorkspaceAppFactoryJob(
751
+ input.workspaceId,
752
+ input.jobId,
753
+ { prompt: input.prompt }
754
+ );
755
+ this.applyFactorySnapshot(input.workspaceId, snapshot);
756
+ }
757
+ async prepareFactoryJobModification(input) {
758
+ try {
759
+ const snapshot = await this.dependencies.gateway.prepareWorkspaceAppFactoryJobModification(
760
+ input.workspaceId,
761
+ input.jobId
762
+ );
763
+ this.applyFactorySnapshot(input.workspaceId, snapshot);
764
+ return snapshot.jobs.find((candidate) => candidate.jobId === input.jobId) ?? null;
765
+ } catch (error) {
766
+ this.setOperationError(error, {
767
+ jobId: input.jobId,
768
+ operation: "app_factory.prepare_modification",
769
+ uiAction: "prepare_factory_job_modification",
770
+ workspaceId: input.workspaceId
771
+ });
772
+ return null;
773
+ }
774
+ }
775
+ async publishFactoryJob(input) {
776
+ const publishKey = factoryJobKey(input.workspaceId, input.jobId);
777
+ if (this.pendingFactoryPublishKeys.has(publishKey)) {
778
+ return null;
779
+ }
780
+ this.pendingFactoryPublishKeys.add(publishKey);
781
+ let result;
782
+ try {
783
+ result = await this.dependencies.gateway.publishWorkspaceAppFactoryJob(
784
+ input.workspaceId,
785
+ input.jobId
786
+ );
787
+ } catch (error) {
788
+ if (this.pendingFactoryPublishKeys.delete(publishKey)) {
789
+ this.setOperationError(error, {
790
+ jobId: input.jobId,
791
+ operation: "app_factory.publish",
792
+ uiAction: "publish_factory_job",
793
+ workspaceId: input.workspaceId
794
+ });
795
+ }
796
+ return null;
797
+ }
798
+ this.pendingFactoryPublishKeys.delete(publishKey);
799
+ this.applyFactorySnapshot(input.workspaceId, result.factorySnapshot);
800
+ this.applySnapshot(input.workspaceId, result.appSnapshot);
801
+ return result.factorySnapshot.jobs.find(
802
+ (candidate) => candidate.jobId === input.jobId
803
+ ) ?? null;
804
+ }
805
+ async refresh(workspaceId) {
806
+ const normalizedWorkspaceId = workspaceId.trim();
807
+ if (!normalizedWorkspaceId) {
808
+ return;
809
+ }
810
+ const appSequence = ++this.appLoadSequence;
811
+ const factorySequence = ++this.factoryLoadSequence;
812
+ const wasIdle = this.store.loadStatus === "idle";
813
+ this.store.workspaceId = normalizedWorkspaceId;
814
+ this.store.error = null;
815
+ if (wasIdle) {
816
+ this.store.loadStatus = "loading";
817
+ }
818
+ try {
819
+ const [appResult, factoryResult] = await Promise.allSettled([
820
+ this.dependencies.gateway.listWorkspaceApps(normalizedWorkspaceId),
821
+ this.dependencies.gateway.listWorkspaceAppFactoryJobs(
822
+ normalizedWorkspaceId
823
+ )
824
+ ]);
825
+ if (appResult.status === "fulfilled" && appSequence !== this.appLoadSequence) {
826
+ this.dependencies.hooks?.onRefreshDiscard?.({
827
+ currentSequence: this.appLoadSequence,
828
+ itemCount: appResult.value.apps.length,
829
+ operation: "app_center.refresh",
830
+ sequence: appSequence,
831
+ snapshotKind: "apps",
832
+ workspaceId: normalizedWorkspaceId
833
+ });
834
+ }
835
+ if (factoryResult.status === "fulfilled" && factorySequence !== this.factoryLoadSequence) {
836
+ this.dependencies.hooks?.onRefreshDiscard?.({
837
+ currentSequence: this.factoryLoadSequence,
838
+ itemCount: factoryResult.value.jobs.length,
839
+ operation: "app_center.refresh",
840
+ sequence: factorySequence,
841
+ snapshotKind: "factory_jobs",
842
+ workspaceId: normalizedWorkspaceId
843
+ });
844
+ }
845
+ let error = null;
846
+ if (appResult.status === "rejected" && appSequence === this.appLoadSequence) {
847
+ error = appResult.reason;
848
+ } else if (factoryResult.status === "rejected" && factorySequence === this.factoryLoadSequence) {
849
+ error = factoryResult.reason;
850
+ }
851
+ if (error) {
852
+ this.setUnavailableError(error, {
853
+ operation: "app_center.refresh",
854
+ workspaceId: normalizedWorkspaceId
855
+ });
856
+ return;
857
+ }
858
+ if (appResult.status === "fulfilled" && appSequence === this.appLoadSequence) {
859
+ this.applySnapshot(normalizedWorkspaceId, appResult.value);
860
+ }
861
+ if (factoryResult.status === "fulfilled" && factorySequence === this.factoryLoadSequence) {
862
+ this.applyFactorySnapshot(normalizedWorkspaceId, factoryResult.value);
863
+ }
864
+ } catch (error) {
865
+ this.setUnavailableError(error, {
866
+ operation: "app_center.refresh",
867
+ workspaceId: normalizedWorkspaceId
868
+ });
869
+ }
870
+ }
871
+ async refreshCatalog(workspaceId) {
872
+ const normalizedWorkspaceId = workspaceId.trim();
873
+ if (!normalizedWorkspaceId) {
874
+ return;
875
+ }
876
+ const sequence = ++this.appLoadSequence;
877
+ this.store.workspaceId = normalizedWorkspaceId;
878
+ this.store.error = null;
879
+ try {
880
+ const snapshot = await this.dependencies.gateway.refreshWorkspaceAppCatalog(
881
+ normalizedWorkspaceId
882
+ );
883
+ if (sequence !== this.appLoadSequence) {
884
+ this.dependencies.hooks?.onRefreshDiscard?.({
885
+ currentSequence: this.appLoadSequence,
886
+ itemCount: snapshot.apps.length,
887
+ operation: "app_center.refresh_catalog",
888
+ sequence,
889
+ snapshotKind: "catalog_apps",
890
+ workspaceId: normalizedWorkspaceId
891
+ });
892
+ return;
893
+ }
894
+ this.applySnapshot(normalizedWorkspaceId, snapshot);
895
+ this.dependencies.hooks?.onCatalogRefreshed?.({
896
+ appCount: snapshot.apps.length,
897
+ errorReason: null,
898
+ success: true
899
+ });
900
+ } catch (error) {
901
+ if (sequence !== this.appLoadSequence) {
902
+ this.dependencies.hooks?.onRefreshDiscard?.({
903
+ currentSequence: this.appLoadSequence,
904
+ operation: "app_center.refresh_catalog",
905
+ sequence,
906
+ snapshotKind: "catalog_apps",
907
+ workspaceId: normalizedWorkspaceId
908
+ });
909
+ return;
910
+ }
911
+ this.setUnavailableError(error, {
912
+ operation: "app_center.refresh_catalog",
913
+ workspaceId: normalizedWorkspaceId
914
+ });
915
+ this.dependencies.hooks?.onCatalogRefreshed?.({
916
+ appCount: null,
917
+ errorReason: this.getErrorReason(error),
918
+ success: false
919
+ });
920
+ }
921
+ }
922
+ async startEnabledApps(workspaceId) {
923
+ this.markEnabledAppsStarting();
924
+ try {
925
+ const snapshot = await this.dependencies.gateway.startEnabledWorkspaceApps(workspaceId);
926
+ this.applySnapshot(workspaceId, snapshot);
927
+ } catch (error) {
928
+ this.setUnavailableError(error, {
929
+ operation: "workspace_app.start_enabled",
930
+ workspaceId
931
+ });
932
+ }
933
+ }
934
+ async uninstallApp(input) {
935
+ const app = this.store.apps.find(
936
+ (candidate) => candidate.appId === input.appId
937
+ );
938
+ const snapshot = await this.dependencies.gateway.uninstallWorkspaceApp(
939
+ input.workspaceId,
940
+ input.appId
941
+ );
942
+ this.applySnapshot(input.workspaceId, snapshot);
943
+ this.dependencies.hooks?.onAppUninstalled?.(app ?? null);
944
+ }
945
+ async updateApp(input) {
946
+ const installKey = appRuntimeKey(input.workspaceId, input.appId);
947
+ if (this.pendingInstallKeys.has(installKey)) {
948
+ return;
949
+ }
950
+ const app = this.store.apps.find(
951
+ (candidate) => candidate.appId === input.appId
952
+ );
953
+ const previousApps = this.store.apps;
954
+ this.pendingInstallKeys.add(installKey);
955
+ this.markAppInstalling(input.appId, { preserveInstalled: true });
956
+ try {
957
+ const snapshot = await this.dependencies.gateway.installWorkspaceApp(
958
+ input.workspaceId,
959
+ input.appId
960
+ );
961
+ this.applySnapshot(input.workspaceId, snapshot);
962
+ this.dependencies.hooks?.onAppUpdated?.({
963
+ app,
964
+ trigger: input.trigger
965
+ });
966
+ if (this.pendingInstallKeys.has(installKey)) {
967
+ this.scheduleInstallRefresh(input.workspaceId, input.appId);
968
+ }
969
+ } catch (error) {
970
+ this.pendingInstallKeys.delete(installKey);
971
+ this.clearInstallRefreshTimer(input.workspaceId, input.appId);
972
+ this.store.apps = previousApps;
973
+ this.setOperationError(error, {
974
+ appId: input.appId,
975
+ operation: "workspace_app.update",
976
+ uiAction: "update_app",
977
+ workspaceId: input.workspaceId
978
+ });
979
+ }
980
+ }
981
+ async retryApp(input) {
982
+ const app = this.store.apps.find(
983
+ (candidate) => candidate.appId === input.appId
984
+ );
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
+ }
1007
+ }
1008
+ waitForLaunchableApp(input) {
1009
+ const current = this.resolveLaunchableApp(input);
1010
+ if (current) {
1011
+ return Promise.resolve(current);
1012
+ }
1013
+ if (this.shouldAbortLaunchWait(input)) {
1014
+ return Promise.resolve(null);
1015
+ }
1016
+ const launchWaitTimeoutMs = this.dependencies.appOpenLaunchWaitTimeoutMs ?? defaultAppOpenLaunchWaitTimeoutMs;
1017
+ return new Promise((resolve) => {
1018
+ let settled = false;
1019
+ let unsubscribe = noop;
1020
+ let timeout = null;
1021
+ const settle = (app) => {
1022
+ if (settled) {
1023
+ return;
1024
+ }
1025
+ settled = true;
1026
+ if (timeout) {
1027
+ clearTimeout(timeout);
1028
+ }
1029
+ unsubscribe();
1030
+ resolve(app);
1031
+ };
1032
+ timeout = setTimeout(() => {
1033
+ void this.refreshLaunchWaitState(input).then((app) => {
1034
+ if (!app) {
1035
+ this.markLaunchWaitTimedOut(input);
1036
+ }
1037
+ settle(app);
1038
+ });
1039
+ }, launchWaitTimeoutMs);
1040
+ unsubscribe = this.subscribe(() => {
1041
+ const app = this.store.apps.find(
1042
+ (candidate) => candidate.appId === input.appId
1043
+ );
1044
+ if (this.shouldAbortLaunchWait(input, app)) {
1045
+ settle(null);
1046
+ return;
1047
+ }
1048
+ const launchable = this.resolveLaunchableApp(input);
1049
+ if (launchable) {
1050
+ settle(launchable);
1051
+ }
1052
+ });
1053
+ });
1054
+ }
1055
+ async refreshLaunchWaitState(input) {
1056
+ try {
1057
+ const snapshot = await this.dependencies.gateway.listWorkspaceApps(
1058
+ input.workspaceId
1059
+ );
1060
+ this.applySnapshot(input.workspaceId, snapshot);
1061
+ } catch (error) {
1062
+ this.setOperationError(error, {
1063
+ appId: input.appId,
1064
+ operation: "workspace_app.refresh_launch_wait_state",
1065
+ uiAction: "refresh_launch_wait_state",
1066
+ workspaceId: input.workspaceId
1067
+ });
1068
+ return null;
1069
+ }
1070
+ return this.resolveLaunchableApp(input);
1071
+ }
1072
+ shouldAbortLaunchWait(input, app = this.store.apps.find((candidate) => candidate.appId === input.appId)) {
1073
+ return this.store.workspaceId !== input.workspaceId || !app?.installed || app.runtimeStatus === "failed";
1074
+ }
1075
+ };
1076
+
1077
+ // src/core/appIdentity.ts
1078
+ var workspaceAppIdPattern = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
1079
+ function isWorkspaceAppId(value) {
1080
+ return workspaceAppIdPattern.test(value);
1081
+ }
1082
+ function normalizeWorkspaceAppId(value) {
1083
+ return value.trim().toLowerCase();
1084
+ }
1085
+ function createWorkspaceAppIdentity(input) {
1086
+ return normalizeWorkspaceAppId(input.id);
1087
+ }
1088
+
1089
+ // src/core/manifestValidation.ts
1090
+ var appWindowMinWidth = 280;
1091
+ var appWindowMinHeight = 160;
1092
+ var appWindowMaxWidth = 1600;
1093
+ var appWindowMaxHeight = 1200;
1094
+ function validateWorkspaceAppManifest(value) {
1095
+ const issues = [];
1096
+ if (!isRecord(value)) {
1097
+ return {
1098
+ issues: [
1099
+ {
1100
+ code: "manifest.notObject",
1101
+ message: "Manifest must be an object.",
1102
+ path: "$"
1103
+ }
1104
+ ],
1105
+ valid: false
1106
+ };
1107
+ }
1108
+ if (value.schemaVersion !== workspaceAppManifestSchemaVersion) {
1109
+ issues.push({
1110
+ code: "manifest.schemaVersion",
1111
+ message: `schemaVersion must be ${workspaceAppManifestSchemaVersion}.`,
1112
+ path: "$.schemaVersion"
1113
+ });
1114
+ }
1115
+ const appId = readOptionalString(value.appId);
1116
+ if (!appId || !isWorkspaceAppId(normalizeWorkspaceAppId(appId))) {
1117
+ issues.push({
1118
+ code: "manifest.appId",
1119
+ message: "appId must contain lowercase letters, numbers, dots, hyphens, or underscores.",
1120
+ path: "$.appId"
1121
+ });
1122
+ }
1123
+ const name = readOptionalString(value.name);
1124
+ if (!name) {
1125
+ issues.push({
1126
+ code: "manifest.name",
1127
+ message: "name is required.",
1128
+ path: "$.name"
1129
+ });
1130
+ }
1131
+ const version = readOptionalString(value.version);
1132
+ if (!version) {
1133
+ issues.push({
1134
+ code: "manifest.version",
1135
+ message: "version is required.",
1136
+ path: "$.version"
1137
+ });
1138
+ }
1139
+ const description = readOptionalString(value.description);
1140
+ if (!description) {
1141
+ issues.push({
1142
+ code: "manifest.description",
1143
+ message: "description is required.",
1144
+ path: "$.description"
1145
+ });
1146
+ }
1147
+ const icon = validateIcon(value.icon, issues);
1148
+ const runtime = validateRuntime(value.runtime, issues);
1149
+ const window = validateWindow(value.window, issues);
1150
+ const author = validateAuthor(value.author, issues);
1151
+ const references = validateReferences(value.references, issues);
1152
+ const tags = validateTags(value.tags, issues);
1153
+ const localizationInfo = validateLocalizationInfo(
1154
+ value.localizationInfo,
1155
+ issues
1156
+ );
1157
+ if (issues.length > 0 || !appId || !name || !version || !description || !runtime) {
1158
+ return {
1159
+ issues,
1160
+ valid: false
1161
+ };
1162
+ }
1163
+ return {
1164
+ issues,
1165
+ manifest: {
1166
+ schemaVersion: workspaceAppManifestSchemaVersion,
1167
+ appId: normalizeWorkspaceAppId(appId),
1168
+ name,
1169
+ version,
1170
+ description,
1171
+ ...icon ? { icon } : {},
1172
+ runtime,
1173
+ ...window ? { window } : {},
1174
+ ...author ? { author } : {},
1175
+ ...references ? { references } : {},
1176
+ ...tags ? { tags } : {},
1177
+ ...localizationInfo ? { localizationInfo } : {}
1178
+ },
1179
+ valid: true
1180
+ };
1181
+ }
1182
+ function validateIcon(value, issues) {
1183
+ if (value === void 0) {
1184
+ return void 0;
1185
+ }
1186
+ if (!isRecord(value)) {
1187
+ issues.push({
1188
+ code: "manifest.icon",
1189
+ message: "icon must be an object when provided.",
1190
+ path: "$.icon"
1191
+ });
1192
+ return void 0;
1193
+ }
1194
+ const type = value.type;
1195
+ const src = readOptionalString(value.src);
1196
+ if (type !== "asset" || !src || !isRelativePackagePath(src)) {
1197
+ issues.push({
1198
+ code: "manifest.icon",
1199
+ message: "icon must include type=asset and a relative src.",
1200
+ path: "$.icon"
1201
+ });
1202
+ return void 0;
1203
+ }
1204
+ return {
1205
+ type,
1206
+ src
1207
+ };
1208
+ }
1209
+ function validateRuntime(value, issues) {
1210
+ if (!isRecord(value)) {
1211
+ issues.push({
1212
+ code: "manifest.runtime",
1213
+ message: "runtime is required.",
1214
+ path: "$.runtime"
1215
+ });
1216
+ return void 0;
1217
+ }
1218
+ const bootstrap = readOptionalString(value.bootstrap);
1219
+ const healthcheckPath = readOptionalString(value.healthcheckPath);
1220
+ if (!bootstrap || !isRelativePackagePath(bootstrap)) {
1221
+ issues.push({
1222
+ code: "manifest.runtime",
1223
+ message: "runtime.bootstrap must be a relative package path.",
1224
+ path: "$.runtime.bootstrap"
1225
+ });
1226
+ }
1227
+ if (!healthcheckPath || !healthcheckPath.startsWith("/")) {
1228
+ issues.push({
1229
+ code: "manifest.runtime",
1230
+ message: "runtime.healthcheckPath must start with /.",
1231
+ path: "$.runtime.healthcheckPath"
1232
+ });
1233
+ }
1234
+ if (!bootstrap || !healthcheckPath || !isRelativePackagePath(bootstrap) || !healthcheckPath.startsWith("/")) {
1235
+ return void 0;
1236
+ }
1237
+ return {
1238
+ bootstrap,
1239
+ healthcheckPath
1240
+ };
1241
+ }
1242
+ function validateWindow(value, issues) {
1243
+ if (value === void 0) {
1244
+ return void 0;
1245
+ }
1246
+ if (!isRecord(value)) {
1247
+ issues.push({
1248
+ code: "manifest.window",
1249
+ message: "window must be an object when provided.",
1250
+ path: "$.window"
1251
+ });
1252
+ return void 0;
1253
+ }
1254
+ const minimizeBehavior = value.minimizeBehavior;
1255
+ if (minimizeBehavior !== void 0 && minimizeBehavior !== "keep-mounted" && minimizeBehavior !== "hibernate") {
1256
+ issues.push({
1257
+ code: "manifest.window",
1258
+ message: "window.minimizeBehavior must be keep-mounted or hibernate when provided.",
1259
+ path: "$.window.minimizeBehavior"
1260
+ });
1261
+ return void 0;
1262
+ }
1263
+ const minWidth = validateWindowSize(
1264
+ value.minWidth,
1265
+ "minWidth",
1266
+ appWindowMinWidth,
1267
+ appWindowMaxWidth,
1268
+ issues
1269
+ );
1270
+ const minHeight = validateWindowSize(
1271
+ value.minHeight,
1272
+ "minHeight",
1273
+ appWindowMinHeight,
1274
+ appWindowMaxHeight,
1275
+ issues
1276
+ );
1277
+ if (minWidth === null || minHeight === null) {
1278
+ return void 0;
1279
+ }
1280
+ return {
1281
+ ...minimizeBehavior ? { minimizeBehavior } : {},
1282
+ ...minHeight === void 0 ? {} : { minHeight },
1283
+ ...minWidth === void 0 ? {} : { minWidth }
1284
+ };
1285
+ }
1286
+ function validateWindowSize(value, field, minimum, maximum, issues) {
1287
+ if (value === void 0) {
1288
+ return void 0;
1289
+ }
1290
+ if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value > maximum) {
1291
+ issues.push({
1292
+ code: "manifest.window",
1293
+ message: `window.${field} must be an integer between ${minimum} and ${maximum} when provided.`,
1294
+ path: `$.window.${field}`
1295
+ });
1296
+ return null;
1297
+ }
1298
+ return value;
1299
+ }
1300
+ function validateAuthor(value, issues) {
1301
+ if (value === void 0) {
1302
+ return void 0;
1303
+ }
1304
+ if (!isRecord(value)) {
1305
+ issues.push({
1306
+ code: "manifest.author",
1307
+ message: "author must be an object when provided.",
1308
+ path: "$.author"
1309
+ });
1310
+ return void 0;
1311
+ }
1312
+ const name = readOptionalString(value.name);
1313
+ const url = readOptionalString(value.url);
1314
+ if (!name || value.url !== void 0 && !url) {
1315
+ issues.push({
1316
+ code: "manifest.author",
1317
+ message: "author must include name and an optional non-empty url.",
1318
+ path: "$.author"
1319
+ });
1320
+ return void 0;
1321
+ }
1322
+ return {
1323
+ name,
1324
+ ...url ? { url } : {}
1325
+ };
1326
+ }
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
+ function validateTags(value, issues) {
1351
+ if (value === void 0) {
1352
+ return void 0;
1353
+ }
1354
+ if (!Array.isArray(value)) {
1355
+ issues.push({
1356
+ code: "manifest.tags",
1357
+ message: "tags must be an array when provided.",
1358
+ path: "$.tags"
1359
+ });
1360
+ return void 0;
1361
+ }
1362
+ const tags = value.map((tag) => readOptionalString(tag)).filter((tag) => Boolean(tag));
1363
+ if (tags.length !== value.length) {
1364
+ issues.push({
1365
+ code: "manifest.tags",
1366
+ message: "tags must only contain non-empty strings.",
1367
+ path: "$.tags"
1368
+ });
1369
+ }
1370
+ return Array.from(new Set(tags));
1371
+ }
1372
+ function validateLocalizationInfo(value, issues) {
1373
+ if (value === void 0) {
1374
+ return void 0;
1375
+ }
1376
+ if (!isRecord(value)) {
1377
+ issues.push({
1378
+ code: "manifest.localizationInfo",
1379
+ message: "localizationInfo must be an object when provided.",
1380
+ path: "$.localizationInfo"
1381
+ });
1382
+ return void 0;
1383
+ }
1384
+ const defaultLocale = readOptionalString(value.defaultLocale);
1385
+ if (!defaultLocale) {
1386
+ issues.push({
1387
+ code: "manifest.localizationInfo",
1388
+ message: "localizationInfo.defaultLocale is required.",
1389
+ path: "$.localizationInfo.defaultLocale"
1390
+ });
1391
+ }
1392
+ const additionalLocalesValue = value.additionalLocales;
1393
+ if (additionalLocalesValue !== void 0 && !Array.isArray(additionalLocalesValue)) {
1394
+ issues.push({
1395
+ code: "manifest.localizationInfo",
1396
+ message: "localizationInfo.additionalLocales must be an array.",
1397
+ path: "$.localizationInfo.additionalLocales"
1398
+ });
1399
+ return void 0;
1400
+ }
1401
+ const seenLocales = new Set(
1402
+ defaultLocale ? [defaultLocale.toLowerCase()] : []
1403
+ );
1404
+ const additionalLocales = [];
1405
+ for (const [index, entry] of (additionalLocalesValue ?? []).entries()) {
1406
+ if (!isRecord(entry)) {
1407
+ issues.push({
1408
+ code: "manifest.localizationInfo",
1409
+ message: "localizationInfo.additionalLocales entries must be objects.",
1410
+ path: `$.localizationInfo.additionalLocales[${index}]`
1411
+ });
1412
+ continue;
1413
+ }
1414
+ const locale = readOptionalString(entry.locale);
1415
+ const file = readOptionalString(entry.file);
1416
+ if (!locale || !file || !isRelativePackagePath(file)) {
1417
+ issues.push({
1418
+ code: "manifest.localizationInfo",
1419
+ message: "localizationInfo.additionalLocales entries must include locale and a relative file path.",
1420
+ path: `$.localizationInfo.additionalLocales[${index}]`
1421
+ });
1422
+ continue;
1423
+ }
1424
+ const localeKey = locale.toLowerCase();
1425
+ if (seenLocales.has(localeKey)) {
1426
+ issues.push({
1427
+ code: "manifest.localizationInfo",
1428
+ message: "localizationInfo locales must be unique.",
1429
+ path: `$.localizationInfo.additionalLocales[${index}].locale`
1430
+ });
1431
+ continue;
1432
+ }
1433
+ seenLocales.add(localeKey);
1434
+ additionalLocales.push({ locale, file });
1435
+ }
1436
+ if (!defaultLocale || issues.some((issue) => issue.code === "manifest.localizationInfo")) {
1437
+ return void 0;
1438
+ }
1439
+ return {
1440
+ defaultLocale,
1441
+ ...additionalLocales.length > 0 ? { additionalLocales } : {}
1442
+ };
1443
+ }
1444
+ function readOptionalString(value) {
1445
+ if (typeof value !== "string") {
1446
+ return void 0;
1447
+ }
1448
+ const trimmed = value.trim();
1449
+ return trimmed.length > 0 ? trimmed : void 0;
1450
+ }
1451
+ function isRelativePackagePath(value) {
1452
+ return !value.startsWith("/") && !value.split(/[\\/]/u).includes("..");
1453
+ }
1454
+ function isRelativeUrlPath(value) {
1455
+ const trimmed = value.trim();
1456
+ if (!trimmed || !trimmed.startsWith("/") || trimmed.startsWith("//") || trimmed.includes("\0") || trimmed.includes("?") || trimmed.includes("#") || trimmed.includes("%")) {
1457
+ return false;
1458
+ }
1459
+ return true;
1460
+ }
1461
+ function isRecord(value) {
1462
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1463
+ }
1464
+
1465
+ // src/core/statusMapping.ts
1466
+ var statusAliases = /* @__PURE__ */ new Map([
1467
+ ["active", "running"],
1468
+ ["created", "idle"],
1469
+ ["crashed", "failed"],
1470
+ ["error", "failed"],
1471
+ ["failed", "failed"],
1472
+ ["idle", "idle"],
1473
+ ["installed", "idle"],
1474
+ ["installing", "installing"],
1475
+ ["downloading_runtime", "preparing"],
1476
+ ["launching", "starting"],
1477
+ ["pending", "starting"],
1478
+ ["preparing", "preparing"],
1479
+ ["ready", "idle"],
1480
+ ["running", "running"],
1481
+ ["runner_unavailable", "unavailable"],
1482
+ ["runtime_unavailable", "unavailable"],
1483
+ ["sandbox_unavailable", "unavailable"],
1484
+ ["started", "running"],
1485
+ ["starting", "starting"],
1486
+ ["stale", "unavailable"],
1487
+ ["stopped", "idle"],
1488
+ ["stopping", "stopping"],
1489
+ ["terminated", "idle"],
1490
+ ["terminating", "stopping"],
1491
+ ["unavailable", "unavailable"],
1492
+ ["unreachable", "unavailable"]
1493
+ ]);
1494
+ function mapWorkspaceAppRuntimeStatus(value) {
1495
+ if (typeof value !== "string") {
1496
+ return "idle";
1497
+ }
1498
+ return statusAliases.get(value.trim().toLowerCase()) ?? "idle";
1499
+ }
1500
+ function normalizeWorkspaceAppRuntimeState(state) {
1501
+ return {
1502
+ ...state,
1503
+ status: mapWorkspaceAppRuntimeStatus(state.status)
1504
+ };
1505
+ }
1506
+ function resolveWorkspaceAppStatusPresentation(status) {
1507
+ switch (status) {
1508
+ case "installing":
1509
+ return {
1510
+ labelKey: "status.installing",
1511
+ pulse: true,
1512
+ tone: "blue"
1513
+ };
1514
+ case "preparing":
1515
+ return {
1516
+ labelKey: "status.preparing",
1517
+ pulse: true,
1518
+ tone: "blue"
1519
+ };
1520
+ case "starting":
1521
+ return {
1522
+ labelKey: "status.starting",
1523
+ pulse: true,
1524
+ tone: "blue"
1525
+ };
1526
+ case "running":
1527
+ return {
1528
+ labelKey: "status.running",
1529
+ pulse: false,
1530
+ tone: "green"
1531
+ };
1532
+ case "failed":
1533
+ return {
1534
+ labelKey: "status.failed",
1535
+ pulse: false,
1536
+ tone: "red"
1537
+ };
1538
+ case "stopping":
1539
+ return {
1540
+ labelKey: "status.stopping",
1541
+ pulse: true,
1542
+ tone: "amber"
1543
+ };
1544
+ case "unavailable":
1545
+ return {
1546
+ labelKey: "status.unavailable",
1547
+ pulse: false,
1548
+ tone: "amber"
1549
+ };
1550
+ case "idle":
1551
+ return {
1552
+ labelKey: "actions.openApp",
1553
+ pulse: false,
1554
+ tone: "neutral"
1555
+ };
1556
+ }
1557
+ }
1558
+
1559
+ // src/core/appCenterViewModel.ts
1560
+ function createAppCenterViewModel({
1561
+ apps,
1562
+ factoryJobs = [],
1563
+ locale = null,
1564
+ replaceableIconAppIds = [],
1565
+ runtimeStates = []
1566
+ }) {
1567
+ const runtimeStateMaps = createRuntimeStateMaps(runtimeStates);
1568
+ const appFactoryJobByAppId = createAppFactoryJobByAppId(factoryJobs);
1569
+ const replaceableIconAppIdSet = new Set(
1570
+ replaceableIconAppIds.map((appId) => appId.trim()).filter(Boolean)
1571
+ );
1572
+ const appCards = apps.map((app) => {
1573
+ const installationId = normalizeOptionalString(
1574
+ app.install?.installationId
1575
+ );
1576
+ const runtime = findRuntimeStateForApp(
1577
+ runtimeStateMaps,
1578
+ app.manifest.appId,
1579
+ installationId
1580
+ );
1581
+ const factoryJob = appFactoryJobByAppId.get(app.manifest.appId);
1582
+ const metadata = resolveWorkspaceAppCatalogMetadata({
1583
+ catalog: app.catalog,
1584
+ locale,
1585
+ manifest: app.manifest
1586
+ });
1587
+ const factoryAgentSessionId = factoryJob?.agentSessionId?.trim() || null;
1588
+ const status = runtime?.status ?? "idle";
1589
+ const presentation = resolveWorkspaceAppStatusPresentation(status);
1590
+ const installed = Boolean(app.install);
1591
+ const sourceKind = resolveCatalogSourceKind(app.catalog);
1592
+ const localApp = sourceKind === "local";
1593
+ const runtimeId = normalizeOptionalString(runtime?.runtimeId);
1594
+ const launchUrl = normalizeOptionalString(runtime?.launchUrl);
1595
+ const installedVersion = normalizeOptionalString(app.install?.version);
1596
+ const manifestVersion = normalizeOptionalString(app.manifest.version);
1597
+ const displayVersion = installedVersion ?? manifestVersion;
1598
+ const comingSoon = isComingSoonApp(metadata.tags) || isComingSoonApp(app.manifest.tags ?? []);
1599
+ const busy = isBusyRuntimeStatus(status);
1600
+ const unavailable = status === "unavailable";
1601
+ const canUpdate = !comingSoon && !busy && !unavailable && installed && (app.updateAvailable ?? false);
1602
+ const canOpen = !comingSoon && installed && canOpenInstalledApp(status);
1603
+ const canRetry = installed && status === "failed";
1604
+ const primaryAction = resolvePrimaryAction({
1605
+ canOpen,
1606
+ canRetry,
1607
+ canUpdate,
1608
+ comingSoon,
1609
+ installed,
1610
+ status
1611
+ });
1612
+ return {
1613
+ id: app.manifest.appId,
1614
+ installationId,
1615
+ runtimeId,
1616
+ launchUrl,
1617
+ name: metadata.name,
1618
+ createdAtUnixMs: app.createdAtUnixMs ?? null,
1619
+ ...metadata.description ? { description: metadata.description } : {},
1620
+ ...displayVersion && !comingSoon ? { version: displayVersion } : {},
1621
+ ...app.availableVersion && !comingSoon ? { availableVersion: app.availableVersion } : {},
1622
+ ...app.category?.trim() ? { category: app.category.trim() } : {},
1623
+ updateAvailable: app.updateAvailable ?? false,
1624
+ ...app.manifest.icon ? { icon: app.manifest.icon } : {},
1625
+ tags: metadata.tags,
1626
+ installed,
1627
+ status,
1628
+ statusLabelKey: comingSoon ? "status.comingSoon" : resolvePrimaryActionLabelKey(primaryAction, presentation.labelKey),
1629
+ statusTone: presentation.tone,
1630
+ statusPulse: presentation.pulse,
1631
+ primaryAction,
1632
+ sourceKind,
1633
+ canOpen,
1634
+ canExport: localApp,
1635
+ canDelete: localApp,
1636
+ canReplaceIcon: replaceableIconAppIdSet.has(app.manifest.appId),
1637
+ canOpenFolder: installed,
1638
+ canOpenPackageFolder: installed && localApp && Boolean(displayVersion),
1639
+ canOpenFactorySession: Boolean(factoryAgentSessionId),
1640
+ canPublishFactoryUpdate: installed && factoryJob?.status === "ready" && Boolean(factoryJob.publishedVersion?.trim()),
1641
+ canUninstall: installed,
1642
+ canRetry,
1643
+ canUpdate,
1644
+ ...factoryAgentSessionId ? { factoryAgentSessionId } : {},
1645
+ ...factoryJob?.jobId ? { factoryJobId: factoryJob.jobId } : {},
1646
+ ...factoryJob?.provider ? { factoryProvider: factoryJob.provider } : {},
1647
+ ...runtime?.error?.message ? { errorMessage: runtime.error.message } : {}
1648
+ };
1649
+ }).sort((left, right) => left.name.localeCompare(right.name));
1650
+ return {
1651
+ apps: appCards,
1652
+ factoryJobs: factoryJobs.filter((job) => !isPublishedAppFactoryJob(job)).map(createFactoryJobViewModel),
1653
+ empty: appCards.length === 0,
1654
+ failedCount: appCards.filter((app) => app.status === "failed").length,
1655
+ installedCount: appCards.filter((app) => app.installed).length,
1656
+ runningCount: appCards.filter((app) => app.status === "running").length
1657
+ };
1658
+ }
1659
+ function createRuntimeStateMaps(runtimeStates) {
1660
+ const fallbackByAppId = /* @__PURE__ */ new Map();
1661
+ const byInstallationId = /* @__PURE__ */ new Map();
1662
+ for (const state of runtimeStates) {
1663
+ const runtime = {
1664
+ ...state,
1665
+ status: mapWorkspaceAppRuntimeStatus(state.status)
1666
+ };
1667
+ const installationId = normalizeOptionalString(runtime.installationId);
1668
+ if (installationId) {
1669
+ byInstallationId.set(installationId, runtime);
1670
+ continue;
1671
+ }
1672
+ const appId = normalizeOptionalString(runtime.appId);
1673
+ if (appId) {
1674
+ fallbackByAppId.set(appId, runtime);
1675
+ }
1676
+ }
1677
+ return {
1678
+ fallbackByAppId,
1679
+ byInstallationId
1680
+ };
1681
+ }
1682
+ function findRuntimeStateForApp(maps, appId, installationId) {
1683
+ if (installationId) {
1684
+ const runtime = maps.byInstallationId.get(installationId);
1685
+ if (runtime) {
1686
+ return runtime;
1687
+ }
1688
+ }
1689
+ return maps.fallbackByAppId.get(appId);
1690
+ }
1691
+ function resolveCatalogSourceKind(catalog) {
1692
+ return catalog?.source?.kind ?? "bundled";
1693
+ }
1694
+ function resolveWorkspaceAppCatalogMetadata(input) {
1695
+ const manifest = input.manifest;
1696
+ const localization = findWorkspaceAppCatalogLocalization(
1697
+ input.catalog,
1698
+ input.locale
1699
+ );
1700
+ const name = localization?.name?.trim() || manifest.name;
1701
+ const description = localization?.description?.trim() || manifest.description || "";
1702
+ const tags = localization?.tags?.map((tag) => tag.trim()).filter((tag) => tag.length > 0) ?? manifest.tags ?? [];
1703
+ return {
1704
+ description,
1705
+ name,
1706
+ tags: Array.from(new Set(tags))
1707
+ };
1708
+ }
1709
+ function findWorkspaceAppCatalogLocalization(catalog, locale) {
1710
+ const localizations = catalog?.localizations ?? [];
1711
+ const normalizedLocale = normalizeLocale(locale);
1712
+ if (!normalizedLocale || localizations.length === 0) {
1713
+ return null;
1714
+ }
1715
+ const exact = localizations.find(
1716
+ (localization) => normalizeLocale(localization.locale) === normalizedLocale
1717
+ );
1718
+ if (exact) {
1719
+ return exact;
1720
+ }
1721
+ const language = normalizedLocale.split("-")[0];
1722
+ if (!language) {
1723
+ return null;
1724
+ }
1725
+ return localizations.find(
1726
+ (localization) => normalizeLocale(localization.locale)?.split("-")[0] === language
1727
+ ) ?? null;
1728
+ }
1729
+ function normalizeLocale(value) {
1730
+ const normalized = value?.trim().replace(/_/gu, "-").toLowerCase() ?? "";
1731
+ return normalized.length > 0 ? normalized : null;
1732
+ }
1733
+ function normalizeOptionalString(value) {
1734
+ const normalized = value?.trim() ?? "";
1735
+ return normalized.length > 0 ? normalized : null;
1736
+ }
1737
+ function resolvePrimaryAction(input) {
1738
+ if (input.comingSoon) {
1739
+ return "none";
1740
+ }
1741
+ if (isBusyRuntimeStatus(input.status)) {
1742
+ return "none";
1743
+ }
1744
+ if (input.status === "unavailable") {
1745
+ return "none";
1746
+ }
1747
+ if (!input.installed) {
1748
+ return "install";
1749
+ }
1750
+ if (input.canUpdate) {
1751
+ return "update";
1752
+ }
1753
+ if (input.canRetry) {
1754
+ return "retry";
1755
+ }
1756
+ if (input.canOpen) {
1757
+ return "open";
1758
+ }
1759
+ return "none";
1760
+ }
1761
+ function isComingSoonApp(tags) {
1762
+ return tags.some((tag) => tag.trim().toLowerCase() === "coming-soon");
1763
+ }
1764
+ function canOpenInstalledApp(status) {
1765
+ return status === "idle" || status === "running";
1766
+ }
1767
+ function isBusyRuntimeStatus(status) {
1768
+ return status === "installing" || status === "preparing" || status === "starting" || status === "stopping";
1769
+ }
1770
+ function resolvePrimaryActionLabelKey(primaryAction, fallbackLabelKey) {
1771
+ switch (primaryAction) {
1772
+ case "install":
1773
+ return "actions.installApp";
1774
+ case "open":
1775
+ return "actions.openApp";
1776
+ case "retry":
1777
+ return "actions.retryApp";
1778
+ case "update":
1779
+ return "actions.updateApp";
1780
+ case "none":
1781
+ return fallbackLabelKey;
1782
+ }
1783
+ }
1784
+ function createAppFactoryJobByAppId(factoryJobs) {
1785
+ const result = /* @__PURE__ */ new Map();
1786
+ for (const job of [...factoryJobs].sort(
1787
+ (left, right) => right.updatedAtUnixMs - left.updatedAtUnixMs
1788
+ )) {
1789
+ if (!isPublishedAppFactoryJob(job)) {
1790
+ continue;
1791
+ }
1792
+ const appId = job.appId?.trim();
1793
+ if (!appId || result.has(appId)) {
1794
+ continue;
1795
+ }
1796
+ result.set(appId, job);
1797
+ }
1798
+ return result;
1799
+ }
1800
+ function isPublishedAppFactoryJob(job) {
1801
+ return job.status === "published" || Boolean(job.publishedVersion?.trim());
1802
+ }
1803
+ function createFactoryJobViewModel(job) {
1804
+ const statusLabelKey = `factory.status.${job.status}`;
1805
+ const title = job.displayName.trim();
1806
+ const agentSessionId = job.agentSessionId?.trim() || null;
1807
+ const canFixValidationFailure = job.status === "failed" && job.validationResult != null;
1808
+ return {
1809
+ id: job.jobId,
1810
+ agentSessionId,
1811
+ appId: job.appId,
1812
+ title,
1813
+ prompt: job.prompt,
1814
+ provider: job.provider,
1815
+ status: job.status,
1816
+ statusLabelKey,
1817
+ canCancel: job.status === "queued" || job.status === "generating" || job.status === "preparing" || job.status === "validating",
1818
+ canDelete: job.status === "canceled" || job.status === "failed" || job.status === "published" || job.status === "ready",
1819
+ canFix: canFixValidationFailure,
1820
+ canOpenAgentSession: Boolean(agentSessionId),
1821
+ canPublish: job.status === "ready",
1822
+ canRetryValidation: canFixValidationFailure,
1823
+ failureReason: job.failureReason,
1824
+ updatedAtUnixMs: job.updatedAtUnixMs
1825
+ };
1826
+ }
1827
+ function createWorkspaceAppRecord(input) {
1828
+ const manifest = input.catalog?.manifest;
1829
+ if (!manifest) {
1830
+ throw new Error("catalog manifest is required.");
1831
+ }
1832
+ return {
1833
+ catalog: input.catalog,
1834
+ category: input.category,
1835
+ createdAtUnixMs: input.createdAtUnixMs ?? null,
1836
+ install: input.install ?? null,
1837
+ manifest
1838
+ };
1839
+ }
1840
+
1841
+ export {
1842
+ createWorkspaceAppCenterStoreState,
1843
+ createWorkspaceAppCenterController,
1844
+ WorkspaceAppCenterController,
1845
+ workspaceAppIdPattern,
1846
+ isWorkspaceAppId,
1847
+ normalizeWorkspaceAppId,
1848
+ createWorkspaceAppIdentity,
1849
+ validateWorkspaceAppManifest,
1850
+ mapWorkspaceAppRuntimeStatus,
1851
+ normalizeWorkspaceAppRuntimeState,
1852
+ resolveWorkspaceAppStatusPresentation,
1853
+ createAppCenterViewModel,
1854
+ resolveWorkspaceAppCatalogMetadata,
1855
+ createWorkspaceAppRecord
1856
+ };
1857
+ //# sourceMappingURL=chunk-5SANC3S6.js.map