@agent-native/dispatch 0.40.30 → 0.40.33

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 (100) hide show
  1. package/CHANGELOG.md +28 -25
  2. package/dist/actions/connect-external-agent.d.ts.map +1 -1
  3. package/dist/actions/connect-external-agent.js +2 -9
  4. package/dist/actions/connect-external-agent.js.map +1 -1
  5. package/dist/actions/list-connected-agents.js +7 -3
  6. package/dist/actions/list-connected-agents.js.map +1 -1
  7. package/dist/actions/list-dispatch-usage-metrics.js +1 -1
  8. package/dist/actions/list-dispatch-usage-metrics.js.map +1 -1
  9. package/dist/actions/list-workspace-connections.d.ts.map +1 -1
  10. package/dist/actions/list-workspace-connections.js +13 -18
  11. package/dist/actions/list-workspace-connections.js.map +1 -1
  12. package/dist/actions/provider-api-register.d.ts +8 -8
  13. package/dist/actions/provider-api-register.js +2 -14
  14. package/dist/actions/provider-api-register.js.map +1 -1
  15. package/dist/components/action-query-error.d.ts +2 -1
  16. package/dist/components/action-query-error.d.ts.map +1 -1
  17. package/dist/components/action-query-error.js +25 -2
  18. package/dist/components/action-query-error.js.map +1 -1
  19. package/dist/components/chat-home-apps.d.ts.map +1 -1
  20. package/dist/components/chat-home-apps.js +2 -2
  21. package/dist/components/chat-home-apps.js.map +1 -1
  22. package/dist/components/dispatch-control-plane.d.ts.map +1 -1
  23. package/dist/components/dispatch-control-plane.js +1 -1
  24. package/dist/components/dispatch-control-plane.js.map +1 -1
  25. package/dist/components/layout/Layout.d.ts +1 -0
  26. package/dist/components/layout/Layout.d.ts.map +1 -1
  27. package/dist/components/layout/Layout.js +7 -0
  28. package/dist/components/layout/Layout.js.map +1 -1
  29. package/dist/db/index.d.ts.map +1 -1
  30. package/dist/db/index.js +4 -9
  31. package/dist/db/index.js.map +1 -1
  32. package/dist/routes/pages/$appId.d.ts.map +1 -1
  33. package/dist/routes/pages/$appId.js +1 -1
  34. package/dist/routes/pages/$appId.js.map +1 -1
  35. package/dist/routes/pages/apps.js +1 -1
  36. package/dist/routes/pages/apps.js.map +1 -1
  37. package/dist/server/lib/admin-config.d.ts.map +1 -1
  38. package/dist/server/lib/admin-config.js +3 -1
  39. package/dist/server/lib/admin-config.js.map +1 -1
  40. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  41. package/dist/server/lib/app-creation-store.js +124 -65
  42. package/dist/server/lib/app-creation-store.js.map +1 -1
  43. package/dist/server/lib/community-apps.js +1 -1
  44. package/dist/server/lib/community-apps.js.map +1 -1
  45. package/dist/server/lib/mcp-access-store.d.ts.map +1 -1
  46. package/dist/server/lib/mcp-access-store.js +2 -10
  47. package/dist/server/lib/mcp-access-store.js.map +1 -1
  48. package/dist/server/lib/thread-debug-store.d.ts.map +1 -1
  49. package/dist/server/lib/thread-debug-store.js +2 -6
  50. package/dist/server/lib/thread-debug-store.js.map +1 -1
  51. package/dist/server/lib/usage-metrics-store.d.ts.map +1 -1
  52. package/dist/server/lib/usage-metrics-store.js +2 -11
  53. package/dist/server/lib/usage-metrics-store.js.map +1 -1
  54. package/dist/server/lib/vault-store.d.ts.map +1 -1
  55. package/dist/server/lib/vault-store.js +2 -9
  56. package/dist/server/lib/vault-store.js.map +1 -1
  57. package/dist/server/lib/workspace-app-action-auth.d.ts.map +1 -1
  58. package/dist/server/lib/workspace-app-action-auth.js +7 -2
  59. package/dist/server/lib/workspace-app-action-auth.js.map +1 -1
  60. package/dist/server/plugins/agent-chat.js +1 -1
  61. package/dist/server/plugins/agent-chat.js.map +1 -1
  62. package/package.json +3 -3
  63. package/src/actions/connect-external-agent.spec.ts +25 -11
  64. package/src/actions/connect-external-agent.ts +2 -9
  65. package/src/actions/index.spec.ts +3 -0
  66. package/src/actions/list-connected-agents.spec.ts +17 -1
  67. package/src/actions/list-connected-agents.ts +7 -2
  68. package/src/actions/list-dispatch-usage-metrics.ts +1 -1
  69. package/src/actions/list-workspace-connections.spec.ts +14 -3
  70. package/src/actions/list-workspace-connections.ts +13 -17
  71. package/src/actions/provider-api-register.spec.ts +15 -9
  72. package/src/actions/provider-api-register.ts +5 -13
  73. package/src/components/action-query-error.spec.tsx +20 -0
  74. package/src/components/action-query-error.tsx +40 -0
  75. package/src/components/chat-home-apps.spec.tsx +32 -1
  76. package/src/components/chat-home-apps.tsx +6 -1
  77. package/src/components/dispatch-control-plane.tsx +1 -0
  78. package/src/components/layout/Layout.tsx +9 -0
  79. package/src/db/index.spec.ts +74 -0
  80. package/src/db/index.ts +4 -9
  81. package/src/routes/pages/$appId.tsx +1 -0
  82. package/src/routes/pages/apps.spec.tsx +26 -2
  83. package/src/routes/pages/apps.tsx +2 -0
  84. package/src/server/lib/admin-config.spec.ts +51 -0
  85. package/src/server/lib/admin-config.ts +2 -1
  86. package/src/server/lib/app-creation-store.spec.ts +308 -10
  87. package/src/server/lib/app-creation-store.ts +174 -70
  88. package/src/server/lib/community-apps.ts +1 -1
  89. package/src/server/lib/mcp-access-store.spec.ts +39 -12
  90. package/src/server/lib/mcp-access-store.ts +3 -10
  91. package/src/server/lib/thread-debug-store.spec.ts +64 -0
  92. package/src/server/lib/thread-debug-store.ts +2 -9
  93. package/src/server/lib/usage-metrics-store.spec.ts +64 -0
  94. package/src/server/lib/usage-metrics-store.ts +2 -17
  95. package/src/server/lib/vault-store.spec.ts +26 -9
  96. package/src/server/lib/vault-store.ts +3 -10
  97. package/src/server/lib/workspace-app-action-auth.spec.ts +37 -6
  98. package/src/server/lib/workspace-app-action-auth.ts +12 -2
  99. package/src/server/plugins/agent-chat.spec.ts +14 -0
  100. package/src/server/plugins/agent-chat.ts +1 -1
