@agent-native/dispatch 0.40.30-nightly-20261021093329 → 0.40.30
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/CHANGELOG.md +6 -47
- package/dist/actions/connect-external-agent.d.ts.map +1 -1
- package/dist/actions/connect-external-agent.js +12 -13
- package/dist/actions/connect-external-agent.js.map +1 -1
- package/dist/actions/create-workspace-resource.d.ts +13 -13
- package/dist/actions/delete-destination.d.ts +12 -12
- package/dist/actions/delete-workspace-resource.d.ts +13 -13
- package/dist/actions/import-agent.d.ts +13 -13
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +8 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/inspect-connect-agent-card.d.ts +5 -0
- package/dist/actions/inspect-connect-agent-card.d.ts.map +1 -0
- package/dist/actions/inspect-connect-agent-card.js +15 -0
- package/dist/actions/inspect-connect-agent-card.js.map +1 -0
- package/dist/actions/install-curated-community-app.d.ts +36 -0
- package/dist/actions/install-curated-community-app.d.ts.map +1 -0
- package/dist/actions/install-curated-community-app.js +26 -0
- package/dist/actions/install-curated-community-app.js.map +1 -0
- package/dist/actions/list-connect-marketplace-apps.d.ts +3 -0
- package/dist/actions/list-connect-marketplace-apps.d.ts.map +1 -0
- package/dist/actions/list-connect-marketplace-apps.js +15 -0
- package/dist/actions/list-connect-marketplace-apps.js.map +1 -0
- package/dist/actions/list-connected-agents.d.ts +2 -0
- package/dist/actions/list-curated-community-apps.d.ts +3 -0
- package/dist/actions/list-curated-community-apps.d.ts.map +1 -0
- package/dist/actions/list-curated-community-apps.js +10 -0
- package/dist/actions/list-curated-community-apps.js.map +1 -0
- package/dist/actions/provider-api-register.d.ts +14 -14
- package/dist/actions/remix-workspace-template.d.ts +6 -0
- package/dist/actions/update-workspace-app-metadata.d.ts +1 -0
- package/dist/actions/update-workspace-app-metadata.js +26 -5
- package/dist/actions/update-workspace-app-metadata.js.map +1 -1
- package/dist/components/community-apps-section.d.ts +2 -0
- package/dist/components/community-apps-section.d.ts.map +1 -0
- package/dist/components/community-apps-section.js +90 -0
- package/dist/components/community-apps-section.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +63 -9
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/lib/connect-apps.d.ts +1 -2
- package/dist/lib/connect-apps.d.ts.map +1 -1
- package/dist/lib/connect-apps.js +41 -65
- package/dist/lib/connect-apps.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/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +2 -1
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/connect.d.ts.map +1 -1
- package/dist/routes/pages/connect.js +5 -7
- package/dist/routes/pages/connect.js.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts +11 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +258 -39
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/community-apps.d.ts +45 -0
- package/dist/server/lib/community-apps.d.ts.map +1 -0
- package/dist/server/lib/community-apps.js +267 -0
- package/dist/server/lib/community-apps.js.map +1 -0
- package/dist/server/lib/community-source.d.ts +19 -0
- package/dist/server/lib/community-source.d.ts.map +1 -0
- package/dist/server/lib/community-source.js +118 -0
- package/dist/server/lib/community-source.js.map +1 -0
- package/dist/server/lib/connect-apps.d.ts +4 -0
- package/dist/server/lib/connect-apps.d.ts.map +1 -0
- package/dist/server/lib/connect-apps.js +99 -0
- package/dist/server/lib/connect-apps.js.map +1 -0
- package/dist/server/lib/org-apps-directory-cache.d.ts +20 -0
- package/dist/server/lib/org-apps-directory-cache.d.ts.map +1 -0
- package/dist/server/lib/org-apps-directory-cache.js +44 -0
- package/dist/server/lib/org-apps-directory-cache.js.map +1 -0
- package/dist/server/plugins/integrations.js +3 -3
- package/dist/server/plugins/integrations.js.map +1 -1
- package/dist/shared/labs.d.ts +2 -0
- package/dist/shared/labs.d.ts.map +1 -0
- package/dist/shared/labs.js +8 -0
- package/dist/shared/labs.js.map +1 -0
- package/package.json +5 -4
- package/src/actions/connect-external-agent.spec.ts +101 -3
- package/src/actions/connect-external-agent.ts +12 -11
- package/src/actions/index.spec.ts +13 -0
- package/src/actions/index.ts +8 -0
- package/src/actions/inspect-connect-agent-card.ts +17 -0
- package/src/actions/install-curated-community-app.ts +32 -0
- package/src/actions/list-connect-marketplace-apps.ts +17 -0
- package/src/actions/list-curated-community-apps.ts +12 -0
- package/src/actions/update-workspace-app-metadata.spec.ts +25 -0
- package/src/actions/update-workspace-app-metadata.ts +48 -24
- package/src/components/community-apps-section.tsx +224 -0
- package/src/components/workspace-app-card.spec.tsx +226 -1
- package/src/components/workspace-app-card.tsx +115 -12
- package/src/lib/connect-apps.spec.ts +118 -66
- package/src/lib/connect-apps.ts +47 -82
- package/src/lib/workspace-apps.ts +1 -0
- package/src/routes/pages/apps.spec.tsx +201 -1
- package/src/routes/pages/apps.tsx +3 -0
- package/src/routes/pages/connect.spec.tsx +53 -6
- package/src/routes/pages/connect.tsx +14 -11
- package/src/server/index.ts +9 -0
- package/src/server/lib/app-creation-store.spec.ts +539 -11
- package/src/server/lib/app-creation-store.ts +332 -41
- package/src/server/lib/community-apps.spec.ts +367 -0
- package/src/server/lib/community-apps.ts +372 -0
- package/src/server/lib/community-source.spec.ts +151 -0
- package/src/server/lib/community-source.ts +186 -0
- package/src/server/lib/connect-apps.ts +149 -0
- package/src/server/lib/org-apps-directory-cache.spec.ts +71 -0
- package/src/server/lib/org-apps-directory-cache.ts +66 -0
- package/src/server/plugins/integrations.spec.ts +60 -0
- package/src/server/plugins/integrations.ts +3 -3
- package/src/shared/labs.ts +9 -0
|
@@ -6,24 +6,27 @@ import { canonicalA2AAudience, signA2AToken } from "@agent-native/core/a2a";
|
|
|
6
6
|
import { isActionContractError } from "@agent-native/core/action";
|
|
7
7
|
import { describeErrorWithCauses } from "@agent-native/core/agent/engine/error-detail";
|
|
8
8
|
import { getDbExec } from "@agent-native/core/db";
|
|
9
|
-
import {
|
|
9
|
+
import { and, eq, inArray } from "@agent-native/core/db/schema";
|
|
10
|
+
import { getOrgDomain, isWorkspaceAppAccessAllowed, workspaceApps, } from "@agent-native/core/org";
|
|
10
11
|
import { CredentialStoreUnavailableError, FeatureNotConfiguredError, createBuilderProject, getBuilderBranchProjectId, getConfiguredAppBasePath, getRequestContext, inferWorkspaceAppRootHomePath, isIntegrationCallerRequest, readConfiguredWorkspaceAppHomePath, resolveAppRuntimeUrl, resolveVercelDeploymentProtectionHeaders, normalizeBuilderAgentAttachments, runBuilderAgent, } from "@agent-native/core/server";
|
|
11
12
|
import { getOrgSetting } from "@agent-native/core/settings";
|
|
12
13
|
import { getSetting, mutateSetting, putSetting, } from "@agent-native/core/settings";
|
|
13
|
-
import { BUILDER_CONNECT_PROVIDER, BUILDER_CONNECT_PROVIDER_LABEL, assertValidWorkspaceAppId, connectRequiredResult, normalizeWorkspaceAppHomePath, } from "@agent-native/core/shared";
|
|
14
|
+
import { BUILDER_CONNECT_PROVIDER, BUILDER_CONNECT_PROVIDER_LABEL, assertValidWorkspaceAppId, connectRequiredResult, normalizeWorkspaceAppHomePath, parseAgentProfileMetadata, } from "@agent-native/core/shared";
|
|
14
15
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
15
|
-
import "../../db/index.js";
|
|
16
|
+
import { getDb } from "../../db/index.js";
|
|
16
17
|
import { isWorkspaceSsoAppUrl } from "../../shared/workspace-sso.js";
|
|
17
18
|
import { getDispatchDefaultOwnerEmail } from "./admin-config.js";
|
|
18
19
|
import { identityKeyForIncoming } from "./dispatch-integrations.js";
|
|
19
20
|
import { currentOrgId, currentOwnerEmail, recordAudit, resolveLinkedOwner, } from "./dispatch-store.js";
|
|
20
21
|
import { projectEnvironmentUrl, requestEnvironmentLane, } from "./environment-lane.js";
|
|
22
|
+
import { invalidateOrgAppsDirectoryCache } from "./org-apps-directory-cache.js";
|
|
21
23
|
import { createRequest, listSecretOptions } from "./vault-store.js";
|
|
22
24
|
import { WORKSPACE_APPS_ACTION_PATH } from "./workspace-app-action-auth.js";
|
|
23
25
|
import { grantWorkspaceResourcesToApp, listWorkspaceResourceOptions, } from "./workspace-resources-store.js";
|
|
24
26
|
const SETTINGS_KEY = "dispatch-app-creation-settings";
|
|
25
27
|
const DEFAULT_BUILDER_WORKSPACE_PROJECT_NAME = "Agent-Native Workspace";
|
|
26
28
|
const APP_CREATION_SETTINGS_AUTHORIZATION_MESSAGE = "Only organization owners and admins can update app creation settings.";
|
|
29
|
+
const WORKSPACE_APP_METADATA_AUTHORIZATION_MESSAGE = "Only organization owners and admins can update workspace app details.";
|
|
27
30
|
const APP_CREATION_SETTINGS_REQUIRED_MESSAGE = "An organization owner or admin must configure the Builder workspace project before members can create apps.";
|
|
28
31
|
const WORKSPACE_APP_METADATA_SETTINGS_KEY = "workspace-app-metadata";
|
|
29
32
|
const WORKSPACE_APP_DEFAULT_VISIBILITY_KEY = "workspace-app-default-visibility";
|
|
@@ -57,6 +60,22 @@ class AppCreationSettingsAuthorizationError extends Error {
|
|
|
57
60
|
this.name = "AppCreationSettingsAuthorizationError";
|
|
58
61
|
}
|
|
59
62
|
}
|
|
63
|
+
class WorkspaceAppMetadataAuthorizationError extends Error {
|
|
64
|
+
statusCode = 403;
|
|
65
|
+
constructor() {
|
|
66
|
+
super(WORKSPACE_APP_METADATA_AUTHORIZATION_MESSAGE);
|
|
67
|
+
this.name = "WorkspaceAppMetadataAuthorizationError";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
class AppCreationOwnerRoleUnavailableError extends Error {
|
|
71
|
+
statusCode = 503;
|
|
72
|
+
constructor(cause) {
|
|
73
|
+
super("Could not verify the organization role. Try again shortly.", {
|
|
74
|
+
cause,
|
|
75
|
+
});
|
|
76
|
+
this.name = "AppCreationOwnerRoleUnavailableError";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
60
79
|
class WorkspaceAppOrgRequiredError extends Error {
|
|
61
80
|
statusCode = 400;
|
|
62
81
|
constructor() {
|
|
@@ -189,7 +208,7 @@ function pendingWorkspaceAppContext() {
|
|
|
189
208
|
}
|
|
190
209
|
}
|
|
191
210
|
async function readSettingsRecord() {
|
|
192
|
-
const raw = await getSetting(scopedSettingsKey())
|
|
211
|
+
const raw = await getSetting(scopedSettingsKey());
|
|
193
212
|
return raw && typeof raw === "object" && !Array.isArray(raw)
|
|
194
213
|
? raw
|
|
195
214
|
: {};
|
|
@@ -197,6 +216,14 @@ async function readSettingsRecord() {
|
|
|
197
216
|
function cleanOptionalText(value) {
|
|
198
217
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
199
218
|
}
|
|
219
|
+
function normalizeCommunitySourceId(value) {
|
|
220
|
+
if (typeof value !== "string" ||
|
|
221
|
+
value.length > 100 ||
|
|
222
|
+
!/^community-[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(value)) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return value.toLowerCase();
|
|
226
|
+
}
|
|
200
227
|
function parseWorkspaceAppMetadataSettings(raw) {
|
|
201
228
|
const record = raw && typeof raw === "object" && !Array.isArray(raw)
|
|
202
229
|
? raw
|
|
@@ -214,14 +241,23 @@ function parseWorkspaceAppMetadataSettings(raw) {
|
|
|
214
241
|
const override = {};
|
|
215
242
|
const name = cleanOptionalText(item.name);
|
|
216
243
|
const description = cleanOptionalText(item.description);
|
|
244
|
+
const profile = parseAgentProfileMetadata({
|
|
245
|
+
whenToUse: item.whenToUse,
|
|
246
|
+
});
|
|
217
247
|
const sourcePrompt = cleanOptionalText(item.sourcePrompt);
|
|
218
248
|
const updatedAt = cleanOptionalText(item.updatedAt);
|
|
219
249
|
const updatedBy = cleanOptionalText(item.updatedBy);
|
|
220
250
|
const createdBy = cleanOptionalText(item.createdBy);
|
|
251
|
+
const communitySourceId = normalizeCommunitySourceId(item.communitySourceId);
|
|
221
252
|
if (name)
|
|
222
253
|
override.name = name;
|
|
223
254
|
if (description)
|
|
224
255
|
override.description = description;
|
|
256
|
+
if (profile?.whenToUse)
|
|
257
|
+
override.whenToUse = profile.whenToUse;
|
|
258
|
+
else if (Array.isArray(item.whenToUse) && item.whenToUse.length === 0) {
|
|
259
|
+
override.whenToUse = [];
|
|
260
|
+
}
|
|
225
261
|
if (item.generated === true)
|
|
226
262
|
override.generated = true;
|
|
227
263
|
if (sourcePrompt)
|
|
@@ -232,6 +268,8 @@ function parseWorkspaceAppMetadataSettings(raw) {
|
|
|
232
268
|
override.updatedBy = updatedBy;
|
|
233
269
|
if (createdBy)
|
|
234
270
|
override.createdBy = createdBy;
|
|
271
|
+
if (communitySourceId)
|
|
272
|
+
override.communitySourceId = communitySourceId;
|
|
235
273
|
if (item.visibility === "private" || item.visibility === "org") {
|
|
236
274
|
override.visibility = item.visibility;
|
|
237
275
|
}
|
|
@@ -241,31 +279,54 @@ function parseWorkspaceAppMetadataSettings(raw) {
|
|
|
241
279
|
return { apps };
|
|
242
280
|
}
|
|
243
281
|
async function readWorkspaceAppMetadataSettings() {
|
|
244
|
-
const raw = await getSetting(workspaceAppMetadataSettingsKey())
|
|
282
|
+
const raw = await getSetting(workspaceAppMetadataSettingsKey());
|
|
245
283
|
return parseWorkspaceAppMetadataSettings(raw);
|
|
246
284
|
}
|
|
247
285
|
async function writeWorkspaceAppMetadataOverride(input) {
|
|
248
286
|
const key = workspaceAppMetadataSettingsKey();
|
|
249
|
-
const current = parseWorkspaceAppMetadataSettings(await getSetting(key)
|
|
287
|
+
const current = parseWorkspaceAppMetadataSettings(await getSetting(key));
|
|
250
288
|
const appId = input.appId.trim();
|
|
251
289
|
const existing = current.apps[appId] ?? {};
|
|
252
290
|
const next = {
|
|
253
291
|
...existing,
|
|
254
292
|
updatedAt: new Date().toISOString(),
|
|
255
293
|
};
|
|
294
|
+
const hasName = Object.hasOwn(input, "name") && input.name !== undefined;
|
|
295
|
+
const hasDescription = Object.hasOwn(input, "description") && input.description !== undefined;
|
|
296
|
+
const hasWhenToUse = Object.hasOwn(input, "whenToUse") && input.whenToUse !== undefined;
|
|
256
297
|
const name = cleanOptionalText(input.name);
|
|
257
298
|
const description = cleanOptionalText(input.description);
|
|
299
|
+
const whenToUse = input.whenToUse;
|
|
258
300
|
const sourcePrompt = cleanOptionalText(input.sourcePrompt);
|
|
259
301
|
const updatedBy = cleanOptionalText(input.updatedBy);
|
|
260
302
|
const createdBy = cleanOptionalText(input.createdBy);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
303
|
+
const communitySourceId = normalizeCommunitySourceId(input.communitySourceId);
|
|
304
|
+
if (hasName) {
|
|
305
|
+
if (name)
|
|
306
|
+
next.name = name;
|
|
307
|
+
else
|
|
308
|
+
delete next.name;
|
|
309
|
+
}
|
|
310
|
+
if (hasDescription) {
|
|
311
|
+
if (description)
|
|
312
|
+
next.description = description;
|
|
313
|
+
else
|
|
314
|
+
delete next.description;
|
|
315
|
+
}
|
|
316
|
+
if (hasWhenToUse) {
|
|
317
|
+
const examples = whenToUse ?? [];
|
|
318
|
+
const profile = parseAgentProfileMetadata({
|
|
319
|
+
description: description ?? next.description,
|
|
320
|
+
whenToUse: examples,
|
|
321
|
+
});
|
|
322
|
+
if (!profile) {
|
|
323
|
+
throw new Error("Workspace app when-to-use entries must contain up to 6 non-empty examples of 140 characters or fewer.");
|
|
324
|
+
}
|
|
325
|
+
next.whenToUse = profile.whenToUse ?? [];
|
|
326
|
+
}
|
|
327
|
+
if (description && description.length > 1000) {
|
|
328
|
+
throw new Error("Workspace app descriptions must be 1,000 characters or fewer.");
|
|
329
|
+
}
|
|
269
330
|
if (input.generated === true)
|
|
270
331
|
next.generated = true;
|
|
271
332
|
else if (input.generated === false)
|
|
@@ -276,6 +337,14 @@ async function writeWorkspaceAppMetadataOverride(input) {
|
|
|
276
337
|
next.updatedBy = updatedBy;
|
|
277
338
|
if (createdBy)
|
|
278
339
|
next.createdBy = createdBy;
|
|
340
|
+
if (input.communitySourceId !== undefined) {
|
|
341
|
+
if (input.communitySourceId === null || !communitySourceId) {
|
|
342
|
+
delete next.communitySourceId;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
next.communitySourceId = communitySourceId;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
279
348
|
if (input.visibility)
|
|
280
349
|
next.visibility = input.visibility;
|
|
281
350
|
current.apps[appId] = next;
|
|
@@ -288,17 +357,26 @@ function applyWorkspaceAppMetadataOverride(app, settings) {
|
|
|
288
357
|
return app;
|
|
289
358
|
const name = cleanOptionalText(override.name);
|
|
290
359
|
const description = cleanOptionalText(override.description);
|
|
360
|
+
const whenToUse = override.whenToUse;
|
|
291
361
|
const generated = override.generated === true;
|
|
292
362
|
const shouldApplyName = !!name && !generated;
|
|
293
363
|
const shouldApplyDescription = !!description && (!generated || !cleanOptionalText(app.description));
|
|
294
364
|
const visibility = override.visibility;
|
|
295
|
-
|
|
365
|
+
const communitySourceId = override.communitySourceId;
|
|
366
|
+
if (!shouldApplyName &&
|
|
367
|
+
!shouldApplyDescription &&
|
|
368
|
+
whenToUse === undefined &&
|
|
369
|
+
!visibility &&
|
|
370
|
+
!communitySourceId) {
|
|
296
371
|
return app;
|
|
372
|
+
}
|
|
297
373
|
return {
|
|
298
374
|
...app,
|
|
299
375
|
...(shouldApplyName ? { name } : {}),
|
|
300
376
|
...(shouldApplyDescription ? { description } : {}),
|
|
377
|
+
...(whenToUse !== undefined ? { whenToUse } : {}),
|
|
301
378
|
...(visibility ? { visibility } : {}),
|
|
379
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
302
380
|
...(app.status === "pending" &&
|
|
303
381
|
!app.createdBy &&
|
|
304
382
|
(override.createdBy || override.updatedBy)
|
|
@@ -385,6 +463,11 @@ function normalizeWorkspaceAppTeams(value) {
|
|
|
385
463
|
}
|
|
386
464
|
function workspaceAppMetadataFromRecord(record) {
|
|
387
465
|
const config = record["agent-native"] ?? record.agentNative;
|
|
466
|
+
const profile = parseAgentProfileMetadata({
|
|
467
|
+
whenToUse: config && typeof config === "object" && !Array.isArray(config)
|
|
468
|
+
? config.whenToUse
|
|
469
|
+
: undefined,
|
|
470
|
+
});
|
|
388
471
|
const workspaceConfig = config && typeof config === "object" && !Array.isArray(config)
|
|
389
472
|
? (config.workspaceApp ?? config.workspace ?? config)
|
|
390
473
|
: {};
|
|
@@ -405,6 +488,7 @@ function workspaceAppMetadataFromRecord(record) {
|
|
|
405
488
|
createdBy: pick(record.createdBy, record.createdByEmail, record.created_by, metadata.createdBy, metadata.createdByEmail, workspaceConfig.createdBy, workspaceConfig.createdByEmail),
|
|
406
489
|
owner: pick(record.owner, record.ownerEmail, record.owner_email, metadata.owner, metadata.ownerEmail, workspaceConfig.owner, workspaceConfig.ownerEmail),
|
|
407
490
|
teams,
|
|
491
|
+
...(profile?.whenToUse ? { whenToUse: profile.whenToUse } : {}),
|
|
408
492
|
};
|
|
409
493
|
}
|
|
410
494
|
function workspaceAppAudienceFromPackageJson(pkg) {
|
|
@@ -462,6 +546,11 @@ function parseWorkspaceAppsManifest(parsed) {
|
|
|
462
546
|
const pathValue = typeof entry.path === "string" ? entry.path.trim() : "";
|
|
463
547
|
if (!id || !pathValue.startsWith("/"))
|
|
464
548
|
return null;
|
|
549
|
+
const profile = parseAgentProfileMetadata({
|
|
550
|
+
whenToUse: entry.whenToUse,
|
|
551
|
+
});
|
|
552
|
+
if (!profile)
|
|
553
|
+
return null;
|
|
465
554
|
const metadata = workspaceAppMetadataFromRecord(entry);
|
|
466
555
|
return {
|
|
467
556
|
id,
|
|
@@ -469,6 +558,7 @@ function parseWorkspaceAppsManifest(parsed) {
|
|
|
469
558
|
? entry.name.trim()
|
|
470
559
|
: titleCase(id),
|
|
471
560
|
description: typeof entry.description === "string" ? entry.description : "",
|
|
561
|
+
...(profile.whenToUse ? { whenToUse: profile.whenToUse } : {}),
|
|
472
562
|
path: pathValue,
|
|
473
563
|
homePath: normalizeWorkspaceAppHomePath(entry.homePath),
|
|
474
564
|
url: workspaceAppLink(pathValue, entry.url),
|
|
@@ -571,6 +661,7 @@ function parsePendingWorkspaceApps(value) {
|
|
|
571
661
|
return null;
|
|
572
662
|
const now = new Date().toISOString();
|
|
573
663
|
const createdAt = cleanOptionalString(record.createdAt) ?? now;
|
|
664
|
+
const communitySourceId = normalizeCommunitySourceId(record.communitySourceId);
|
|
574
665
|
return {
|
|
575
666
|
id,
|
|
576
667
|
name: typeof record.name === "string" && record.name.trim()
|
|
@@ -600,6 +691,7 @@ function parsePendingWorkspaceApps(value) {
|
|
|
600
691
|
...(record.visibility === "private" || record.visibility === "org"
|
|
601
692
|
? { visibility: record.visibility }
|
|
602
693
|
: {}),
|
|
694
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
603
695
|
createdAt,
|
|
604
696
|
updatedAt: typeof record.updatedAt === "string" && record.updatedAt.trim()
|
|
605
697
|
? record.updatedAt.trim()
|
|
@@ -680,6 +772,7 @@ async function reservePendingWorkspaceApp(input) {
|
|
|
680
772
|
const now = new Date().toISOString();
|
|
681
773
|
const context = pendingWorkspaceAppContext();
|
|
682
774
|
const creatorEmail = currentOwnerEmail();
|
|
775
|
+
const communitySourceId = normalizeCommunitySourceId(input.communitySourceId);
|
|
683
776
|
await mutateSetting(scopedSettingsKey(), async (current) => {
|
|
684
777
|
const raw = current && typeof current === "object" && !Array.isArray(current)
|
|
685
778
|
? current
|
|
@@ -709,6 +802,7 @@ async function reservePendingWorkspaceApp(input) {
|
|
|
709
802
|
contextId: context?.id ?? null,
|
|
710
803
|
contextLabel: context?.label ?? null,
|
|
711
804
|
visibility: input.visibility,
|
|
805
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
712
806
|
createdBy: creatorEmail,
|
|
713
807
|
owner: creatorEmail,
|
|
714
808
|
createdAt: now,
|
|
@@ -761,6 +855,7 @@ export async function archiveWorkspaceApp(input) {
|
|
|
761
855
|
if (!current.includes(appId))
|
|
762
856
|
current.push(appId);
|
|
763
857
|
await putSetting(scopedSettingsKey(), { ...raw, archivedAppIds: current });
|
|
858
|
+
invalidateOrgAppsDirectoryCache();
|
|
764
859
|
await recordAudit({
|
|
765
860
|
action: "workspace-app.archived",
|
|
766
861
|
targetType: "workspace-app",
|
|
@@ -776,6 +871,7 @@ export async function unarchiveWorkspaceApp(input) {
|
|
|
776
871
|
const raw = await readSettingsRecord();
|
|
777
872
|
const current = parseArchivedAppIds(raw.archivedAppIds).filter((id) => id !== appId);
|
|
778
873
|
await putSetting(scopedSettingsKey(), { ...raw, archivedAppIds: current });
|
|
874
|
+
invalidateOrgAppsDirectoryCache();
|
|
779
875
|
await recordAudit({
|
|
780
876
|
action: "workspace-app.unarchived",
|
|
781
877
|
targetType: "workspace-app",
|
|
@@ -818,6 +914,7 @@ function pendingAppToSummary(app) {
|
|
|
818
914
|
status: "pending",
|
|
819
915
|
statusLabel: "Pending Builder branch",
|
|
820
916
|
visibility: app.visibility,
|
|
917
|
+
communitySourceId: app.communitySourceId,
|
|
821
918
|
builderUrl: app.builderUrl,
|
|
822
919
|
branchName: app.branchName,
|
|
823
920
|
createdAt: app.createdAt,
|
|
@@ -1161,6 +1258,21 @@ async function filterWorkspaceAppsByAccess(apps) {
|
|
|
1161
1258
|
}
|
|
1162
1259
|
return apps.filter((app) => visibleIds.has(app.id));
|
|
1163
1260
|
}
|
|
1261
|
+
async function filterOrganizationVisibleWorkspaceApps(apps, orgId) {
|
|
1262
|
+
const readyApps = apps.filter((app) => app.status === "ready" && !app.archived);
|
|
1263
|
+
const visibleIds = new Set();
|
|
1264
|
+
const db = getDb();
|
|
1265
|
+
for (let start = 0; start < readyApps.length; start += 500) {
|
|
1266
|
+
const batch = readyApps.slice(start, start + 500);
|
|
1267
|
+
const rows = await db
|
|
1268
|
+
.select({ id: workspaceApps.id })
|
|
1269
|
+
.from(workspaceApps)
|
|
1270
|
+
.where(and(eq(workspaceApps.orgId, orgId), eq(workspaceApps.visibility, "org"), eq(workspaceApps.orgEnabled, true), inArray(workspaceApps.id, batch.map((app) => app.id))));
|
|
1271
|
+
for (const row of rows)
|
|
1272
|
+
visibleIds.add(row.id);
|
|
1273
|
+
}
|
|
1274
|
+
return readyApps.filter((app) => visibleIds.has(app.id));
|
|
1275
|
+
}
|
|
1164
1276
|
async function recordPendingWorkspaceApp(input) {
|
|
1165
1277
|
const now = new Date().toISOString();
|
|
1166
1278
|
const context = pendingWorkspaceAppContext();
|
|
@@ -1174,6 +1286,8 @@ async function recordPendingWorkspaceApp(input) {
|
|
|
1174
1286
|
.find(samePendingEntry);
|
|
1175
1287
|
const creatorEmail = currentOwnerEmail();
|
|
1176
1288
|
const existingOwnerEmail = existing?.createdBy ?? existing?.owner;
|
|
1289
|
+
const communitySourceId = normalizeCommunitySourceId(input.communitySourceId) ??
|
|
1290
|
+
normalizeCommunitySourceId(existing?.communitySourceId);
|
|
1177
1291
|
if (existingOwnerEmail &&
|
|
1178
1292
|
existingOwnerEmail.trim().toLowerCase() !== creatorEmail.toLowerCase()) {
|
|
1179
1293
|
throw new Error(`Workspace app "${input.appId}" is already being created by another member.`);
|
|
@@ -1190,6 +1304,7 @@ async function recordPendingWorkspaceApp(input) {
|
|
|
1190
1304
|
contextId: context?.id ?? null,
|
|
1191
1305
|
contextLabel: context?.label ?? null,
|
|
1192
1306
|
visibility: input.visibility,
|
|
1307
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
1193
1308
|
createdBy: existing?.createdBy ?? existing?.owner ?? creatorEmail,
|
|
1194
1309
|
owner: existing?.owner ?? existing?.createdBy ?? creatorEmail,
|
|
1195
1310
|
createdAt: existing?.createdAt || now,
|
|
@@ -1205,12 +1320,14 @@ async function recordPendingWorkspaceApp(input) {
|
|
|
1205
1320
|
});
|
|
1206
1321
|
await writeWorkspaceAppMetadataOverride({
|
|
1207
1322
|
appId: input.appId,
|
|
1323
|
+
name: null,
|
|
1208
1324
|
description: input.description,
|
|
1209
1325
|
generated: true,
|
|
1210
1326
|
sourcePrompt: input.sourcePrompt,
|
|
1211
1327
|
updatedBy: creatorEmail,
|
|
1212
1328
|
createdBy: next.createdBy,
|
|
1213
1329
|
visibility: input.visibility,
|
|
1330
|
+
communitySourceId,
|
|
1214
1331
|
});
|
|
1215
1332
|
await recordAudit({
|
|
1216
1333
|
action: "workspace-app.pending",
|
|
@@ -1495,6 +1612,10 @@ export async function updateWorkspaceAppMetadata(input) {
|
|
|
1495
1612
|
const appId = input.appId.trim();
|
|
1496
1613
|
assertValidWorkspaceAppId(appId);
|
|
1497
1614
|
requireWorkspaceAppOrgId();
|
|
1615
|
+
const role = await requestOwnerRole();
|
|
1616
|
+
if (role !== "owner" && role !== "admin") {
|
|
1617
|
+
throw new WorkspaceAppMetadataAuthorizationError();
|
|
1618
|
+
}
|
|
1498
1619
|
const apps = await listWorkspaceApps({
|
|
1499
1620
|
includeAgentCards: false,
|
|
1500
1621
|
includeArchived: true,
|
|
@@ -1502,34 +1623,54 @@ export async function updateWorkspaceAppMetadata(input) {
|
|
|
1502
1623
|
const app = apps.find((candidate) => candidate.id === appId);
|
|
1503
1624
|
if (!app)
|
|
1504
1625
|
throw new Error(`Workspace app "${appId}" was not found.`);
|
|
1505
|
-
const
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1626
|
+
const nameProvided = Object.hasOwn(input, "name") && input.name !== undefined;
|
|
1627
|
+
const descriptionProvided = Object.hasOwn(input, "description") && input.description !== undefined;
|
|
1628
|
+
const whenToUseProvided = Object.hasOwn(input, "whenToUse") && input.whenToUse !== undefined;
|
|
1629
|
+
const name = nameProvided ? (input.name?.trim() ?? null) : app.name;
|
|
1630
|
+
const description = descriptionProvided
|
|
1631
|
+
? (input.description?.trim() ?? null)
|
|
1632
|
+
: (app.description ?? undefined);
|
|
1633
|
+
const whenToUse = whenToUseProvided ? (input.whenToUse ?? []) : app.whenToUse;
|
|
1634
|
+
const metadataOverride = {
|
|
1510
1635
|
appId,
|
|
1511
|
-
name,
|
|
1512
|
-
description,
|
|
1513
|
-
generated: false,
|
|
1514
1636
|
updatedBy: currentOwnerEmail(),
|
|
1637
|
+
};
|
|
1638
|
+
if (nameProvided)
|
|
1639
|
+
metadataOverride.name = input.name?.trim() ?? null;
|
|
1640
|
+
if (descriptionProvided) {
|
|
1641
|
+
metadataOverride.description = input.description?.trim() ?? null;
|
|
1642
|
+
}
|
|
1643
|
+
if (whenToUseProvided)
|
|
1644
|
+
metadataOverride.whenToUse = input.whenToUse ?? [];
|
|
1645
|
+
if (nameProvided || descriptionProvided)
|
|
1646
|
+
metadataOverride.generated = false;
|
|
1647
|
+
await writeWorkspaceAppMetadataOverride({
|
|
1648
|
+
...metadataOverride,
|
|
1515
1649
|
});
|
|
1650
|
+
invalidateOrgAppsDirectoryCache();
|
|
1516
1651
|
await recordAudit({
|
|
1517
1652
|
action: "workspace-app.metadata-updated",
|
|
1518
1653
|
targetType: "workspace-app",
|
|
1519
1654
|
targetId: appId,
|
|
1520
|
-
summary: `Updated workspace app details for ${name}`,
|
|
1655
|
+
summary: `Updated workspace app details for ${name ?? app.name}`,
|
|
1521
1656
|
metadata: {
|
|
1522
|
-
name,
|
|
1657
|
+
name: name ?? app.name,
|
|
1523
1658
|
descriptionConfigured: !!description,
|
|
1659
|
+
whenToUseCount: whenToUse?.length ?? 0,
|
|
1524
1660
|
},
|
|
1525
1661
|
});
|
|
1526
1662
|
const updated = (await listWorkspaceApps({
|
|
1527
1663
|
includeAgentCards: false,
|
|
1528
1664
|
includeArchived: true,
|
|
1529
1665
|
})).find((candidate) => candidate.id === appId);
|
|
1530
|
-
return updated ?? {
|
|
1666
|
+
return (updated ?? {
|
|
1667
|
+
...app,
|
|
1668
|
+
name: name ?? app.name,
|
|
1669
|
+
description: description ?? app.description,
|
|
1670
|
+
whenToUse,
|
|
1671
|
+
});
|
|
1531
1672
|
}
|
|
1532
|
-
|
|
1673
|
+
async function listWorkspaceAppsWithPrincipal(options = {}, principal = "user") {
|
|
1533
1674
|
const finalize = async (apps, { persist = true } = {}) => {
|
|
1534
1675
|
const annotated = await applyArchivedAndPending(apps);
|
|
1535
1676
|
const recorded = await ensureWorkspaceAppRecords(annotated, {
|
|
@@ -1538,7 +1679,10 @@ export async function listWorkspaceApps(options = {}) {
|
|
|
1538
1679
|
const listed = options.includeArchived
|
|
1539
1680
|
? recorded
|
|
1540
1681
|
: recorded.filter((app) => !app.archived);
|
|
1541
|
-
const
|
|
1682
|
+
const candidates = filterAppsByAudience(listed, options.audience);
|
|
1683
|
+
const visible = principal === "organization"
|
|
1684
|
+
? await filterOrganizationVisibleWorkspaceApps(candidates, requireWorkspaceAppOrgId())
|
|
1685
|
+
: await filterWorkspaceAppsByAccess(candidates);
|
|
1542
1686
|
return maybeIncludeAgentCards(visible, options);
|
|
1543
1687
|
};
|
|
1544
1688
|
let gatewayDenial = null;
|
|
@@ -1598,6 +1742,13 @@ export async function listWorkspaceApps(options = {}) {
|
|
|
1598
1742
|
return finalize(apps, { persist: !unverified });
|
|
1599
1743
|
return finalize([], { persist: !unverified });
|
|
1600
1744
|
}
|
|
1745
|
+
export function listWorkspaceApps(options = {}) {
|
|
1746
|
+
return listWorkspaceAppsWithPrincipal(options, "user");
|
|
1747
|
+
}
|
|
1748
|
+
export async function listOrganizationVisibleWorkspaceApps() {
|
|
1749
|
+
requireWorkspaceAppOrgId();
|
|
1750
|
+
return listWorkspaceAppsWithPrincipal({ includeAgentCards: false, includeArchived: false, audience: "all" }, "organization");
|
|
1751
|
+
}
|
|
1601
1752
|
const ADDABLE_TEMPLATES = [
|
|
1602
1753
|
{
|
|
1603
1754
|
name: "mail",
|
|
@@ -1748,6 +1899,8 @@ export async function scaffoldWorkspaceAppFromTemplate(input) {
|
|
|
1748
1899
|
try {
|
|
1749
1900
|
await writeWorkspaceAppMetadataOverride({
|
|
1750
1901
|
appId,
|
|
1902
|
+
name: null,
|
|
1903
|
+
description: null,
|
|
1751
1904
|
updatedBy: creatorEmail,
|
|
1752
1905
|
createdBy: creatorEmail,
|
|
1753
1906
|
visibility,
|
|
@@ -1783,6 +1936,7 @@ export async function scaffoldWorkspaceAppFromTemplate(input) {
|
|
|
1783
1936
|
visibility,
|
|
1784
1937
|
},
|
|
1785
1938
|
]);
|
|
1939
|
+
invalidateOrgAppsDirectoryCache();
|
|
1786
1940
|
return { appId, template, output };
|
|
1787
1941
|
}
|
|
1788
1942
|
function runScaffoldCli(input) {
|
|
@@ -1945,8 +2099,8 @@ async function requestOwnerRole() {
|
|
|
1945
2099
|
const role = rows[0]?.role;
|
|
1946
2100
|
return typeof role === "string" ? role : null;
|
|
1947
2101
|
}
|
|
1948
|
-
catch {
|
|
1949
|
-
|
|
2102
|
+
catch (error) {
|
|
2103
|
+
throw new AppCreationOwnerRoleUnavailableError(error);
|
|
1950
2104
|
}
|
|
1951
2105
|
}
|
|
1952
2106
|
async function assertCanManageAppCreationSettings() {
|
|
@@ -2198,6 +2352,10 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2198
2352
|
description: input.description,
|
|
2199
2353
|
template: input.template,
|
|
2200
2354
|
});
|
|
2355
|
+
if (input.communitySourceId !== undefined &&
|
|
2356
|
+
!normalizeCommunitySourceId(input.communitySourceId)) {
|
|
2357
|
+
throw new Error("Community source ids must be valid bounded identifiers.");
|
|
2358
|
+
}
|
|
2201
2359
|
assertValidWorkspaceAppId(initial.appId);
|
|
2202
2360
|
requireWorkspaceAppOrgId();
|
|
2203
2361
|
normalizeBuilderAgentAttachments(input.attachments);
|
|
@@ -2244,15 +2402,41 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2244
2402
|
const appDescription = input.description?.trim() ||
|
|
2245
2403
|
generateWorkspaceAppDescription(input.prompt, built.appId);
|
|
2246
2404
|
if (isLocal) {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2405
|
+
if (input.communitySourceId) {
|
|
2406
|
+
try {
|
|
2407
|
+
await reservePendingWorkspaceApp({
|
|
2408
|
+
appId: built.appId,
|
|
2409
|
+
description: appDescription,
|
|
2410
|
+
projectId: null,
|
|
2411
|
+
visibility: creationVisibility,
|
|
2412
|
+
communitySourceId: input.communitySourceId,
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
catch (err) {
|
|
2416
|
+
if (err instanceof WorkspaceAppIdTakenError)
|
|
2417
|
+
return appIdTakenResult(err);
|
|
2418
|
+
throw err;
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
try {
|
|
2422
|
+
await writeWorkspaceAppMetadataOverride({
|
|
2423
|
+
appId: built.appId,
|
|
2424
|
+
name: null,
|
|
2425
|
+
description: appDescription,
|
|
2426
|
+
generated: true,
|
|
2427
|
+
sourcePrompt: input.prompt,
|
|
2428
|
+
updatedBy: currentOwnerEmail(),
|
|
2429
|
+
createdBy: currentOwnerEmail(),
|
|
2430
|
+
visibility: creationVisibility,
|
|
2431
|
+
communitySourceId: input.communitySourceId,
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
catch (error) {
|
|
2435
|
+
if (input.communitySourceId) {
|
|
2436
|
+
await releasePendingWorkspaceAppReservation(built.appId);
|
|
2437
|
+
}
|
|
2438
|
+
throw error;
|
|
2439
|
+
}
|
|
2256
2440
|
await requestSelectedVaultKeys({
|
|
2257
2441
|
appId: built.appId,
|
|
2258
2442
|
selectedKeys,
|
|
@@ -2265,6 +2449,20 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2265
2449
|
mode: "local-agent",
|
|
2266
2450
|
appId: built.appId,
|
|
2267
2451
|
prompt,
|
|
2452
|
+
...(input.communitySourceId && input.attachments?.length
|
|
2453
|
+
? {
|
|
2454
|
+
attachments: input.attachments.flatMap((attachment) => attachment.type === "upload" && attachment.text !== undefined
|
|
2455
|
+
? [
|
|
2456
|
+
{
|
|
2457
|
+
type: "file",
|
|
2458
|
+
name: attachment.name,
|
|
2459
|
+
contentType: attachment.contentType,
|
|
2460
|
+
text: attachment.text,
|
|
2461
|
+
},
|
|
2462
|
+
]
|
|
2463
|
+
: []),
|
|
2464
|
+
}
|
|
2465
|
+
: {}),
|
|
2268
2466
|
message: "Use the local code agent to create this app in the workspace, then open it from /dispatch/apps.",
|
|
2269
2467
|
};
|
|
2270
2468
|
}
|
|
@@ -2300,6 +2498,9 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2300
2498
|
description: appDescription,
|
|
2301
2499
|
projectId: builderProjectId,
|
|
2302
2500
|
visibility: creationVisibility,
|
|
2501
|
+
...(normalizeCommunitySourceId(input.communitySourceId)
|
|
2502
|
+
? { communitySourceId: input.communitySourceId }
|
|
2503
|
+
: {}),
|
|
2303
2504
|
});
|
|
2304
2505
|
}
|
|
2305
2506
|
catch (err) {
|
|
@@ -2307,6 +2508,23 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2307
2508
|
return appIdTakenResult(err);
|
|
2308
2509
|
throw err;
|
|
2309
2510
|
}
|
|
2511
|
+
if (input.communitySourceId) {
|
|
2512
|
+
try {
|
|
2513
|
+
await writeWorkspaceAppMetadataOverride({
|
|
2514
|
+
appId: built.appId,
|
|
2515
|
+
communitySourceId: input.communitySourceId,
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
catch (error) {
|
|
2519
|
+
try {
|
|
2520
|
+
await releasePendingWorkspaceAppReservation(built.appId);
|
|
2521
|
+
}
|
|
2522
|
+
catch (cleanupError) {
|
|
2523
|
+
console.warn("[dispatch] failed to release pending workspace app reservation", cleanupError);
|
|
2524
|
+
}
|
|
2525
|
+
throw error;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2310
2528
|
let result;
|
|
2311
2529
|
try {
|
|
2312
2530
|
result = normalizeBuilderRunResult(await runBuilderAgent({
|
|
@@ -2337,6 +2555,7 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2337
2555
|
description: appDescription,
|
|
2338
2556
|
sourcePrompt: input.prompt,
|
|
2339
2557
|
visibility: creationVisibility,
|
|
2558
|
+
communitySourceId: input.communitySourceId,
|
|
2340
2559
|
branchName: result.branchName,
|
|
2341
2560
|
builderUrl: result.url,
|
|
2342
2561
|
});
|