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