@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
|
@@ -3,13 +3,23 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
canonicalA2AAudience,
|
|
8
|
+
getGlobalA2ASecret,
|
|
9
|
+
signA2AToken,
|
|
10
|
+
} from "@agent-native/core/a2a";
|
|
11
|
+
import {
|
|
12
|
+
ActionContractError,
|
|
13
|
+
isActionContractError,
|
|
14
|
+
} from "@agent-native/core/action";
|
|
8
15
|
import { describeErrorWithCauses } from "@agent-native/core/agent/engine/error-detail";
|
|
9
16
|
import { getDbExec } from "@agent-native/core/db";
|
|
17
|
+
import { and, eq, inArray } from "@agent-native/core/db/schema";
|
|
10
18
|
import {
|
|
19
|
+
getOrgA2ASecret,
|
|
11
20
|
getOrgDomain,
|
|
12
21
|
isWorkspaceAppAccessAllowed,
|
|
22
|
+
workspaceApps,
|
|
13
23
|
} from "@agent-native/core/org";
|
|
14
24
|
import {
|
|
15
25
|
CredentialStoreUnavailableError,
|
|
@@ -39,11 +49,12 @@ import {
|
|
|
39
49
|
assertValidWorkspaceAppId,
|
|
40
50
|
connectRequiredResult,
|
|
41
51
|
normalizeWorkspaceAppHomePath,
|
|
52
|
+
parseAgentProfileMetadata,
|
|
42
53
|
type ConnectRequiredCard,
|
|
43
54
|
} from "@agent-native/core/shared";
|
|
44
55
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
45
56
|
|
|
46
|
-
import "../../db/index.js";
|
|
57
|
+
import { getDb } from "../../db/index.js";
|
|
47
58
|
import { isWorkspaceSsoAppUrl } from "../../shared/workspace-sso.js";
|
|
48
59
|
import { getDispatchDefaultOwnerEmail } from "./admin-config.js";
|
|
49
60
|
import { identityKeyForIncoming } from "./dispatch-integrations.js";
|
|
@@ -57,6 +68,7 @@ import {
|
|
|
57
68
|
projectEnvironmentUrl,
|
|
58
69
|
requestEnvironmentLane,
|
|
59
70
|
} from "./environment-lane.js";
|
|
71
|
+
import { invalidateOrgAppsDirectoryCache } from "./org-apps-directory-cache.js";
|
|
60
72
|
import { createRequest, listSecretOptions } from "./vault-store.js";
|
|
61
73
|
import { WORKSPACE_APPS_ACTION_PATH } from "./workspace-app-action-auth.js";
|
|
62
74
|
import {
|
|
@@ -69,6 +81,8 @@ const SETTINGS_KEY = "dispatch-app-creation-settings";
|
|
|
69
81
|
const DEFAULT_BUILDER_WORKSPACE_PROJECT_NAME = "Agent-Native Workspace";
|
|
70
82
|
const APP_CREATION_SETTINGS_AUTHORIZATION_MESSAGE =
|
|
71
83
|
"Only organization owners and admins can update app creation settings.";
|
|
84
|
+
const WORKSPACE_APP_METADATA_AUTHORIZATION_MESSAGE =
|
|
85
|
+
"Only organization owners and admins can update workspace app details.";
|
|
72
86
|
const APP_CREATION_SETTINGS_REQUIRED_MESSAGE =
|
|
73
87
|
"An organization owner or admin must configure the Builder workspace project before members can create apps.";
|
|
74
88
|
const WORKSPACE_APP_METADATA_SETTINGS_KEY = "workspace-app-metadata";
|
|
@@ -109,6 +123,26 @@ class AppCreationSettingsAuthorizationError extends Error {
|
|
|
109
123
|
}
|
|
110
124
|
}
|
|
111
125
|
|
|
126
|
+
class WorkspaceAppMetadataAuthorizationError extends Error {
|
|
127
|
+
statusCode = 403;
|
|
128
|
+
|
|
129
|
+
constructor() {
|
|
130
|
+
super(WORKSPACE_APP_METADATA_AUTHORIZATION_MESSAGE);
|
|
131
|
+
this.name = "WorkspaceAppMetadataAuthorizationError";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class AppCreationOwnerRoleUnavailableError extends Error {
|
|
136
|
+
statusCode = 503;
|
|
137
|
+
|
|
138
|
+
constructor(cause: unknown) {
|
|
139
|
+
super("Could not verify the organization role. Try again shortly.", {
|
|
140
|
+
cause,
|
|
141
|
+
});
|
|
142
|
+
this.name = "AppCreationOwnerRoleUnavailableError";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
112
146
|
class WorkspaceAppOrgRequiredError extends Error {
|
|
113
147
|
statusCode = 400;
|
|
114
148
|
|
|
@@ -126,17 +160,15 @@ function requireWorkspaceAppOrgId(): string {
|
|
|
126
160
|
return orgId;
|
|
127
161
|
}
|
|
128
162
|
|
|
129
|
-
class WorkspaceAppsGatewayAuthorizationError extends
|
|
163
|
+
class WorkspaceAppsGatewayAuthorizationError extends ActionContractError {
|
|
130
164
|
constructor(
|
|
131
|
-
statusCode: 401 | 403,
|
|
165
|
+
statusCode: 401 | 403 | 503,
|
|
166
|
+
errorCode = "workspace_gateway_rejected",
|
|
132
167
|
message = `Workspace apps gateway rejected the request with HTTP ${statusCode}.`,
|
|
133
168
|
) {
|
|
134
|
-
super(message);
|
|
169
|
+
super(message, { statusCode, errorCode });
|
|
135
170
|
this.name = "WorkspaceAppsGatewayAuthorizationError";
|
|
136
|
-
this.statusCode = statusCode;
|
|
137
171
|
}
|
|
138
|
-
|
|
139
|
-
statusCode: 401 | 403;
|
|
140
172
|
}
|
|
141
173
|
|
|
142
174
|
function warnWorkspaceAppsGatewayDenial(
|
|
@@ -156,6 +188,8 @@ export interface WorkspaceAppSummary {
|
|
|
156
188
|
id: string;
|
|
157
189
|
name: string;
|
|
158
190
|
description: string;
|
|
191
|
+
whenToUse?: string[];
|
|
192
|
+
communitySourceId?: string;
|
|
159
193
|
path: string;
|
|
160
194
|
homePath?: string;
|
|
161
195
|
url: string | null;
|
|
@@ -233,6 +267,7 @@ interface PendingWorkspaceApp {
|
|
|
233
267
|
contextLabel: string | null;
|
|
234
268
|
audience?: WorkspaceAppAudience;
|
|
235
269
|
visibility?: WorkspaceAppVisibility;
|
|
270
|
+
communitySourceId?: string;
|
|
236
271
|
createdBy?: string | null;
|
|
237
272
|
owner?: string | null;
|
|
238
273
|
teams?: string[];
|
|
@@ -244,12 +279,14 @@ interface PendingWorkspaceApp {
|
|
|
244
279
|
interface WorkspaceAppMetadataOverride {
|
|
245
280
|
name?: string;
|
|
246
281
|
description?: string;
|
|
282
|
+
whenToUse?: string[];
|
|
247
283
|
generated?: boolean;
|
|
248
284
|
sourcePrompt?: string;
|
|
249
285
|
updatedAt?: string;
|
|
250
286
|
updatedBy?: string;
|
|
251
287
|
createdBy?: string;
|
|
252
288
|
visibility?: WorkspaceAppVisibility;
|
|
289
|
+
communitySourceId?: string;
|
|
253
290
|
}
|
|
254
291
|
|
|
255
292
|
interface WorkspaceAppMetadataSettings {
|
|
@@ -379,7 +416,7 @@ function pendingWorkspaceAppContext(): { id: string; label: string } | null {
|
|
|
379
416
|
}
|
|
380
417
|
|
|
381
418
|
async function readSettingsRecord(): Promise<Record<string, any>> {
|
|
382
|
-
const raw = await getSetting(scopedSettingsKey())
|
|
419
|
+
const raw = await getSetting(scopedSettingsKey());
|
|
383
420
|
return raw && typeof raw === "object" && !Array.isArray(raw)
|
|
384
421
|
? (raw as Record<string, any>)
|
|
385
422
|
: {};
|
|
@@ -389,6 +426,17 @@ function cleanOptionalText(value: unknown): string | undefined {
|
|
|
389
426
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
390
427
|
}
|
|
391
428
|
|
|
429
|
+
function normalizeCommunitySourceId(value: unknown): string | undefined {
|
|
430
|
+
if (
|
|
431
|
+
typeof value !== "string" ||
|
|
432
|
+
value.length > 100 ||
|
|
433
|
+
!/^community-[a-z0-9]+(?:-[a-z0-9]+)*$/i.test(value)
|
|
434
|
+
) {
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
return value.toLowerCase();
|
|
438
|
+
}
|
|
439
|
+
|
|
392
440
|
function parseWorkspaceAppMetadataSettings(
|
|
393
441
|
raw: unknown,
|
|
394
442
|
): WorkspaceAppMetadataSettings {
|
|
@@ -410,18 +458,29 @@ function parseWorkspaceAppMetadataSettings(
|
|
|
410
458
|
const override: WorkspaceAppMetadataOverride = {};
|
|
411
459
|
const name = cleanOptionalText(item.name);
|
|
412
460
|
const description = cleanOptionalText(item.description);
|
|
461
|
+
const profile = parseAgentProfileMetadata({
|
|
462
|
+
whenToUse: item.whenToUse,
|
|
463
|
+
});
|
|
413
464
|
const sourcePrompt = cleanOptionalText(item.sourcePrompt);
|
|
414
465
|
const updatedAt = cleanOptionalText(item.updatedAt);
|
|
415
466
|
const updatedBy = cleanOptionalText(item.updatedBy);
|
|
416
467
|
const createdBy = cleanOptionalText(item.createdBy);
|
|
468
|
+
const communitySourceId = normalizeCommunitySourceId(
|
|
469
|
+
item.communitySourceId,
|
|
470
|
+
);
|
|
417
471
|
|
|
418
472
|
if (name) override.name = name;
|
|
419
473
|
if (description) override.description = description;
|
|
474
|
+
if (profile?.whenToUse) override.whenToUse = profile.whenToUse;
|
|
475
|
+
else if (Array.isArray(item.whenToUse) && item.whenToUse.length === 0) {
|
|
476
|
+
override.whenToUse = [];
|
|
477
|
+
}
|
|
420
478
|
if (item.generated === true) override.generated = true;
|
|
421
479
|
if (sourcePrompt) override.sourcePrompt = sourcePrompt;
|
|
422
480
|
if (updatedAt) override.updatedAt = updatedAt;
|
|
423
481
|
if (updatedBy) override.updatedBy = updatedBy;
|
|
424
482
|
if (createdBy) override.createdBy = createdBy;
|
|
483
|
+
if (communitySourceId) override.communitySourceId = communitySourceId;
|
|
425
484
|
if (item.visibility === "private" || item.visibility === "org") {
|
|
426
485
|
override.visibility = item.visibility;
|
|
427
486
|
}
|
|
@@ -433,9 +492,7 @@ function parseWorkspaceAppMetadataSettings(
|
|
|
433
492
|
}
|
|
434
493
|
|
|
435
494
|
async function readWorkspaceAppMetadataSettings(): Promise<WorkspaceAppMetadataSettings> {
|
|
436
|
-
const raw = await getSetting(workspaceAppMetadataSettingsKey())
|
|
437
|
-
() => null,
|
|
438
|
-
);
|
|
495
|
+
const raw = await getSetting(workspaceAppMetadataSettingsKey());
|
|
439
496
|
return parseWorkspaceAppMetadataSettings(raw);
|
|
440
497
|
}
|
|
441
498
|
|
|
@@ -443,37 +500,73 @@ async function writeWorkspaceAppMetadataOverride(input: {
|
|
|
443
500
|
appId: string;
|
|
444
501
|
name?: string | null;
|
|
445
502
|
description?: string | null;
|
|
503
|
+
whenToUse?: string[] | null;
|
|
446
504
|
generated?: boolean;
|
|
447
505
|
sourcePrompt?: string | null;
|
|
448
506
|
updatedBy?: string | null;
|
|
449
507
|
createdBy?: string | null;
|
|
450
508
|
visibility?: WorkspaceAppVisibility | null;
|
|
509
|
+
communitySourceId?: string | null;
|
|
451
510
|
}): Promise<WorkspaceAppMetadataSettings> {
|
|
452
511
|
const key = workspaceAppMetadataSettingsKey();
|
|
453
|
-
const current = parseWorkspaceAppMetadataSettings(
|
|
454
|
-
await getSetting(key).catch(() => null),
|
|
455
|
-
);
|
|
512
|
+
const current = parseWorkspaceAppMetadataSettings(await getSetting(key));
|
|
456
513
|
const appId = input.appId.trim();
|
|
457
514
|
const existing = current.apps[appId] ?? {};
|
|
458
515
|
const next: WorkspaceAppMetadataOverride = {
|
|
459
516
|
...existing,
|
|
460
517
|
updatedAt: new Date().toISOString(),
|
|
461
518
|
};
|
|
519
|
+
const hasName = Object.hasOwn(input, "name") && input.name !== undefined;
|
|
520
|
+
const hasDescription =
|
|
521
|
+
Object.hasOwn(input, "description") && input.description !== undefined;
|
|
522
|
+
const hasWhenToUse =
|
|
523
|
+
Object.hasOwn(input, "whenToUse") && input.whenToUse !== undefined;
|
|
462
524
|
const name = cleanOptionalText(input.name);
|
|
463
525
|
const description = cleanOptionalText(input.description);
|
|
526
|
+
const whenToUse = input.whenToUse;
|
|
464
527
|
const sourcePrompt = cleanOptionalText(input.sourcePrompt);
|
|
465
528
|
const updatedBy = cleanOptionalText(input.updatedBy);
|
|
466
529
|
const createdBy = cleanOptionalText(input.createdBy);
|
|
530
|
+
const communitySourceId = normalizeCommunitySourceId(input.communitySourceId);
|
|
467
531
|
|
|
468
|
-
if (
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
532
|
+
if (hasName) {
|
|
533
|
+
if (name) next.name = name;
|
|
534
|
+
else delete next.name;
|
|
535
|
+
}
|
|
536
|
+
if (hasDescription) {
|
|
537
|
+
if (description) next.description = description;
|
|
538
|
+
else delete next.description;
|
|
539
|
+
}
|
|
540
|
+
if (hasWhenToUse) {
|
|
541
|
+
const examples = whenToUse ?? [];
|
|
542
|
+
const profile = parseAgentProfileMetadata({
|
|
543
|
+
description: description ?? next.description,
|
|
544
|
+
whenToUse: examples,
|
|
545
|
+
});
|
|
546
|
+
if (!profile) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
"Workspace app when-to-use entries must contain up to 6 non-empty examples of 140 characters or fewer.",
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
next.whenToUse = profile.whenToUse ?? [];
|
|
552
|
+
}
|
|
553
|
+
if (description && description.length > 1000) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
"Workspace app descriptions must be 1,000 characters or fewer.",
|
|
556
|
+
);
|
|
557
|
+
}
|
|
472
558
|
if (input.generated === true) next.generated = true;
|
|
473
559
|
else if (input.generated === false) delete next.generated;
|
|
474
560
|
if (sourcePrompt) next.sourcePrompt = sourcePrompt;
|
|
475
561
|
if (updatedBy) next.updatedBy = updatedBy;
|
|
476
562
|
if (createdBy) next.createdBy = createdBy;
|
|
563
|
+
if (input.communitySourceId !== undefined) {
|
|
564
|
+
if (input.communitySourceId === null || !communitySourceId) {
|
|
565
|
+
delete next.communitySourceId;
|
|
566
|
+
} else {
|
|
567
|
+
next.communitySourceId = communitySourceId;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
477
570
|
if (input.visibility) next.visibility = input.visibility;
|
|
478
571
|
|
|
479
572
|
current.apps[appId] = next;
|
|
@@ -490,18 +583,30 @@ function applyWorkspaceAppMetadataOverride(
|
|
|
490
583
|
|
|
491
584
|
const name = cleanOptionalText(override.name);
|
|
492
585
|
const description = cleanOptionalText(override.description);
|
|
586
|
+
const whenToUse = override.whenToUse;
|
|
493
587
|
const generated = override.generated === true;
|
|
494
588
|
const shouldApplyName = !!name && !generated;
|
|
495
589
|
const shouldApplyDescription =
|
|
496
590
|
!!description && (!generated || !cleanOptionalText(app.description));
|
|
497
591
|
const visibility = override.visibility;
|
|
498
|
-
|
|
592
|
+
const communitySourceId = override.communitySourceId;
|
|
593
|
+
if (
|
|
594
|
+
!shouldApplyName &&
|
|
595
|
+
!shouldApplyDescription &&
|
|
596
|
+
whenToUse === undefined &&
|
|
597
|
+
!visibility &&
|
|
598
|
+
!communitySourceId
|
|
599
|
+
) {
|
|
600
|
+
return app;
|
|
601
|
+
}
|
|
499
602
|
|
|
500
603
|
return {
|
|
501
604
|
...app,
|
|
502
605
|
...(shouldApplyName ? { name } : {}),
|
|
503
606
|
...(shouldApplyDescription ? { description } : {}),
|
|
607
|
+
...(whenToUse !== undefined ? { whenToUse } : {}),
|
|
504
608
|
...(visibility ? { visibility } : {}),
|
|
609
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
505
610
|
...(app.status === "pending" &&
|
|
506
611
|
!app.createdBy &&
|
|
507
612
|
(override.createdBy || override.updatedBy)
|
|
@@ -602,8 +707,17 @@ function normalizeWorkspaceAppTeams(value: unknown): string[] {
|
|
|
602
707
|
|
|
603
708
|
function workspaceAppMetadataFromRecord(
|
|
604
709
|
record: Record<string, any>,
|
|
605
|
-
): Pick<
|
|
710
|
+
): Pick<
|
|
711
|
+
WorkspaceAppSummary,
|
|
712
|
+
"createdAt" | "createdBy" | "owner" | "teams" | "whenToUse"
|
|
713
|
+
> {
|
|
606
714
|
const config = record["agent-native"] ?? record.agentNative;
|
|
715
|
+
const profile = parseAgentProfileMetadata({
|
|
716
|
+
whenToUse:
|
|
717
|
+
config && typeof config === "object" && !Array.isArray(config)
|
|
718
|
+
? config.whenToUse
|
|
719
|
+
: undefined,
|
|
720
|
+
});
|
|
607
721
|
const workspaceConfig =
|
|
608
722
|
config && typeof config === "object" && !Array.isArray(config)
|
|
609
723
|
? (config.workspaceApp ?? config.workspace ?? config)
|
|
@@ -651,6 +765,7 @@ function workspaceAppMetadataFromRecord(
|
|
|
651
765
|
workspaceConfig.ownerEmail,
|
|
652
766
|
),
|
|
653
767
|
teams,
|
|
768
|
+
...(profile?.whenToUse ? { whenToUse: profile.whenToUse } : {}),
|
|
654
769
|
};
|
|
655
770
|
}
|
|
656
771
|
|
|
@@ -719,6 +834,10 @@ function parseWorkspaceAppsManifest(parsed: any): WorkspaceAppSummary[] | null {
|
|
|
719
834
|
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
720
835
|
const pathValue = typeof entry.path === "string" ? entry.path.trim() : "";
|
|
721
836
|
if (!id || !pathValue.startsWith("/")) return null;
|
|
837
|
+
const profile = parseAgentProfileMetadata({
|
|
838
|
+
whenToUse: entry.whenToUse,
|
|
839
|
+
});
|
|
840
|
+
if (!profile) return null;
|
|
722
841
|
const metadata = workspaceAppMetadataFromRecord(entry);
|
|
723
842
|
return {
|
|
724
843
|
id,
|
|
@@ -728,6 +847,7 @@ function parseWorkspaceAppsManifest(parsed: any): WorkspaceAppSummary[] | null {
|
|
|
728
847
|
: titleCase(id),
|
|
729
848
|
description:
|
|
730
849
|
typeof entry.description === "string" ? entry.description : "",
|
|
850
|
+
...(profile.whenToUse ? { whenToUse: profile.whenToUse } : {}),
|
|
731
851
|
path: pathValue,
|
|
732
852
|
homePath: normalizeWorkspaceAppHomePath(entry.homePath),
|
|
733
853
|
url: workspaceAppLink(pathValue, entry.url),
|
|
@@ -757,6 +877,21 @@ function parseWorkspaceAppsManifest(parsed: any): WorkspaceAppSummary[] | null {
|
|
|
757
877
|
return apps.length ? apps : null;
|
|
758
878
|
}
|
|
759
879
|
|
|
880
|
+
function parseWorkspaceAppsGatewayResponse(
|
|
881
|
+
parsed: unknown,
|
|
882
|
+
): WorkspaceAppSummary[] | null {
|
|
883
|
+
const rawApps =
|
|
884
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
885
|
+
? (parsed as { apps?: unknown }).apps
|
|
886
|
+
: parsed;
|
|
887
|
+
if (!Array.isArray(rawApps)) return null;
|
|
888
|
+
if (rawApps.length === 0) return [];
|
|
889
|
+
const apps = parseWorkspaceAppsManifest(parsed);
|
|
890
|
+
if (!apps || apps.length !== rawApps.length) return null;
|
|
891
|
+
const ids = new Set(apps.map((app) => app.id.toLowerCase()));
|
|
892
|
+
return ids.size === apps.length ? apps : null;
|
|
893
|
+
}
|
|
894
|
+
|
|
760
895
|
function parseDateMs(value: string | null | undefined): number | null {
|
|
761
896
|
if (!value) return null;
|
|
762
897
|
const ms = Date.parse(value);
|
|
@@ -850,6 +985,9 @@ function parsePendingWorkspaceApps(value: unknown): PendingWorkspaceApp[] {
|
|
|
850
985
|
if (!id || !pathValue.startsWith("/")) return null;
|
|
851
986
|
const now = new Date().toISOString();
|
|
852
987
|
const createdAt = cleanOptionalString(record.createdAt) ?? now;
|
|
988
|
+
const communitySourceId = normalizeCommunitySourceId(
|
|
989
|
+
record.communitySourceId,
|
|
990
|
+
);
|
|
853
991
|
return {
|
|
854
992
|
id,
|
|
855
993
|
name:
|
|
@@ -884,6 +1022,7 @@ function parsePendingWorkspaceApps(value: unknown): PendingWorkspaceApp[] {
|
|
|
884
1022
|
...(record.visibility === "private" || record.visibility === "org"
|
|
885
1023
|
? { visibility: record.visibility }
|
|
886
1024
|
: {}),
|
|
1025
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
887
1026
|
createdAt,
|
|
888
1027
|
updatedAt:
|
|
889
1028
|
typeof record.updatedAt === "string" && record.updatedAt.trim()
|
|
@@ -987,11 +1126,13 @@ async function reservePendingWorkspaceApp(input: {
|
|
|
987
1126
|
description: string;
|
|
988
1127
|
projectId: string | null;
|
|
989
1128
|
visibility: WorkspaceAppVisibility;
|
|
1129
|
+
communitySourceId?: string;
|
|
990
1130
|
}): Promise<void> {
|
|
991
1131
|
await assertWorkspaceAppIdRegisteredFree(input.appId);
|
|
992
1132
|
const now = new Date().toISOString();
|
|
993
1133
|
const context = pendingWorkspaceAppContext();
|
|
994
1134
|
const creatorEmail = currentOwnerEmail();
|
|
1135
|
+
const communitySourceId = normalizeCommunitySourceId(input.communitySourceId);
|
|
995
1136
|
|
|
996
1137
|
await mutateSetting(scopedSettingsKey(), async (current) => {
|
|
997
1138
|
const raw =
|
|
@@ -1027,6 +1168,7 @@ async function reservePendingWorkspaceApp(input: {
|
|
|
1027
1168
|
contextId: context?.id ?? null,
|
|
1028
1169
|
contextLabel: context?.label ?? null,
|
|
1029
1170
|
visibility: input.visibility,
|
|
1171
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
1030
1172
|
createdBy: creatorEmail,
|
|
1031
1173
|
owner: creatorEmail,
|
|
1032
1174
|
createdAt: now,
|
|
@@ -1083,6 +1225,7 @@ export async function archiveWorkspaceApp(input: {
|
|
|
1083
1225
|
const current = parseArchivedAppIds(raw.archivedAppIds);
|
|
1084
1226
|
if (!current.includes(appId)) current.push(appId);
|
|
1085
1227
|
await putSetting(scopedSettingsKey(), { ...raw, archivedAppIds: current });
|
|
1228
|
+
invalidateOrgAppsDirectoryCache();
|
|
1086
1229
|
await recordAudit({
|
|
1087
1230
|
action: "workspace-app.archived",
|
|
1088
1231
|
targetType: "workspace-app",
|
|
@@ -1102,6 +1245,7 @@ export async function unarchiveWorkspaceApp(input: {
|
|
|
1102
1245
|
(id) => id !== appId,
|
|
1103
1246
|
);
|
|
1104
1247
|
await putSetting(scopedSettingsKey(), { ...raw, archivedAppIds: current });
|
|
1248
|
+
invalidateOrgAppsDirectoryCache();
|
|
1105
1249
|
await recordAudit({
|
|
1106
1250
|
action: "workspace-app.unarchived",
|
|
1107
1251
|
targetType: "workspace-app",
|
|
@@ -1148,6 +1292,7 @@ function pendingAppToSummary(app: PendingWorkspaceApp): WorkspaceAppSummary {
|
|
|
1148
1292
|
status: "pending",
|
|
1149
1293
|
statusLabel: "Pending Builder branch",
|
|
1150
1294
|
visibility: app.visibility,
|
|
1295
|
+
communitySourceId: app.communitySourceId,
|
|
1151
1296
|
builderUrl: app.builderUrl,
|
|
1152
1297
|
branchName: app.branchName,
|
|
1153
1298
|
createdAt: app.createdAt,
|
|
@@ -1590,12 +1735,45 @@ async function filterWorkspaceAppsByAccess(
|
|
|
1590
1735
|
return apps.filter((app) => visibleIds.has(app.id));
|
|
1591
1736
|
}
|
|
1592
1737
|
|
|
1738
|
+
async function filterOrganizationVisibleWorkspaceApps(
|
|
1739
|
+
apps: WorkspaceAppSummary[],
|
|
1740
|
+
orgId: string,
|
|
1741
|
+
): Promise<WorkspaceAppSummary[]> {
|
|
1742
|
+
const readyApps = apps.filter(
|
|
1743
|
+
(app) => app.status === "ready" && !app.archived,
|
|
1744
|
+
);
|
|
1745
|
+
const visibleIds = new Set<string>();
|
|
1746
|
+
const db = getDb();
|
|
1747
|
+
|
|
1748
|
+
for (let start = 0; start < readyApps.length; start += 500) {
|
|
1749
|
+
const batch = readyApps.slice(start, start + 500);
|
|
1750
|
+
const rows = await db
|
|
1751
|
+
.select({ id: workspaceApps.id })
|
|
1752
|
+
.from(workspaceApps)
|
|
1753
|
+
.where(
|
|
1754
|
+
and(
|
|
1755
|
+
eq(workspaceApps.orgId, orgId),
|
|
1756
|
+
eq(workspaceApps.visibility, "org"),
|
|
1757
|
+
eq(workspaceApps.orgEnabled, true),
|
|
1758
|
+
inArray(
|
|
1759
|
+
workspaceApps.id,
|
|
1760
|
+
batch.map((app) => app.id),
|
|
1761
|
+
),
|
|
1762
|
+
),
|
|
1763
|
+
);
|
|
1764
|
+
for (const row of rows) visibleIds.add(row.id);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
return readyApps.filter((app) => visibleIds.has(app.id));
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1593
1770
|
async function recordPendingWorkspaceApp(input: {
|
|
1594
1771
|
appId: string;
|
|
1595
1772
|
projectId: string | null;
|
|
1596
1773
|
description: string;
|
|
1597
1774
|
sourcePrompt: string;
|
|
1598
1775
|
visibility: WorkspaceAppVisibility;
|
|
1776
|
+
communitySourceId?: string;
|
|
1599
1777
|
branchName?: string | null;
|
|
1600
1778
|
builderUrl?: string | null;
|
|
1601
1779
|
}) {
|
|
@@ -1612,6 +1790,9 @@ async function recordPendingWorkspaceApp(input: {
|
|
|
1612
1790
|
.find(samePendingEntry);
|
|
1613
1791
|
const creatorEmail = currentOwnerEmail();
|
|
1614
1792
|
const existingOwnerEmail = existing?.createdBy ?? existing?.owner;
|
|
1793
|
+
const communitySourceId =
|
|
1794
|
+
normalizeCommunitySourceId(input.communitySourceId) ??
|
|
1795
|
+
normalizeCommunitySourceId(existing?.communitySourceId);
|
|
1615
1796
|
if (
|
|
1616
1797
|
existingOwnerEmail &&
|
|
1617
1798
|
existingOwnerEmail.trim().toLowerCase() !== creatorEmail.toLowerCase()
|
|
@@ -1633,6 +1814,7 @@ async function recordPendingWorkspaceApp(input: {
|
|
|
1633
1814
|
contextId: context?.id ?? null,
|
|
1634
1815
|
contextLabel: context?.label ?? null,
|
|
1635
1816
|
visibility: input.visibility,
|
|
1817
|
+
...(communitySourceId ? { communitySourceId } : {}),
|
|
1636
1818
|
createdBy: existing?.createdBy ?? existing?.owner ?? creatorEmail,
|
|
1637
1819
|
owner: existing?.owner ?? existing?.createdBy ?? creatorEmail,
|
|
1638
1820
|
createdAt: existing?.createdAt || now,
|
|
@@ -1650,12 +1832,14 @@ async function recordPendingWorkspaceApp(input: {
|
|
|
1650
1832
|
|
|
1651
1833
|
await writeWorkspaceAppMetadataOverride({
|
|
1652
1834
|
appId: input.appId,
|
|
1835
|
+
name: null,
|
|
1653
1836
|
description: input.description,
|
|
1654
1837
|
generated: true,
|
|
1655
1838
|
sourcePrompt: input.sourcePrompt,
|
|
1656
1839
|
updatedBy: creatorEmail,
|
|
1657
1840
|
createdBy: next.createdBy,
|
|
1658
1841
|
visibility: input.visibility,
|
|
1842
|
+
communitySourceId,
|
|
1659
1843
|
});
|
|
1660
1844
|
|
|
1661
1845
|
await recordAudit({
|
|
@@ -1700,37 +1884,89 @@ async function readWorkspaceAppsFromGateway(): Promise<WorkspaceAppDiscovery | n
|
|
|
1700
1884
|
}
|
|
1701
1885
|
|
|
1702
1886
|
const requestContext = getRequestContext();
|
|
1887
|
+
const isLocalGateway = isLocalWorkspaceGateway(baseUrl);
|
|
1888
|
+
const requestOrigin = requestContext?.requestOrigin;
|
|
1889
|
+
const isSameOriginGateway = requestOrigin
|
|
1890
|
+
? (() => {
|
|
1891
|
+
try {
|
|
1892
|
+
return new URL(requestOrigin).origin === baseUrl.origin;
|
|
1893
|
+
} catch {
|
|
1894
|
+
// coercion-ok: an invalid request origin cannot prove a self-fetch.
|
|
1895
|
+
return false;
|
|
1896
|
+
}
|
|
1897
|
+
})()
|
|
1898
|
+
: false;
|
|
1899
|
+
if (isSameOriginGateway && !isLocalGateway) return null;
|
|
1900
|
+
|
|
1703
1901
|
const authHeaders: Record<string, string> = {};
|
|
1704
1902
|
if (requestContext?.userEmail) {
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1903
|
+
let orgDomain: string | undefined;
|
|
1904
|
+
let orgSecret: string | undefined;
|
|
1905
|
+
if (requestContext.orgId) {
|
|
1906
|
+
try {
|
|
1907
|
+
orgDomain =
|
|
1908
|
+
(await getOrgDomain(requestContext.orgId))?.trim().toLowerCase() ||
|
|
1909
|
+
undefined;
|
|
1910
|
+
} catch {
|
|
1911
|
+
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
1912
|
+
503,
|
|
1913
|
+
"workspace_gateway_org_domain_unavailable",
|
|
1914
|
+
"Workspace apps gateway organization domain could not be read.",
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1708
1918
|
if (requestContext.orgId && !orgDomain) {
|
|
1709
1919
|
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
1710
1920
|
401,
|
|
1921
|
+
"workspace_gateway_org_domain_missing",
|
|
1711
1922
|
"Workspace apps gateway cannot authenticate without a resolved organization domain.",
|
|
1712
1923
|
);
|
|
1713
1924
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1925
|
+
if (requestContext.orgId) {
|
|
1926
|
+
try {
|
|
1927
|
+
orgSecret =
|
|
1928
|
+
(await getOrgA2ASecret(requestContext.orgId))?.trim() || undefined;
|
|
1929
|
+
} catch {
|
|
1930
|
+
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
1931
|
+
503,
|
|
1932
|
+
"workspace_gateway_org_a2a_secret_unavailable",
|
|
1933
|
+
"Workspace apps gateway organization A2A secret could not be read.",
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
const globalSecret = getGlobalA2ASecret();
|
|
1938
|
+
const allowLocalUnauthenticatedRequest =
|
|
1939
|
+
isLocalGateway && !orgSecret && !globalSecret;
|
|
1940
|
+
if (!orgSecret && !globalSecret && !allowLocalUnauthenticatedRequest) {
|
|
1941
|
+
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
1942
|
+
503,
|
|
1943
|
+
"workspace_gateway_a2a_secret_missing",
|
|
1944
|
+
"Workspace apps gateway cannot authenticate because no A2A secret is configured.",
|
|
1727
1945
|
);
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1946
|
+
}
|
|
1947
|
+
if (!allowLocalUnauthenticatedRequest) {
|
|
1948
|
+
try {
|
|
1949
|
+
const token = await signA2AToken(
|
|
1950
|
+
requestContext.userEmail,
|
|
1951
|
+
orgDomain,
|
|
1952
|
+
orgSecret,
|
|
1953
|
+
{
|
|
1954
|
+
expiresIn: "1m",
|
|
1955
|
+
preferGlobalSecret: false,
|
|
1956
|
+
audience: canonicalA2AAudience(
|
|
1957
|
+
baseUrl.toString(),
|
|
1958
|
+
getConfiguredAppBasePath() || undefined,
|
|
1959
|
+
),
|
|
1960
|
+
},
|
|
1961
|
+
);
|
|
1962
|
+
authHeaders.Authorization = `Bearer ${token}`;
|
|
1963
|
+
} catch {
|
|
1964
|
+
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
1965
|
+
503,
|
|
1966
|
+
"workspace_gateway_token_signing_failed",
|
|
1967
|
+
"Workspace apps gateway could not sign the authenticated request.",
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1734
1970
|
}
|
|
1735
1971
|
}
|
|
1736
1972
|
|
|
@@ -1760,9 +1996,34 @@ async function readWorkspaceAppsFromGateway(): Promise<WorkspaceAppDiscovery | n
|
|
|
1760
1996
|
const protectedRedirect = protectionHeaders["x-vercel-protection-bypass"]
|
|
1761
1997
|
? { redirect: "manual" as const }
|
|
1762
1998
|
: {};
|
|
1999
|
+
const readAppsResponse = async (response: Response) => {
|
|
2000
|
+
let payload: unknown;
|
|
2001
|
+
try {
|
|
2002
|
+
payload = await response.json();
|
|
2003
|
+
} catch {
|
|
2004
|
+
throw new ActionContractError(
|
|
2005
|
+
"Workspace apps gateway returned an unreadable response.",
|
|
2006
|
+
{
|
|
2007
|
+
statusCode: 503,
|
|
2008
|
+
errorCode: "workspace_gateway_invalid_response",
|
|
2009
|
+
},
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
const apps = parseWorkspaceAppsGatewayResponse(payload);
|
|
2013
|
+
if (!apps) {
|
|
2014
|
+
throw new ActionContractError(
|
|
2015
|
+
"Workspace apps gateway returned an invalid response.",
|
|
2016
|
+
{
|
|
2017
|
+
statusCode: 503,
|
|
2018
|
+
errorCode: "workspace_gateway_invalid_response",
|
|
2019
|
+
},
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
return { apps };
|
|
2023
|
+
};
|
|
1763
2024
|
|
|
1764
2025
|
try {
|
|
1765
|
-
if (
|
|
2026
|
+
if (isLocalGateway) {
|
|
1766
2027
|
const localResponse = await fetch(
|
|
1767
2028
|
gatewayUrl(WORKSPACE_APPS_GATEWAY_PATH),
|
|
1768
2029
|
{
|
|
@@ -1772,29 +2033,25 @@ async function readWorkspaceAppsFromGateway(): Promise<WorkspaceAppDiscovery | n
|
|
|
1772
2033
|
},
|
|
1773
2034
|
);
|
|
1774
2035
|
if (localResponse.status === 401 || localResponse.status === 403) {
|
|
1775
|
-
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
2036
|
+
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
2037
|
+
localResponse.status,
|
|
2038
|
+
"workspace_gateway_rejected",
|
|
2039
|
+
);
|
|
1776
2040
|
}
|
|
1777
|
-
if (localResponse.
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
2041
|
+
if (localResponse.status !== 404 && localResponse.status !== 200) {
|
|
2042
|
+
throw new ActionContractError(
|
|
2043
|
+
`Workspace apps gateway returned HTTP ${localResponse.status}.`,
|
|
2044
|
+
{
|
|
2045
|
+
statusCode: 503,
|
|
2046
|
+
errorCode: `workspace_gateway_http_${localResponse.status}`,
|
|
2047
|
+
},
|
|
1782
2048
|
);
|
|
1783
|
-
|
|
2049
|
+
}
|
|
2050
|
+
if (localResponse.status === 200) {
|
|
2051
|
+
return await readAppsResponse(localResponse);
|
|
1784
2052
|
}
|
|
1785
2053
|
}
|
|
1786
2054
|
|
|
1787
|
-
const requestOrigin = requestContext?.requestOrigin;
|
|
1788
|
-
const isSameOriginGateway = requestOrigin
|
|
1789
|
-
? (() => {
|
|
1790
|
-
try {
|
|
1791
|
-
return new URL(requestOrigin).origin === baseUrl.origin;
|
|
1792
|
-
} catch {
|
|
1793
|
-
// coercion-ok: an invalid request origin cannot prove a self-fetch.
|
|
1794
|
-
return false;
|
|
1795
|
-
}
|
|
1796
|
-
})()
|
|
1797
|
-
: false;
|
|
1798
2055
|
if (isSameOriginGateway || !authHeaders.Authorization) return null;
|
|
1799
2056
|
const actionUrl = gatewayUrl(WORKSPACE_APPS_ACTION_PATH);
|
|
1800
2057
|
actionUrl.searchParams.set("includeAgentCards", "false");
|
|
@@ -1805,18 +2062,31 @@ async function readWorkspaceAppsFromGateway(): Promise<WorkspaceAppDiscovery | n
|
|
|
1805
2062
|
signal: controller.signal,
|
|
1806
2063
|
});
|
|
1807
2064
|
if (actionResponse.status === 401 || actionResponse.status === 403) {
|
|
1808
|
-
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
2065
|
+
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
2066
|
+
actionResponse.status,
|
|
2067
|
+
"workspace_gateway_rejected",
|
|
2068
|
+
);
|
|
1809
2069
|
}
|
|
1810
|
-
if (
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2070
|
+
if (actionResponse.status === 404) return null;
|
|
2071
|
+
if (actionResponse.status !== 200) {
|
|
2072
|
+
throw new ActionContractError(
|
|
2073
|
+
`Workspace apps gateway returned HTTP ${actionResponse.status}.`,
|
|
2074
|
+
{
|
|
2075
|
+
statusCode: 503,
|
|
2076
|
+
errorCode: `workspace_gateway_http_${actionResponse.status}`,
|
|
2077
|
+
},
|
|
2078
|
+
);
|
|
2079
|
+
}
|
|
2080
|
+
return await readAppsResponse(actionResponse);
|
|
1817
2081
|
} catch (error) {
|
|
1818
|
-
if (error instanceof
|
|
1819
|
-
|
|
2082
|
+
if (error instanceof ActionContractError) throw error;
|
|
2083
|
+
throw new ActionContractError(
|
|
2084
|
+
"Workspace apps gateway could not be reached.",
|
|
2085
|
+
{
|
|
2086
|
+
statusCode: 503,
|
|
2087
|
+
errorCode: "workspace_gateway_unavailable",
|
|
2088
|
+
},
|
|
2089
|
+
);
|
|
1820
2090
|
} finally {
|
|
1821
2091
|
clearTimeout(timeout);
|
|
1822
2092
|
}
|
|
@@ -1989,10 +2259,15 @@ export async function updateWorkspaceAppMetadata(input: {
|
|
|
1989
2259
|
appId: string;
|
|
1990
2260
|
name?: string | null;
|
|
1991
2261
|
description?: string | null;
|
|
2262
|
+
whenToUse?: string[] | null;
|
|
1992
2263
|
}): Promise<WorkspaceAppSummary> {
|
|
1993
2264
|
const appId = input.appId.trim();
|
|
1994
2265
|
assertValidWorkspaceAppId(appId);
|
|
1995
2266
|
requireWorkspaceAppOrgId();
|
|
2267
|
+
const role = await requestOwnerRole();
|
|
2268
|
+
if (role !== "owner" && role !== "admin") {
|
|
2269
|
+
throw new WorkspaceAppMetadataAuthorizationError();
|
|
2270
|
+
}
|
|
1996
2271
|
|
|
1997
2272
|
const apps = await listWorkspaceApps({
|
|
1998
2273
|
includeAgentCards: false,
|
|
@@ -2001,27 +2276,42 @@ export async function updateWorkspaceAppMetadata(input: {
|
|
|
2001
2276
|
const app = apps.find((candidate) => candidate.id === appId);
|
|
2002
2277
|
if (!app) throw new Error(`Workspace app "${appId}" was not found.`);
|
|
2003
2278
|
|
|
2004
|
-
const
|
|
2005
|
-
const
|
|
2006
|
-
input.description
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2279
|
+
const nameProvided = Object.hasOwn(input, "name") && input.name !== undefined;
|
|
2280
|
+
const descriptionProvided =
|
|
2281
|
+
Object.hasOwn(input, "description") && input.description !== undefined;
|
|
2282
|
+
const whenToUseProvided =
|
|
2283
|
+
Object.hasOwn(input, "whenToUse") && input.whenToUse !== undefined;
|
|
2284
|
+
const name = nameProvided ? (input.name?.trim() ?? null) : app.name;
|
|
2285
|
+
const description = descriptionProvided
|
|
2286
|
+
? (input.description?.trim() ?? null)
|
|
2287
|
+
: (app.description ?? undefined);
|
|
2288
|
+
const whenToUse = whenToUseProvided ? (input.whenToUse ?? []) : app.whenToUse;
|
|
2289
|
+
const metadataOverride: Parameters<
|
|
2290
|
+
typeof writeWorkspaceAppMetadataOverride
|
|
2291
|
+
>[0] = {
|
|
2010
2292
|
appId,
|
|
2011
|
-
name,
|
|
2012
|
-
description,
|
|
2013
|
-
generated: false,
|
|
2014
2293
|
updatedBy: currentOwnerEmail(),
|
|
2294
|
+
};
|
|
2295
|
+
if (nameProvided) metadataOverride.name = input.name?.trim() ?? null;
|
|
2296
|
+
if (descriptionProvided) {
|
|
2297
|
+
metadataOverride.description = input.description?.trim() ?? null;
|
|
2298
|
+
}
|
|
2299
|
+
if (whenToUseProvided) metadataOverride.whenToUse = input.whenToUse ?? [];
|
|
2300
|
+
if (nameProvided || descriptionProvided) metadataOverride.generated = false;
|
|
2301
|
+
await writeWorkspaceAppMetadataOverride({
|
|
2302
|
+
...metadataOverride,
|
|
2015
2303
|
});
|
|
2304
|
+
invalidateOrgAppsDirectoryCache();
|
|
2016
2305
|
|
|
2017
2306
|
await recordAudit({
|
|
2018
2307
|
action: "workspace-app.metadata-updated",
|
|
2019
2308
|
targetType: "workspace-app",
|
|
2020
2309
|
targetId: appId,
|
|
2021
|
-
summary: `Updated workspace app details for ${name}`,
|
|
2310
|
+
summary: `Updated workspace app details for ${name ?? app.name}`,
|
|
2022
2311
|
metadata: {
|
|
2023
|
-
name,
|
|
2312
|
+
name: name ?? app.name,
|
|
2024
2313
|
descriptionConfigured: !!description,
|
|
2314
|
+
whenToUseCount: whenToUse?.length ?? 0,
|
|
2025
2315
|
},
|
|
2026
2316
|
});
|
|
2027
2317
|
|
|
@@ -2031,11 +2321,19 @@ export async function updateWorkspaceAppMetadata(input: {
|
|
|
2031
2321
|
includeArchived: true,
|
|
2032
2322
|
})
|
|
2033
2323
|
).find((candidate) => candidate.id === appId);
|
|
2034
|
-
return
|
|
2324
|
+
return (
|
|
2325
|
+
updated ?? {
|
|
2326
|
+
...app,
|
|
2327
|
+
name: name ?? app.name,
|
|
2328
|
+
description: description ?? app.description,
|
|
2329
|
+
whenToUse,
|
|
2330
|
+
}
|
|
2331
|
+
);
|
|
2035
2332
|
}
|
|
2036
2333
|
|
|
2037
|
-
|
|
2334
|
+
async function listWorkspaceAppsWithPrincipal(
|
|
2038
2335
|
options: ListWorkspaceAppsOptions = {},
|
|
2336
|
+
principal: "user" | "organization" = "user",
|
|
2039
2337
|
): Promise<WorkspaceAppSummary[]> {
|
|
2040
2338
|
const finalize = async (
|
|
2041
2339
|
apps: WorkspaceAppSummary[],
|
|
@@ -2048,9 +2346,14 @@ export async function listWorkspaceApps(
|
|
|
2048
2346
|
const listed = options.includeArchived
|
|
2049
2347
|
? recorded
|
|
2050
2348
|
: recorded.filter((app) => !app.archived);
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2053
|
-
|
|
2349
|
+
const candidates = filterAppsByAudience(listed, options.audience);
|
|
2350
|
+
const visible =
|
|
2351
|
+
principal === "organization"
|
|
2352
|
+
? await filterOrganizationVisibleWorkspaceApps(
|
|
2353
|
+
candidates,
|
|
2354
|
+
requireWorkspaceAppOrgId(),
|
|
2355
|
+
)
|
|
2356
|
+
: await filterWorkspaceAppsByAccess(candidates);
|
|
2054
2357
|
return maybeIncludeAgentCards(visible, options);
|
|
2055
2358
|
};
|
|
2056
2359
|
let gatewayDenial: WorkspaceAppsGatewayAuthorizationError | null = null;
|
|
@@ -2064,6 +2367,9 @@ export async function listWorkspaceApps(
|
|
|
2064
2367
|
if (gatewayApps) {
|
|
2065
2368
|
return finalize(gatewayApps.apps);
|
|
2066
2369
|
}
|
|
2370
|
+
if (gatewayDenial && gatewayDenial.statusCode >= 500) {
|
|
2371
|
+
throw gatewayDenial;
|
|
2372
|
+
}
|
|
2067
2373
|
const unverified = gatewayDenial !== null;
|
|
2068
2374
|
|
|
2069
2375
|
const workspaceRoot = findWorkspaceRoot();
|
|
@@ -2088,7 +2394,9 @@ export async function listWorkspaceApps(
|
|
|
2088
2394
|
// means the registry will not show this reader its apps, so they get the
|
|
2089
2395
|
// deployment's own list instead, and none of it is recorded. (A manifest
|
|
2090
2396
|
// answers earlier for both, with the warning above.)
|
|
2091
|
-
if (gatewayDenial
|
|
2397
|
+
if (gatewayDenial && gatewayDenial.statusCode !== 403) {
|
|
2398
|
+
throw gatewayDenial;
|
|
2399
|
+
}
|
|
2092
2400
|
warnWorkspaceAppsGatewayDenial(gatewayDenial, "deployment's own app list");
|
|
2093
2401
|
|
|
2094
2402
|
if (!workspaceRoot) {
|
|
@@ -2117,6 +2425,22 @@ export async function listWorkspaceApps(
|
|
|
2117
2425
|
return finalize([], { persist: !unverified });
|
|
2118
2426
|
}
|
|
2119
2427
|
|
|
2428
|
+
export function listWorkspaceApps(
|
|
2429
|
+
options: ListWorkspaceAppsOptions = {},
|
|
2430
|
+
): Promise<WorkspaceAppSummary[]> {
|
|
2431
|
+
return listWorkspaceAppsWithPrincipal(options, "user");
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
export async function listOrganizationVisibleWorkspaceApps(): Promise<
|
|
2435
|
+
WorkspaceAppSummary[]
|
|
2436
|
+
> {
|
|
2437
|
+
requireWorkspaceAppOrgId();
|
|
2438
|
+
return listWorkspaceAppsWithPrincipal(
|
|
2439
|
+
{ includeAgentCards: false, includeArchived: false, audience: "all" },
|
|
2440
|
+
"organization",
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2120
2444
|
const ADDABLE_TEMPLATES: AvailableWorkspaceTemplate[] = [
|
|
2121
2445
|
{
|
|
2122
2446
|
name: "mail",
|
|
@@ -2282,6 +2606,8 @@ export async function scaffoldWorkspaceAppFromTemplate(input: {
|
|
|
2282
2606
|
try {
|
|
2283
2607
|
await writeWorkspaceAppMetadataOverride({
|
|
2284
2608
|
appId,
|
|
2609
|
+
name: null,
|
|
2610
|
+
description: null,
|
|
2285
2611
|
updatedBy: creatorEmail,
|
|
2286
2612
|
createdBy: creatorEmail,
|
|
2287
2613
|
visibility,
|
|
@@ -2318,6 +2644,7 @@ export async function scaffoldWorkspaceAppFromTemplate(input: {
|
|
|
2318
2644
|
visibility,
|
|
2319
2645
|
},
|
|
2320
2646
|
]);
|
|
2647
|
+
invalidateOrgAppsDirectoryCache();
|
|
2321
2648
|
|
|
2322
2649
|
return { appId, template, output };
|
|
2323
2650
|
}
|
|
@@ -2512,8 +2839,8 @@ async function requestOwnerRole(): Promise<string | null> {
|
|
|
2512
2839
|
});
|
|
2513
2840
|
const role = (rows[0] as any)?.role;
|
|
2514
2841
|
return typeof role === "string" ? role : null;
|
|
2515
|
-
} catch {
|
|
2516
|
-
|
|
2842
|
+
} catch (error) {
|
|
2843
|
+
throw new AppCreationOwnerRoleUnavailableError(error);
|
|
2517
2844
|
}
|
|
2518
2845
|
}
|
|
2519
2846
|
|
|
@@ -2853,6 +3180,12 @@ export interface AppCreationLocalAgentResult {
|
|
|
2853
3180
|
appId: string;
|
|
2854
3181
|
prompt: string;
|
|
2855
3182
|
message: string;
|
|
3183
|
+
attachments?: Array<{
|
|
3184
|
+
type: "file";
|
|
3185
|
+
name: string;
|
|
3186
|
+
contentType: string;
|
|
3187
|
+
text: string;
|
|
3188
|
+
}>;
|
|
2856
3189
|
}
|
|
2857
3190
|
|
|
2858
3191
|
export interface AppCreationComingSoonResult {
|
|
@@ -2897,6 +3230,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2897
3230
|
secretIds?: string[];
|
|
2898
3231
|
resourceIds?: string[];
|
|
2899
3232
|
attachments?: BuilderAgentAttachment[];
|
|
3233
|
+
communitySourceId?: string;
|
|
2900
3234
|
}): Promise<StartWorkspaceAppCreationResult> {
|
|
2901
3235
|
const initial = buildWorkspaceAppPrompt({
|
|
2902
3236
|
prompt: input.prompt,
|
|
@@ -2904,6 +3238,12 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2904
3238
|
description: input.description,
|
|
2905
3239
|
template: input.template,
|
|
2906
3240
|
});
|
|
3241
|
+
if (
|
|
3242
|
+
input.communitySourceId !== undefined &&
|
|
3243
|
+
!normalizeCommunitySourceId(input.communitySourceId)
|
|
3244
|
+
) {
|
|
3245
|
+
throw new Error("Community source ids must be valid bounded identifiers.");
|
|
3246
|
+
}
|
|
2907
3247
|
assertValidWorkspaceAppId(initial.appId);
|
|
2908
3248
|
requireWorkspaceAppOrgId();
|
|
2909
3249
|
normalizeBuilderAgentAttachments(input.attachments);
|
|
@@ -2955,15 +3295,39 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2955
3295
|
generateWorkspaceAppDescription(input.prompt, built.appId);
|
|
2956
3296
|
|
|
2957
3297
|
if (isLocal) {
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
3298
|
+
if (input.communitySourceId) {
|
|
3299
|
+
try {
|
|
3300
|
+
await reservePendingWorkspaceApp({
|
|
3301
|
+
appId: built.appId,
|
|
3302
|
+
description: appDescription,
|
|
3303
|
+
projectId: null,
|
|
3304
|
+
visibility: creationVisibility,
|
|
3305
|
+
communitySourceId: input.communitySourceId,
|
|
3306
|
+
});
|
|
3307
|
+
} catch (err) {
|
|
3308
|
+
if (err instanceof WorkspaceAppIdTakenError)
|
|
3309
|
+
return appIdTakenResult(err);
|
|
3310
|
+
throw err;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
try {
|
|
3314
|
+
await writeWorkspaceAppMetadataOverride({
|
|
3315
|
+
appId: built.appId,
|
|
3316
|
+
name: null,
|
|
3317
|
+
description: appDescription,
|
|
3318
|
+
generated: true,
|
|
3319
|
+
sourcePrompt: input.prompt,
|
|
3320
|
+
updatedBy: currentOwnerEmail(),
|
|
3321
|
+
createdBy: currentOwnerEmail(),
|
|
3322
|
+
visibility: creationVisibility,
|
|
3323
|
+
communitySourceId: input.communitySourceId,
|
|
3324
|
+
});
|
|
3325
|
+
} catch (error) {
|
|
3326
|
+
if (input.communitySourceId) {
|
|
3327
|
+
await releasePendingWorkspaceAppReservation(built.appId);
|
|
3328
|
+
}
|
|
3329
|
+
throw error;
|
|
3330
|
+
}
|
|
2967
3331
|
await requestSelectedVaultKeys({
|
|
2968
3332
|
appId: built.appId,
|
|
2969
3333
|
selectedKeys,
|
|
@@ -2976,6 +3340,22 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2976
3340
|
mode: "local-agent",
|
|
2977
3341
|
appId: built.appId,
|
|
2978
3342
|
prompt,
|
|
3343
|
+
...(input.communitySourceId && input.attachments?.length
|
|
3344
|
+
? {
|
|
3345
|
+
attachments: input.attachments.flatMap((attachment) =>
|
|
3346
|
+
attachment.type === "upload" && attachment.text !== undefined
|
|
3347
|
+
? [
|
|
3348
|
+
{
|
|
3349
|
+
type: "file" as const,
|
|
3350
|
+
name: attachment.name,
|
|
3351
|
+
contentType: attachment.contentType,
|
|
3352
|
+
text: attachment.text,
|
|
3353
|
+
},
|
|
3354
|
+
]
|
|
3355
|
+
: [],
|
|
3356
|
+
),
|
|
3357
|
+
}
|
|
3358
|
+
: {}),
|
|
2979
3359
|
message:
|
|
2980
3360
|
"Use the local code agent to create this app in the workspace, then open it from /dispatch/apps.",
|
|
2981
3361
|
};
|
|
@@ -3018,12 +3398,34 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
3018
3398
|
description: appDescription,
|
|
3019
3399
|
projectId: builderProjectId,
|
|
3020
3400
|
visibility: creationVisibility,
|
|
3401
|
+
...(normalizeCommunitySourceId(input.communitySourceId)
|
|
3402
|
+
? { communitySourceId: input.communitySourceId }
|
|
3403
|
+
: {}),
|
|
3021
3404
|
});
|
|
3022
3405
|
} catch (err) {
|
|
3023
3406
|
if (err instanceof WorkspaceAppIdTakenError) return appIdTakenResult(err);
|
|
3024
3407
|
throw err;
|
|
3025
3408
|
}
|
|
3026
3409
|
|
|
3410
|
+
if (input.communitySourceId) {
|
|
3411
|
+
try {
|
|
3412
|
+
await writeWorkspaceAppMetadataOverride({
|
|
3413
|
+
appId: built.appId,
|
|
3414
|
+
communitySourceId: input.communitySourceId,
|
|
3415
|
+
});
|
|
3416
|
+
} catch (error) {
|
|
3417
|
+
try {
|
|
3418
|
+
await releasePendingWorkspaceAppReservation(built.appId);
|
|
3419
|
+
} catch (cleanupError) {
|
|
3420
|
+
console.warn(
|
|
3421
|
+
"[dispatch] failed to release pending workspace app reservation",
|
|
3422
|
+
cleanupError,
|
|
3423
|
+
);
|
|
3424
|
+
}
|
|
3425
|
+
throw error;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3027
3429
|
let result: {
|
|
3028
3430
|
branchName: string;
|
|
3029
3431
|
url: string;
|
|
@@ -3063,6 +3465,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
3063
3465
|
description: appDescription,
|
|
3064
3466
|
sourcePrompt: input.prompt,
|
|
3065
3467
|
visibility: creationVisibility,
|
|
3468
|
+
communitySourceId: input.communitySourceId,
|
|
3066
3469
|
branchName: result.branchName,
|
|
3067
3470
|
builderUrl: result.url,
|
|
3068
3471
|
});
|