@@ -2,13 +2,13 @@ import { spawn } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { canonicalA2AAudience, signA2AToken } from "@agent-native/core/a2a";
6
- import { isActionContractError } from "@agent-native/core/action";
5
+ import { canonicalA2AAudience, getGlobalA2ASecret, signA2AToken, } from "@agent-native/core/a2a";
6
+ import { ActionContractError, isActionContractError, } from "@agent-native/core/action";
7
7
  import { describeErrorWithCauses } from "@agent-native/core/agent/engine/error-detail";
8
8
  import { getDbExec } from "@agent-native/core/db";
9
9
  import { and, eq, inArray } from "@agent-native/core/db/schema";
10
- import { getOrgDomain, isWorkspaceAppAccessAllowed, workspaceApps, } from "@agent-native/core/org";
11
- import { CredentialStoreUnavailableError, FeatureNotConfiguredError, createBuilderProject, getBuilderBranchProjectId, getConfiguredAppBasePath, getRequestContext, inferWorkspaceAppRootHomePath, isIntegrationCallerRequest, readConfiguredWorkspaceAppHomePath, resolveAppRuntimeUrl, resolveVercelDeploymentProtectionHeaders, normalizeBuilderAgentAttachments, runBuilderAgent, } from "@agent-native/core/server";
10
+ import { getOrgA2ASecret, getOrgDomain, isWorkspaceAppAccessAllowed, workspaceApps, } from "@agent-native/core/org";
11
+ import { CredentialStoreUnavailableError, FeatureNotConfiguredError, createBuilderProject, getBuilderBranchProjectId, getConfiguredAppBasePath, getRequestContext, inferWorkspaceAppRootHomePath, isIntegrationCallerRequest, readConfiguredWorkspaceAppHomePath, readOrgMemberRole, resolveAppRuntimeUrl, resolveVercelDeploymentProtectionHeaders, normalizeBuilderAgentAttachments, runBuilderAgent, } from "@agent-native/core/server";
12
12
  import { getOrgSetting } from "@agent-native/core/settings";
