@agent-native/dispatch 0.40.30-nightly-20261021093329 → 0.40.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +6 -47
  2. package/dist/actions/connect-external-agent.d.ts.map +1 -1
  3. package/dist/actions/connect-external-agent.js +12 -13
  4. package/dist/actions/connect-external-agent.js.map +1 -1
  5. package/dist/actions/create-workspace-resource.d.ts +13 -13
  6. package/dist/actions/delete-destination.d.ts +12 -12
  7. package/dist/actions/delete-workspace-resource.d.ts +13 -13
  8. package/dist/actions/import-agent.d.ts +13 -13
  9. package/dist/actions/index.d.ts.map +1 -1
  10. package/dist/actions/index.js +8 -0
  11. package/dist/actions/index.js.map +1 -1
  12. package/dist/actions/inspect-connect-agent-card.d.ts +5 -0
  13. package/dist/actions/inspect-connect-agent-card.d.ts.map +1 -0
  14. package/dist/actions/inspect-connect-agent-card.js +15 -0
  15. package/dist/actions/inspect-connect-agent-card.js.map +1 -0
  16. package/dist/actions/install-curated-community-app.d.ts +36 -0
  17. package/dist/actions/install-curated-community-app.d.ts.map +1 -0
  18. package/dist/actions/install-curated-community-app.js +26 -0
  19. package/dist/actions/install-curated-community-app.js.map +1 -0
  20. package/dist/actions/list-connect-marketplace-apps.d.ts +3 -0
  21. package/dist/actions/list-connect-marketplace-apps.d.ts.map +1 -0
  22. package/dist/actions/list-connect-marketplace-apps.js +15 -0
  23. package/dist/actions/list-connect-marketplace-apps.js.map +1 -0
  24. package/dist/actions/list-connected-agents.d.ts +2 -0
  25. package/dist/actions/list-curated-community-apps.d.ts +3 -0
  26. package/dist/actions/list-curated-community-apps.d.ts.map +1 -0
  27. package/dist/actions/list-curated-community-apps.js +10 -0
  28. package/dist/actions/list-curated-community-apps.js.map +1 -0
  29. package/dist/actions/provider-api-register.d.ts +14 -14
  30. package/dist/actions/remix-workspace-template.d.ts +6 -0
  31. package/dist/actions/update-workspace-app-metadata.d.ts +1 -0
  32. package/dist/actions/update-workspace-app-metadata.js +26 -5
  33. package/dist/actions/update-workspace-app-metadata.js.map +1 -1
  34. package/dist/components/community-apps-section.d.ts +2 -0
  35. package/dist/components/community-apps-section.d.ts.map +1 -0
  36. package/dist/components/community-apps-section.js +90 -0
  37. package/dist/components/community-apps-section.js.map +1 -0
  38. package/dist/components/workspace-app-card.d.ts.map +1 -1
  39. package/dist/components/workspace-app-card.js +63 -9
  40. package/dist/components/workspace-app-card.js.map +1 -1
  41. package/dist/lib/connect-apps.d.ts +1 -2
  42. package/dist/lib/connect-apps.d.ts.map +1 -1
  43. package/dist/lib/connect-apps.js +41 -65
  44. package/dist/lib/connect-apps.js.map +1 -1
  45. package/dist/lib/workspace-apps.d.ts +1 -0
  46. package/dist/lib/workspace-apps.d.ts.map +1 -1
  47. package/dist/lib/workspace-apps.js.map +1 -1
  48. package/dist/routes/pages/apps.d.ts.map +1 -1
  49. package/dist/routes/pages/apps.js +2 -1
  50. package/dist/routes/pages/apps.js.map +1 -1
  51. package/dist/routes/pages/connect.d.ts.map +1 -1
  52. package/dist/routes/pages/connect.js +5 -7
  53. package/dist/routes/pages/connect.js.map +1 -1
  54. package/dist/server/index.d.ts +2 -0
  55. package/dist/server/index.d.ts.map +1 -1
  56. package/dist/server/index.js +2 -0
  57. package/dist/server/index.js.map +1 -1
  58. package/dist/server/lib/app-creation-store.d.ts +11 -1
  59. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  60. package/dist/server/lib/app-creation-store.js +258 -39
  61. package/dist/server/lib/app-creation-store.js.map +1 -1
  62. package/dist/server/lib/community-apps.d.ts +45 -0
  63. package/dist/server/lib/community-apps.d.ts.map +1 -0
  64. package/dist/server/lib/community-apps.js +267 -0
  65. package/dist/server/lib/community-apps.js.map +1 -0
  66. package/dist/server/lib/community-source.d.ts +19 -0
  67. package/dist/server/lib/community-source.d.ts.map +1 -0
  68. package/dist/server/lib/community-source.js +118 -0
  69. package/dist/server/lib/community-source.js.map +1 -0
  70. package/dist/server/lib/connect-apps.d.ts +4 -0
  71. package/dist/server/lib/connect-apps.d.ts.map +1 -0
  72. package/dist/server/lib/connect-apps.js +99 -0
  73. package/dist/server/lib/connect-apps.js.map +1 -0
  74. package/dist/server/lib/org-apps-directory-cache.d.ts +20 -0
  75. package/dist/server/lib/org-apps-directory-cache.d.ts.map +1 -0
  76. package/dist/server/lib/org-apps-directory-cache.js +44 -0
  77. package/dist/server/lib/org-apps-directory-cache.js.map +1 -0
  78. package/dist/server/plugins/integrations.js +3 -3
  79. package/dist/server/plugins/integrations.js.map +1 -1
  80. package/dist/shared/labs.d.ts +2 -0
  81. package/dist/shared/labs.d.ts.map +1 -0
  82. package/dist/shared/labs.js +8 -0
  83. package/dist/shared/labs.js.map +1 -0
  84. package/package.json +5 -4
  85. package/src/actions/connect-external-agent.spec.ts +101 -3
  86. package/src/actions/connect-external-agent.ts +12 -11
  87. package/src/actions/index.spec.ts +13 -0
  88. package/src/actions/index.ts +8 -0
  89. package/src/actions/inspect-connect-agent-card.ts +17 -0
  90. package/src/actions/install-curated-community-app.ts +32 -0
  91. package/src/actions/list-connect-marketplace-apps.ts +17 -0
  92. package/src/actions/list-curated-community-apps.ts +12 -0
  93. package/src/actions/update-workspace-app-metadata.spec.ts +25 -0
  94. package/src/actions/update-workspace-app-metadata.ts +48 -24
  95. package/src/components/community-apps-section.tsx +224 -0
  96. package/src/components/workspace-app-card.spec.tsx +226 -1
  97. package/src/components/workspace-app-card.tsx +115 -12
  98. package/src/lib/connect-apps.spec.ts +118 -66
  99. package/src/lib/connect-apps.ts +47 -82
  100. package/src/lib/workspace-apps.ts +1 -0
  101. package/src/routes/pages/apps.spec.tsx +201 -1
  102. package/src/routes/pages/apps.tsx +3 -0
  103. package/src/routes/pages/connect.spec.tsx +53 -6
  104. package/src/routes/pages/connect.tsx +14 -11
  105. package/src/server/index.ts +9 -0
  106. package/src/server/lib/app-creation-store.spec.ts +539 -11
  107. package/src/server/lib/app-creation-store.ts +332 -41
  108. package/src/server/lib/community-apps.spec.ts +367 -0
  109. package/src/server/lib/community-apps.ts +372 -0
  110. package/src/server/lib/community-source.spec.ts +151 -0
  111. package/src/server/lib/community-source.ts +186 -0
  112. package/src/server/lib/connect-apps.ts +149 -0
  113. package/src/server/lib/org-apps-directory-cache.spec.ts +71 -0
  114. package/src/server/lib/org-apps-directory-cache.ts +66 -0
  115. package/src/server/plugins/integrations.spec.ts +60 -0
  116. package/src/server/plugins/integrations.ts +3 -3
  117. package/src/shared/labs.ts +9 -0
