@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.
Files changed (150) hide show
  1. package/CHANGELOG.md +15 -60
  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-connected-agents.js +7 -3
  26. package/dist/actions/list-connected-agents.js.map +1 -1
  27. package/dist/actions/list-curated-community-apps.d.ts +3 -0
  28. package/dist/actions/list-curated-community-apps.d.ts.map +1 -0
  29. package/dist/actions/list-curated-community-apps.js +10 -0
  30. package/dist/actions/list-curated-community-apps.js.map +1 -0
  31. package/dist/actions/list-workspace-connections.d.ts.map +1 -1
  32. package/dist/actions/list-workspace-connections.js +13 -18
  33. package/dist/actions/list-workspace-connections.js.map +1 -1
  34. package/dist/actions/provider-api-register.d.ts +14 -14
  35. package/dist/actions/remix-workspace-template.d.ts +6 -0
  36. package/dist/actions/update-workspace-app-metadata.d.ts +1 -0
  37. package/dist/actions/update-workspace-app-metadata.js +26 -5
  38. package/dist/actions/update-workspace-app-metadata.js.map +1 -1
  39. package/dist/components/action-query-error.d.ts +2 -1
  40. package/dist/components/action-query-error.d.ts.map +1 -1
  41. package/dist/components/action-query-error.js +25 -2
  42. package/dist/components/action-query-error.js.map +1 -1
  43. package/dist/components/chat-home-apps.d.ts.map +1 -1
  44. package/dist/components/chat-home-apps.js +2 -2
  45. package/dist/components/chat-home-apps.js.map +1 -1
  46. package/dist/components/community-apps-section.d.ts +2 -0
  47. package/dist/components/community-apps-section.d.ts.map +1 -0
  48. package/dist/components/community-apps-section.js +90 -0
  49. package/dist/components/community-apps-section.js.map +1 -0
  50. package/dist/components/dispatch-control-plane.d.ts.map +1 -1
  51. package/dist/components/dispatch-control-plane.js +1 -1
  52. package/dist/components/dispatch-control-plane.js.map +1 -1
  53. package/dist/components/layout/Layout.d.ts +1 -0
  54. package/dist/components/layout/Layout.d.ts.map +1 -1
  55. package/dist/components/layout/Layout.js +7 -0
  56. package/dist/components/layout/Layout.js.map +1 -1
  57. package/dist/components/workspace-app-card.d.ts.map +1 -1
  58. package/dist/components/workspace-app-card.js +63 -9
  59. package/dist/components/workspace-app-card.js.map +1 -1
  60. package/dist/lib/connect-apps.d.ts +1 -2
  61. package/dist/lib/connect-apps.d.ts.map +1 -1
  62. package/dist/lib/connect-apps.js +41 -65
  63. package/dist/lib/connect-apps.js.map +1 -1
  64. package/dist/lib/workspace-apps.d.ts +1 -0
  65. package/dist/lib/workspace-apps.d.ts.map +1 -1
  66. package/dist/lib/workspace-apps.js.map +1 -1
  67. package/dist/routes/pages/$appId.d.ts.map +1 -1
  68. package/dist/routes/pages/$appId.js +1 -1
  69. package/dist/routes/pages/$appId.js.map +1 -1
  70. package/dist/routes/pages/apps.d.ts.map +1 -1
  71. package/dist/routes/pages/apps.js +3 -2
  72. package/dist/routes/pages/apps.js.map +1 -1
  73. package/dist/routes/pages/connect.d.ts.map +1 -1
  74. package/dist/routes/pages/connect.js +5 -7
  75. package/dist/routes/pages/connect.js.map +1 -1
  76. package/dist/server/index.d.ts +2 -0
  77. package/dist/server/index.d.ts.map +1 -1
  78. package/dist/server/index.js +2 -0
  79. package/dist/server/index.js.map +1 -1
  80. package/dist/server/lib/app-creation-store.d.ts +11 -1
  81. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  82. package/dist/server/lib/app-creation-store.js +379 -92
  83. package/dist/server/lib/app-creation-store.js.map +1 -1
  84. package/dist/server/lib/community-apps.d.ts +45 -0
  85. package/dist/server/lib/community-apps.d.ts.map +1 -0
  86. package/dist/server/lib/community-apps.js +267 -0
  87. package/dist/server/lib/community-apps.js.map +1 -0
  88. package/dist/server/lib/community-source.d.ts +19 -0
  89. package/dist/server/lib/community-source.d.ts.map +1 -0
  90. package/dist/server/lib/community-source.js +118 -0
  91. package/dist/server/lib/community-source.js.map +1 -0
  92. package/dist/server/lib/connect-apps.d.ts +4 -0
  93. package/dist/server/lib/connect-apps.d.ts.map +1 -0
  94. package/dist/server/lib/connect-apps.js +99 -0
  95. package/dist/server/lib/connect-apps.js.map +1 -0
  96. package/dist/server/lib/org-apps-directory-cache.d.ts +20 -0
  97. package/dist/server/lib/org-apps-directory-cache.d.ts.map +1 -0
  98. package/dist/server/lib/org-apps-directory-cache.js +44 -0
  99. package/dist/server/lib/org-apps-directory-cache.js.map +1 -0
  100. package/dist/server/plugins/integrations.js +3 -3
  101. package/dist/server/plugins/integrations.js.map +1 -1
  102. package/dist/shared/labs.d.ts +2 -0
  103. package/dist/shared/labs.d.ts.map +1 -0
  104. package/dist/shared/labs.js +8 -0
  105. package/dist/shared/labs.js.map +1 -0
  106. package/package.json +5 -4
  107. package/src/actions/connect-external-agent.spec.ts +101 -3
  108. package/src/actions/connect-external-agent.ts +12 -11
  109. package/src/actions/index.spec.ts +13 -0
  110. package/src/actions/index.ts +8 -0
  111. package/src/actions/inspect-connect-agent-card.ts +17 -0
  112. package/src/actions/install-curated-community-app.ts +32 -0
  113. package/src/actions/list-connect-marketplace-apps.ts +17 -0
  114. package/src/actions/list-connected-agents.spec.ts +17 -1
  115. package/src/actions/list-connected-agents.ts +7 -2
  116. package/src/actions/list-curated-community-apps.ts +12 -0
  117. package/src/actions/list-workspace-connections.spec.ts +14 -3
  118. package/src/actions/list-workspace-connections.ts +13 -17
  119. package/src/actions/update-workspace-app-metadata.spec.ts +25 -0
  120. package/src/actions/update-workspace-app-metadata.ts +48 -24
  121. package/src/components/action-query-error.spec.tsx +20 -0
  122. package/src/components/action-query-error.tsx +40 -0
  123. package/src/components/chat-home-apps.spec.tsx +32 -1
  124. package/src/components/chat-home-apps.tsx +6 -1
  125. package/src/components/community-apps-section.tsx +224 -0
  126. package/src/components/dispatch-control-plane.tsx +1 -0
  127. package/src/components/layout/Layout.tsx +9 -0
  128. package/src/components/workspace-app-card.spec.tsx +226 -1
  129. package/src/components/workspace-app-card.tsx +115 -12
  130. package/src/lib/connect-apps.spec.ts +118 -66
  131. package/src/lib/connect-apps.ts +47 -82
  132. package/src/lib/workspace-apps.ts +1 -0
  133. package/src/routes/pages/$appId.tsx +1 -0
  134. package/src/routes/pages/apps.spec.tsx +227 -3
  135. package/src/routes/pages/apps.tsx +5 -0
  136. package/src/routes/pages/connect.spec.tsx +53 -6
  137. package/src/routes/pages/connect.tsx +14 -11
  138. package/src/server/index.ts +9 -0
  139. package/src/server/lib/app-creation-store.spec.ts +820 -16
  140. package/src/server/lib/app-creation-store.ts +504 -101
  141. package/src/server/lib/community-apps.spec.ts +367 -0
  142. package/src/server/lib/community-apps.ts +372 -0
  143. package/src/server/lib/community-source.spec.ts +151 -0
  144. package/src/server/lib/community-source.ts +186 -0
  145. package/src/server/lib/connect-apps.ts +149 -0
  146. package/src/server/lib/org-apps-directory-cache.spec.ts +71 -0
  147. package/src/server/lib/org-apps-directory-cache.ts +66 -0
  148. package/src/server/plugins/integrations.spec.ts +60 -0
  149. package/src/server/plugins/integrations.ts +3 -3
  150. package/src/shared/labs.ts +9 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-apps.js","sourceRoot":"","sources":["../../../src/server/lib/connect-apps.ts"],"names":[],"mappings":"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","sourcesContent":["import { ssrfSafeFetch } from \"@agent-native/core/extensions/url-safety\";\n\nimport {\n buildAgentCardUrl,\n normalizeConnectUrl,\n parseConnectAgentCard,\n type ConnectAgentCard,\n type MarketplaceApp,\n} from \"../../lib/connect-apps.js\";\n\nconst CONNECT_FETCH_TIMEOUT_MS = 5_000;\nconst MARKETPLACE_MAX_BYTES = 1_000_000;\nconst AGENT_CARD_MAX_BYTES = 256_000;\nconst MAX_MARKETPLACE_CANDIDATES = 50;\n\nasync function readLimitedText(\n response: Response,\n maxBytes: number,\n label: string,\n): Promise<string> {\n const declaredLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n throw new Error(`${label} is too large`);\n }\n\n if (!response.body) return \"\";\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let totalBytes = 0;\n let body = \"\";\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n totalBytes += value.byteLength;\n if (totalBytes > maxBytes) {\n await reader.cancel();\n throw new Error(`${label} is too large`);\n }\n body += decoder.decode(value, { stream: true });\n }\n return body + decoder.decode();\n } finally {\n reader.releaseLock();\n }\n}\n\nasync function fetchJsonText(\n url: string,\n maxBytes: number,\n label: string,\n assertUrlAllowed?: (url: string) => void,\n) {\n const response = await ssrfSafeFetch(\n url,\n {\n headers: { accept: \"application/json\" },\n signal: AbortSignal.timeout(CONNECT_FETCH_TIMEOUT_MS),\n },\n {\n maxRedirects: 3,\n requireDispatcher: true,\n httpsOnly: true,\n ...(assertUrlAllowed ? { assertUrlAllowed } : {}),\n },\n );\n if (!response.ok) throw new Error(`${label} returned ${response.status}`);\n return readLimitedText(response, maxBytes, label);\n}\n\nexport async function fetchConnectAgentCard(\n appUrl: string,\n): Promise<ConnectAgentCard> {\n const target = normalizeConnectUrl(appUrl);\n if (!target || target.protocol !== \"https:\") {\n throw new Error(\"Enter a secure app URL.\");\n }\n\n const cardUrl = new URL(buildAgentCardUrl(target.toString()));\n const body = await fetchJsonText(\n cardUrl.toString(),\n AGENT_CARD_MAX_BYTES,\n \"Agent card\",\n (url) => {\n const redirect = new URL(url);\n if (\n redirect.origin !== cardUrl.origin ||\n redirect.pathname !== cardUrl.pathname\n ) {\n throw new Error(\"Agent card redirects must stay on the requested URL.\");\n }\n },\n );\n const card = parseConnectAgentCard(\n JSON.parse(body),\n target.origin,\n target.pathname,\n );\n if (!card) {\n throw new Error(\"This app has an invalid or incompatible agent card.\");\n }\n return card;\n}\n\nexport async function fetchMarketplaceApps(\n feedUrl = \"https://www.agent-native.com/apps.json\",\n): Promise<MarketplaceApp[]> {\n const feed = normalizeConnectUrl(feedUrl);\n if (!feed || feed.protocol !== \"https:\") {\n throw new Error(\"Marketplace feed URL must use HTTPS.\");\n }\n\n const body = await fetchJsonText(\n feed.toString(),\n MARKETPLACE_MAX_BYTES,\n \"Marketplace feed\",\n );\n const payload = JSON.parse(body) as { apps?: unknown };\n if (!Array.isArray(payload.apps)) {\n throw new Error(\"Marketplace feed is invalid\");\n }\n\n const candidates = payload.apps\n .filter((entry): entry is MarketplaceApp => {\n if (!entry || typeof entry !== \"object\") return false;\n const app = entry as Record<string, unknown>;\n const url =\n typeof app.url === \"string\" ? normalizeConnectUrl(app.url) : null;\n return (\n typeof app.id === \"string\" &&\n typeof app.name === \"string\" &&\n typeof app.description === \"string\" &&\n Boolean(url && url.protocol === \"https:\") &&\n Array.isArray(app.capabilities) &&\n app.capabilities.every((capability) => typeof capability === \"string\")\n );\n })\n .slice(0, MAX_MARKETPLACE_CANDIDATES);\n\n const verified = await Promise.allSettled(\n candidates.map(async (app) => {\n const card = await fetchConnectAgentCard(app.url);\n return card.connect ? { ...app, capabilities: [\"connect\"] } : null;\n }),\n );\n return verified.flatMap((result) =>\n result.status === \"fulfilled\" && result.value ? [result.value] : [],\n );\n}\n"]}