13
13
  import { getSetting, mutateSetting, putSetting, } from "@agent-native/core/settings";
14
14
  import { BUILDER_CONNECT_PROVIDER, BUILDER_CONNECT_PROVIDER_LABEL, assertValidWorkspaceAppId, connectRequiredResult, normalizeWorkspaceAppHomePath, parseAgentProfileMetadata, } from "@agent-native/core/shared";
@@ -89,13 +89,11 @@ function requireWorkspaceAppOrgId() {
89
89
  throw new WorkspaceAppOrgRequiredError();
90
90
  return orgId;
91
91
  }
92
- class WorkspaceAppsGatewayAuthorizationError extends Error {
93
- constructor(statusCode, message = `Workspace apps gateway rejected the request with HTTP ${statusCode}.`) {
94
- super(message);
92
+ class WorkspaceAppsGatewayAuthorizationError extends ActionContractError {
93
+ constructor(statusCode, errorCode = "workspace_gateway_rejected", message = `Workspace apps gateway rejected the request with HTTP ${statusCode}.`) {
94
+ super(message, { statusCode, errorCode });
95
95
  this.name = "WorkspaceAppsGatewayAuthorizationError";
96
- this.statusCode = statusCode;
97
96
  }
98
- statusCode;
99
97
  }
100
98
  function warnWorkspaceAppsGatewayDenial(denial, source) {
101
99
  if (!denial)
@@ -581,6 +579,20 @@ function parseWorkspaceAppsManifest(parsed) {
581
579
  .sort(sortWorkspaceApps);
582
580
  return apps.length ? apps : null;
583
581
  }
582
+ function parseWorkspaceAppsGatewayResponse(parsed) {
583
+ const rawApps = parsed && typeof parsed === "object" && !Array.isArray(parsed)
584
+ ? parsed.apps
585
+ : parsed;
586
+ if (!Array.isArray(rawApps))
587
+ return null;
588
+ if (rawApps.length === 0)
589
+ return [];
590
+ const apps = parseWorkspaceAppsManifest(parsed);
591
+ if (!apps || apps.length !== rawApps.length)
592
+ return null;
593
+ const ids = new Set(apps.map((app) => app.id.toLowerCase()));
594
+ return ids.size === apps.length ? apps : null;
595
+ }
584
596
  function parseDateMs(value) {
585
597
  if (!value)
586
598
  return null;
@@ -1371,27 +1383,64 @@ async function readWorkspaceAppsFromGateway() {
1371
1383
  return null;
1372
1384
  }
1373
1385
  const requestContext = getRequestContext();
1386
+ const isLocalGateway = isLocalWorkspaceGateway(baseUrl);
1387
+ const requestOrigin = requestContext?.requestOrigin;
1388
+ const isSameOriginGateway = requestOrigin
1389
+ ? (() => {
1390
+ try {
1391
+ return new URL(requestOrigin).origin === baseUrl.origin;
1392
+ }
1393
+ catch {
1394
+ // coercion-ok: an invalid request origin cannot prove a self-fetch.
1395
+ return false;
1396
+ }
1397
+ })()
1398
+ : false;
1399
+ if (isSameOriginGateway && !isLocalGateway)
1400
+ return null;
1374
1401
  const authHeaders = {};
1375
1402
  if (requestContext?.userEmail) {
1376
- const orgDomain = requestContext.orgId
1377
- ? (await getOrgDomain(requestContext.orgId))?.trim().toLowerCase()
1378
- : undefined;
1403
+ let orgDomain;
1404
+ let orgSecret;
1405
+ if (requestContext.orgId) {
1406
+ try {
1407
+ orgDomain =
1408
+ (await getOrgDomain(requestContext.orgId))?.trim().toLowerCase() ||
1409
+ undefined;
1410
+ }
1411
+ catch {
1412
+ throw new WorkspaceAppsGatewayAuthorizationError(503, "workspace_gateway_org_domain_unavailable", "Workspace apps gateway organization domain could not be read.");
1413
+ }
1414
+ }
1379
1415
  if (requestContext.orgId && !orgDomain) {
1380
- throw new WorkspaceAppsGatewayAuthorizationError(401, "Workspace apps gateway cannot authenticate without a resolved organization domain.");
1416
+ throw new WorkspaceAppsGatewayAuthorizationError(401, "workspace_gateway_org_domain_missing", "Workspace apps gateway cannot authenticate without a resolved organization domain.");
1381
1417
  }
1382
- try {
1383
- const token = await signA2AToken(requestContext.userEmail, orgDomain, undefined, {
1384
- expiresIn: "1m",
1385
- preferGlobalSecret: true,
1386
- audience: canonicalA2AAudience(baseUrl.toString(), getConfiguredAppBasePath() || undefined),
1387
- });
1388
- authHeaders.Authorization = `Bearer ${token}`;
1418
+ if (requestContext.orgId) {
1419
+ try {
1420
+ orgSecret =
1421
+ (await getOrgA2ASecret(requestContext.orgId))?.trim() || undefined;
1422
+ }
1423
+ catch {
1424
+ throw new WorkspaceAppsGatewayAuthorizationError(503, "workspace_gateway_org_a2a_secret_unavailable", "Workspace apps gateway organization A2A secret could not be read.");
1425
+ }
1389
1426
  }
1390
- catch {
1391
- // coercion-ok: absent signing credentials keep local unauthenticated
1392
- // gateway discovery available and make hosted discovery fail closed.
1393
- // Keep the unauthenticated local-dev gateway path available. A hosted
1394
- // gateway will fail closed below when its action route needs identity.
1427
+ const globalSecret = getGlobalA2ASecret();
1428
+ const allowLocalUnauthenticatedRequest = isLocalGateway && !orgSecret && !globalSecret;
1429
+ if (!orgSecret && !globalSecret && !allowLocalUnauthenticatedRequest) {
1430
+ throw new WorkspaceAppsGatewayAuthorizationError(503, "workspace_gateway_a2a_secret_missing", "Workspace apps gateway cannot authenticate because no A2A secret is configured.");
1431
+ }
1432
+ if (!allowLocalUnauthenticatedRequest) {
1433
+ try {
1434
+ const token = await signA2AToken(requestContext.userEmail, orgDomain, orgSecret, {
1435
+ expiresIn: "1m",
1436
+ preferGlobalSecret: false,
1437
+ audience: canonicalA2AAudience(baseUrl.toString(), getConfiguredAppBasePath() || undefined),
1438
+ });
1439
+ authHeaders.Authorization = `Bearer ${token}`;
1440
+ }
1441
+ catch {
1442
+ throw new WorkspaceAppsGatewayAuthorizationError(503, "workspace_gateway_token_signing_failed", "Workspace apps gateway could not sign the authenticated request.");
1443
+ }
1395
1444
  }
1396
1445
  }
1397
1446
  const controller = new AbortController();
@@ -1413,36 +1462,46 @@ async function readWorkspaceAppsFromGateway() {
1413
1462
  const protectedRedirect = protectionHeaders["x-vercel-protection-bypass"]
1414
1463
  ? { redirect: "manual" }
1415
1464
  : {};
1465
+ const readAppsResponse = async (response) => {
1466
+ let payload;
1467
+ try {
1468
+ payload = await response.json();
1469
+ }
1470
+ catch {
1471
+ throw new ActionContractError("Workspace apps gateway returned an unreadable response.", {
1472
+ statusCode: 503,
1473
+ errorCode: "workspace_gateway_invalid_response",
1474
+ });
1475
+ }
1476
+ const apps = parseWorkspaceAppsGatewayResponse(payload);
1477
+ if (!apps) {
1478
+ throw new ActionContractError("Workspace apps gateway returned an invalid response.", {
1479
+ statusCode: 503,
1480
+ errorCode: "workspace_gateway_invalid_response",
1481
+ });
1482
+ }
1483
+ return { apps };
1484
+ };
1416
1485
  try {
1417
- if (isLocalWorkspaceGateway(baseUrl)) {
1486
+ if (isLocalGateway) {
1418
1487
  const localResponse = await fetch(gatewayUrl(WORKSPACE_APPS_GATEWAY_PATH), {
1419
1488
  headers,
1420
1489
  ...protectedRedirect,
1421
1490
  signal: controller.signal,
1422
1491
  });
1423
1492
  if (localResponse.status === 401 || localResponse.status === 403) {
1424
- throw new WorkspaceAppsGatewayAuthorizationError(localResponse.status);
1493
+ throw new WorkspaceAppsGatewayAuthorizationError(localResponse.status, "workspace_gateway_rejected");
1494
+ }
1495
+ if (localResponse.status !== 404 && localResponse.status !== 200) {
1496
+ throw new ActionContractError(`Workspace apps gateway returned HTTP ${localResponse.status}.`, {
1497
+ statusCode: 503,
1498
+ errorCode: `workspace_gateway_http_${localResponse.status}`,
1499
+ });
1425
1500
  }
1426
- if (localResponse.ok) {
1427
- const apps = parseWorkspaceAppsManifest(
1428
- // coercion-ok: malformed gateway JSON is an unavailable registry and
1429
- // must fall through to the local manifest sources.
1430
- await localResponse.json().catch(() => null));
1431
- return apps ? { apps } : null;
1501
+ if (localResponse.status === 200) {
1502
+ return await readAppsResponse(localResponse);
1432
1503
  }
1433
1504
  }
1434
- const requestOrigin = requestContext?.requestOrigin;
1435
- const isSameOriginGateway = requestOrigin
1436
- ? (() => {
1437
- try {
1438
- return new URL(requestOrigin).origin === baseUrl.origin;
1439
- }
1440
- catch {
1441
- // coercion-ok: an invalid request origin cannot prove a self-fetch.
1442
- return false;
1443
- }
1444
- })()
1445
- : false;
1446
1505
  if (isSameOriginGateway || !authHeaders.Authorization)
1447
1506
  return null;
1448
1507
  const actionUrl = gatewayUrl(WORKSPACE_APPS_ACTION_PATH);
@@ -1454,20 +1513,25 @@ async function readWorkspaceAppsFromGateway() {
1454
1513
  signal: controller.signal,
1455
1514
  });
1456
1515
  if (actionResponse.status === 401 || actionResponse.status === 403) {
1457
- throw new WorkspaceAppsGatewayAuthorizationError(actionResponse.status);
1516
+ throw new WorkspaceAppsGatewayAuthorizationError(actionResponse.status, "workspace_gateway_rejected");
1458
1517
  }
1459
- if (!actionResponse.ok)
1518
+ if (actionResponse.status === 404)
1460
1519
  return null;
1461
- const apps = parseWorkspaceAppsManifest(
1462
- // coercion-ok: malformed gateway JSON is an unavailable registry and
1463
- // must fall through to the local manifest sources.
1464
- await actionResponse.json().catch(() => null));
1465
- return apps ? { apps } : null;
1520
+ if (actionResponse.status !== 200) {
1521
+ throw new ActionContractError(`Workspace apps gateway returned HTTP ${actionResponse.status}.`, {
1522
+ statusCode: 503,
1523
+ errorCode: `workspace_gateway_http_${actionResponse.status}`,
1524
+ });
1525
+ }
1526
+ return await readAppsResponse(actionResponse);
1466
1527
  }
1467
1528
  catch (error) {
1468
- if (error instanceof WorkspaceAppsGatewayAuthorizationError)
1529
+ if (error instanceof ActionContractError)
1469
1530
  throw error;
1470
- return null;
1531
+ throw new ActionContractError("Workspace apps gateway could not be reached.", {
1532
+ statusCode: 503,
1533
+ errorCode: "workspace_gateway_unavailable",
1534
+ });
1471
1535
  }
1472
1536
  finally {
1473
1537
  clearTimeout(timeout);
@@ -1698,6 +1762,9 @@ async function listWorkspaceAppsWithPrincipal(options = {}, principal = "user")
1698
1762
  if (gatewayApps) {
1699
1763
  return finalize(gatewayApps.apps);
1700
1764
  }
1765
+ if (gatewayDenial && gatewayDenial.statusCode >= 500) {
1766
+ throw gatewayDenial;
1767
+ }
1701
1768
  const unverified = gatewayDenial !== null;
1702
1769
  const workspaceRoot = findWorkspaceRoot();
1703
1770
  const localFilesystemApps = workspaceRoot && isLocalAppCreationRuntime()
@@ -1717,8 +1784,9 @@ async function listWorkspaceAppsWithPrincipal(options = {}, principal = "user")
1717
1784
  // means the registry will not show this reader its apps, so they get the
1718
1785
  // deployment's own list instead, and none of it is recorded. (A manifest
1719
1786
  // answers earlier for both, with the warning above.)
1720
- if (gatewayDenial?.statusCode === 401)
1787
+ if (gatewayDenial && gatewayDenial.statusCode !== 403) {
1721
1788
  throw gatewayDenial;
1789
+ }
1722
1790
  warnWorkspaceAppsGatewayDenial(gatewayDenial, "deployment's own app list");
1723
1791
  if (!workspaceRoot) {
1724
1792
  return finalize([
@@ -2085,19 +2153,10 @@ function isSyntheticIntegrationOwner(ownerEmail) {
2085
2153
  }
2086
2154
  async function requestOwnerRole() {
2087
2155
  const orgId = currentOrgId();
2088
- const ownerEmail = currentOwnerEmail();
2089
2156
  if (!orgId)
2090
2157
  return null;
2091
2158
  try {
2092
- const { rows } = await getDbExec().execute({
2093
- sql: `SELECT role FROM org_members
2094
- WHERE org_id = ? AND LOWER(email) = ?
2095
- AND federation_removal_pending_at IS NULL
2096
- LIMIT 1`,
2097
- args: [orgId, ownerEmail.toLowerCase()],
2098
- });
2099
- const role = rows[0]?.role;
2100
- return typeof role === "string" ? role : null;
2159
+ return await readOrgMemberRole(orgId, currentOwnerEmail());
2101
2160
  }
2102
2161
  catch (error) {
2103
2162
  throw new AppCreationOwnerRoleUnavailableError(error);