@@ -0,0 +1,45 @@
1
+ import type { StartWorkspaceAppCreationResult, WorkspaceAppSummary } from "./app-creation-store.js";
2
+ import { type PinnedCommunitySource } from "./community-source.js";
3
+ export interface CuratedCommunityAppSource extends PinnedCommunitySource {
4
+ id: string;
5
+ name: string;
6
+ description: string;
7
+ appId: string;
8
+ license: string;
9
+ provenance: string;
10
+ }
11
+ export interface CuratedCommunityApp extends CuratedCommunityAppSource {
12
+ installed: boolean;
13
+ pending: boolean;
14
+ installable: boolean;
15
+ installUnavailableReason?: "workspace-required" | "app-id-conflict" | "builder-install-unverified";
16
+ }
17
+ export declare const CURATED_COMMUNITY_APP_SOURCES: readonly CuratedCommunityAppSource[];
18
+ export declare function listCuratedCommunityApps(): Promise<CuratedCommunityApp[]>;
19
+ declare function existingInstallResult(source: CuratedCommunityAppSource, app: WorkspaceAppSummary): {
20
+ mode: "already-installed" | "pending";
21
+ source: CuratedCommunityAppSource;
22
+ app: {
23
+ id: string;
24
+ name: string;
25
+ path: string;
26
+ url: string | null;
27
+ status: "pending" | "ready";
28
+ statusLabel: string | null;
29
+ builderUrl: string | null;
30
+ branchName: string | null;
31
+ };
32
+ };
33
+ export declare function installCuratedCommunityApp(input: {
34
+ sourceId: string;
35
+ appId?: string;
36
+ }): Promise<ReturnType<typeof existingInstallResult> | (StartWorkspaceAppCreationResult & {
37
+ mode: Exclude<StartWorkspaceAppCreationResult["mode"], "app-id-taken">;
38
+ source: CuratedCommunityAppSource;
39
+ }) | (Extract<StartWorkspaceAppCreationResult, {
40
+ mode: "app-id-taken";
41
+ }> & {
42
+ source: CuratedCommunityAppSource;
43
+ })>;
44
+ export {};
45
+ //# sourceMappingURL=community-apps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"community-apps.d.ts","sourceRoot":"","sources":["../../../src/server/lib/community-apps.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,+BAA+B,EAC/B,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAQ/B,MAAM,WAAW,yBAA0B,SAAQ,qBAAqB;IACtE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAoB,SAAQ,yBAAyB;IACpE,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,wBAAwB,CAAC,EACrB,oBAAoB,GACpB,iBAAiB,GACjB,4BAA4B,CAAC;CAClC;AAED,eAAO,MAAM,6BAA6B,EAAE,SAAS,yBAAyB,EAgE3E,CAAC;AAsEJ,wBAAsB,wBAAwB,IAAI,OAAO,CACvD,mBAAmB,EAAE,CACtB,CA8BA;AASD,iBAAS,qBAAqB,CAC5B,MAAM,EAAE,yBAAyB,EACjC,GAAG,EAAE,mBAAmB;;;;QASpB,EAAE;QACF,IAAI;QACJ,IAAI;QACJ,GAAG;QACH,MAAM;QACN,WAAW;QACX,UAAU;QACV,UAAU;;EAGf;AAgBD,wBAAsB,0BAA0B,CAAC,KAAK,EAAE;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CACP,UAAU,CAAC,OAAO,qBAAqB,CAAC,GACxC,CAAC,+BAA+B,GAAG;IACjC,IAAI,EAAE,OAAO,CAAC,+BAA+B,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC;IACvE,MAAM,EAAE,yBAAyB,CAAC;CACnC,CAAC,GACF,CAAC,OAAO,CAAC,+BAA+B,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC,GAAG;IACpE,MAAM,EAAE,yBAAyB,CAAC;CACnC,CAAC,CACL,CA8FA"}
@@ -0,0 +1,267 @@
1
+ import { fail } from "@agent-native/core/action";
2
+ import { getUserLabEnabled } from "@agent-native/core/labs/server";
3
+ import { getWorkspaceAppIdValidationError, normalizeWorkspaceAppId, } from "@agent-native/core/shared";
4
+ import { BUILTIN_AGENT_CATALOG_IDS, HIDDEN_FIRST_PARTY_AGENT_IDS, } from "@agent-native/core/shared/first-party-agents";
5
+ import { COMMUNITY_APPS_LAB } from "../../shared/labs.js";
6
+ import { getAppCreationSettings, getWorkspaceInfo, isLocalAppCreationRuntime, listWorkspaceApps, startWorkspaceAppCreation, } from "./app-creation-store.js";
7
+ import { loadPinnedCommunitySourceAttachments, } from "./community-source.js";
8
+ import { CURATED_WORKSPACE_TEMPLATES } from "./curated-workspace-templates.js";
9
+ import { currentOrgId, currentOwnerEmail, recordAudit, } from "./dispatch-store.js";
10
+ export const CURATED_COMMUNITY_APP_SOURCES = [
11
+ {
12
+ id: "community-qr",
13
+ name: "QR",
14
+ description: "Generate public PNG or SVG QR images from links or text.",
15
+ appId: "community-qr",
16
+ repo: "BuilderIO/agent-native",
17
+ ref: "main",
18
+ license: "MIT",
19
+ provenance: "Reviewed Agent-Native Framework template, sanitized for independent installation.",
20
+ templatePath: "templates/community/qr",
21
+ files: [
22
+ {
23
+ path: "AGENTS.md",
24
+ blobSha: "7dc9e18bc426dfd21526f91225cc615759450816",
25
+ },
26
+ {
27
+ path: "LICENSE",
28
+ blobSha: "69c7182efb3eb1a8b7f6fd5989f3e4fd737d0645",
29
+ },
30
+ {
31
+ path: "README.md",
32
+ blobSha: "e05e72bb9213732f56ccbc96f7dcb1c3a064cb19",
33
+ },
34
+ {
35
+ path: "actions/generate-qr-code.spec.ts",
36
+ blobSha: "3459217b7252c6ee0076c68cfdb83001cc74f29e",
37
+ },
38
+ {
39
+ path: "actions/generate-qr-code.ts",
40
+ blobSha: "98d30e218bfed796d5e402d5ce19a8626d8da0cf",
41
+ },
42
+ {
43
+ path: "actions/run.ts",
44
+ blobSha: "b46ab15b667c34741364ba08f010f7aca9d0f02e",
45
+ },
46
+ {
47
+ path: "agent-native.json",
48
+ blobSha: "accc32476792b84557f69e2b48b317c27b5546dc",
49
+ },
50
+ {
51
+ path: "package.json",
52
+ blobSha: "c7803d346368f927621329b2acbf5f6cc744dc18",
53
+ },
54
+ {
55
+ path: "server/db/index.ts",
56
+ blobSha: "17007b456530f75a72e7209bdfe9f4bd4c9224e0",
57
+ },
58
+ {
59
+ path: "server/db/schema.ts",
60
+ blobSha: "2e4cd01c5f46463e78bc7beb6e954f3e526c0b57",
61
+ },
62
+ {
63
+ path: "server/plugins/db.ts",
64
+ blobSha: "78a5b431c1ad1ea14f593123c090f5376a36df10",
65
+ },
66
+ {
67
+ path: "tsconfig.json",
68
+ blobSha: "5267473a32dcab8e5dc2c9d9a720afed7f40174c",
69
+ },
70
+ ],
71
+ },
72
+ ];
73
+ const communitySourceById = new Map(CURATED_COMMUNITY_APP_SOURCES.map((source) => [source.id, source]));
74
+ function requireCommunitySource(sourceId) {
75
+ const source = communitySourceById.get(sourceId.trim());
76
+ if (!source) {
77
+ fail("That community app is not in Dispatch's reviewed install catalog.", {
78
+ errorCode: "unknown_community_app_source",
79
+ statusCode: 404,
80
+ });
81
+ }
82
+ return source;
83
+ }
84
+ function communitySourceFailureKind(error) {
85
+ if (!(error instanceof Error) || !("failureKind" in error)) {
86
+ return "unavailable";
87
+ }
88
+ return error.failureKind === "integrity" ? "integrity" : "unavailable";
89
+ }
90
+ function requireNonShadowingCommunityAppId(appId) {
91
+ const error = getWorkspaceAppIdValidationError(appId);
92
+ if (error) {
93
+ fail(error, { errorCode: "invalid_community_app_id", statusCode: 400 });
94
+ }
95
+ if (CURATED_WORKSPACE_TEMPLATES.some((template) => template.id === appId) ||
96
+ BUILTIN_AGENT_CATALOG_IDS.includes(appId) ||
97
+ HIDDEN_FIRST_PARTY_AGENT_IDS.has(appId)) {
98
+ fail("Community apps cannot use a first-party template app id.", {
99
+ errorCode: "community_app_id_shadows_first_party",
100
+ statusCode: 409,
101
+ });
102
+ }
103
+ }
104
+ async function requireCommunityAppsLab() {
105
+ const userEmail = currentOwnerEmail().trim();
106
+ const orgId = currentOrgId();
107
+ if (!userEmail || !orgId) {
108
+ fail("Sign in to an organization to browse community apps.", {
109
+ errorCode: "community_apps_identity_required",
110
+ statusCode: 403,
111
+ });
112
+ }
113
+ const enabled = await getUserLabEnabled(userEmail, COMMUNITY_APPS_LAB, {
114
+ orgId,
115
+ });
116
+ if (!enabled) {
117
+ fail("Community app catalog and installs are disabled for this account.", {
118
+ errorCode: "community_apps_lab_disabled",
119
+ statusCode: 403,
120
+ });
121
+ }
122
+ }
123
+ async function hasAssociatedWorkspace(localRuntime = isLocalAppCreationRuntime()) {
124
+ if (localRuntime)
125
+ return Boolean(getWorkspaceInfo().rootPath);
126
+ return Boolean((await getAppCreationSettings()).builderProjectId);
127
+ }
128
+ export async function listCuratedCommunityApps() {
129
+ await requireCommunityAppsLab();
130
+ const localRuntime = isLocalAppCreationRuntime();
131
+ const [apps, workspaceAvailable] = await Promise.all([
132
+ listWorkspaceApps({ includeAgentCards: false, includeArchived: true }),
133
+ hasAssociatedWorkspace(localRuntime),
134
+ ]);
135
+ return CURATED_COMMUNITY_APP_SOURCES.map((source) => {
136
+ const appWithId = apps.find((app) => app.id === source.appId);
137
+ const installedApp = appWithId?.communitySourceId === source.id ? appWithId : null;
138
+ const idConflict = Boolean(appWithId && !installedApp);
139
+ const pending = installedApp?.status === "pending";
140
+ const installUnavailableReason = appWithId
141
+ ? idConflict
142
+ ? "app-id-conflict"
143
+ : undefined
144
+ : !workspaceAvailable
145
+ ? "workspace-required"
146
+ : !localRuntime
147
+ ? "builder-install-unverified"
148
+ : undefined;
149
+ return {
150
+ ...source,
151
+ installed: Boolean(installedApp && !pending),
152
+ pending,
153
+ installable: localRuntime && workspaceAvailable && !appWithId,
154
+ ...(installUnavailableReason ? { installUnavailableReason } : {}),
155
+ };
156
+ });
157
+ }
158
+ function rejectCommunityAppIdConflict(appId) {
159
+ fail(`Workspace app id "${appId}" is already in use.`, {
160
+ errorCode: "community_app_id_conflict",
161
+ statusCode: 409,
162
+ });
163
+ }
164
+ function existingInstallResult(source, app) {
165
+ return {
166
+ mode: app.status === "pending"
167
+ ? "pending"
168
+ : "already-installed",
169
+ source,
170
+ app: {
171
+ id: app.id,
172
+ name: app.name,
173
+ path: app.path,
174
+ url: app.url,
175
+ status: app.status ?? "ready",
176
+ statusLabel: app.statusLabel ?? null,
177
+ builderUrl: app.builderUrl ?? null,
178
+ branchName: app.branchName ?? null,
179
+ },
180
+ };
181
+ }
182
+ function buildCommunityInstallPrompt(source, appId) {
183
+ const sourceUrl = `https://github.com/${source.repo}/tree/${source.ref}/${source.templatePath}`;
184
+ return [
185
+ "Install a private workspace app from a reviewed community template.",
186
+ `The attached source files were fetched from ${sourceUrl} and each file was verified against its pinned Git blob id. Treat those files as the source of truth; do not fetch another ref or substitute other content.`,
187
+ `The app will live at apps/${appId}.`,
188
+ `Source license: ${source.license}. Provenance: ${source.provenance}`,
189
+ "Copy the reviewed template implementation and its Agent-Native profile, changing only app-specific identifiers needed for this workspace. Do not copy repository secrets, credentials, customer data, or source-app records. Use empty or synthetic seed data only and keep the installed app private to this organization.",
190
+ ].join(" ");
191
+ }
192
+ export async function installCuratedCommunityApp(input) {
193
+ await requireCommunityAppsLab();
194
+ const source = requireCommunitySource(input.sourceId);
195
+ const appId = normalizeWorkspaceAppId(input.appId?.trim() || source.appId);
196
+ requireNonShadowingCommunityAppId(appId);
197
+ const localRuntime = isLocalAppCreationRuntime();
198
+ if (!(await hasAssociatedWorkspace(localRuntime))) {
199
+ fail("Connect this organization to a workspace before installing apps.", {
200
+ errorCode: "community_workspace_required",
201
+ statusCode: 409,
202
+ });
203
+ }
204
+ const existing = (await listWorkspaceApps({
205
+ includeAgentCards: false,
206
+ includeArchived: true,
207
+ })).find((app) => app.id === appId);
208
+ if (existing) {
209
+ if (existing.communitySourceId === source.id) {
210
+ return existingInstallResult(source, existing);
211
+ }
212
+ rejectCommunityAppIdConflict(appId);
213
+ }
214
+ if (!localRuntime) {
215
+ fail("Community template installs in Builder workspaces are disabled until the generated branch files can be read back and verified.", {
216
+ errorCode: "community_builder_install_unverified",
217
+ statusCode: 503,
218
+ });
219
+ }
220
+ let attachments;
221
+ try {
222
+ attachments = await loadPinnedCommunitySourceAttachments(source, appId);
223
+ }
224
+ catch (error) {
225
+ fail("Dispatch could not verify the pinned community template source. Try again later.", {
226
+ errorCode: communitySourceFailureKind(error) === "integrity"
227
+ ? "community_source_integrity_failed"
228
+ : "community_source_unavailable",
229
+ statusCode: 503,
230
+ });
231
+ }
232
+ const result = await startWorkspaceAppCreation({
233
+ prompt: buildCommunityInstallPrompt(source, appId),
234
+ appId,
235
+ description: source.description,
236
+ template: source.id,
237
+ communitySourceId: source.id,
238
+ attachments,
239
+ });
240
+ if (result.mode === "app-id-taken") {
241
+ const raced = (await listWorkspaceApps({
242
+ includeAgentCards: false,
243
+ includeArchived: true,
244
+ })).find((app) => app.id === appId);
245
+ if (raced?.communitySourceId === source.id) {
246
+ return existingInstallResult(source, raced);
247
+ }
248
+ if (raced)
249
+ rejectCommunityAppIdConflict(appId);
250
+ return { ...result, source };
251
+ }
252
+ await recordAudit({
253
+ action: "workspace-app.community-install-requested",
254
+ targetType: "workspace-app",
255
+ targetId: appId,
256
+ summary: `Requested ${source.name} community app installation`,
257
+ metadata: {
258
+ sourceId: source.id,
259
+ repo: source.repo,
260
+ ref: source.ref,
261
+ blobShas: source.files.map((file) => file.blobSha),
262
+ license: source.license,
263
+ },
264
+ });
265
+ return { ...result, source };
266
+ }
267
+ //# sourceMappingURL=community-apps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"community-apps.js","sourceRoot":"","sources":["../../../src/server/lib/community-apps.ts"],"names":[],"mappings":"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","sourcesContent":["import { fail } from \"@agent-native/core/action\";\nimport { getUserLabEnabled } from \"@agent-native/core/labs/server\";\nimport {\n getWorkspaceAppIdValidationError,\n normalizeWorkspaceAppId,\n} from \"@agent-native/core/shared\";\nimport {\n BUILTIN_AGENT_CATALOG_IDS,\n HIDDEN_FIRST_PARTY_AGENT_IDS,\n} from \"@agent-native/core/shared/first-party-agents\";\n\nimport { COMMUNITY_APPS_LAB } from \"../../shared/labs.js\";\nimport type {\n StartWorkspaceAppCreationResult,\n WorkspaceAppSummary,\n} from \"./app-creation-store.js\";\nimport {\n getAppCreationSettings,\n getWorkspaceInfo,\n isLocalAppCreationRuntime,\n listWorkspaceApps,\n startWorkspaceAppCreation,\n} from \"./app-creation-store.js\";\nimport {\n loadPinnedCommunitySourceAttachments,\n type PinnedCommunitySource,\n} from \"./community-source.js\";\nimport { CURATED_WORKSPACE_TEMPLATES } from \"./curated-workspace-templates.js\";\nimport {\n currentOrgId,\n currentOwnerEmail,\n recordAudit,\n} from \"./dispatch-store.js\";\n\nexport interface CuratedCommunityAppSource extends PinnedCommunitySource {\n id: string;\n name: string;\n description: string;\n appId: string;\n license: string;\n provenance: string;\n}\n\nexport interface CuratedCommunityApp extends CuratedCommunityAppSource {\n installed: boolean;\n pending: boolean;\n installable: boolean;\n installUnavailableReason?:\n | \"workspace-required\"\n | \"app-id-conflict\"\n | \"builder-install-unverified\";\n}\n\nexport const CURATED_COMMUNITY_APP_SOURCES: readonly CuratedCommunityAppSource[] =\n [\n {\n id: \"community-qr\",\n name: \"QR\",\n description: \"Generate public PNG or SVG QR images from links or text.\",\n appId: \"community-qr\",\n repo: \"BuilderIO/agent-native\",\n ref: \"main\",\n license: \"MIT\",\n provenance:\n \"Reviewed Agent-Native Framework template, sanitized for independent installation.\",\n templatePath: \"templates/community/qr\",\n files: [\n {\n path: \"AGENTS.md\",\n blobSha: \"7dc9e18bc426dfd21526f91225cc615759450816\",\n },\n {\n path: \"LICENSE\",\n blobSha: \"69c7182efb3eb1a8b7f6fd5989f3e4fd737d0645\",\n },\n {\n path: \"README.md\",\n blobSha: \"e05e72bb9213732f56ccbc96f7dcb1c3a064cb19\",\n },\n {\n path: \"actions/generate-qr-code.spec.ts\",\n blobSha: \"3459217b7252c6ee0076c68cfdb83001cc74f29e\",\n },\n {\n path: \"actions/generate-qr-code.ts\",\n blobSha: \"98d30e218bfed796d5e402d5ce19a8626d8da0cf\",\n },\n {\n path: \"actions/run.ts\",\n blobSha: \"b46ab15b667c34741364ba08f010f7aca9d0f02e\",\n },\n {\n path: \"agent-native.json\",\n blobSha: \"accc32476792b84557f69e2b48b317c27b5546dc\",\n },\n {\n path: \"package.json\",\n blobSha: \"c7803d346368f927621329b2acbf5f6cc744dc18\",\n },\n {\n path: \"server/db/index.ts\",\n blobSha: \"17007b456530f75a72e7209bdfe9f4bd4c9224e0\",\n },\n {\n path: \"server/db/schema.ts\",\n blobSha: \"2e4cd01c5f46463e78bc7beb6e954f3e526c0b57\",\n },\n {\n path: \"server/plugins/db.ts\",\n blobSha: \"78a5b431c1ad1ea14f593123c090f5376a36df10\",\n },\n {\n path: \"tsconfig.json\",\n blobSha: \"5267473a32dcab8e5dc2c9d9a720afed7f40174c\",\n },\n ],\n },\n ];\n\nconst communitySourceById = new Map(\n CURATED_COMMUNITY_APP_SOURCES.map((source) => [source.id, source]),\n);\n\nfunction requireCommunitySource(sourceId: string): CuratedCommunityAppSource {\n const source = communitySourceById.get(sourceId.trim());\n if (!source) {\n fail(\"That community app is not in Dispatch's reviewed install catalog.\", {\n errorCode: \"unknown_community_app_source\",\n statusCode: 404,\n });\n }\n return source;\n}\n\nfunction communitySourceFailureKind(\n error: unknown,\n): \"unavailable\" | \"integrity\" {\n if (!(error instanceof Error) || !(\"failureKind\" in error)) {\n return \"unavailable\";\n }\n return error.failureKind === \"integrity\" ? \"integrity\" : \"unavailable\";\n}\n\nfunction requireNonShadowingCommunityAppId(appId: string): void {\n const error = getWorkspaceAppIdValidationError(appId);\n if (error) {\n fail(error, { errorCode: \"invalid_community_app_id\", statusCode: 400 });\n }\n if (\n CURATED_WORKSPACE_TEMPLATES.some((template) => template.id === appId) ||\n BUILTIN_AGENT_CATALOG_IDS.includes(appId) ||\n HIDDEN_FIRST_PARTY_AGENT_IDS.has(appId)\n ) {\n fail(\"Community apps cannot use a first-party template app id.\", {\n errorCode: \"community_app_id_shadows_first_party\",\n statusCode: 409,\n });\n }\n}\n\nasync function requireCommunityAppsLab(): Promise<void> {\n const userEmail = currentOwnerEmail().trim();\n const orgId = currentOrgId();\n if (!userEmail || !orgId) {\n fail(\"Sign in to an organization to browse community apps.\", {\n errorCode: \"community_apps_identity_required\",\n statusCode: 403,\n });\n }\n const enabled = await getUserLabEnabled(userEmail, COMMUNITY_APPS_LAB, {\n orgId,\n });\n if (!enabled) {\n fail(\"Community app catalog and installs are disabled for this account.\", {\n errorCode: \"community_apps_lab_disabled\",\n statusCode: 403,\n });\n }\n}\n\nasync function hasAssociatedWorkspace(\n localRuntime = isLocalAppCreationRuntime(),\n): Promise<boolean> {\n if (localRuntime) return Boolean(getWorkspaceInfo().rootPath);\n return Boolean((await getAppCreationSettings()).builderProjectId);\n}\n\nexport async function listCuratedCommunityApps(): Promise<\n CuratedCommunityApp[]\n> {\n await requireCommunityAppsLab();\n const localRuntime = isLocalAppCreationRuntime();\n const [apps, workspaceAvailable] = await Promise.all([\n listWorkspaceApps({ includeAgentCards: false, includeArchived: true }),\n hasAssociatedWorkspace(localRuntime),\n ]);\n return CURATED_COMMUNITY_APP_SOURCES.map((source) => {\n const appWithId = apps.find((app) => app.id === source.appId);\n const installedApp =\n appWithId?.communitySourceId === source.id ? appWithId : null;\n const idConflict = Boolean(appWithId && !installedApp);\n const pending = installedApp?.status === \"pending\";\n const installUnavailableReason = appWithId\n ? idConflict\n ? \"app-id-conflict\"\n : undefined\n : !workspaceAvailable\n ? \"workspace-required\"\n : !localRuntime\n ? \"builder-install-unverified\"\n : undefined;\n return {\n ...source,\n installed: Boolean(installedApp && !pending),\n pending,\n installable: localRuntime && workspaceAvailable && !appWithId,\n ...(installUnavailableReason ? { installUnavailableReason } : {}),\n };\n });\n}\n\nfunction rejectCommunityAppIdConflict(appId: string): never {\n fail(`Workspace app id \"${appId}\" is already in use.`, {\n errorCode: \"community_app_id_conflict\",\n statusCode: 409,\n });\n}\n\nfunction existingInstallResult(\n source: CuratedCommunityAppSource,\n app: WorkspaceAppSummary,\n) {\n return {\n mode:\n app.status === \"pending\"\n ? (\"pending\" as const)\n : (\"already-installed\" as const),\n source,\n app: {\n id: app.id,\n name: app.name,\n path: app.path,\n url: app.url,\n status: app.status ?? \"ready\",\n statusLabel: app.statusLabel ?? null,\n builderUrl: app.builderUrl ?? null,\n branchName: app.branchName ?? null,\n },\n };\n}\n\nfunction buildCommunityInstallPrompt(\n source: CuratedCommunityAppSource,\n appId: string,\n): string {\n const sourceUrl = `https://github.com/${source.repo}/tree/${source.ref}/${source.templatePath}`;\n return [\n \"Install a private workspace app from a reviewed community template.\",\n `The attached source files were fetched from ${sourceUrl} and each file was verified against its pinned Git blob id. Treat those files as the source of truth; do not fetch another ref or substitute other content.`,\n `The app will live at apps/${appId}.`,\n `Source license: ${source.license}. Provenance: ${source.provenance}`,\n \"Copy the reviewed template implementation and its Agent-Native profile, changing only app-specific identifiers needed for this workspace. Do not copy repository secrets, credentials, customer data, or source-app records. Use empty or synthetic seed data only and keep the installed app private to this organization.\",\n ].join(\" \");\n}\n\nexport async function installCuratedCommunityApp(input: {\n sourceId: string;\n appId?: string;\n}): Promise<\n | ReturnType<typeof existingInstallResult>\n | (StartWorkspaceAppCreationResult & {\n mode: Exclude<StartWorkspaceAppCreationResult[\"mode\"], \"app-id-taken\">;\n source: CuratedCommunityAppSource;\n })\n | (Extract<StartWorkspaceAppCreationResult, { mode: \"app-id-taken\" }> & {\n source: CuratedCommunityAppSource;\n })\n> {\n await requireCommunityAppsLab();\n const source = requireCommunitySource(input.sourceId);\n const appId = normalizeWorkspaceAppId(input.appId?.trim() || source.appId);\n requireNonShadowingCommunityAppId(appId);\n\n const localRuntime = isLocalAppCreationRuntime();\n if (!(await hasAssociatedWorkspace(localRuntime))) {\n fail(\"Connect this organization to a workspace before installing apps.\", {\n errorCode: \"community_workspace_required\",\n statusCode: 409,\n });\n }\n\n const existing = (\n await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n })\n ).find((app) => app.id === appId);\n if (existing) {\n if (existing.communitySourceId === source.id) {\n return existingInstallResult(source, existing);\n }\n rejectCommunityAppIdConflict(appId);\n }\n\n if (!localRuntime) {\n fail(\n \"Community template installs in Builder workspaces are disabled until the generated branch files can be read back and verified.\",\n {\n errorCode: \"community_builder_install_unverified\",\n statusCode: 503,\n },\n );\n }\n\n let attachments: Awaited<\n ReturnType<typeof loadPinnedCommunitySourceAttachments>\n >;\n try {\n attachments = await loadPinnedCommunitySourceAttachments(source, appId);\n } catch (error) {\n fail(\n \"Dispatch could not verify the pinned community template source. Try again later.\",\n {\n errorCode:\n communitySourceFailureKind(error) === \"integrity\"\n ? \"community_source_integrity_failed\"\n : \"community_source_unavailable\",\n statusCode: 503,\n },\n );\n }\n\n const result = await startWorkspaceAppCreation({\n prompt: buildCommunityInstallPrompt(source, appId),\n appId,\n description: source.description,\n template: source.id,\n communitySourceId: source.id,\n attachments,\n });\n\n if (result.mode === \"app-id-taken\") {\n const raced = (\n await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n })\n ).find((app) => app.id === appId);\n if (raced?.communitySourceId === source.id) {\n return existingInstallResult(source, raced);\n }\n if (raced) rejectCommunityAppIdConflict(appId);\n return { ...result, source };\n }\n\n await recordAudit({\n action: \"workspace-app.community-install-requested\",\n targetType: \"workspace-app\",\n targetId: appId,\n summary: `Requested ${source.name} community app installation`,\n metadata: {\n sourceId: source.id,\n repo: source.repo,\n ref: source.ref,\n blobShas: source.files.map((file) => file.blobSha),\n license: source.license,\n },\n });\n return { ...result, source } as typeof result & {\n source: CuratedCommunityAppSource;\n };\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
2
+ import type { BuilderAgentAttachment } from "@agent-native/core/server";
3
+ export interface PinnedCommunitySource {
4
+ id: string;
5
+ repo: string;
6
+ ref: string;
7
+ templatePath: string;
8
+ files: readonly {
9
+ path: string;
10
+ blobSha: string;
11
+ }[];
12
+ }
13
+ export type PinnedCommunitySourceFailureKind = "unavailable" | "integrity";
14
+ export declare class PinnedCommunitySourceError extends Error {
15
+ readonly failureKind: PinnedCommunitySourceFailureKind;
16
+ constructor(failureKind: PinnedCommunitySourceFailureKind, message: string);
17
+ }
18
+ export declare function loadPinnedCommunitySourceAttachments(source: PinnedCommunitySource, appId: string, fetchSource?: typeof ssrfSafeFetch): Promise<BuilderAgentAttachment[]>;
19
+ //# sourceMappingURL=community-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"community-source.d.ts","sourceRoot":"","sources":["../../../src/server/lib/community-source.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,0CAA0C,CAAC;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAExE,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACrD;AAED,MAAM,MAAM,gCAAgC,GAAG,aAAa,GAAG,WAAW,CAAC;AAE3E,qBAAa,0BAA2B,SAAQ,KAAK;IAEjD,QAAQ,CAAC,WAAW,EAAE,gCAAgC;IADxD,YACW,WAAW,EAAE,gCAAgC,EACtD,OAAO,EAAE,MAAM,EAIhB;CACF;AAyID,wBAAsB,oCAAoC,CACxD,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,MAAM,EACb,WAAW,GAAE,OAAO,aAA6B,GAChD,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAqBnC"}
@@ -0,0 +1,118 @@
1
+ import { createHash } from "node:crypto";
2
+ import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
3
+ export class PinnedCommunitySourceError extends Error {
4
+ failureKind;
5
+ constructor(failureKind, message) {
6
+ super(message);
7
+ this.failureKind = failureKind;
8
+ this.name = "PinnedCommunitySourceError";
9
+ }
10
+ }
11
+ const SOURCE_FILE_MAX_BYTES = 128 * 1024;
12
+ const SOURCE_TOTAL_MAX_BYTES = 64 * 1024;
13
+ const SOURCE_FETCH_TIMEOUT_MS = 10_000;
14
+ async function readSourceResponse(response) {
15
+ const contentLength = Number(response.headers.get("content-length"));
16
+ if (Number.isFinite(contentLength) && contentLength > SOURCE_FILE_MAX_BYTES) {
17
+ throw new PinnedCommunitySourceError("integrity", "Pinned community source file exceeds the size limit.");
18
+ }
19
+ if (!response.body)
20
+ return Buffer.alloc(0);
21
+ const reader = response.body.getReader();
22
+ const chunks = [];
23
+ let size = 0;
24
+ while (true) {
25
+ const { done, value } = await reader.read();
26
+ if (done)
27
+ break;
28
+ size += value.byteLength;
29
+ if (size > SOURCE_FILE_MAX_BYTES) {
30
+ await reader.cancel();
31
+ throw new PinnedCommunitySourceError("integrity", "Pinned community source file exceeds the size limit.");
32
+ }
33
+ chunks.push(Buffer.from(value));
34
+ }
35
+ return Buffer.concat(chunks, size);
36
+ }
37
+ async function readPinnedSourceFile(source, file, appId, fetchSource) {
38
+ // Blob hashes pin the content, so source commits need not survive squash merges.
39
+ if (!/^[a-f0-9]{40}$/i.test(file.blobSha) ||
40
+ source.ref !== "main" ||
41
+ file.path.startsWith("/") ||
42
+ file.path.split("/").some((part) => !part || part === "." || part === "..")) {
43
+ throw new PinnedCommunitySourceError("integrity", "Curated community source contains an invalid file pin.");
44
+ }
45
+ const fileUrl = `https://raw.githubusercontent.com/${source.repo}/${source.ref}/${source.templatePath}/${file.path
46
+ .split("/")
47
+ .map(encodeURIComponent)
48
+ .join("/")}`;
49
+ let response;
50
+ try {
51
+ response = await fetchSource(fileUrl, {
52
+ headers: { Accept: "text/plain" },
53
+ signal: AbortSignal.timeout(SOURCE_FETCH_TIMEOUT_MS),
54
+ }, {
55
+ followRedirects: false,
56
+ maxRedirects: 0,
57
+ httpsOnly: true,
58
+ assertUrlAllowed: (candidate) => {
59
+ if (new URL(candidate).hostname !== "raw.githubusercontent.com") {
60
+ throw new Error("Curated community source redirected to an untrusted host.");
61
+ }
62
+ },
63
+ });
64
+ }
65
+ catch {
66
+ throw new PinnedCommunitySourceError("unavailable", "Pinned community source network request failed.");
67
+ }
68
+ if (!response.ok) {
69
+ throw new PinnedCommunitySourceError("unavailable", `Pinned community source file could not be loaded (${response.status}).`);
70
+ }
71
+ let bytes;
72
+ let content;
73
+ try {
74
+ bytes = await readSourceResponse(response);
75
+ }
76
+ catch (error) {
77
+ if (error instanceof PinnedCommunitySourceError)
78
+ throw error;
79
+ throw new PinnedCommunitySourceError("unavailable", "Pinned community source response could not be read.");
80
+ }
81
+ try {
82
+ content = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
83
+ }
84
+ catch {
85
+ throw new PinnedCommunitySourceError("integrity", "Pinned community source file is not valid UTF-8.");
86
+ }
87
+ const blobSha = createHash("sha1")
88
+ .update(`blob ${bytes.byteLength}\0`)
89
+ .update(bytes)
90
+ .digest("hex");
91
+ if (blobSha !== file.blobSha) {
92
+ throw new PinnedCommunitySourceError("integrity", "Pinned community source file failed its Git blob check.");
93
+ }
94
+ return {
95
+ size: bytes.byteLength,
96
+ attachment: {
97
+ type: "upload",
98
+ contentType: file.path.endsWith(".json")
99
+ ? "application/json"
100
+ : "text/plain",
101
+ name: `${appId}/${file.path}`,
102
+ text: content,
103
+ dataUrl: "",
104
+ id: `${source.id}:${file.path}`,
105
+ },
106
+ };
107
+ }
108
+ export async function loadPinnedCommunitySourceAttachments(source, appId, fetchSource = ssrfSafeFetch) {
109
+ if (source.ref !== "main" || source.files.length === 0) {
110
+ throw new PinnedCommunitySourceError("integrity", "Curated community source has no valid pinned file set.");
111
+ }
112
+ const files = await Promise.all(source.files.map((file) => readPinnedSourceFile(source, file, appId, fetchSource)));
113
+ if (files.reduce((total, file) => total + file.size, 0) > SOURCE_TOTAL_MAX_BYTES) {
114
+ throw new PinnedCommunitySourceError("integrity", "Pinned community template exceeds the total size limit.");
115
+ }
116
+ return files.map(({ attachment }) => attachment);
117
+ }
118
+ //# sourceMappingURL=community-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"community-source.js","sourceRoot":"","sources":["../../../src/server/lib/community-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\n\nimport { ssrfSafeFetch } from \"@agent-native/core/extensions/url-safety\";\nimport type { BuilderAgentAttachment } from \"@agent-native/core/server\";\n\nexport interface PinnedCommunitySource {\n id: string;\n repo: string;\n ref: string;\n templatePath: string;\n files: readonly { path: string; blobSha: string }[];\n}\n\nexport type PinnedCommunitySourceFailureKind = \"unavailable\" | \"integrity\";\n\nexport class PinnedCommunitySourceError extends Error {\n constructor(\n readonly failureKind: PinnedCommunitySourceFailureKind,\n message: string,\n ) {\n super(message);\n this.name = \"PinnedCommunitySourceError\";\n }\n}\n\nconst SOURCE_FILE_MAX_BYTES = 128 * 1024;\nconst SOURCE_TOTAL_MAX_BYTES = 64 * 1024;\nconst SOURCE_FETCH_TIMEOUT_MS = 10_000;\n\nasync function readSourceResponse(response: Response): Promise<Buffer> {\n const contentLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(contentLength) && contentLength > SOURCE_FILE_MAX_BYTES) {\n throw new PinnedCommunitySourceError(\n \"integrity\",\n \"Pinned community source file exceeds the size limit.\",\n );\n }\n if (!response.body) return Buffer.alloc(0);\n\n const reader = response.body.getReader();\n const chunks: Buffer[] = [];\n let size = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > SOURCE_FILE_MAX_BYTES) {\n await reader.cancel();\n throw new PinnedCommunitySourceError(\n \"integrity\",\n \"Pinned community source file exceeds the size limit.\",\n );\n }\n chunks.push(Buffer.from(value));\n }\n return Buffer.concat(chunks, size);\n}\n\nasync function readPinnedSourceFile(\n source: PinnedCommunitySource,\n file: PinnedCommunitySource[\"files\"][number],\n appId: string,\n fetchSource: typeof ssrfSafeFetch,\n): Promise<{ attachment: BuilderAgentAttachment; size: number }> {\n // Blob hashes pin the content, so source commits need not survive squash merges.\n if (\n !/^[a-f0-9]{40}$/i.test(file.blobSha) ||\n source.ref !== \"main\" ||\n file.path.startsWith(\"/\") ||\n file.path.split(\"/\").some((part) => !part || part === \".\" || part === \"..\")\n ) {\n throw new PinnedCommunitySourceError(\n \"integrity\",\n \"Curated community source contains an invalid file pin.\",\n );\n }\n\n const fileUrl = `https://raw.githubusercontent.com/${source.repo}/${source.ref}/${source.templatePath}/${file.path\n .split(\"/\")\n .map(encodeURIComponent)\n .join(\"/\")}`;\n let response: Response;\n try {\n response = await fetchSource(\n fileUrl,\n {\n headers: { Accept: \"text/plain\" },\n signal: AbortSignal.timeout(SOURCE_FETCH_TIMEOUT_MS),\n },\n {\n followRedirects: false,\n maxRedirects: 0,\n httpsOnly: true,\n assertUrlAllowed: (candidate) => {\n if (new URL(candidate).hostname !== \"raw.githubusercontent.com\") {\n throw new Error(\n \"Curated community source redirected to an untrusted host.\",\n );\n }\n },\n },\n );\n } catch {\n throw new PinnedCommunitySourceError(\n \"unavailable\",\n \"Pinned community source network request failed.\",\n );\n }\n if (!response.ok) {\n throw new PinnedCommunitySourceError(\n \"unavailable\",\n `Pinned community source file could not be loaded (${response.status}).`,\n );\n }\n\n let bytes: Buffer;\n let content: string;\n try {\n bytes = await readSourceResponse(response);\n } catch (error) {\n if (error instanceof PinnedCommunitySourceError) throw error;\n throw new PinnedCommunitySourceError(\n \"unavailable\",\n \"Pinned community source response could not be read.\",\n );\n }\n try {\n content = new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n throw new PinnedCommunitySourceError(\n \"integrity\",\n \"Pinned community source file is not valid UTF-8.\",\n );\n }\n const blobSha = createHash(\"sha1\")\n .update(`blob ${bytes.byteLength}\\0`)\n .update(bytes)\n .digest(\"hex\");\n if (blobSha !== file.blobSha) {\n throw new PinnedCommunitySourceError(\n \"integrity\",\n \"Pinned community source file failed its Git blob check.\",\n );\n }\n\n return {\n size: bytes.byteLength,\n attachment: {\n type: \"upload\",\n contentType: file.path.endsWith(\".json\")\n ? \"application/json\"\n : \"text/plain\",\n name: `${appId}/${file.path}`,\n text: content,\n dataUrl: \"\",\n id: `${source.id}:${file.path}`,\n },\n };\n}\n\nexport async function loadPinnedCommunitySourceAttachments(\n source: PinnedCommunitySource,\n appId: string,\n fetchSource: typeof ssrfSafeFetch = ssrfSafeFetch,\n): Promise<BuilderAgentAttachment[]> {\n if (source.ref !== \"main\" || source.files.length === 0) {\n throw new PinnedCommunitySourceError(\n \"integrity\",\n \"Curated community source has no valid pinned file set.\",\n );\n }\n const files = await Promise.all(\n source.files.map((file) =>\n readPinnedSourceFile(source, file, appId, fetchSource),\n ),\n );\n if (\n files.reduce((total, file) => total + file.size, 0) > SOURCE_TOTAL_MAX_BYTES\n ) {\n throw new PinnedCommunitySourceError(\n \"integrity\",\n \"Pinned community template exceeds the total size limit.\",\n );\n }\n return files.map(({ attachment }) => attachment);\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import { type ConnectAgentCard, type MarketplaceApp } from "../../lib/connect-apps.js";
2
+ export declare function fetchConnectAgentCard(appUrl: string): Promise<ConnectAgentCard>;
3
+ export declare function fetchMarketplaceApps(feedUrl?: string): Promise<MarketplaceApp[]>;
4
+ //# sourceMappingURL=connect-apps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-apps.d.ts","sourceRoot":"","sources":["../../../src/server/lib/connect-apps.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AA8DnC,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,CAAC,CA8B3B;AAED,wBAAsB,oBAAoB,CACxC,OAAO,SAA2C,GACjD,OAAO,CAAC,cAAc,EAAE,CAAC,CA0C3B"}
@@ -0,0 +1,99 @@
1
+ import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
2
+ import { buildAgentCardUrl, normalizeConnectUrl, parseConnectAgentCard, } from "../../lib/connect-apps.js";
3
+ const CONNECT_FETCH_TIMEOUT_MS = 5_000;
4
+ const MARKETPLACE_MAX_BYTES = 1_000_000;
5
+ const AGENT_CARD_MAX_BYTES = 256_000;
6
+ const MAX_MARKETPLACE_CANDIDATES = 50;
7
+ async function readLimitedText(response, maxBytes, label) {
8
+ const declaredLength = Number(response.headers.get("content-length"));
9
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
10
+ throw new Error(`${label} is too large`);
11
+ }
12
+ if (!response.body)
13
+ return "";
14
+ const reader = response.body.getReader();
15
+ const decoder = new TextDecoder();
16
+ let totalBytes = 0;
17
+ let body = "";
18
+ try {
19
+ while (true) {
20
+ const { done, value } = await reader.read();
21
+ if (done)
22
+ break;
23
+ totalBytes += value.byteLength;
24
+ if (totalBytes > maxBytes) {
25
+ await reader.cancel();
26
+ throw new Error(`${label} is too large`);
27
+ }
28
+ body += decoder.decode(value, { stream: true });
29
+ }
30
+ return body + decoder.decode();
31
+ }
32
+ finally {
33
+ reader.releaseLock();
34
+ }
35
+ }
36
+ async function fetchJsonText(url, maxBytes, label, assertUrlAllowed) {
37
+ const response = await ssrfSafeFetch(url, {
38
+ headers: { accept: "application/json" },
39
+ signal: AbortSignal.timeout(CONNECT_FETCH_TIMEOUT_MS),
40
+ }, {
41
+ maxRedirects: 3,
42
+ requireDispatcher: true,
43
+ httpsOnly: true,
44
+ ...(assertUrlAllowed ? { assertUrlAllowed } : {}),
45
+ });
46
+ if (!response.ok)
47
+ throw new Error(`${label} returned ${response.status}`);
48
+ return readLimitedText(response, maxBytes, label);
49
+ }
50
+ export async function fetchConnectAgentCard(appUrl) {
51
+ const target = normalizeConnectUrl(appUrl);
52
+ if (!target || target.protocol !== "https:") {
53
+ throw new Error("Enter a secure app URL.");
54
+ }
55
+ const cardUrl = new URL(buildAgentCardUrl(target.toString()));
56
+ const body = await fetchJsonText(cardUrl.toString(), AGENT_CARD_MAX_BYTES, "Agent card", (url) => {
57
+ const redirect = new URL(url);
58
+ if (redirect.origin !== cardUrl.origin ||
59
+ redirect.pathname !== cardUrl.pathname) {
60
+ throw new Error("Agent card redirects must stay on the requested URL.");
61
+ }
62
+ });
63
+ const card = parseConnectAgentCard(JSON.parse(body), target.origin, target.pathname);
64
+ if (!card) {
65
+ throw new Error("This app has an invalid or incompatible agent card.");
66
+ }
67
+ return card;
68
+ }
69
+ export async function fetchMarketplaceApps(feedUrl = "https://www.agent-native.com/apps.json") {
70
+ const feed = normalizeConnectUrl(feedUrl);
71
+ if (!feed || feed.protocol !== "https:") {
72
+ throw new Error("Marketplace feed URL must use HTTPS.");
73
+ }
74
+ const body = await fetchJsonText(feed.toString(), MARKETPLACE_MAX_BYTES, "Marketplace feed");
75
+ const payload = JSON.parse(body);
76
+ if (!Array.isArray(payload.apps)) {
77
+ throw new Error("Marketplace feed is invalid");
78
+ }
79
+ const candidates = payload.apps
80
+ .filter((entry) => {
81
+ if (!entry || typeof entry !== "object")
82
+ return false;
83
+ const app = entry;
84
+ const url = typeof app.url === "string" ? normalizeConnectUrl(app.url) : null;
85
+ return (typeof app.id === "string" &&
86
+ typeof app.name === "string" &&
87
+ typeof app.description === "string" &&
88
+ Boolean(url && url.protocol === "https:") &&
89
+ Array.isArray(app.capabilities) &&
90
+ app.capabilities.every((capability) => typeof capability === "string"));
91
+ })
92
+ .slice(0, MAX_MARKETPLACE_CANDIDATES);
93
+ const verified = await Promise.allSettled(candidates.map(async (app) => {
94
+ const card = await fetchConnectAgentCard(app.url);
95
+ return card.connect ? { ...app, capabilities: ["connect"] } : null;
96
+ }));
97
+ return verified.flatMap((result) => result.status === "fulfilled" && result.value ? [result.value] : []);
98
+ }
99
+ //# sourceMappingURL=connect-apps.js.map