@agent-native/dispatch 0.40.30-nightly-20261021093329 → 0.40.31
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 +15 -60
- 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-connected-agents.js +7 -3
- package/dist/actions/list-connected-agents.js.map +1 -1
- 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/list-workspace-connections.d.ts.map +1 -1
- package/dist/actions/list-workspace-connections.js +13 -18
- package/dist/actions/list-workspace-connections.js.map +1 -1
- 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/action-query-error.d.ts +2 -1
- package/dist/components/action-query-error.d.ts.map +1 -1
- package/dist/components/action-query-error.js +25 -2
- package/dist/components/action-query-error.js.map +1 -1
- package/dist/components/chat-home-apps.d.ts.map +1 -1
- package/dist/components/chat-home-apps.js +2 -2
- package/dist/components/chat-home-apps.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/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +1 -1
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +7 -0
- package/dist/components/layout/Layout.js.map +1 -1
- 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/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +1 -1
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +3 -2
- 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 +379 -92
- 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-connected-agents.spec.ts +17 -1
- package/src/actions/list-connected-agents.ts +7 -2
- package/src/actions/list-curated-community-apps.ts +12 -0
- package/src/actions/list-workspace-connections.spec.ts +14 -3
- package/src/actions/list-workspace-connections.ts +13 -17
- package/src/actions/update-workspace-app-metadata.spec.ts +25 -0
- package/src/actions/update-workspace-app-metadata.ts +48 -24
- package/src/components/action-query-error.spec.tsx +20 -0
- package/src/components/action-query-error.tsx +40 -0
- package/src/components/chat-home-apps.spec.tsx +32 -1
- package/src/components/chat-home-apps.tsx +6 -1
- package/src/components/community-apps-section.tsx +224 -0
- package/src/components/dispatch-control-plane.tsx +1 -0
- package/src/components/layout/Layout.tsx +9 -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/$appId.tsx +1 -0
- package/src/routes/pages/apps.spec.tsx +227 -3
- package/src/routes/pages/apps.tsx +5 -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 +820 -16
- package/src/server/lib/app-creation-store.ts +504 -101
- 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
|
@@ -2,28 +2,31 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { canonicalA2AAudience, signA2AToken } from "@agent-native/core/a2a";
|
|
6
|
-
import { isActionContractError } from "@agent-native/core/action";
|
|
5
|
+
import { canonicalA2AAudience, getGlobalA2ASecret, signA2AToken, } from "@agent-native/core/a2a";
|
|
6
|
+
import { ActionContractError, 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 { getOrgA2ASecret, 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() {
|
|
@@ -70,13 +89,11 @@ function requireWorkspaceAppOrgId() {
|
|
|
70
89
|
throw new WorkspaceAppOrgRequiredError();
|
|
71
90
|
return orgId;
|
|
72
91
|
}
|
|
73
|
-
class WorkspaceAppsGatewayAuthorizationError extends
|
|
74
|
-
constructor(statusCode, message = `Workspace apps gateway rejected the request with HTTP ${statusCode}.`) {
|
|
75
|
-
super(message);
|
|
92
|
+
class WorkspaceAppsGatewayAuthorizationError extends ActionContractError {
|
|
93
|
+
constructor(statusCode, errorCode = "workspace_gateway_rejected", message = `Workspace apps gateway rejected the request with HTTP ${statusCode}.`) {
|
|
94
|
+
super(message, { statusCode, errorCode });
|
|
76
95
|
this.name = "WorkspaceAppsGatewayAuthorizationError";
|
|
77
|
-
this.statusCode = statusCode;
|
|
78
96
|
}
|
|
79
|
-
statusCode;
|
|
80
97
|
}
|
|
81
98
|
function warnWorkspaceAppsGatewayDenial(denial, source) {
|
|
82
99
|
if (!denial)
|
|
@@ -189,7 +206,7 @@ function pendingWorkspaceAppContext() {
|
|
|
189
206
|
}
|
|
190
207
|
}
|
|
191
208
|
async function readSettingsRecord() {
|
|
192
|
-
const raw = await getSetting(scopedSettingsKey())
|
|
209
|
+
const raw = await getSetting(scopedSettingsKey());
|
|
193
210
|
return raw && typeof raw === "object" && !Array.isArray(raw)
|
|
194
211
|
? raw
|
|
195
212
|
: {};
|
|
@@ -197,6 +214,14 @@ async function readSettingsRecord() {
|
|
|
197
214
|
function cleanOptionalText(value) {
|
|
198
215
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
199
216
|
}
|
|
217
|
+
function normalizeCommunitySourceId(value) {
|
|
218
|
+
if (typeof value !== "string" ||
|
|
219
|
+
value.length > 100 ||
|
|
220
|
+
!/^community-[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(value)) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
return value.toLowerCase();
|
|
224
|
+
}
|
|
200
225
|
function parseWorkspaceAppMetadataSettings(raw) {
|
|
201
226
|
const record = raw && typeof raw === "object" && !Array.isArray(raw)
|
|
202
227
|
? raw
|
|
@@ -214,14 +239,23 @@ function parseWorkspaceAppMetadataSettings(raw) {
|
|
|
214
239
|
const override = {};
|
|
215
240
|
const name = cleanOptionalText(item.name);
|
|
216
241
|
const description = cleanOptionalText(item.description);
|
|
242
|
+
const profile = parseAgentProfileMetadata({
|
|
243
|
+
whenToUse: item.whenToUse,
|
|
244
|
+
});
|
|
217
245
|
const sourcePrompt = cleanOptionalText(item.sourcePrompt);
|
|
218
246
|
const updatedAt = cleanOptionalText(item.updatedAt);
|
|
219
247
|
const updatedBy = cleanOptionalText(item.updatedBy);
|
|
220
248
|
const createdBy = cleanOptionalText(item.createdBy);
|
|
249
|
+
const communitySourceId = normalizeCommunitySourceId(item.communitySourceId);
|
|
221
250
|
if (name)
|
|
222
251
|
override.name = name;
|
|
223
252
|
if (description)
|
|
224
253
|
override.description = description;
|
|
254
|
+
if (profile?.whenToUse)
|
|
255
|
+
override.whenToUse = profile.whenToUse;
|
|
256
|
+
else if (Array.isArray(item.whenToUse) && item.whenToUse.length === 0) {
|
|
257
|
+
override.whenToUse = [];
|
|
258
|
+
}
|
|
225
259
|
if (item.generated === true)
|
|
226
260
|
override.generated = true;
|
|
227
261
|
if (sourcePrompt)
|
|
@@ -232,6 +266,8 @@ function parseWorkspaceAppMetadataSettings(raw) {
|
|
|
232
266
|
override.updatedBy = updatedBy;
|
|
233
267
|
if (createdBy)
|
|
234
268
|
override.createdBy = createdBy;
|
|
269
|
+
if (communitySourceId)
|
|
270
|
+
override.communitySourceId = communitySourceId;
|
|
235
271
|
if (item.visibility === "private" || item.visibility === "org") {
|
|
236
272
|
override.visibility = item.visibility;
|
|
237
273
|
}
|
|
@@ -241,31 +277,54 @@ function parseWorkspaceAppMetadataSettings(raw) {
|
|
|
241
277
|
return { apps };
|
|
242
278
|
}
|
|
243
279
|
async function readWorkspaceAppMetadataSettings() {
|
|
244
|
-
const raw = await getSetting(workspaceAppMetadataSettingsKey())
|
|
280
|
+
const raw = await getSetting(workspaceAppMetadataSettingsKey());
|
|
245
281
|
return parseWorkspaceAppMetadataSettings(raw);
|
|
246
282
|
}
|
|
247
283
|
async function writeWorkspaceAppMetadataOverride(input) {
|
|
248
284
|
const key = workspaceAppMetadataSettingsKey();
|
|
249
|
-
const current = parseWorkspaceAppMetadataSettings(await getSetting(key)
|
|
285
|
+
const current = parseWorkspaceAppMetadataSettings(await getSetting(key));
|
|
250
286
|
const appId = input.appId.trim();
|
|
251
287
|
const existing = current.apps[appId] ?? {};
|
|
252
288
|
const next = {
|
|
253
289
|
...existing,
|
|
254
290
|
updatedAt: new Date().toISOString(),
|
|
255
291
|
};
|
|
292
|
+
const hasName = Object.hasOwn(input, "name") && input.name !== undefined;
|
|
293
|
+
const hasDescription = Object.hasOwn(input, "description") && input.description !== undefined;
|
|
294
|
+
const hasWhenToUse = Object.hasOwn(input, "whenToUse") && input.whenToUse !== undefined;
|
|
256
295
|
const name = cleanOptionalText(input.name);
|
|
257
296
|
const description = cleanOptionalText(input.description);
|
|
297
|
+
const whenToUse = input.whenToUse;
|
|
258
298
|
const sourcePrompt = cleanOptionalText(input.sourcePrompt);
|
|
259
299
|
const updatedBy = cleanOptionalText(input.updatedBy);
|
|
260
300
|
const createdBy = cleanOptionalText(input.createdBy);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
301
|
+
const communitySourceId = normalizeCommunitySourceId(input.communitySourceId);
|
|
302
|
+
if (hasName) {
|
|
303
|
+
if (name)
|
|
304
|
+
next.name = name;
|
|
305
|
+
else
|
|
306
|
+
delete next.name;
|
|
307
|
+
}
|
|
308
|
+
if (hasDescription) {
|
|
309
|
+
if (description)
|
|
310
|
+
next.description = description;
|
|
311
|
+
else
|
|
312
|
+
delete next.description;
|
|
313
|
+
}
|
|
314
|
+
if (hasWhenToUse) {
|
|
315
|
+
const examples = whenToUse ?? [];
|
|
316
|
+
const profile = parseAgentProfileMetadata({
|
|
317
|
+
description: description ?? next.description,
|
|
318
|
+
whenToUse: examples,
|
|
319
|
+
});
|
|
320
|
+
if (!profile) {
|
|
321
|
+
throw new Error("Workspace app when-to-use entries must contain up to 6 non-empty examples of 140 characters or fewer.");
|
|
322
|
+
}
|
|
323
|
+
next.whenToUse = profile.whenToUse ?? [];
|
|
324
|
+
}
|
|
325
|
+
if (description && description.length > 1000) {
|
|
326
|
+
throw new Error("Workspace app descriptions must be 1,000 characters or fewer.");
|
|
327
|
+
}
|
|
269
328
|
if (input.generated === true)
|
|
270
329
|
next.generated = true;
|
|
271
330
|
else if (input.generated === false)
|
|
@@ -276,6 +335,14 @@ async function writeWorkspaceAppMetadataOverride(input) {
|
|
|
276
335
|
next.updatedBy = updatedBy;
|
|
277
336
|
if (createdBy)
|
|
278
337
|
next.createdBy = createdBy;
|
|
338
|
+
if (input.communitySourceId !== undefined) {
|
|
339
|
+
if (input.communitySourceId === null || !communitySourceId) {
|
|
340
|
+
delete next.communitySourceId;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
next.communitySourceId = communitySourceId;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
279
346
|
if (input.visibility)
|
|
280
347
|
next.visibility = input.visibility;
|
|
281
348
|
current.apps[appId] = next;
|
|
@@ -288,17 +355,26 @@ function applyWorkspaceAppMetadataOverride(app, settings) {
|
|
|
288
355
|
return app;
|
|
289
356
|
const name = cleanOptionalText(override.name);
|
|
290
357
|
const description = cleanOptionalText(override.description);
|
|
358
|
+
const whenToUse = override.whenToUse;
|
|
291
359
|
const generated = override.generated === true;
|
|
292
360
|
const shouldApplyName = !!name && !generated;
|
|
293
361
|
const shouldApplyDescription = !!description && (!generated || !cleanOptionalText(app.description));
|
|
294
362
|
const visibility = override.visibility;
|
|
295
|
-
|
|
363
|
+
const communitySourceId = override.communitySourceId;
|
|
364
|
+
if (!shouldApplyName &&
|
|
365
|
+
!shouldApplyDescription &&
|
|
366
|
+
whenToUse === undefined &&
|
|
367
|
+
!visibility &&
|
|
368
|
+
!communitySourceId) {
|
|
296
369
|
return app;
|
|
370
|
+
}
|
|
297
371
|
return {
|
|
298
372
|
...app,
|
|
299
373
|
...(shouldApplyName ? { name } : {}),
|
|
300
374
|
...(shouldApplyDescription ? { description } : {}),
|
|
375
|
+
...(whenToUse !== undefined ? { whenToUse } : {}),
|
|
301
376
|
...(visibility ? { visibility } : {}),
|
|
377
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
302
378
|
...(app.status === "pending" &&
|
|
303
379
|
!app.createdBy &&
|
|
304
380
|
(override.createdBy || override.updatedBy)
|
|
@@ -385,6 +461,11 @@ function normalizeWorkspaceAppTeams(value) {
|
|
|
385
461
|
}
|
|
386
462
|
function workspaceAppMetadataFromRecord(record) {
|
|
387
463
|
const config = record["agent-native"] ?? record.agentNative;
|
|
464
|
+
const profile = parseAgentProfileMetadata({
|
|
465
|
+
whenToUse: config && typeof config === "object" && !Array.isArray(config)
|
|
466
|
+
? config.whenToUse
|
|
467
|
+
: undefined,
|
|
468
|
+
});
|
|
388
469
|
const workspaceConfig = config && typeof config === "object" && !Array.isArray(config)
|
|
389
470
|
? (config.workspaceApp ?? config.workspace ?? config)
|
|
390
471
|
: {};
|
|
@@ -405,6 +486,7 @@ function workspaceAppMetadataFromRecord(record) {
|
|
|
405
486
|
createdBy: pick(record.createdBy, record.createdByEmail, record.created_by, metadata.createdBy, metadata.createdByEmail, workspaceConfig.createdBy, workspaceConfig.createdByEmail),
|
|
406
487
|
owner: pick(record.owner, record.ownerEmail, record.owner_email, metadata.owner, metadata.ownerEmail, workspaceConfig.owner, workspaceConfig.ownerEmail),
|
|
407
488
|
teams,
|
|
489
|
+
...(profile?.whenToUse ? { whenToUse: profile.whenToUse } : {}),
|
|
408
490
|
};
|
|
409
491
|
}
|
|
410
492
|
function workspaceAppAudienceFromPackageJson(pkg) {
|
|
@@ -462,6 +544,11 @@ function parseWorkspaceAppsManifest(parsed) {
|
|
|
462
544
|
const pathValue = typeof entry.path === "string" ? entry.path.trim() : "";
|
|
463
545
|
if (!id || !pathValue.startsWith("/"))
|
|
464
546
|
return null;
|
|
547
|
+
const profile = parseAgentProfileMetadata({
|
|
548
|
+
whenToUse: entry.whenToUse,
|
|
549
|
+
});
|
|
550
|
+
if (!profile)
|
|
551
|
+
return null;
|
|
465
552
|
const metadata = workspaceAppMetadataFromRecord(entry);
|
|
466
553
|
return {
|
|
467
554
|
id,
|
|
@@ -469,6 +556,7 @@ function parseWorkspaceAppsManifest(parsed) {
|
|
|
469
556
|
? entry.name.trim()
|
|
470
557
|
: titleCase(id),
|
|
471
558
|
description: typeof entry.description === "string" ? entry.description : "",
|
|
559
|
+
...(profile.whenToUse ? { whenToUse: profile.whenToUse } : {}),
|
|
472
560
|
path: pathValue,
|
|
473
561
|
homePath: normalizeWorkspaceAppHomePath(entry.homePath),
|
|
474
562
|
url: workspaceAppLink(pathValue, entry.url),
|
|
@@ -491,6 +579,20 @@ function parseWorkspaceAppsManifest(parsed) {
|
|
|
491
579
|
.sort(sortWorkspaceApps);
|
|
492
580
|
return apps.length ? apps : null;
|
|
493
581
|
}
|
|
582
|
+
function parseWorkspaceAppsGatewayResponse(parsed) {
|
|
583
|
+
const rawApps = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
584
|
+
? parsed.apps
|
|
585
|
+
: parsed;
|
|
586
|
+
if (!Array.isArray(rawApps))
|
|
587
|
+
return null;
|
|
588
|
+
if (rawApps.length === 0)
|
|
589
|
+
return [];
|
|
590
|
+
const apps = parseWorkspaceAppsManifest(parsed);
|
|
591
|
+
if (!apps || apps.length !== rawApps.length)
|
|
592
|
+
return null;
|
|
593
|
+
const ids = new Set(apps.map((app) => app.id.toLowerCase()));
|
|
594
|
+
return ids.size === apps.length ? apps : null;
|
|
595
|
+
}
|
|
494
596
|
function parseDateMs(value) {
|
|
495
597
|
if (!value)
|
|
496
598
|
return null;
|
|
@@ -571,6 +673,7 @@ function parsePendingWorkspaceApps(value) {
|
|
|
571
673
|
return null;
|
|
572
674
|
const now = new Date().toISOString();
|
|
573
675
|
const createdAt = cleanOptionalString(record.createdAt) ?? now;
|
|
676
|
+
const communitySourceId = normalizeCommunitySourceId(record.communitySourceId);
|
|
574
677
|
return {
|
|
575
678
|
id,
|
|
576
679
|
name: typeof record.name === "string" && record.name.trim()
|
|
@@ -600,6 +703,7 @@ function parsePendingWorkspaceApps(value) {
|
|
|
600
703
|
...(record.visibility === "private" || record.visibility === "org"
|
|
601
704
|
? { visibility: record.visibility }
|
|
602
705
|
: {}),
|
|
706
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
603
707
|
createdAt,
|
|
604
708
|
updatedAt: typeof record.updatedAt === "string" && record.updatedAt.trim()
|
|
605
709
|
? record.updatedAt.trim()
|
|
@@ -680,6 +784,7 @@ async function reservePendingWorkspaceApp(input) {
|
|
|
680
784
|
const now = new Date().toISOString();
|
|
681
785
|
const context = pendingWorkspaceAppContext();
|
|
682
786
|
const creatorEmail = currentOwnerEmail();
|
|
787
|
+
const communitySourceId = normalizeCommunitySourceId(input.communitySourceId);
|
|
683
788
|
await mutateSetting(scopedSettingsKey(), async (current) => {
|
|
684
789
|
const raw = current && typeof current === "object" && !Array.isArray(current)
|
|
685
790
|
? current
|
|
@@ -709,6 +814,7 @@ async function reservePendingWorkspaceApp(input) {
|
|
|
709
814
|
contextId: context?.id ?? null,
|
|
710
815
|
contextLabel: context?.label ?? null,
|
|
711
816
|
visibility: input.visibility,
|
|
817
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
712
818
|
createdBy: creatorEmail,
|
|
713
819
|
owner: creatorEmail,
|
|
714
820
|
createdAt: now,
|
|
@@ -761,6 +867,7 @@ export async function archiveWorkspaceApp(input) {
|
|
|
761
867
|
if (!current.includes(appId))
|
|
762
868
|
current.push(appId);
|
|
763
869
|
await putSetting(scopedSettingsKey(), { ...raw, archivedAppIds: current });
|
|
870
|
+
invalidateOrgAppsDirectoryCache();
|
|
764
871
|
await recordAudit({
|
|
765
872
|
action: "workspace-app.archived",
|
|
766
873
|
targetType: "workspace-app",
|
|
@@ -776,6 +883,7 @@ export async function unarchiveWorkspaceApp(input) {
|
|
|
776
883
|
const raw = await readSettingsRecord();
|
|
777
884
|
const current = parseArchivedAppIds(raw.archivedAppIds).filter((id) => id !== appId);
|
|
778
885
|
await putSetting(scopedSettingsKey(), { ...raw, archivedAppIds: current });
|
|
886
|
+
invalidateOrgAppsDirectoryCache();
|
|
779
887
|
await recordAudit({
|
|
780
888
|
action: "workspace-app.unarchived",
|
|
781
889
|
targetType: "workspace-app",
|
|
@@ -818,6 +926,7 @@ function pendingAppToSummary(app) {
|
|
|
818
926
|
status: "pending",
|
|
819
927
|
statusLabel: "Pending Builder branch",
|
|
820
928
|
visibility: app.visibility,
|
|
929
|
+
communitySourceId: app.communitySourceId,
|
|
821
930
|
builderUrl: app.builderUrl,
|
|
822
931
|
branchName: app.branchName,
|
|
823
932
|
createdAt: app.createdAt,
|
|
@@ -1161,6 +1270,21 @@ async function filterWorkspaceAppsByAccess(apps) {
|
|
|
1161
1270
|
}
|
|
1162
1271
|
return apps.filter((app) => visibleIds.has(app.id));
|
|
1163
1272
|
}
|
|
1273
|
+
async function filterOrganizationVisibleWorkspaceApps(apps, orgId) {
|
|
1274
|
+
const readyApps = apps.filter((app) => app.status === "ready" && !app.archived);
|
|
1275
|
+
const visibleIds = new Set();
|
|
1276
|
+
const db = getDb();
|
|
1277
|
+
for (let start = 0; start < readyApps.length; start += 500) {
|
|
1278
|
+
const batch = readyApps.slice(start, start + 500);
|
|
1279
|
+
const rows = await db
|
|
1280
|
+
.select({ id: workspaceApps.id })
|
|
1281
|
+
.from(workspaceApps)
|
|
1282
|
+
.where(and(eq(workspaceApps.orgId, orgId), eq(workspaceApps.visibility, "org"), eq(workspaceApps.orgEnabled, true), inArray(workspaceApps.id, batch.map((app) => app.id))));
|
|
1283
|
+
for (const row of rows)
|
|
1284
|
+
visibleIds.add(row.id);
|
|
1285
|
+
}
|
|
1286
|
+
return readyApps.filter((app) => visibleIds.has(app.id));
|
|
1287
|
+
}
|
|
1164
1288
|
async function recordPendingWorkspaceApp(input) {
|
|
1165
1289
|
const now = new Date().toISOString();
|
|
1166
1290
|
const context = pendingWorkspaceAppContext();
|
|
@@ -1174,6 +1298,8 @@ async function recordPendingWorkspaceApp(input) {
|
|
|
1174
1298
|
.find(samePendingEntry);
|
|
1175
1299
|
const creatorEmail = currentOwnerEmail();
|
|
1176
1300
|
const existingOwnerEmail = existing?.createdBy ?? existing?.owner;
|
|
1301
|
+
const communitySourceId = normalizeCommunitySourceId(input.communitySourceId) ??
|
|
1302
|
+
normalizeCommunitySourceId(existing?.communitySourceId);
|
|
1177
1303
|
if (existingOwnerEmail &&
|
|
1178
1304
|
existingOwnerEmail.trim().toLowerCase() !== creatorEmail.toLowerCase()) {
|
|
1179
1305
|
throw new Error(`Workspace app "${input.appId}" is already being created by another member.`);
|
|
@@ -1190,6 +1316,7 @@ async function recordPendingWorkspaceApp(input) {
|
|
|
1190
1316
|
contextId: context?.id ?? null,
|
|
1191
1317
|
contextLabel: context?.label ?? null,
|
|
1192
1318
|
visibility: input.visibility,
|
|
1319
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
1193
1320
|
createdBy: existing?.createdBy ?? existing?.owner ?? creatorEmail,
|
|
1194
1321
|
owner: existing?.owner ?? existing?.createdBy ?? creatorEmail,
|
|
1195
1322
|
createdAt: existing?.createdAt || now,
|
|
@@ -1205,12 +1332,14 @@ async function recordPendingWorkspaceApp(input) {
|
|
|
1205
1332
|
});
|
|
1206
1333
|
await writeWorkspaceAppMetadataOverride({
|
|
1207
1334
|
appId: input.appId,
|
|
1335
|
+
name: null,
|
|
1208
1336
|
description: input.description,
|
|
1209
1337
|
generated: true,
|
|
1210
1338
|
sourcePrompt: input.sourcePrompt,
|
|
1211
1339
|
updatedBy: creatorEmail,
|
|
1212
1340
|
createdBy: next.createdBy,
|
|
1213
1341
|
visibility: input.visibility,
|
|
1342
|
+
communitySourceId,
|
|
1214
1343
|
});
|
|
1215
1344
|
await recordAudit({
|
|
1216
1345
|
action: "workspace-app.pending",
|
|
@@ -1254,27 +1383,64 @@ async function readWorkspaceAppsFromGateway() {
|
|
|
1254
1383
|
return null;
|
|
1255
1384
|
}
|
|
1256
1385
|
const requestContext = getRequestContext();
|
|
1386
|
+
const isLocalGateway = isLocalWorkspaceGateway(baseUrl);
|
|
1387
|
+
const requestOrigin = requestContext?.requestOrigin;
|
|
1388
|
+
const isSameOriginGateway = requestOrigin
|
|
1389
|
+
? (() => {
|
|
1390
|
+
try {
|
|
1391
|
+
return new URL(requestOrigin).origin === baseUrl.origin;
|
|
1392
|
+
}
|
|
1393
|
+
catch {
|
|
1394
|
+
// coercion-ok: an invalid request origin cannot prove a self-fetch.
|
|
1395
|
+
return false;
|
|
1396
|
+
}
|
|
1397
|
+
})()
|
|
1398
|
+
: false;
|
|
1399
|
+
if (isSameOriginGateway && !isLocalGateway)
|
|
1400
|
+
return null;
|
|
1257
1401
|
const authHeaders = {};
|
|
1258
1402
|
if (requestContext?.userEmail) {
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1403
|
+
let orgDomain;
|
|
1404
|
+
let orgSecret;
|
|
1405
|
+
if (requestContext.orgId) {
|
|
1406
|
+
try {
|
|
1407
|
+
orgDomain =
|
|
1408
|
+
(await getOrgDomain(requestContext.orgId))?.trim().toLowerCase() ||
|
|
1409
|
+
undefined;
|
|
1410
|
+
}
|
|
1411
|
+
catch {
|
|
1412
|
+
throw new WorkspaceAppsGatewayAuthorizationError(503, "workspace_gateway_org_domain_unavailable", "Workspace apps gateway organization domain could not be read.");
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1262
1415
|
if (requestContext.orgId && !orgDomain) {
|
|
1263
|
-
throw new WorkspaceAppsGatewayAuthorizationError(401, "Workspace apps gateway cannot authenticate without a resolved organization domain.");
|
|
1416
|
+
throw new WorkspaceAppsGatewayAuthorizationError(401, "workspace_gateway_org_domain_missing", "Workspace apps gateway cannot authenticate without a resolved organization domain.");
|
|
1264
1417
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1418
|
+
if (requestContext.orgId) {
|
|
1419
|
+
try {
|
|
1420
|
+
orgSecret =
|
|
1421
|
+
(await getOrgA2ASecret(requestContext.orgId))?.trim() || undefined;
|
|
1422
|
+
}
|
|
1423
|
+
catch {
|
|
1424
|
+
throw new WorkspaceAppsGatewayAuthorizationError(503, "workspace_gateway_org_a2a_secret_unavailable", "Workspace apps gateway organization A2A secret could not be read.");
|
|
1425
|
+
}
|
|
1272
1426
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1427
|
+
const globalSecret = getGlobalA2ASecret();
|
|
1428
|
+
const allowLocalUnauthenticatedRequest = isLocalGateway && !orgSecret && !globalSecret;
|
|
1429
|
+
if (!orgSecret && !globalSecret && !allowLocalUnauthenticatedRequest) {
|
|
1430
|
+
throw new WorkspaceAppsGatewayAuthorizationError(503, "workspace_gateway_a2a_secret_missing", "Workspace apps gateway cannot authenticate because no A2A secret is configured.");
|
|
1431
|
+
}
|
|
1432
|
+
if (!allowLocalUnauthenticatedRequest) {
|
|
1433
|
+
try {
|
|
1434
|
+
const token = await signA2AToken(requestContext.userEmail, orgDomain, orgSecret, {
|
|
1435
|
+
expiresIn: "1m",
|
|
1436
|
+
preferGlobalSecret: false,
|
|
1437
|
+
audience: canonicalA2AAudience(baseUrl.toString(), getConfiguredAppBasePath() || undefined),
|
|
1438
|
+
});
|
|
1439
|
+
authHeaders.Authorization = `Bearer ${token}`;
|
|
1440
|
+
}
|
|
1441
|
+
catch {
|
|
1442
|
+
throw new WorkspaceAppsGatewayAuthorizationError(503, "workspace_gateway_token_signing_failed", "Workspace apps gateway could not sign the authenticated request.");
|
|
1443
|
+
}
|
|
1278
1444
|
}
|
|
1279
1445
|
}
|
|
1280
1446
|
const controller = new AbortController();
|
|
@@ -1296,36 +1462,46 @@ async function readWorkspaceAppsFromGateway() {
|
|
|
1296
1462
|
const protectedRedirect = protectionHeaders["x-vercel-protection-bypass"]
|
|
1297
1463
|
? { redirect: "manual" }
|
|
1298
1464
|
: {};
|
|
1465
|
+
const readAppsResponse = async (response) => {
|
|
1466
|
+
let payload;
|
|
1467
|
+
try {
|
|
1468
|
+
payload = await response.json();
|
|
1469
|
+
}
|
|
1470
|
+
catch {
|
|
1471
|
+
throw new ActionContractError("Workspace apps gateway returned an unreadable response.", {
|
|
1472
|
+
statusCode: 503,
|
|
1473
|
+
errorCode: "workspace_gateway_invalid_response",
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
const apps = parseWorkspaceAppsGatewayResponse(payload);
|
|
1477
|
+
if (!apps) {
|
|
1478
|
+
throw new ActionContractError("Workspace apps gateway returned an invalid response.", {
|
|
1479
|
+
statusCode: 503,
|
|
1480
|
+
errorCode: "workspace_gateway_invalid_response",
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
return { apps };
|
|
1484
|
+
};
|
|
1299
1485
|
try {
|
|
1300
|
-
if (
|
|
1486
|
+
if (isLocalGateway) {
|
|
1301
1487
|
const localResponse = await fetch(gatewayUrl(WORKSPACE_APPS_GATEWAY_PATH), {
|
|
1302
1488
|
headers,
|
|
1303
1489
|
...protectedRedirect,
|
|
1304
1490
|
signal: controller.signal,
|
|
1305
1491
|
});
|
|
1306
1492
|
if (localResponse.status === 401 || localResponse.status === 403) {
|
|
1307
|
-
throw new WorkspaceAppsGatewayAuthorizationError(localResponse.status);
|
|
1493
|
+
throw new WorkspaceAppsGatewayAuthorizationError(localResponse.status, "workspace_gateway_rejected");
|
|
1308
1494
|
}
|
|
1309
|
-
if (localResponse.
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1495
|
+
if (localResponse.status !== 404 && localResponse.status !== 200) {
|
|
1496
|
+
throw new ActionContractError(`Workspace apps gateway returned HTTP ${localResponse.status}.`, {
|
|
1497
|
+
statusCode: 503,
|
|
1498
|
+
errorCode: `workspace_gateway_http_${localResponse.status}`,
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
if (localResponse.status === 200) {
|
|
1502
|
+
return await readAppsResponse(localResponse);
|
|
1315
1503
|
}
|
|
1316
1504
|
}
|
|
1317
|
-
const requestOrigin = requestContext?.requestOrigin;
|
|
1318
|
-
const isSameOriginGateway = requestOrigin
|
|
1319
|
-
? (() => {
|
|
1320
|
-
try {
|
|
1321
|
-
return new URL(requestOrigin).origin === baseUrl.origin;
|
|
1322
|
-
}
|
|
1323
|
-
catch {
|
|
1324
|
-
// coercion-ok: an invalid request origin cannot prove a self-fetch.
|
|
1325
|
-
return false;
|
|
1326
|
-
}
|
|
1327
|
-
})()
|
|
1328
|
-
: false;
|
|
1329
1505
|
if (isSameOriginGateway || !authHeaders.Authorization)
|
|
1330
1506
|
return null;
|
|
1331
1507
|
const actionUrl = gatewayUrl(WORKSPACE_APPS_ACTION_PATH);
|
|
@@ -1337,20 +1513,25 @@ async function readWorkspaceAppsFromGateway() {
|
|
|
1337
1513
|
signal: controller.signal,
|
|
1338
1514
|
});
|
|
1339
1515
|
if (actionResponse.status === 401 || actionResponse.status === 403) {
|
|
1340
|
-
throw new WorkspaceAppsGatewayAuthorizationError(actionResponse.status);
|
|
1516
|
+
throw new WorkspaceAppsGatewayAuthorizationError(actionResponse.status, "workspace_gateway_rejected");
|
|
1341
1517
|
}
|
|
1342
|
-
if (
|
|
1518
|
+
if (actionResponse.status === 404)
|
|
1343
1519
|
return null;
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1520
|
+
if (actionResponse.status !== 200) {
|
|
1521
|
+
throw new ActionContractError(`Workspace apps gateway returned HTTP ${actionResponse.status}.`, {
|
|
1522
|
+
statusCode: 503,
|
|
1523
|
+
errorCode: `workspace_gateway_http_${actionResponse.status}`,
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
return await readAppsResponse(actionResponse);
|
|
1349
1527
|
}
|
|
1350
1528
|
catch (error) {
|
|
1351
|
-
if (error instanceof
|
|
1529
|
+
if (error instanceof ActionContractError)
|
|
1352
1530
|
throw error;
|
|
1353
|
-
|
|
1531
|
+
throw new ActionContractError("Workspace apps gateway could not be reached.", {
|
|
1532
|
+
statusCode: 503,
|
|
1533
|
+
errorCode: "workspace_gateway_unavailable",
|
|
1534
|
+
});
|
|
1354
1535
|
}
|
|
1355
1536
|
finally {
|
|
1356
1537
|
clearTimeout(timeout);
|
|
@@ -1495,6 +1676,10 @@ export async function updateWorkspaceAppMetadata(input) {
|
|
|
1495
1676
|
const appId = input.appId.trim();
|
|
1496
1677
|
assertValidWorkspaceAppId(appId);
|
|
1497
1678
|
requireWorkspaceAppOrgId();
|
|
1679
|
+
const role = await requestOwnerRole();
|
|
1680
|
+
if (role !== "owner" && role !== "admin") {
|
|
1681
|
+
throw new WorkspaceAppMetadataAuthorizationError();
|
|
1682
|
+
}
|
|
1498
1683
|
const apps = await listWorkspaceApps({
|
|
1499
1684
|
includeAgentCards: false,
|
|
1500
1685
|
includeArchived: true,
|
|
@@ -1502,34 +1687,54 @@ export async function updateWorkspaceAppMetadata(input) {
|
|
|
1502
1687
|
const app = apps.find((candidate) => candidate.id === appId);
|
|
1503
1688
|
if (!app)
|
|
1504
1689
|
throw new Error(`Workspace app "${appId}" was not found.`);
|
|
1505
|
-
const
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1690
|
+
const nameProvided = Object.hasOwn(input, "name") && input.name !== undefined;
|
|
1691
|
+
const descriptionProvided = Object.hasOwn(input, "description") && input.description !== undefined;
|
|
1692
|
+
const whenToUseProvided = Object.hasOwn(input, "whenToUse") && input.whenToUse !== undefined;
|
|
1693
|
+
const name = nameProvided ? (input.name?.trim() ?? null) : app.name;
|
|
1694
|
+
const description = descriptionProvided
|
|
1695
|
+
? (input.description?.trim() ?? null)
|
|
1696
|
+
: (app.description ?? undefined);
|
|
1697
|
+
const whenToUse = whenToUseProvided ? (input.whenToUse ?? []) : app.whenToUse;
|
|
1698
|
+
const metadataOverride = {
|
|
1510
1699
|
appId,
|
|
1511
|
-
name,
|
|
1512
|
-
description,
|
|
1513
|
-
generated: false,
|
|
1514
1700
|
updatedBy: currentOwnerEmail(),
|
|
1701
|
+
};
|
|
1702
|
+
if (nameProvided)
|
|
1703
|
+
metadataOverride.name = input.name?.trim() ?? null;
|
|
1704
|
+
if (descriptionProvided) {
|
|
1705
|
+
metadataOverride.description = input.description?.trim() ?? null;
|
|
1706
|
+
}
|
|
1707
|
+
if (whenToUseProvided)
|
|
1708
|
+
metadataOverride.whenToUse = input.whenToUse ?? [];
|
|
1709
|
+
if (nameProvided || descriptionProvided)
|
|
1710
|
+
metadataOverride.generated = false;
|
|
1711
|
+
await writeWorkspaceAppMetadataOverride({
|
|
1712
|
+
...metadataOverride,
|
|
1515
1713
|
});
|
|
1714
|
+
invalidateOrgAppsDirectoryCache();
|
|
1516
1715
|
await recordAudit({
|
|
1517
1716
|
action: "workspace-app.metadata-updated",
|
|
1518
1717
|
targetType: "workspace-app",
|
|
1519
1718
|
targetId: appId,
|
|
1520
|
-
summary: `Updated workspace app details for ${name}`,
|
|
1719
|
+
summary: `Updated workspace app details for ${name ?? app.name}`,
|
|
1521
1720
|
metadata: {
|
|
1522
|
-
name,
|
|
1721
|
+
name: name ?? app.name,
|
|
1523
1722
|
descriptionConfigured: !!description,
|
|
1723
|
+
whenToUseCount: whenToUse?.length ?? 0,
|
|
1524
1724
|
},
|
|
1525
1725
|
});
|
|
1526
1726
|
const updated = (await listWorkspaceApps({
|
|
1527
1727
|
includeAgentCards: false,
|
|
1528
1728
|
includeArchived: true,
|
|
1529
1729
|
})).find((candidate) => candidate.id === appId);
|
|
1530
|
-
return updated ?? {
|
|
1730
|
+
return (updated ?? {
|
|
1731
|
+
...app,
|
|
1732
|
+
name: name ?? app.name,
|
|
1733
|
+
description: description ?? app.description,
|
|
1734
|
+
whenToUse,
|
|
1735
|
+
});
|
|
1531
1736
|
}
|
|
1532
|
-
|
|
1737
|
+
async function listWorkspaceAppsWithPrincipal(options = {}, principal = "user") {
|
|
1533
1738
|
const finalize = async (apps, { persist = true } = {}) => {
|
|
1534
1739
|
const annotated = await applyArchivedAndPending(apps);
|
|
1535
1740
|
const recorded = await ensureWorkspaceAppRecords(annotated, {
|
|
@@ -1538,7 +1743,10 @@ export async function listWorkspaceApps(options = {}) {
|
|
|
1538
1743
|
const listed = options.includeArchived
|
|
1539
1744
|
? recorded
|
|
1540
1745
|
: recorded.filter((app) => !app.archived);
|
|
1541
|
-
const
|
|
1746
|
+
const candidates = filterAppsByAudience(listed, options.audience);
|
|
1747
|
+
const visible = principal === "organization"
|
|
1748
|
+
? await filterOrganizationVisibleWorkspaceApps(candidates, requireWorkspaceAppOrgId())
|
|
1749
|
+
: await filterWorkspaceAppsByAccess(candidates);
|
|
1542
1750
|
return maybeIncludeAgentCards(visible, options);
|
|
1543
1751
|
};
|
|
1544
1752
|
let gatewayDenial = null;
|
|
@@ -1554,6 +1762,9 @@ export async function listWorkspaceApps(options = {}) {
|
|
|
1554
1762
|
if (gatewayApps) {
|
|
1555
1763
|
return finalize(gatewayApps.apps);
|
|
1556
1764
|
}
|
|
1765
|
+
if (gatewayDenial && gatewayDenial.statusCode >= 500) {
|
|
1766
|
+
throw gatewayDenial;
|
|
1767
|
+
}
|
|
1557
1768
|
const unverified = gatewayDenial !== null;
|
|
1558
1769
|
const workspaceRoot = findWorkspaceRoot();
|
|
1559
1770
|
const localFilesystemApps = workspaceRoot && isLocalAppCreationRuntime()
|
|
@@ -1573,8 +1784,9 @@ export async function listWorkspaceApps(options = {}) {
|
|
|
1573
1784
|
// means the registry will not show this reader its apps, so they get the
|
|
1574
1785
|
// deployment's own list instead, and none of it is recorded. (A manifest
|
|
1575
1786
|
// answers earlier for both, with the warning above.)
|
|
1576
|
-
if (gatewayDenial
|
|
1787
|
+
if (gatewayDenial && gatewayDenial.statusCode !== 403) {
|
|
1577
1788
|
throw gatewayDenial;
|
|
1789
|
+
}
|
|
1578
1790
|
warnWorkspaceAppsGatewayDenial(gatewayDenial, "deployment's own app list");
|
|
1579
1791
|
if (!workspaceRoot) {
|
|
1580
1792
|
return finalize([
|
|
@@ -1598,6 +1810,13 @@ export async function listWorkspaceApps(options = {}) {
|
|
|
1598
1810
|
return finalize(apps, { persist: !unverified });
|
|
1599
1811
|
return finalize([], { persist: !unverified });
|
|
1600
1812
|
}
|
|
1813
|
+
export function listWorkspaceApps(options = {}) {
|
|
1814
|
+
return listWorkspaceAppsWithPrincipal(options, "user");
|
|
1815
|
+
}
|
|
1816
|
+
export async function listOrganizationVisibleWorkspaceApps() {
|
|
1817
|
+
requireWorkspaceAppOrgId();
|
|
1818
|
+
return listWorkspaceAppsWithPrincipal({ includeAgentCards: false, includeArchived: false, audience: "all" }, "organization");
|
|
1819
|
+
}
|
|
1601
1820
|
const ADDABLE_TEMPLATES = [
|
|
1602
1821
|
{
|
|
1603
1822
|
name: "mail",
|
|
@@ -1748,6 +1967,8 @@ export async function scaffoldWorkspaceAppFromTemplate(input) {
|
|
|
1748
1967
|
try {
|
|
1749
1968
|
await writeWorkspaceAppMetadataOverride({
|
|
1750
1969
|
appId,
|
|
1970
|
+
name: null,
|
|
1971
|
+
description: null,
|
|
1751
1972
|
updatedBy: creatorEmail,
|
|
1752
1973
|
createdBy: creatorEmail,
|
|
1753
1974
|
visibility,
|
|
@@ -1783,6 +2004,7 @@ export async function scaffoldWorkspaceAppFromTemplate(input) {
|
|
|
1783
2004
|
visibility,
|
|
1784
2005
|
},
|
|
1785
2006
|
]);
|
|
2007
|
+
invalidateOrgAppsDirectoryCache();
|
|
1786
2008
|
return { appId, template, output };
|
|
1787
2009
|
}
|
|
1788
2010
|
function runScaffoldCli(input) {
|
|
@@ -1945,8 +2167,8 @@ async function requestOwnerRole() {
|
|
|
1945
2167
|
const role = rows[0]?.role;
|
|
1946
2168
|
return typeof role === "string" ? role : null;
|
|
1947
2169
|
}
|
|
1948
|
-
catch {
|
|
1949
|
-
|
|
2170
|
+
catch (error) {
|
|
2171
|
+
throw new AppCreationOwnerRoleUnavailableError(error);
|
|
1950
2172
|
}
|
|
1951
2173
|
}
|
|
1952
2174
|
async function assertCanManageAppCreationSettings() {
|
|
@@ -2198,6 +2420,10 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2198
2420
|
description: input.description,
|
|
2199
2421
|
template: input.template,
|
|
2200
2422
|
});
|
|
2423
|
+
if (input.communitySourceId !== undefined &&
|
|
2424
|
+
!normalizeCommunitySourceId(input.communitySourceId)) {
|
|
2425
|
+
throw new Error("Community source ids must be valid bounded identifiers.");
|
|
2426
|
+
}
|
|
2201
2427
|
assertValidWorkspaceAppId(initial.appId);
|
|
2202
2428
|
requireWorkspaceAppOrgId();
|
|
2203
2429
|
normalizeBuilderAgentAttachments(input.attachments);
|
|
@@ -2244,15 +2470,41 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2244
2470
|
const appDescription = input.description?.trim() ||
|
|
2245
2471
|
generateWorkspaceAppDescription(input.prompt, built.appId);
|
|
2246
2472
|
if (isLocal) {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2473
|
+
if (input.communitySourceId) {
|
|
2474
|
+
try {
|
|
2475
|
+
await reservePendingWorkspaceApp({
|
|
2476
|
+
appId: built.appId,
|
|
2477
|
+
description: appDescription,
|
|
2478
|
+
projectId: null,
|
|
2479
|
+
visibility: creationVisibility,
|
|
2480
|
+
communitySourceId: input.communitySourceId,
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
catch (err) {
|
|
2484
|
+
if (err instanceof WorkspaceAppIdTakenError)
|
|
2485
|
+
return appIdTakenResult(err);
|
|
2486
|
+
throw err;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
try {
|
|
2490
|
+
await writeWorkspaceAppMetadataOverride({
|
|
2491
|
+
appId: built.appId,
|
|
2492
|
+
name: null,
|
|
2493
|
+
description: appDescription,
|
|
2494
|
+
generated: true,
|
|
2495
|
+
sourcePrompt: input.prompt,
|
|
2496
|
+
updatedBy: currentOwnerEmail(),
|
|
2497
|
+
createdBy: currentOwnerEmail(),
|
|
2498
|
+
visibility: creationVisibility,
|
|
2499
|
+
communitySourceId: input.communitySourceId,
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
catch (error) {
|
|
2503
|
+
if (input.communitySourceId) {
|
|
2504
|
+
await releasePendingWorkspaceAppReservation(built.appId);
|
|
2505
|
+
}
|
|
2506
|
+
throw error;
|
|
2507
|
+
}
|
|
2256
2508
|
await requestSelectedVaultKeys({
|
|
2257
2509
|
appId: built.appId,
|
|
2258
2510
|
selectedKeys,
|
|
@@ -2265,6 +2517,20 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2265
2517
|
mode: "local-agent",
|
|
2266
2518
|
appId: built.appId,
|
|
2267
2519
|
prompt,
|
|
2520
|
+
...(input.communitySourceId && input.attachments?.length
|
|
2521
|
+
? {
|
|
2522
|
+
attachments: input.attachments.flatMap((attachment) => attachment.type === "upload" && attachment.text !== undefined
|
|
2523
|
+
? [
|
|
2524
|
+
{
|
|
2525
|
+
type: "file",
|
|
2526
|
+
name: attachment.name,
|
|
2527
|
+
contentType: attachment.contentType,
|
|
2528
|
+
text: attachment.text,
|
|
2529
|
+
},
|
|
2530
|
+
]
|
|
2531
|
+
: []),
|
|
2532
|
+
}
|
|
2533
|
+
: {}),
|
|
2268
2534
|
message: "Use the local code agent to create this app in the workspace, then open it from /dispatch/apps.",
|
|
2269
2535
|
};
|
|
2270
2536
|
}
|
|
@@ -2300,6 +2566,9 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2300
2566
|
description: appDescription,
|
|
2301
2567
|
projectId: builderProjectId,
|
|
2302
2568
|
visibility: creationVisibility,
|
|
2569
|
+
...(normalizeCommunitySourceId(input.communitySourceId)
|
|
2570
|
+
? { communitySourceId: input.communitySourceId }
|
|
2571
|
+
: {}),
|
|
2303
2572
|
});
|
|
2304
2573
|
}
|
|
2305
2574
|
catch (err) {
|
|
@@ -2307,6 +2576,23 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2307
2576
|
return appIdTakenResult(err);
|
|
2308
2577
|
throw err;
|
|
2309
2578
|
}
|
|
2579
|
+
if (input.communitySourceId) {
|
|
2580
|
+
try {
|
|
2581
|
+
await writeWorkspaceAppMetadataOverride({
|
|
2582
|
+
appId: built.appId,
|
|
2583
|
+
communitySourceId: input.communitySourceId,
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
catch (error) {
|
|
2587
|
+
try {
|
|
2588
|
+
await releasePendingWorkspaceAppReservation(built.appId);
|
|
2589
|
+
}
|
|
2590
|
+
catch (cleanupError) {
|
|
2591
|
+
console.warn("[dispatch] failed to release pending workspace app reservation", cleanupError);
|
|
2592
|
+
}
|
|
2593
|
+
throw error;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2310
2596
|
let result;
|
|
2311
2597
|
try {
|
|
2312
2598
|
result = normalizeBuilderRunResult(await runBuilderAgent({
|
|
@@ -2337,6 +2623,7 @@ export async function startWorkspaceAppCreation(input) {
|
|
|
2337
2623
|
description: appDescription,
|
|
2338
2624
|
sourcePrompt: input.prompt,
|
|
2339
2625
|
visibility: creationVisibility,
|
|
2626
|
+
communitySourceId: input.communitySourceId,
|
|
2340
2627
|
branchName: result.branchName,
|
|
2341
2628
|
builderUrl: result.url,
|
|
2342
2629
|
});
|