@@ -0,0 +1,20 @@
1
+ export interface CachedOrgDirectoryAgent {
2
+ id: string;
3
+ name: string;
4
+ description?: string;
5
+ whenToUse?: string[];
6
+ url: string;
7
+ source?: "workspace" | "org-directory" | "remote-agent" | "builtin";
8
+ }
9
+ export interface OrgDirectorySuccessCache<T> {
10
+ get(key: string, load: () => Promise<T>): Promise<T>;
11
+ clear(): void;
12
+ }
13
+ export declare function createOrgDirectorySuccessCache<T>(input?: {
14
+ ttlMs?: number;
15
+ now?: () => number;
16
+ }): OrgDirectorySuccessCache<T>;
17
+ export declare const orgAppsDirectoryCache: OrgDirectorySuccessCache<CachedOrgDirectoryAgent[]>;
18
+ export declare const orgAppsDirectoryAccessCache: OrgDirectorySuccessCache<Set<string>>;
19
+ export declare function invalidateOrgAppsDirectoryCache(): void;
20
+ //# sourceMappingURL=org-apps-directory-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-apps-directory-cache.d.ts","sourceRoot":"","sources":["../../../src/server/lib/org-apps-directory-cache.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,WAAW,GAAG,eAAe,GAAG,cAAc,GAAG,SAAS,CAAC;CACrE;AAED,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,8BAA8B,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,GAAG,wBAAwB,CAAC,CAAC,CAAC,CAoC9B;AAED,eAAO,MAAM,qBAAqB,qDAC2B,CAAC;AAE9D,eAAO,MAAM,2BAA2B,uCAEpB,CAAC;AAErB,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD"}
@@ -0,0 +1,44 @@
1
+ export function createOrgDirectorySuccessCache(input) {
2
+ const ttlMs = input?.ttlMs ?? 60_000;
3
+ const now = input?.now ?? Date.now;
4
+ const values = new Map();
5
+ const inFlight = new Map();
6
+ let generation = 0;
7
+ return {
8
+ async get(key, load) {
9
+ const cached = values.get(key);
10
+ if (cached && cached.expiresAt > now())
11
+ return cached.value;
12
+ values.delete(key);
13
+ const pending = inFlight.get(key);
14
+ if (pending)
15
+ return pending;
16
+ const loadGeneration = generation;
17
+ const refresh = load()
18
+ .then((value) => {
19
+ if (loadGeneration === generation) {
20
+ values.set(key, { value, expiresAt: now() + ttlMs });
21
+ }
22
+ return value;
23
+ })
24
+ .finally(() => {
25
+ if (inFlight.get(key) === refresh)
26
+ inFlight.delete(key);
27
+ });
28
+ inFlight.set(key, refresh);
29
+ return refresh;
30
+ },
31
+ clear() {
32
+ generation += 1;
33
+ values.clear();
34
+ inFlight.clear();
35
+ },
36
+ };
37
+ }
38
+ export const orgAppsDirectoryCache = createOrgDirectorySuccessCache();
39
+ export const orgAppsDirectoryAccessCache = createOrgDirectorySuccessCache({ ttlMs: 15_000 });
40
+ export function invalidateOrgAppsDirectoryCache() {
41
+ orgAppsDirectoryCache.clear();
42
+ orgAppsDirectoryAccessCache.clear();
43
+ }
44
+ //# sourceMappingURL=org-apps-directory-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-apps-directory-cache.js","sourceRoot":"","sources":["../../../src/server/lib/org-apps-directory-cache.ts"],"names":[],"mappings":"AAcA,MAAM,UAAU,8BAA8B,CAAI,KAGjD;IACC,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,MAAM,CAAC;IACrC,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2C,CAAC;IAClE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC/C,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,OAAO;QACL,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI;YACjB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE;gBAAE,OAAO,MAAM,CAAC,KAAK,CAAC;YAC5D,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAEnB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,OAAO;gBAAE,OAAO,OAAO,CAAC;YAE5B,MAAM,cAAc,GAAG,UAAU,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,EAAE;iBACnB,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBACd,IAAI,cAAc,KAAK,UAAU,EAAE,CAAC;oBAClC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;gBACvD,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO;oBAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1D,CAAC,CAAC,CAAC;YACL,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC3B,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,KAAK;YACH,UAAU,IAAI,CAAC,CAAC;YAChB,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAChC,8BAA8B,EAA6B,CAAC;AAE9D,MAAM,CAAC,MAAM,2BAA2B,GAAG,8BAA8B,CAEvE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;AAErB,MAAM,UAAU,+BAA+B;IAC7C,qBAAqB,CAAC,KAAK,EAAE,CAAC;IAC9B,2BAA2B,CAAC,KAAK,EAAE,CAAC;AACtC,CAAC","sourcesContent":["export interface CachedOrgDirectoryAgent {\n id: string;\n name: string;\n description?: string;\n whenToUse?: string[];\n url: string;\n source?: \"workspace\" | \"org-directory\" | \"remote-agent\" | \"builtin\";\n}\n\nexport interface OrgDirectorySuccessCache<T> {\n get(key: string, load: () => Promise<T>): Promise<T>;\n clear(): void;\n}\n\nexport function createOrgDirectorySuccessCache<T>(input?: {\n ttlMs?: number;\n now?: () => number;\n}): OrgDirectorySuccessCache<T> {\n const ttlMs = input?.ttlMs ?? 60_000;\n const now = input?.now ?? Date.now;\n const values = new Map<string, { value: T; expiresAt: number }>();\n const inFlight = new Map<string, Promise<T>>();\n let generation = 0;\n\n return {\n async get(key, load) {\n const cached = values.get(key);\n if (cached && cached.expiresAt > now()) return cached.value;\n values.delete(key);\n\n const pending = inFlight.get(key);\n if (pending) return pending;\n\n const loadGeneration = generation;\n const refresh = load()\n .then((value) => {\n if (loadGeneration === generation) {\n values.set(key, { value, expiresAt: now() + ttlMs });\n }\n return value;\n })\n .finally(() => {\n if (inFlight.get(key) === refresh) inFlight.delete(key);\n });\n inFlight.set(key, refresh);\n return refresh;\n },\n clear() {\n generation += 1;\n values.clear();\n inFlight.clear();\n },\n };\n}\n\nexport const orgAppsDirectoryCache =\n createOrgDirectorySuccessCache<CachedOrgDirectoryAgent[]>();\n\nexport const orgAppsDirectoryAccessCache = createOrgDirectorySuccessCache<\n Set<string>\n>({ ttlMs: 15_000 });\n\nexport function invalidateOrgAppsDirectoryCache(): void {\n orgAppsDirectoryCache.clear();\n orgAppsDirectoryAccessCache.clear();\n}\n"]}
@@ -13,9 +13,9 @@ const DISPATCH_INTEGRATION_SYSTEM_PROMPT = `You are the central dispatch for thi
13
13
 
14
14
  Default posture:
15
15
  - Treat Slack, Telegram, and email as shared entrypoints into the workspace.
16
- - Heavily delegate domain work to specialized agents through A2A (call-agent) when another app owns the job. Apps you can delegate to include slides (decks/presentations), analytics (data/dashboards), content (docs/articles), forms (form builder), clips (screen recordings), design (visual designs), and assets (brand libraries plus generated images/videos).
17
- - Use the available-apps prompt context first, then list-connected-agents when you need fresh details, to see what agents are available before assuming a request must be handled locally.
18
- - Hosted/connected A2A neighbors such as Analytics and Content come from the available-apps context or list-connected-agents. list-workspace-apps only inventories apps mounted inside this workspace deployment; never use a missing row there to conclude that a connected agent is unavailable.
16
+ - Heavily delegate domain work to specialized agents through A2A (call-agent) when another app owns the job.
17
+ - Use relevant-apps prompt profiles first, then list-connected-agents when you need fresh details. If that context is absent or does not list the needed capability, search with describe-workspace-apps query="<capability>" before assuming a request must be handled locally.
18
+ - Hosted/connected A2A neighbors such as Analytics and Content come from the relevant-apps context or list-connected-agents. If neither identifies the needed capability, use describe-workspace-apps before concluding a connected agent is unavailable. list-workspace-apps only inventories apps mounted inside this workspace deployment; never use a missing row there to conclude that a connected agent is unavailable.
19
19
  - When asked whether a mounted workspace app exposes an agent card or A2A endpoint, call list-workspace-apps with includeAgentCards=true. Without that probe, missing agent-card fields mean unchecked, not unavailable.
20
20
  - Treat first-party apps such as Mail, Calendar, Analytics, Brain, Assets, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.
21
21
  - Integration grants are not provider capability limits. For ad hoc provider inspection, querying, reporting, or troubleshooting, call provider-api-catalog/provider-api-docs, then provider-api-request against the provider's real HTTP API. Use connectionId for a specific shared grant and accountId for a specific OAuth account. Never expose secret values or silently widen app access while doing this.
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/server/plugins/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,+BAA+B,GAChC,MAAM,iCAAiC,CAAC;AAEzC,MAAM,0BAA0B,GAAG;IACjC,GAAG,eAAe;IAClB,OAAO,EAAE;QACP,GAAG,eAAe,CAAC,OAAO;QAC1B,SAAS,EAAE,KAAK;KACjB;CACF,CAAC;AAEF,MAAM,kCAAkC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4FA6BiD,CAAC;AAE7F,MAAM,0BAA0B,GAAG,KAAK,EAAE,QAAa,EAAE,EAAE;IACzD,MAAM,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAClD,MAAM,cAAc,GAAG,YAAY,CAAC,YAAY,CAAC;IACjD,MAAM,YAAY,GAChB,OAAO,cAAc,KAAK,QAAQ;QAChC,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,OAAO,cAAc,KAAK,UAAU;YACpC,CAAC,CAAC,cAAc,CAAC,kCAAkC,CAAC;YACpD,CAAC,CAAC,kCAAkC,CAAC;IAE3C,MAAM,MAAM,GAAG,wBAAwB,CAAC;QACtC,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,0BAA0B;QACnC,uBAAuB,EAAE,+BAA+B;QACxD,aAAa,EAAE,qBAAqB;QACpC,YAAY;QACZ,wDAAwD;QACxD,yEAAyE;QACzE,+DAA+D;QAC/D,6EAA6E;KAC9E,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,eAAe,0BAA0B,CAAC","sourcesContent":["import { createIntegrationsPlugin } from \"@agent-native/core/server\";\n\nimport { dispatchActions } from \"../../actions/index.js\";\nimport { getDispatchConfig } from \"../index.js\";\nimport {\n beforeDispatchProcess,\n resolveDispatchExecutionContext,\n} from \"../lib/dispatch-integrations.js\";\n\nconst dispatchIntegrationActions = {\n ...dispatchActions,\n ask_app: {\n ...dispatchActions.ask_app,\n agentTool: false,\n },\n};\n\nconst DISPATCH_INTEGRATION_SYSTEM_PROMPT = `You are the central dispatch for this workspace, responding via a messaging platform integration (Slack, Telegram, email, etc.).\n\nDefault posture:\n- Treat Slack, Telegram, and email as shared entrypoints into the workspace.\n- Heavily delegate domain work to specialized agents through A2A (call-agent) when another app owns the job. Apps you can delegate to include slides (decks/presentations), analytics (data/dashboards), content (docs/articles), forms (form builder), clips (screen recordings), design (visual designs), and assets (brand libraries plus generated images/videos).\n- Use the available-apps prompt context first, then list-connected-agents when you need fresh details, to see what agents are available before assuming a request must be handled locally.\n- Hosted/connected A2A neighbors such as Analytics and Content come from the available-apps context or list-connected-agents. list-workspace-apps only inventories apps mounted inside this workspace deployment; never use a missing row there to conclude that a connected agent is unavailable.\n- When asked whether a mounted workspace app exposes an agent card or A2A endpoint, call list-workspace-apps with includeAgentCards=true. Without that probe, missing agent-card fields mean unchecked, not unavailable.\n- Treat first-party apps such as Mail, Calendar, Analytics, Brain, Assets, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.\n- Integration grants are not provider capability limits. For ad hoc provider inspection, querying, reporting, or troubleshooting, call provider-api-catalog/provider-api-docs, then provider-api-request against the provider's real HTTP API. Use connectionId for a specific shared grant and accountId for a specific OAuth account. Never expose secret values or silently widen app access while doing this.\n- Keep durable memory and operating instructions in resources rather than ephemeral chat.\n- Reply in the originating thread unless the user explicitly asks you to send to a saved destination.\n\nWhen a user asks for something:\n- If it belongs to analytics, content, slides, clips, assets, etc., delegate via call-agent — do not re-implement the domain logic in dispatch.\n- Synthetic uptime, health-check, and URL-availability monitors belong to Analytics, even when the monitored target is another app such as Clips. Delegate creation to Analytics and relay its exact monitor URL. Dispatch-native recurring jobs are for reminders, digests, and agent workflows, not HTTP uptime probes.\n- Route by the requested artifact type, not by organization-specific names stored in code. For structured records, databases, tables, queues, boards, and intake forms, resolve the owning app and canonical destination from loaded workspace instructions/resources plus discovered app capabilities; do not assume Content, a database ID, schema, owner, or required fields. Visual designs, mockups, wireframes, screens, and interfaces belong to Design. A trusted Required target agent hint in integration context is authoritative.\n- When delegating structured intake to the resolved owning app, preserve the exact Source thread URL and the workspace instruction context, inspect the destination's current required fields, ask only for missing values, submit once, verify the saved record, and return the exact link.\n- In messaging integrations, use call-agent for cross-app delegation; do not use ask_app.\n- After call-agent returns an answer, RELAY IT DIRECTLY to the user with at most a one-line preface — do not rephrase, summarize, or add commentary. The downstream agent already crafted the answer; your job is delivery, not editing. This minimizes round-trips and keeps the user-visible reply fast.\n- Exception: if the downstream agent reports a missing model/provider credential, do not name exact env vars, Vault keys, tokens, or secrets. Say the target app needs an LLM connection and recommend using Builder/managed LLM for that app; keep bring-your-own provider keys as a secondary option only if the user asks.\n- If the user asks to create, build, make, scaffold, or generate an \"agent\" from Dispatch chat or by tagging @agent-native in Slack, email, or Telegram, first classify the ask. If it is a simple Dispatch-native behavior like a reminder, digest, monitor, routing rule, saved instruction, or recurring workflow, create or update the recurring job/resource/destination in Dispatch. If it is a robust unique product or teammate that needs its own UI, data model, actions, integrations, or domain workflow, treat it as a new workspace app and call start-workspace-app-creation.\n- If a user asks to rename an existing workspace app or change its Dispatch title/description, call update-workspace-app-metadata with the existing appId from list-workspace-apps. Do not call start-workspace-app-creation for a metadata-only edit because that starts a new app and Builder branch.\n- If a new-app prompt asks for access to Mail, Calendar, Analytics, Brain, Assets, or similar first-party app data/agents, keep using the existing hosted/connected app and A2A path. Do not ask Builder to scaffold those apps as children of the new app unless the user explicitly asks for a customized app from that template.\n- If the chat template is used, treat it as scaffolding only: the finished app must be branded as the requested app with its own home screen/navigation/package metadata/manifest, and must not leave visible \"Chat\", \"Starter\", \"Blank app\", or \"New app\" UI behind.\n- If the user explicitly asks for a new app or workspace app, call start-workspace-app-creation with their prompt and include a concise generated description by default. Do not satisfy a new-app request by adding a route, page, component, or file inside apps/chat or another existing app unless the user explicitly asks to modify that existing app. When the prompt contains a source brief or concrete repeatable workflow, choose recommended defaults and do not ask non-blocking product or UX questions; ask only for authorization, credentials, a destructive action, an ambiguous target workspace, or a genuinely missing workflow. If the request is too vague to classify, ask one concise follow-up. If the action returns mode \"builder\", reply with the Builder branch URL; Builder is responsible for creating the separate workspace app under apps/<app-id>, mounting it at /<app-id>, ensuring apps/<app-id>/package.json exists with name/displayName and description so Dispatch discovers it, using relative /<app-id> links instead of hardcoded localhost/dev ports, and preserving APP_BASE_PATH/VITE_APP_BASE_PATH in the React Router client entry. The new app lives at the workspace root /<app-id>, NOT under /dispatch/<app-id>, /apps/<app-id>, or any other Dispatch tab — when telling the user where to find it, link to /<app-id> only. There is no separate workspace app registry to edit. If it returns mode \"local-agent\", tell the user it is ready for the local code agent and include the returned app path/prompt summary. Dispatch reuses the connected workspace Builder project or provisions it through the Builder Projects API when an organization owner/admin starts the first app; members need an owner/admin to configure the project first. Do not ask the user to use Builder org/beta settings. If it returns mode \"builder-unavailable\", the action also returns a \\`reason\\` code plus a user-facing \\`message\\` and (for some reasons) an operator-facing \\`detail\\`. Relay the \\`message\\` to the user as-is; only surface \\`detail\\` when the user is clearly an operator debugging the deployment (e.g. troubleshooting a broken Builder connection for the workspace), never as routine detail for an end user. If it returns mode \"app-id-taken\", the app id is already in use: \\`conflict\\` is \"registered\" for a live app or \"pending\" for a creation in flight, and \\`owner\\` names who started it. Relay the \\`message\\` and propose a different app id; never retry the same id.\n- For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.\n- Keep responses concise and operational — messaging platforms have character limits.\n- Use markdown sparingly (bold and lists are fine, avoid complex formatting).\n- If a task requires many steps, summarize what you did rather than streaming every detail.`;\n\nconst dispatchIntegrationsPlugin = async (nitroApp: any) => {\n const { integrations = {} } = getDispatchConfig();\n const promptOverride = integrations.systemPrompt;\n const systemPrompt =\n typeof promptOverride === \"string\"\n ? promptOverride\n : typeof promptOverride === \"function\"\n ? promptOverride(DISPATCH_INTEGRATION_SYSTEM_PROMPT)\n : DISPATCH_INTEGRATION_SYSTEM_PROMPT;\n\n const plugin = createIntegrationsPlugin({\n appId: \"dispatch\",\n actions: dispatchIntegrationActions,\n resolveExecutionContext: resolveDispatchExecutionContext,\n beforeProcess: beforeDispatchProcess,\n systemPrompt,\n // Inherit the framework default (claude-sonnet-4-6 from\n // packages/core/src/integrations/plugin.ts). Haiku was tried for latency\n // but hallucinated URLs/IDs after delegated call-agent results\n // (e.g. inventing `https://slides.workspace.com/deck/builder-io-deck-2024`).\n });\n\n return plugin(nitroApp);\n};\n\nexport default dispatchIntegrationsPlugin;\n"]}
1
+ {"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/server/plugins/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,+BAA+B,GAChC,MAAM,iCAAiC,CAAC;AAEzC,MAAM,0BAA0B,GAAG;IACjC,GAAG,eAAe;IAClB,OAAO,EAAE;QACP,GAAG,eAAe,CAAC,OAAO;QAC1B,SAAS,EAAE,KAAK;KACjB;CACF,CAAC;AAEF,MAAM,kCAAkC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4FA6BiD,CAAC;AAE7F,MAAM,0BAA0B,GAAG,KAAK,EAAE,QAAa,EAAE,EAAE;IACzD,MAAM,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAClD,MAAM,cAAc,GAAG,YAAY,CAAC,YAAY,CAAC;IACjD,MAAM,YAAY,GAChB,OAAO,cAAc,KAAK,QAAQ;QAChC,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,OAAO,cAAc,KAAK,UAAU;YACpC,CAAC,CAAC,cAAc,CAAC,kCAAkC,CAAC;YACpD,CAAC,CAAC,kCAAkC,CAAC;IAE3C,MAAM,MAAM,GAAG,wBAAwB,CAAC;QACtC,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,0BAA0B;QACnC,uBAAuB,EAAE,+BAA+B;QACxD,aAAa,EAAE,qBAAqB;QACpC,YAAY;QACZ,wDAAwD;QACxD,yEAAyE;QACzE,+DAA+D;QAC/D,6EAA6E;KAC9E,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,eAAe,0BAA0B,CAAC","sourcesContent":["import { createIntegrationsPlugin } from \"@agent-native/core/server\";\n\nimport { dispatchActions } from \"../../actions/index.js\";\nimport { getDispatchConfig } from \"../index.js\";\nimport {\n beforeDispatchProcess,\n resolveDispatchExecutionContext,\n} from \"../lib/dispatch-integrations.js\";\n\nconst dispatchIntegrationActions = {\n ...dispatchActions,\n ask_app: {\n ...dispatchActions.ask_app,\n agentTool: false,\n },\n};\n\nconst DISPATCH_INTEGRATION_SYSTEM_PROMPT = `You are the central dispatch for this workspace, responding via a messaging platform integration (Slack, Telegram, email, etc.).\n\nDefault posture:\n- Treat Slack, Telegram, and email as shared entrypoints into the workspace.\n- Heavily delegate domain work to specialized agents through A2A (call-agent) when another app owns the job.\n- Use relevant-apps prompt profiles first, then list-connected-agents when you need fresh details. If that context is absent or does not list the needed capability, search with describe-workspace-apps query=\"<capability>\" before assuming a request must be handled locally.\n- Hosted/connected A2A neighbors such as Analytics and Content come from the relevant-apps context or list-connected-agents. If neither identifies the needed capability, use describe-workspace-apps before concluding a connected agent is unavailable. list-workspace-apps only inventories apps mounted inside this workspace deployment; never use a missing row there to conclude that a connected agent is unavailable.\n- When asked whether a mounted workspace app exposes an agent card or A2A endpoint, call list-workspace-apps with includeAgentCards=true. Without that probe, missing agent-card fields mean unchecked, not unavailable.\n- Treat first-party apps such as Mail, Calendar, Analytics, Brain, Assets, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.\n- Integration grants are not provider capability limits. For ad hoc provider inspection, querying, reporting, or troubleshooting, call provider-api-catalog/provider-api-docs, then provider-api-request against the provider's real HTTP API. Use connectionId for a specific shared grant and accountId for a specific OAuth account. Never expose secret values or silently widen app access while doing this.\n- Keep durable memory and operating instructions in resources rather than ephemeral chat.\n- Reply in the originating thread unless the user explicitly asks you to send to a saved destination.\n\nWhen a user asks for something:\n- If it belongs to analytics, content, slides, clips, assets, etc., delegate via call-agent — do not re-implement the domain logic in dispatch.\n- Synthetic uptime, health-check, and URL-availability monitors belong to Analytics, even when the monitored target is another app such as Clips. Delegate creation to Analytics and relay its exact monitor URL. Dispatch-native recurring jobs are for reminders, digests, and agent workflows, not HTTP uptime probes.\n- Route by the requested artifact type, not by organization-specific names stored in code. For structured records, databases, tables, queues, boards, and intake forms, resolve the owning app and canonical destination from loaded workspace instructions/resources plus discovered app capabilities; do not assume Content, a database ID, schema, owner, or required fields. Visual designs, mockups, wireframes, screens, and interfaces belong to Design. A trusted Required target agent hint in integration context is authoritative.\n- When delegating structured intake to the resolved owning app, preserve the exact Source thread URL and the workspace instruction context, inspect the destination's current required fields, ask only for missing values, submit once, verify the saved record, and return the exact link.\n- In messaging integrations, use call-agent for cross-app delegation; do not use ask_app.\n- After call-agent returns an answer, RELAY IT DIRECTLY to the user with at most a one-line preface — do not rephrase, summarize, or add commentary. The downstream agent already crafted the answer; your job is delivery, not editing. This minimizes round-trips and keeps the user-visible reply fast.\n- Exception: if the downstream agent reports a missing model/provider credential, do not name exact env vars, Vault keys, tokens, or secrets. Say the target app needs an LLM connection and recommend using Builder/managed LLM for that app; keep bring-your-own provider keys as a secondary option only if the user asks.\n- If the user asks to create, build, make, scaffold, or generate an \"agent\" from Dispatch chat or by tagging @agent-native in Slack, email, or Telegram, first classify the ask. If it is a simple Dispatch-native behavior like a reminder, digest, monitor, routing rule, saved instruction, or recurring workflow, create or update the recurring job/resource/destination in Dispatch. If it is a robust unique product or teammate that needs its own UI, data model, actions, integrations, or domain workflow, treat it as a new workspace app and call start-workspace-app-creation.\n- If a user asks to rename an existing workspace app or change its Dispatch title/description, call update-workspace-app-metadata with the existing appId from list-workspace-apps. Do not call start-workspace-app-creation for a metadata-only edit because that starts a new app and Builder branch.\n- If a new-app prompt asks for access to Mail, Calendar, Analytics, Brain, Assets, or similar first-party app data/agents, keep using the existing hosted/connected app and A2A path. Do not ask Builder to scaffold those apps as children of the new app unless the user explicitly asks for a customized app from that template.\n- If the chat template is used, treat it as scaffolding only: the finished app must be branded as the requested app with its own home screen/navigation/package metadata/manifest, and must not leave visible \"Chat\", \"Starter\", \"Blank app\", or \"New app\" UI behind.\n- If the user explicitly asks for a new app or workspace app, call start-workspace-app-creation with their prompt and include a concise generated description by default. Do not satisfy a new-app request by adding a route, page, component, or file inside apps/chat or another existing app unless the user explicitly asks to modify that existing app. When the prompt contains a source brief or concrete repeatable workflow, choose recommended defaults and do not ask non-blocking product or UX questions; ask only for authorization, credentials, a destructive action, an ambiguous target workspace, or a genuinely missing workflow. If the request is too vague to classify, ask one concise follow-up. If the action returns mode \"builder\", reply with the Builder branch URL; Builder is responsible for creating the separate workspace app under apps/<app-id>, mounting it at /<app-id>, ensuring apps/<app-id>/package.json exists with name/displayName and description so Dispatch discovers it, using relative /<app-id> links instead of hardcoded localhost/dev ports, and preserving APP_BASE_PATH/VITE_APP_BASE_PATH in the React Router client entry. The new app lives at the workspace root /<app-id>, NOT under /dispatch/<app-id>, /apps/<app-id>, or any other Dispatch tab — when telling the user where to find it, link to /<app-id> only. There is no separate workspace app registry to edit. If it returns mode \"local-agent\", tell the user it is ready for the local code agent and include the returned app path/prompt summary. Dispatch reuses the connected workspace Builder project or provisions it through the Builder Projects API when an organization owner/admin starts the first app; members need an owner/admin to configure the project first. Do not ask the user to use Builder org/beta settings. If it returns mode \"builder-unavailable\", the action also returns a \\`reason\\` code plus a user-facing \\`message\\` and (for some reasons) an operator-facing \\`detail\\`. Relay the \\`message\\` to the user as-is; only surface \\`detail\\` when the user is clearly an operator debugging the deployment (e.g. troubleshooting a broken Builder connection for the workspace), never as routine detail for an end user. If it returns mode \"app-id-taken\", the app id is already in use: \\`conflict\\` is \"registered\" for a live app or \"pending\" for a creation in flight, and \\`owner\\` names who started it. Relay the \\`message\\` and propose a different app id; never retry the same id.\n- For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.\n- Keep responses concise and operational — messaging platforms have character limits.\n- Use markdown sparingly (bold and lists are fine, avoid complex formatting).\n- If a task requires many steps, summarize what you did rather than streaming every detail.`;\n\nconst dispatchIntegrationsPlugin = async (nitroApp: any) => {\n const { integrations = {} } = getDispatchConfig();\n const promptOverride = integrations.systemPrompt;\n const systemPrompt =\n typeof promptOverride === \"string\"\n ? promptOverride\n : typeof promptOverride === \"function\"\n ? promptOverride(DISPATCH_INTEGRATION_SYSTEM_PROMPT)\n : DISPATCH_INTEGRATION_SYSTEM_PROMPT;\n\n const plugin = createIntegrationsPlugin({\n appId: \"dispatch\",\n actions: dispatchIntegrationActions,\n resolveExecutionContext: resolveDispatchExecutionContext,\n beforeProcess: beforeDispatchProcess,\n systemPrompt,\n // Inherit the framework default (claude-sonnet-4-6 from\n // packages/core/src/integrations/plugin.ts). Haiku was tried for latency\n // but hallucinated URLs/IDs after delegated call-agent results\n // (e.g. inventing `https://slides.workspace.com/deck/builder-io-deck-2024`).\n });\n\n return plugin(nitroApp);\n};\n\nexport default dispatchIntegrationsPlugin;\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const COMMUNITY_APPS_LAB: import("@agent-native/core/labs/registry").LabDefinition;
2
+ //# sourceMappingURL=labs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labs.d.ts","sourceRoot":"","sources":["../../src/shared/labs.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,0DAM7B,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { defineLab } from "@agent-native/core/labs/registry";
2
+ export const COMMUNITY_APPS_LAB = defineLab({
3
+ key: "dispatch.community-apps",
4
+ displayName: "Community apps",
5
+ description: "Connect trusted remote agents and install reviewed, pinned community app templates.",
6
+ keywords: "community apps templates connect install",
7
+ });
8
+ //# sourceMappingURL=labs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labs.js","sourceRoot":"","sources":["../../src/shared/labs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAE7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;IAC1C,GAAG,EAAE,yBAAyB;IAC9B,WAAW,EAAE,gBAAgB;IAC7B,WAAW,EACT,qFAAqF;IACvF,QAAQ,EAAE,0CAA0C;CACrD,CAAC,CAAC","sourcesContent":["import { defineLab } from \"@agent-native/core/labs/registry\";\n\nexport const COMMUNITY_APPS_LAB = defineLab({\n key: \"dispatch.community-apps\",\n displayName: \"Community apps\",\n description:\n \"Connect trusted remote agents and install reviewed, pinned community app templates.\",\n keywords: \"community apps templates connect install\",\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/dispatch",
3
- "version": "0.40.30-nightly-20261021093329",
3
+ "version": "0.40.31",
4
4
  "description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -31,6 +31,7 @@
31
31
  "./server": "./dist/server/index.js",
32
32
  "./shared/app-roles": "./dist/shared/app-roles.js",
33
33
  "./shared/feature-flags": "./dist/shared/feature-flags.js",
34
+ "./shared/labs": "./dist/shared/labs.js",
34
35
  "./shared/workspace-sso": "./dist/shared/workspace-sso.js",
35
36
  "./server/lib/thread-link-preview": "./dist/server/lib/thread-link-preview.js",
36
37
  "./actions": "./dist/actions/index.js",
@@ -89,7 +90,7 @@
89
90
  "tailwind-merge": "^3.5.0",
90
91
  "vaul": "^1.1.2",
91
92
  "zod": "^4.3.6",
92
- "@agent-native/toolkit": "0.212.0-nightly-20261021093329"
93
+ "@agent-native/toolkit": "0.212.1"
93
94
  },
94
95
  "devDependencies": {
95
96
  "@react-router/dev": "^8.1.0",
@@ -103,10 +104,10 @@
103
104
  "typescript-7": "npm:typescript@^7.0.2",
104
105
  "vite": "8.1.0",
105
106
  "vitest": "^4.1.5",
106
- "@agent-native/core": "0.212.0-nightly-20261021093329"
107
+ "@agent-native/core": "0.212.1"
107
108
  },
108
109
  "peerDependencies": {
109
- "@agent-native/core": "0.212.0-nightly-20261021093329",
110
+ "@agent-native/core": ">=0.209.1",
110
111
  "react": ">=19.2.7",
111
112
  "react-dom": ">=19.2.7",
112
113
  "react-router": ">=8"
@@ -29,10 +29,13 @@ import action from "./connect-external-agent.js";
29
29
  describe("connect-external-agent action", () => {
30
30
  beforeEach(() => {
31
31
  vi.clearAllMocks();
32
- mocks.currentOrgId.mockReturnValue(null);
32
+ mocks.currentOrgId.mockReturnValue("org_1");
33
33
  mocks.currentOwnerEmail.mockReturnValue("owner@example.com");
34
34
  mocks.sharedResourceOwner.mockReturnValue("shared-owner");
35
35
  mocks.resourcePutIfAbsent.mockResolvedValue({ id: "resource_1" });
36
+ mocks.getDbExec.mockReturnValue({
37
+ execute: vi.fn().mockResolvedValue({ rows: [{ role: "owner" }] }),
38
+ });
36
39
  });
37
40
 
38
41
  it("rejects a malformed endpoint URL with a clean validation error instead of an unhandled throw", async () => {
@@ -46,7 +49,7 @@ describe("connect-external-agent action", () => {
46
49
  }
47
50
 
48
51
  expect(isActionContractError(caught)).toBe(true);
49
- expect((caught as Error).message).toMatch(/valid url/i);
52
+ expect((caught as Error).message).toMatch(/https/i);
50
53
  expect(mocks.resourcePutIfAbsent).not.toHaveBeenCalled();
51
54
  });
52
55
 
@@ -62,7 +65,102 @@ describe("connect-external-agent action", () => {
62
65
  }
63
66
 
64
67
  expect(isActionContractError(caught)).toBe(true);
65
- expect((caught as Error).message).toMatch(/http:\/\/ or https:\/\//i);
68
+ expect((caught as Error).message).toMatch(/https/i);
69
+ });
70
+
71
+ it("rejects localhost HTTP endpoints", async () => {
72
+ let caught: unknown;
73
+ try {
74
+ await action.run({
75
+ url: "http://localhost:3000",
76
+ scope: "personal",
77
+ } as never);
78
+ } catch (err) {
79
+ caught = err;
80
+ }
81
+
82
+ expect(isActionContractError(caught)).toBe(true);
83
+ expect((caught as Error).message).toMatch(/https/i);
84
+ expect(mocks.resourcePutIfAbsent).not.toHaveBeenCalled();
85
+ });
86
+
87
+ it.each([
88
+ "https://127.0.0.1",
89
+ "https://10.0.0.8",
90
+ "https://192.168.1.2",
91
+ "https://[::1]",
92
+ "https://user:pass@agent.example.com",
93
+ ])("rejects unsafe endpoint %s without writing", async (url) => {
94
+ let caught: unknown;
95
+ try {
96
+ await action.run({ url, scope: "personal" } as never);
97
+ } catch (err) {
98
+ caught = err;
99
+ }
100
+
101
+ expect(isActionContractError(caught)).toBe(true);
102
+ expect(mocks.resourcePutIfAbsent).not.toHaveBeenCalled();
103
+ });
104
+
105
+ it("requires an active organization for shared agents before any writes", async () => {
106
+ mocks.currentOrgId.mockReturnValue(null);
107
+ let caught: unknown;
108
+ try {
109
+ await action.run({
110
+ url: "https://agent.example.com",
111
+ scope: "shared",
112
+ } as never);
113
+ } catch (err) {
114
+ caught = err;
115
+ }
116
+
117
+ expect(isActionContractError(caught)).toBe(true);
118
+ expect(mocks.getDbExec).not.toHaveBeenCalled();
119
+ expect(mocks.sharedResourceOwner).not.toHaveBeenCalled();
120
+ expect(mocks.resourcePutIfAbsent).not.toHaveBeenCalled();
121
+ });
122
+
123
+ it("requires an owner or admin actor for shared agents before writing", async () => {
124
+ mocks.currentOrgId.mockReturnValue("org_1");
125
+ mocks.getDbExec.mockReturnValue({
126
+ execute: vi.fn().mockResolvedValue({ rows: [{ role: "member" }] }),
127
+ });
128
+
129
+ let caught: unknown;
130
+ try {
131
+ await action.run({
132
+ url: "https://agent.example.com",
133
+ scope: "shared",
134
+ } as never);
135
+ } catch (err) {
136
+ caught = err;
137
+ }
138
+
139
+ expect(isActionContractError(caught)).toBe(true);
140
+ expect((caught as { statusCode?: number }).statusCode).toBe(403);
141
+ expect(mocks.sharedResourceOwner).not.toHaveBeenCalled();
142
+ expect(mocks.resourcePutIfAbsent).not.toHaveBeenCalled();
143
+ });
144
+
145
+ it("lets organization members create personal connections without shared-agent admin access", async () => {
146
+ mocks.currentOrgId.mockReturnValue("org_1");
147
+ mocks.currentOwnerEmail.mockReturnValue("member@example.com");
148
+
149
+ const result = await action.run({
150
+ url: "https://agent.example.com",
151
+ name: "Personal Agent",
152
+ scope: "personal",
153
+ } as never);
154
+
155
+ expect(result.status).toBe("created");
156
+ expect(result.scope).toBe("personal");
157
+ expect(mocks.getDbExec).not.toHaveBeenCalled();
158
+ expect(mocks.resourcePutIfAbsent).toHaveBeenCalledWith(
159
+ "member@example.com",
160
+ "remote-agents/personal-agent.json",
161
+ expect.any(String),
162
+ "application/json",
163
+ );
66
164
  });
67
165
 
68
166
  it("connects a well-formed https endpoint", async () => {
@@ -6,7 +6,7 @@ import {
6
6
  } from "@agent-native/core/resources/store";
7
7
  import { z } from "zod";
8
8
 
9
- import { parseAgentEndpointUrl } from "../lib/agent-endpoint-url.js";
9
+ import { normalizeConnectUrl } from "../lib/connect-apps.js";
10
10
  import {
11
11
  currentOrgId,
12
12
  currentOwnerEmail,
@@ -24,7 +24,11 @@ function slugify(value: string): string {
24
24
 
25
25
  async function assertCanManageSharedAgent() {
26
26
  const orgId = currentOrgId();
27
- if (!orgId) return;
27
+ if (!orgId) {
28
+ fail("An active organization is required to connect an agent.", {
29
+ statusCode: 403,
30
+ });
31
+ }
28
32
  const actor = currentOwnerEmail().trim().toLowerCase();
29
33
  const result = await getDbExec().execute({
30
34
  sql: `SELECT role FROM org_members
@@ -35,7 +39,7 @@ async function assertCanManageSharedAgent() {
35
39
  });
36
40
  const role = result.rows[0]?.role;
37
41
  if (role !== "owner" && role !== "admin") {
38
- fail("Only organization owners and admins can connect shared agents.", {
42
+ fail("Only organization owners and admins can connect agents.", {
39
43
  statusCode: 403,
40
44
  });
41
45
  }
@@ -43,9 +47,9 @@ async function assertCanManageSharedAgent() {
43
47
 
44
48
  export default defineAction({
45
49
  description:
46
- "Connect an existing HTTP/A2A agent to Dispatch. This stores only its public endpoint and metadata; authentication remains in the normal A2A/MCP connection flow.",
50
+ "Connect an existing HTTPS/A2A agent to Dispatch. This stores only its public endpoint and metadata; authentication remains in the normal A2A/MCP connection flow.",
47
51
  schema: z.object({
48
- url: z.string().min(1).describe("HTTP or HTTPS agent endpoint"),
52
+ url: z.string().min(1).describe("HTTPS agent endpoint"),
49
53
  name: z.string().max(160).optional().describe("Agent name"),
50
54
  description: z.string().max(500).optional().describe("Short description"),
51
55
  scope: z
@@ -54,12 +58,9 @@ export default defineAction({
54
58
  .describe("Share with the workspace or keep the connection personal"),
55
59
  }),
56
60
  run: async ({ url, name, description, scope }) => {
57
- let parsed: URL;
58
- try {
59
- parsed = parseAgentEndpointUrl(url);
60
- } catch (err) {
61
- fail(err instanceof Error ? err.message : "Enter a valid URL.");
62
- }
61
+ const parsed = normalizeConnectUrl(url);
62
+ if (!parsed)
63
+ fail("Use an HTTPS URL without credentials or private addresses.");
63
64
 
64
65
  if (scope === "shared") await assertCanManageSharedAgent();
65
66
  const agentName = name?.trim() || parsed.hostname.replace(/^www\./, "");
@@ -5,6 +5,8 @@ import { dispatchActions } from "./index.js";
5
5
  describe("dispatch action registry", () => {
6
6
  it("keeps workspace resources runtime-inherited instead of exposing sync actions", () => {
7
7
  expect(dispatchActions).toHaveProperty("list-workspace-resources-for-app");
8
+ expect(dispatchActions).toHaveProperty("list-connect-marketplace-apps");
9
+ expect(dispatchActions).toHaveProperty("inspect-connect-agent-card");
8
10
  expect(dispatchActions).toHaveProperty("connect-external-agent");
9
11
  expect(dispatchActions).toHaveProperty("import-agent");
10
12
  expect(dispatchActions).toHaveProperty("list-mcp-app-access");
@@ -60,6 +62,17 @@ describe("dispatch action registry", () => {
60
62
  );
61
63
  });
62
64
 
65
+ it("keeps Connect URL inspection UI-only", () => {
66
+ expect(dispatchActions["list-connect-marketplace-apps"].agentTool).toBe(
67
+ false,
68
+ );
69
+ expect(dispatchActions["list-connect-marketplace-apps"].mcpTool).toBe(
70
+ false,
71
+ );
72
+ expect(dispatchActions["inspect-connect-agent-card"].agentTool).toBe(false);
73
+ expect(dispatchActions["inspect-connect-agent-card"].mcpTool).toBe(false);
74
+ });
75
+
63
76
  it("teaches MCP hosts to route through their existing Dispatch connection", () => {
64
77
  expect(dispatchActions.ask_app.tool.description).toContain(
65
78
  "do not require a separate app MCP connection",
@@ -36,11 +36,15 @@ import grantVaultSecretsToApp from "./grant-vault-secrets-to-app.js";
36
36
  import grantWorkspaceResourcesToApp from "./grant-workspace-resources-to-app.js";
37
37
  import importAgentPack from "./import-agent-pack.js";
38
38
  import importAgent from "./import-agent.js";
39
+ import inspectConnectAgentCard from "./inspect-connect-agent-card.js";
40
+ import installCuratedCommunityApp from "./install-curated-community-app.js";
39
41
  import listAgentPack from "./list-agent-pack.js";
40
42
  import listAgentRunFailures from "./list-agent-run-failures.js";
41
43
  import listAgentThreadSources from "./list-agent-thread-sources.js";
42
44
  import listAvailableWorkspaceTemplates from "./list-available-workspace-templates.js";
45
+ import listConnectMarketplaceApps from "./list-connect-marketplace-apps.js";
43
46
  import listConnectedAgents from "./list-connected-agents.js";
47
+ import listCuratedCommunityApps from "./list-curated-community-apps.js";
44
48
  import listCuratedWorkspaceTemplates from "./list-curated-workspace-templates.js";
45
49
  import listDestinations from "./list-destinations.js";
46
50
  import listDispatchApprovals from "./list-dispatch-approvals.js";
@@ -140,7 +144,9 @@ export const dispatchActions: Record<string, ActionEntry> = {
140
144
  "list-agent-thread-sources": listAgentThreadSources,
141
145
  "list-agent-run-failures": listAgentRunFailures,
142
146
  "list-available-workspace-templates": listAvailableWorkspaceTemplates,
147
+ "list-curated-community-apps": listCuratedCommunityApps,
143
148
  "list-curated-workspace-templates": listCuratedWorkspaceTemplates,
149
+ "list-connect-marketplace-apps": listConnectMarketplaceApps,
144
150
  "list-connected-agents": listConnectedAgents,
145
151
  "list-destinations": listDestinations,
146
152
  "list-dispatch-approvals": listDispatchApprovals,
@@ -166,7 +172,9 @@ export const dispatchActions: Record<string, ActionEntry> = {
166
172
  "list-workspace-resources-for-app": listWorkspaceResourcesForApp,
167
173
  "list-workspace-resources": listWorkspaceResources,
168
174
  "import-agent": importAgent,
175
+ "inspect-connect-agent-card": inspectConnectAgentCard,
169
176
  "import-agent-pack": importAgentPack,
177
+ "install-curated-community-app": installCuratedCommunityApp,
170
178
  "list-agent-pack": listAgentPack,
171
179
  navigate: navigate,
172
180
  open_app: openApp,
@@ -0,0 +1,17 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { z } from "zod";
3
+
4
+ import { fetchConnectAgentCard } from "../server/lib/connect-apps.js";
5
+
6
+ export default defineAction({
7
+ description:
8
+ "Inspect a user-entered Agent-Native app URL for a compatible connection card.",
9
+ schema: z.object({ url: z.string().trim().min(1).max(2048) }),
10
+ http: { method: "POST" },
11
+ readOnly: true,
12
+ parallelSafe: true,
13
+ agentTool: false,
14
+ mcpTool: false,
15
+ toolCallable: false,
16
+ run: async ({ url }) => fetchConnectAgentCard(url),
17
+ });
@@ -0,0 +1,32 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import {
3
+ getWorkspaceAppIdValidationError,
4
+ normalizeWorkspaceAppId,
5
+ } from "@agent-native/core/shared";
6
+ import { z } from "zod";
7
+
8
+ import { installCuratedCommunityApp } from "../server/lib/community-apps.js";
9
+
10
+ export default defineAction({
11
+ mcpAnnotations: {
12
+ readOnlyHint: false,
13
+ destructiveHint: false,
14
+ openWorldHint: false,
15
+ },
16
+ description:
17
+ "Install one reviewed, pinned community app template into the associated workspace. Select a sourceId from list-curated-community-apps; repository and revision are fixed by Dispatch and cannot be supplied by the caller.",
18
+ schema: z.object({
19
+ sourceId: z.string().min(1).max(100).describe("Curated source id"),
20
+ appId: z
21
+ .string()
22
+ .max(64)
23
+ .transform(normalizeWorkspaceAppId)
24
+ .refine((appId) => !getWorkspaceAppIdValidationError(appId), {
25
+ message:
26
+ "Use a non-reserved app id with lowercase letters, numbers, and hyphens.",
27
+ })
28
+ .optional()
29
+ .describe("Optional target app id; defaults to the template's app id"),
30
+ }),
31
+ run: async (input) => installCuratedCommunityApp(input),
32
+ });
@@ -0,0 +1,17 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { z } from "zod";
3
+
4
+ import { fetchMarketplaceApps } from "../server/lib/connect-apps.js";
5
+
6
+ export default defineAction({
7
+ description:
8
+ "List verified Agent-Native marketplace apps that support connecting to a workspace.",
9
+ schema: z.object({}),
10
+ http: { method: "GET" },
11
+ readOnly: true,
12
+ parallelSafe: true,
13
+ agentTool: false,
14
+ mcpTool: false,
15
+ toolCallable: false,
16
+ run: async () => fetchMarketplaceApps(),
17
+ });
@@ -3,7 +3,9 @@ import { describe, expect, it, vi } from "vitest";
3
3
  const mocks = vi.hoisted(() => ({
4
4
  discoverAgents: vi.fn(),
5
5
  getBuiltinAgents: vi.fn(),
6
- getRequestUserEmail: vi.fn(() => "owner@example.test"),
6
+ getRequestUserEmail: vi.fn<() => string | undefined>(
7
+ () => "owner@example.test",
8
+ ),
7
9
  resourceGet: vi.fn(),
8
10
  resourceListAccessible: vi.fn(async (): Promise<unknown[]> => []),
9
11
  parseRemoteAgentManifest: vi.fn(),
@@ -141,4 +143,18 @@ describe("list-connected-agents", () => {
141
143
  expect(entry).toMatchObject({ id: "mail", source: "workspace" });
142
144
  expect(entry).not.toHaveProperty("homeUrl");
143
145
  });
146
+
147
+ it("returns a typed authentication error when the session has no user email", async () => {
148
+ mocks.discoverAgents.mockResolvedValue([]);
149
+ mocks.getBuiltinAgents.mockReturnValue([]);
150
+ mocks.loadWorkspaceAppsManifest.mockResolvedValue(null);
151
+ mocks.getRequestUserEmail.mockReturnValueOnce(undefined);
152
+
153
+ const { default: action } = await import("./list-connected-agents.js");
154
+
155
+ await expect(action.run({})).rejects.toMatchObject({
156
+ statusCode: 401,
157
+ errorCode: "dispatch_user_identity_missing",
158
+ });
159
+ });
144
160
  });
@@ -1,4 +1,4 @@
1
- import { defineAction } from "@agent-native/core/action";
1
+ import { defineAction, fail } from "@agent-native/core/action";
2
2
  import {
3
3
  REMOTE_AGENT_RESOURCE_PREFIXES,
4
4
  parseRemoteAgentManifest,
@@ -45,7 +45,12 @@ export default defineAction({
45
45
  builtins.map((agent) => [agent.id, agent.url]),
46
46
  );
47
47
  const ownerEmail = getRequestUserEmail();
48
- if (!ownerEmail) throw new Error("no authenticated user");
48
+ if (!ownerEmail) {
49
+ fail("Sign in to list connected agents.", {
50
+ errorCode: "dispatch_user_identity_missing",
51
+ statusCode: 401,
52
+ });
53
+ }
49
54
  const resources: Array<{ id: string; path: string; owner: string }> = [];
50
55
  for (const prefix of [...REMOTE_AGENT_RESOURCE_PREFIXES].reverse()) {
51
56
  resources.push(...(await resourceListAccessible(ownerEmail, prefix)));
@@ -0,0 +1,12 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { z } from "zod";
3
+
4
+ import { listCuratedCommunityApps } from "../server/lib/community-apps.js";
5
+
6
+ export default defineAction({
7
+ description:
8
+ "List reviewed, pinned community app templates that can be installed in the associated workspace. This catalog is separate from deployed agents in Connect.",
9
+ schema: z.object({}),
10
+ http: { method: "GET" },
11
+ run: async () => listCuratedCommunityApps(),
12
+ });