@agent-native/dispatch 0.29.4 → 0.30.0
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/actions/create-workspace-resource.d.ts +13 -13
- package/dist/actions/delete-workspace-resource.d.ts +13 -13
- package/dist/actions/provider-api-register.d.ts +7 -7
- package/dist/actions/update-workspace-resource.d.ts +13 -13
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +2 -1
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +26 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema.d.ts +1 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +4 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +1 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts +3 -0
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +206 -11
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/package.json +3 -3
- package/src/components/dispatch-control-plane.spec.tsx +17 -3
- package/src/components/workspace-app-card.spec.tsx +9 -0
- package/src/components/workspace-app-card.tsx +12 -0
- package/src/db/index.ts +28 -1
- package/src/db/schema.ts +5 -0
- package/src/lib/workspace-apps.ts +1 -0
- package/src/server/lib/app-creation-store.spec.ts +120 -0
- package/src/server/lib/app-creation-store.ts +264 -41
|
@@ -14,9 +14,13 @@ import {
|
|
|
14
14
|
resolveBuilderCredentialsDetailed,
|
|
15
15
|
runBuilderAgent,
|
|
16
16
|
} from "@agent-native/core/server";
|
|
17
|
+
import { getOrgSetting } from "@agent-native/core/settings";
|
|
17
18
|
import { getSetting, putSetting } from "@agent-native/core/settings";
|
|
18
19
|
import { assertValidWorkspaceAppId } from "@agent-native/core/shared";
|
|
20
|
+
import { resolveAccess } from "@agent-native/core/sharing";
|
|
19
21
|
|
|
22
|
+
// Register the workspace-app shareable resource before any access lookup.
|
|
23
|
+
import "../../db/index.js";
|
|
20
24
|
import { isWorkspaceSsoAppUrl } from "../../shared/workspace-sso.js";
|
|
21
25
|
import { identityKeyForIncoming } from "./dispatch-integrations.js";
|
|
22
26
|
import {
|
|
@@ -43,10 +47,12 @@ const APP_CREATION_SETTINGS_AUTHORIZATION_MESSAGE =
|
|
|
43
47
|
const APP_CREATION_SETTINGS_REQUIRED_MESSAGE =
|
|
44
48
|
"An organization owner or admin must configure the Builder workspace project before members can create apps.";
|
|
45
49
|
const WORKSPACE_APP_METADATA_SETTINGS_KEY = "workspace-app-metadata";
|
|
50
|
+
const WORKSPACE_APP_DEFAULT_VISIBILITY_KEY = "workspace-app-default-visibility";
|
|
46
51
|
const WORKSPACE_APPS_ENV_KEY = "AGENT_NATIVE_WORKSPACE_APPS_JSON";
|
|
47
52
|
const WORKSPACE_APPS_MANIFEST_FILE = "workspace-apps.json";
|
|
48
53
|
const WORKSPACE_APPS_GATEWAY_PATH = "/_workspace/apps";
|
|
49
54
|
const WORKSPACE_APPS_GATEWAY_TIMEOUT_MS = 2_500;
|
|
55
|
+
const WORKSPACE_APP_ACCESS_CONCURRENCY = 8;
|
|
50
56
|
const MAX_PENDING_APPS = 50;
|
|
51
57
|
const PENDING_WORKSPACE_APP_TTL_MS = 7 * 24 * 60 * 60 * 1_000;
|
|
52
58
|
const AGENT_CARD_PATH = "/.well-known/agent-card.json";
|
|
@@ -79,6 +85,7 @@ class AppCreationSettingsAuthorizationError extends Error {
|
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
type WorkspaceAppAudience = "internal" | "public";
|
|
88
|
+
type WorkspaceAppVisibility = "private" | "org";
|
|
82
89
|
|
|
83
90
|
export interface WorkspaceAppSummary {
|
|
84
91
|
id: string;
|
|
@@ -96,6 +103,7 @@ export interface WorkspaceAppSummary {
|
|
|
96
103
|
branchName?: string | null;
|
|
97
104
|
createdAt?: string | null;
|
|
98
105
|
createdBy?: string | null;
|
|
106
|
+
visibility?: WorkspaceAppVisibility;
|
|
99
107
|
owner?: string | null;
|
|
100
108
|
teams?: string[];
|
|
101
109
|
agentCardUrl?: string | null;
|
|
@@ -174,6 +182,8 @@ interface WorkspaceAppMetadataOverride {
|
|
|
174
182
|
sourcePrompt?: string;
|
|
175
183
|
updatedAt?: string;
|
|
176
184
|
updatedBy?: string;
|
|
185
|
+
createdBy?: string;
|
|
186
|
+
visibility?: WorkspaceAppVisibility;
|
|
177
187
|
}
|
|
178
188
|
|
|
179
189
|
interface WorkspaceAppMetadataSettings {
|
|
@@ -337,6 +347,7 @@ function parseWorkspaceAppMetadataSettings(
|
|
|
337
347
|
const sourcePrompt = cleanOptionalText(item.sourcePrompt);
|
|
338
348
|
const updatedAt = cleanOptionalText(item.updatedAt);
|
|
339
349
|
const updatedBy = cleanOptionalText(item.updatedBy);
|
|
350
|
+
const createdBy = cleanOptionalText(item.createdBy);
|
|
340
351
|
|
|
341
352
|
if (name) override.name = name;
|
|
342
353
|
if (description) override.description = description;
|
|
@@ -344,6 +355,10 @@ function parseWorkspaceAppMetadataSettings(
|
|
|
344
355
|
if (sourcePrompt) override.sourcePrompt = sourcePrompt;
|
|
345
356
|
if (updatedAt) override.updatedAt = updatedAt;
|
|
346
357
|
if (updatedBy) override.updatedBy = updatedBy;
|
|
358
|
+
if (createdBy) override.createdBy = createdBy;
|
|
359
|
+
if (item.visibility === "private" || item.visibility === "org") {
|
|
360
|
+
override.visibility = item.visibility;
|
|
361
|
+
}
|
|
347
362
|
|
|
348
363
|
if (Object.keys(override).length > 0) apps[id.trim()] = override;
|
|
349
364
|
}
|
|
@@ -365,6 +380,8 @@ async function writeWorkspaceAppMetadataOverride(input: {
|
|
|
365
380
|
generated?: boolean;
|
|
366
381
|
sourcePrompt?: string | null;
|
|
367
382
|
updatedBy?: string | null;
|
|
383
|
+
createdBy?: string | null;
|
|
384
|
+
visibility?: WorkspaceAppVisibility | null;
|
|
368
385
|
}): Promise<WorkspaceAppMetadataSettings> {
|
|
369
386
|
const key = workspaceAppMetadataSettingsKey();
|
|
370
387
|
const current = parseWorkspaceAppMetadataSettings(
|
|
@@ -380,6 +397,7 @@ async function writeWorkspaceAppMetadataOverride(input: {
|
|
|
380
397
|
const description = cleanOptionalText(input.description);
|
|
381
398
|
const sourcePrompt = cleanOptionalText(input.sourcePrompt);
|
|
382
399
|
const updatedBy = cleanOptionalText(input.updatedBy);
|
|
400
|
+
const createdBy = cleanOptionalText(input.createdBy);
|
|
383
401
|
|
|
384
402
|
if (name) next.name = name;
|
|
385
403
|
else delete next.name;
|
|
@@ -389,6 +407,8 @@ async function writeWorkspaceAppMetadataOverride(input: {
|
|
|
389
407
|
else if (input.generated === false) delete next.generated;
|
|
390
408
|
if (sourcePrompt) next.sourcePrompt = sourcePrompt;
|
|
391
409
|
if (updatedBy) next.updatedBy = updatedBy;
|
|
410
|
+
if (createdBy) next.createdBy = createdBy;
|
|
411
|
+
if (input.visibility) next.visibility = input.visibility;
|
|
392
412
|
|
|
393
413
|
current.apps[appId] = next;
|
|
394
414
|
await putSetting(key, { apps: current.apps });
|
|
@@ -408,17 +428,23 @@ function applyWorkspaceAppMetadataOverride(
|
|
|
408
428
|
const shouldApplyName = !!name && !generated;
|
|
409
429
|
const shouldApplyDescription =
|
|
410
430
|
!!description && (!generated || !cleanOptionalText(app.description));
|
|
411
|
-
|
|
431
|
+
const visibility = override.visibility;
|
|
432
|
+
if (!shouldApplyName && !shouldApplyDescription && !visibility) return app;
|
|
412
433
|
|
|
413
434
|
return {
|
|
414
435
|
...app,
|
|
415
436
|
...(shouldApplyName ? { name } : {}),
|
|
416
437
|
...(shouldApplyDescription ? { description } : {}),
|
|
417
|
-
...(
|
|
418
|
-
|
|
438
|
+
...(visibility ? { visibility } : {}),
|
|
439
|
+
...(app.status === "pending" &&
|
|
440
|
+
!app.createdBy &&
|
|
441
|
+
(override.createdBy || override.updatedBy)
|
|
442
|
+
? { createdBy: override.createdBy ?? override.updatedBy }
|
|
419
443
|
: {}),
|
|
420
|
-
...(app.status === "pending" &&
|
|
421
|
-
|
|
444
|
+
...(app.status === "pending" &&
|
|
445
|
+
!app.owner &&
|
|
446
|
+
(override.createdBy || override.updatedBy)
|
|
447
|
+
? { owner: override.createdBy ?? override.updatedBy }
|
|
422
448
|
: {}),
|
|
423
449
|
};
|
|
424
450
|
}
|
|
@@ -1002,6 +1028,198 @@ async function maybeIncludeAgentCards(
|
|
|
1002
1028
|
);
|
|
1003
1029
|
}
|
|
1004
1030
|
|
|
1031
|
+
async function workspaceAppDefaultVisibility(): Promise<WorkspaceAppVisibility> {
|
|
1032
|
+
const orgId = currentOrgId();
|
|
1033
|
+
if (!orgId) return "org";
|
|
1034
|
+
const setting = await getOrgSetting(
|
|
1035
|
+
orgId,
|
|
1036
|
+
WORKSPACE_APP_DEFAULT_VISIBILITY_KEY,
|
|
1037
|
+
);
|
|
1038
|
+
if (setting === null) return "org";
|
|
1039
|
+
if (setting.visibility === "private" || setting.visibility === "org") {
|
|
1040
|
+
return setting.visibility;
|
|
1041
|
+
}
|
|
1042
|
+
throw new Error(
|
|
1043
|
+
"Workspace app default visibility is invalid; refusing to widen access.",
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function appRecordTimestamp(value: string | null | undefined): number {
|
|
1048
|
+
const parsed = value ? Date.parse(value) : NaN;
|
|
1049
|
+
return Number.isFinite(parsed) ? parsed : Date.now();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Persist creator and visibility separately from the deploy manifest. The
|
|
1054
|
+
* manifest describes code; this SQL record is the access-control source of
|
|
1055
|
+
* truth shared by Dispatch and the mounted app's auth guard.
|
|
1056
|
+
*/
|
|
1057
|
+
async function ensureWorkspaceAppRecords(
|
|
1058
|
+
apps: WorkspaceAppSummary[],
|
|
1059
|
+
): Promise<WorkspaceAppSummary[]> {
|
|
1060
|
+
const readyApps = apps.filter(
|
|
1061
|
+
(app) => app.status !== "pending" && !app.isDispatch,
|
|
1062
|
+
);
|
|
1063
|
+
if (readyApps.length === 0) return apps;
|
|
1064
|
+
|
|
1065
|
+
const orgId = currentOrgId();
|
|
1066
|
+
const metadata = await readWorkspaceAppMetadataSettings();
|
|
1067
|
+
const defaultVisibility = await workspaceAppDefaultVisibility();
|
|
1068
|
+
const db = getDbExec();
|
|
1069
|
+
const records = new Map<
|
|
1070
|
+
string,
|
|
1071
|
+
{
|
|
1072
|
+
ownerEmail: string;
|
|
1073
|
+
orgId: string | null;
|
|
1074
|
+
visibility: WorkspaceAppVisibility;
|
|
1075
|
+
}
|
|
1076
|
+
>();
|
|
1077
|
+
|
|
1078
|
+
try {
|
|
1079
|
+
const existingRecords = new Map<
|
|
1080
|
+
string,
|
|
1081
|
+
{ owner_email?: unknown; org_id?: unknown; visibility?: unknown }
|
|
1082
|
+
>();
|
|
1083
|
+
for (let start = 0; start < readyApps.length; start += 500) {
|
|
1084
|
+
const ids = readyApps.slice(start, start + 500).map((app) => app.id);
|
|
1085
|
+
const result = await db.execute({
|
|
1086
|
+
sql: `SELECT id, owner_email, org_id, visibility
|
|
1087
|
+
FROM workspace_apps
|
|
1088
|
+
WHERE id IN (${ids.map(() => "?").join(", ")})`,
|
|
1089
|
+
args: ids,
|
|
1090
|
+
});
|
|
1091
|
+
for (const row of result.rows) {
|
|
1092
|
+
const id = cleanOptionalText((row as Record<string, unknown>).id);
|
|
1093
|
+
if (id) {
|
|
1094
|
+
existingRecords.set(id, row as Record<string, unknown>);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
for (const app of readyApps) {
|
|
1100
|
+
const existing = existingRecords.get(app.id);
|
|
1101
|
+
if (!existing) {
|
|
1102
|
+
const override = metadata.apps[app.id];
|
|
1103
|
+
// Never infer ownership from the person who happened to list apps.
|
|
1104
|
+
// Legacy manifests without trusted creation metadata remain
|
|
1105
|
+
// ownerless until an admin-controlled migration/claim flow exists.
|
|
1106
|
+
const ownerEmail =
|
|
1107
|
+
cleanOptionalText(override?.createdBy) ??
|
|
1108
|
+
cleanOptionalText(app.createdBy) ??
|
|
1109
|
+
"";
|
|
1110
|
+
const hasTrustedCreationMetadata = Boolean(ownerEmail);
|
|
1111
|
+
const visibility: WorkspaceAppVisibility =
|
|
1112
|
+
override?.visibility === "private"
|
|
1113
|
+
? "private"
|
|
1114
|
+
: override?.visibility === "org"
|
|
1115
|
+
? "org"
|
|
1116
|
+
: hasTrustedCreationMetadata
|
|
1117
|
+
? defaultVisibility
|
|
1118
|
+
: "org";
|
|
1119
|
+
const createdAt = appRecordTimestamp(app.createdAt);
|
|
1120
|
+
await db.execute({
|
|
1121
|
+
sql: `INSERT INTO workspace_apps
|
|
1122
|
+
(id, owner_email, org_id, visibility, name, description, path, created_at, updated_at)
|
|
1123
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1124
|
+
args: [
|
|
1125
|
+
app.id,
|
|
1126
|
+
ownerEmail,
|
|
1127
|
+
orgId,
|
|
1128
|
+
visibility,
|
|
1129
|
+
app.name,
|
|
1130
|
+
app.description || null,
|
|
1131
|
+
app.path,
|
|
1132
|
+
createdAt,
|
|
1133
|
+
Date.now(),
|
|
1134
|
+
],
|
|
1135
|
+
});
|
|
1136
|
+
records.set(app.id, { ownerEmail, orgId, visibility });
|
|
1137
|
+
} else {
|
|
1138
|
+
records.set(app.id, {
|
|
1139
|
+
ownerEmail:
|
|
1140
|
+
typeof existing.owner_email === "string"
|
|
1141
|
+
? existing.owner_email
|
|
1142
|
+
: "",
|
|
1143
|
+
orgId:
|
|
1144
|
+
(typeof existing.org_id === "string"
|
|
1145
|
+
? existing.org_id
|
|
1146
|
+
: ""
|
|
1147
|
+
).trim() || null,
|
|
1148
|
+
visibility: existing.visibility === "private" ? "private" : "org",
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
console.warn("[dispatch] workspace app access records unavailable", error);
|
|
1154
|
+
return apps;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
return apps.map((app) => {
|
|
1158
|
+
const record = records.get(app.id);
|
|
1159
|
+
return record ? { ...app, visibility: record.visibility } : app;
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function filterWorkspaceAppsByAccess(
|
|
1164
|
+
apps: WorkspaceAppSummary[],
|
|
1165
|
+
): Promise<WorkspaceAppSummary[]> {
|
|
1166
|
+
let userEmail: string;
|
|
1167
|
+
try {
|
|
1168
|
+
userEmail = currentOwnerEmail();
|
|
1169
|
+
} catch {
|
|
1170
|
+
// coercion-ok: anonymous requests fail closed by receiving no app metadata.
|
|
1171
|
+
// App metadata is access-controlled. An anonymous request must not receive
|
|
1172
|
+
// the full registry simply because there is no caller to resolve.
|
|
1173
|
+
return [];
|
|
1174
|
+
}
|
|
1175
|
+
const orgId = currentOrgId() ?? undefined;
|
|
1176
|
+
const visibleIds = new Set<string>();
|
|
1177
|
+
const candidates: WorkspaceAppSummary[] = [];
|
|
1178
|
+
for (const app of apps) {
|
|
1179
|
+
if (app.status === "pending" || app.isDispatch) {
|
|
1180
|
+
visibleIds.add(app.id);
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
candidates.push(app);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
for (
|
|
1187
|
+
let start = 0;
|
|
1188
|
+
start < candidates.length;
|
|
1189
|
+
start += WORKSPACE_APP_ACCESS_CONCURRENCY
|
|
1190
|
+
) {
|
|
1191
|
+
const batch = candidates.slice(
|
|
1192
|
+
start,
|
|
1193
|
+
start + WORKSPACE_APP_ACCESS_CONCURRENCY,
|
|
1194
|
+
);
|
|
1195
|
+
const decisions = await Promise.all(
|
|
1196
|
+
batch.map(async (app) => {
|
|
1197
|
+
try {
|
|
1198
|
+
const access = await resolveAccess(
|
|
1199
|
+
"workspace-app",
|
|
1200
|
+
app.id,
|
|
1201
|
+
{ userEmail, orgId },
|
|
1202
|
+
{ skipResourceBody: true },
|
|
1203
|
+
);
|
|
1204
|
+
return { app, allowed: Boolean(access) };
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
// Rolling deployments may discover an app before the additive access
|
|
1207
|
+
// migrations have run. A missing schema is not an authorization
|
|
1208
|
+
// result: returning the candidate would expose private metadata
|
|
1209
|
+
// during a migration failure.
|
|
1210
|
+
console.warn("[dispatch] workspace app access lookup failed", error);
|
|
1211
|
+
return { app, allowed: false };
|
|
1212
|
+
}
|
|
1213
|
+
}),
|
|
1214
|
+
);
|
|
1215
|
+
for (const decision of decisions) {
|
|
1216
|
+
if (decision.allowed) visibleIds.add(decision.app.id);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
return apps.filter((app) => visibleIds.has(app.id));
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1005
1223
|
async function recordPendingWorkspaceApp(input: {
|
|
1006
1224
|
appId: string;
|
|
1007
1225
|
projectId: string | null;
|
|
@@ -1054,6 +1272,7 @@ async function recordPendingWorkspaceApp(input: {
|
|
|
1054
1272
|
generated: true,
|
|
1055
1273
|
sourcePrompt: input.sourcePrompt,
|
|
1056
1274
|
updatedBy: currentOwnerEmail(),
|
|
1275
|
+
createdBy: currentOwnerEmail(),
|
|
1057
1276
|
});
|
|
1058
1277
|
|
|
1059
1278
|
await recordAudit({
|
|
@@ -1413,12 +1632,15 @@ export async function updateWorkspaceAppMetadata(input: {
|
|
|
1413
1632
|
export async function listWorkspaceApps(
|
|
1414
1633
|
options: ListWorkspaceAppsOptions = {},
|
|
1415
1634
|
): Promise<WorkspaceAppSummary[]> {
|
|
1635
|
+
const finalize = async (apps: WorkspaceAppSummary[]) => {
|
|
1636
|
+
const annotated = await applyArchivedAndPending(apps, options);
|
|
1637
|
+
const recorded = await ensureWorkspaceAppRecords(annotated);
|
|
1638
|
+
const visible = await filterWorkspaceAppsByAccess(recorded);
|
|
1639
|
+
return maybeIncludeAgentCards(visible, options);
|
|
1640
|
+
};
|
|
1416
1641
|
const gatewayApps = await readWorkspaceAppsFromGateway();
|
|
1417
1642
|
if (gatewayApps) {
|
|
1418
|
-
return
|
|
1419
|
-
await applyArchivedAndPending(gatewayApps, options),
|
|
1420
|
-
options,
|
|
1421
|
-
);
|
|
1643
|
+
return finalize(gatewayApps);
|
|
1422
1644
|
}
|
|
1423
1645
|
|
|
1424
1646
|
const workspaceRoot = findWorkspaceRoot();
|
|
@@ -1427,49 +1649,34 @@ export async function listWorkspaceApps(
|
|
|
1427
1649
|
? readWorkspaceAppsFromFilesystem(workspaceRoot)
|
|
1428
1650
|
: null;
|
|
1429
1651
|
if (localFilesystemApps) {
|
|
1430
|
-
return
|
|
1431
|
-
await applyArchivedAndPending(localFilesystemApps, options),
|
|
1432
|
-
options,
|
|
1433
|
-
);
|
|
1652
|
+
return finalize(localFilesystemApps);
|
|
1434
1653
|
}
|
|
1435
1654
|
|
|
1436
1655
|
const manifestApps =
|
|
1437
1656
|
readWorkspaceAppsFromEnv() ?? readWorkspaceAppsFromManifestFile();
|
|
1438
1657
|
if (manifestApps) {
|
|
1439
|
-
return
|
|
1440
|
-
await applyArchivedAndPending(manifestApps, options),
|
|
1441
|
-
options,
|
|
1442
|
-
);
|
|
1658
|
+
return finalize(manifestApps);
|
|
1443
1659
|
}
|
|
1444
1660
|
|
|
1445
1661
|
if (!workspaceRoot) {
|
|
1446
|
-
return
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
},
|
|
1461
|
-
],
|
|
1462
|
-
options,
|
|
1463
|
-
),
|
|
1464
|
-
options,
|
|
1465
|
-
);
|
|
1662
|
+
return finalize([
|
|
1663
|
+
{
|
|
1664
|
+
id: "dispatch",
|
|
1665
|
+
name: "Dispatch",
|
|
1666
|
+
description: "Workspace control plane",
|
|
1667
|
+
path: "/dispatch",
|
|
1668
|
+
url: workspaceAppUrl("/dispatch"),
|
|
1669
|
+
isDispatch: true,
|
|
1670
|
+
audience: DEFAULT_WORKSPACE_APP_AUDIENCE,
|
|
1671
|
+
publicPaths: [],
|
|
1672
|
+
protectedPaths: [],
|
|
1673
|
+
status: "ready",
|
|
1674
|
+
},
|
|
1675
|
+
]);
|
|
1466
1676
|
}
|
|
1467
1677
|
|
|
1468
1678
|
const apps = readWorkspaceAppsFromFilesystem(workspaceRoot) ?? [];
|
|
1469
|
-
return
|
|
1470
|
-
await applyArchivedAndPending(apps, options),
|
|
1471
|
-
options,
|
|
1472
|
-
);
|
|
1679
|
+
return finalize(apps);
|
|
1473
1680
|
}
|
|
1474
1681
|
|
|
1475
1682
|
/**
|
|
@@ -1644,6 +1851,22 @@ export async function scaffoldWorkspaceAppFromTemplate(input: {
|
|
|
1644
1851
|
metadata: { template },
|
|
1645
1852
|
});
|
|
1646
1853
|
|
|
1854
|
+
await ensureWorkspaceAppRecords([
|
|
1855
|
+
{
|
|
1856
|
+
id: appId,
|
|
1857
|
+
name: titleCase(appId),
|
|
1858
|
+
description: "",
|
|
1859
|
+
path: `/${appId}`,
|
|
1860
|
+
url: workspaceAppUrl(`/${appId}`),
|
|
1861
|
+
isDispatch: false,
|
|
1862
|
+
audience: DEFAULT_WORKSPACE_APP_AUDIENCE,
|
|
1863
|
+
publicPaths: [],
|
|
1864
|
+
protectedPaths: [],
|
|
1865
|
+
status: "ready",
|
|
1866
|
+
createdBy: currentOwnerEmail(),
|
|
1867
|
+
},
|
|
1868
|
+
]);
|
|
1869
|
+
|
|
1647
1870
|
return { appId, template, output };
|
|
1648
1871
|
}
|
|
1649
1872
|
|