@agent-native/dispatch 0.37.0 → 0.37.1-nightly-20260916021205

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.
@@ -14,7 +14,6 @@ export declare const CANONICAL_WORKSPACE_SSO_APP_ORIGINS: {
14
14
  readonly dispatch: "https://dispatch.agent-native.com";
15
15
  readonly factory: "https://factory.agent-native.com";
16
16
  readonly forms: "https://forms.agent-native.com";
17
- readonly macros: "https://macros.agent-native.com";
18
17
  readonly mail: "https://mail.agent-native.com";
19
18
  readonly plan: "https://plan.agent-native.com";
20
19
  readonly slides: "https://slides.agent-native.com";
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-sso.d.ts","sourceRoot":"","sources":["../../src/shared/workspace-sso.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,EAAE,2BAA2B,EAAE,CAAC;AAEvC,+EAA+E;AAC/E,eAAO,MAAM,mCAAmC;aAC9C,SAAS,EAAE,oCAAoC;aAC/C,MAAM,EAAE,iCAAiC;aACzC,KAAK,EAAE,gCAAgC;aACvC,QAAQ,EAAE,mCAAmC;aAC7C,IAAI,EAAE,+BAA+B;aACrC,KAAK,EAAE,gCAAgC;aACvC,OAAO,EAAE,kCAAkC;aAC3C,GAAG,EAAE,8BAA8B;aACnC,MAAM,EAAE,iCAAiC;aACzC,QAAQ,EAAE,mCAAmC;aAC7C,OAAO,EAAE,kCAAkC;aAC3C,KAAK,EAAE,gCAAgC;aACvC,MAAM,EAAE,iCAAiC;aACzC,IAAI,EAAE,+BAA+B;aACrC,IAAI,EAAE,+BAA+B;aACrC,MAAM,EAAE,iCAAiC;aACzC,KAAK,EAAE,gCAAgC;CAC/B,CAAC;AAEX,eAAO,MAAM,2BAA2B,EACtC,kCAA2C,CAAC;AAM9C,KAAK,2BAA2B,GAAG,YAAY,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,2BAA2B,CAAC;CAClD;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAwBnE;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,GAAG,EAAE,OAAO,GACX,2BAA2B,EAAE,CA6C/B;AA4CD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,EAClC,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,2BAA2B,CAAC;CAC1C,GACL,OAAO,CAgCT"}
1
+ {"version":3,"file":"workspace-sso.d.ts","sourceRoot":"","sources":["../../src/shared/workspace-sso.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,EAAE,2BAA2B,EAAE,CAAC;AAEvC,+EAA+E;AAC/E,eAAO,MAAM,mCAAmC;aAC9C,SAAS,EAAE,oCAAoC;aAC/C,MAAM,EAAE,iCAAiC;aACzC,KAAK,EAAE,gCAAgC;aACvC,QAAQ,EAAE,mCAAmC;aAC7C,IAAI,EAAE,+BAA+B;aACrC,KAAK,EAAE,gCAAgC;aACvC,OAAO,EAAE,kCAAkC;aAC3C,GAAG,EAAE,8BAA8B;aACnC,MAAM,EAAE,iCAAiC;aACzC,QAAQ,EAAE,mCAAmC;aAC7C,OAAO,EAAE,kCAAkC;aAC3C,KAAK,EAAE,gCAAgC;aACvC,IAAI,EAAE,+BAA+B;aACrC,IAAI,EAAE,+BAA+B;aACrC,MAAM,EAAE,iCAAiC;aACzC,KAAK,EAAE,gCAAgC;CAC/B,CAAC;AAEX,eAAO,MAAM,2BAA2B,EACtC,kCAA2C,CAAC;AAM9C,KAAK,2BAA2B,GAAG,YAAY,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,2BAA2B,CAAC;CAClD;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAwBnE;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,GAAG,EAAE,OAAO,GACX,2BAA2B,EAAE,CA6C/B;AA4CD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,EAClC,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,2BAA2B,CAAC;CAC1C,GACL,OAAO,CAgCT"}
@@ -15,7 +15,6 @@ export const CANONICAL_WORKSPACE_SSO_APP_ORIGINS = {
15
15
  dispatch: "https://dispatch.agent-native.com",
16
16
  factory: "https://factory.agent-native.com",
17
17
  forms: "https://forms.agent-native.com",
18
- macros: "https://macros.agent-native.com",
19
18
  mail: "https://mail.agent-native.com",
20
19
  plan: "https://plan.agent-native.com",
21
20
  slides: "https://slides.agent-native.com",
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-sso.js","sourceRoot":"","sources":["../../src/shared/workspace-sso.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveEnvironmentTargets } from \"@agent-native/core/shared\";\n\nimport { DISPATCH_WORKSPACE_SSO_FLAG } from \"./feature-flags.js\";\n\nexport { DISPATCH_WORKSPACE_SSO_FLAG };\n\n/** Exact first-party origins. Never replace this with a suffix or wildcard. */\nexport const CANONICAL_WORKSPACE_SSO_APP_ORIGINS = {\n analytics: \"https://analytics.agent-native.com\",\n assets: \"https://assets.agent-native.com\",\n brain: \"https://brain.agent-native.com\",\n calendar: \"https://calendar.agent-native.com\",\n chat: \"https://chat.agent-native.com\",\n clips: \"https://clips.agent-native.com\",\n content: \"https://content.agent-native.com\",\n crm: \"https://crm.agent-native.com\",\n design: \"https://design.agent-native.com\",\n dispatch: \"https://dispatch.agent-native.com\",\n factory: \"https://factory.agent-native.com\",\n forms: \"https://forms.agent-native.com\",\n macros: \"https://macros.agent-native.com\",\n mail: \"https://mail.agent-native.com\",\n plan: \"https://plan.agent-native.com\",\n slides: \"https://slides.agent-native.com\",\n tasks: \"https://tasks.agent-native.com\",\n} as const;\n\nexport const WORKSPACE_SSO_CALLBACK_PATH =\n \"/_agent-native/identity/callback\" as const;\n\nconst CONTROL_CHARS = /[\\u0000-\\u001f\\u007f]/;\nconst APP_ID = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;\nconst CLIENT_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;\nconst LOCALHOST_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"]);\ntype WorkspaceSsoEnvironmentLane = \"production\" | \"beta\";\n\nexport interface WorkspaceSsoAppRegistration {\n appId: string;\n clientId: string;\n origin: string;\n callbackPath: typeof WORKSPACE_SSO_CALLBACK_PATH;\n}\n\nexport function exactWorkspaceSsoOrigin(raw: unknown): string | null {\n if (typeof raw !== \"string\" || !raw || CONTROL_CHARS.test(raw)) return null;\n try {\n const url = new URL(raw);\n if (\n url.protocol !== \"https:\" &&\n !(url.protocol === \"http:\" && LOCALHOST_HOSTS.has(url.hostname))\n ) {\n return null;\n }\n if (\n url.username ||\n url.password ||\n url.pathname !== \"/\" ||\n url.search ||\n url.hash\n ) {\n return null;\n }\n return url.origin;\n } catch {\n // coercion-ok: malformed registry origins are rejected as absent.\n return null;\n }\n}\n\n/**\n * Parse the same additive custom registry used by browser identity SSO. The\n * raw value is passed in so this shared module stays safe to import in the\n * browser bundle.\n */\nexport function parseWorkspaceSsoAppRegistrations(\n raw: unknown,\n): WorkspaceSsoAppRegistration[] {\n if (typeof raw !== \"string\" || !raw.trim()) return [];\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch {\n // coercion-ok: malformed custom registry data must fail closed.\n return [];\n }\n if (!Array.isArray(value)) return [];\n\n const registrations: WorkspaceSsoAppRegistration[] = [];\n const seen = new Set<string>();\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue;\n const candidate = entry as Record<string, unknown>;\n const appId = candidate.appId;\n const clientId = candidate.clientId;\n const origin = exactWorkspaceSsoOrigin(candidate.origin);\n if (\n typeof appId !== \"string\" ||\n !APP_ID.test(appId) ||\n typeof clientId !== \"string\" ||\n !CLIENT_ID.test(clientId) ||\n !origin ||\n candidate.callbackPath !== WORKSPACE_SSO_CALLBACK_PATH ||\n !Array.isArray(candidate.capabilities) ||\n !candidate.capabilities.includes(\"identity-sso\") ||\n seen.has(appId) ||\n Object.prototype.hasOwnProperty.call(\n CANONICAL_WORKSPACE_SSO_APP_ORIGINS,\n appId,\n )\n ) {\n continue;\n }\n seen.add(appId);\n registrations.push({\n appId,\n clientId,\n origin,\n callbackPath: WORKSPACE_SSO_CALLBACK_PATH,\n });\n }\n return registrations;\n}\n\nfunction appOrigin(rawUrl: unknown): string | null {\n if (typeof rawUrl !== \"string\" || !rawUrl.trim()) return null;\n try {\n const url = new URL(rawUrl.trim());\n return url.protocol === \"http:\" || url.protocol === \"https:\"\n ? url.origin\n : null;\n } catch {\n // coercion-ok: malformed app metadata is not eligible for workspace SSO.\n return null;\n }\n}\n\nfunction isLoopbackOrigin(origin: string): boolean {\n try {\n return LOCALHOST_HOSTS.has(new URL(origin).hostname);\n } catch {\n // coercion-ok: only a previously parsed origin reaches this helper.\n return false;\n }\n}\n\nfunction isCanonicalWorkspaceSsoOrigin(\n origin: string,\n canonicalOrigin: string,\n environmentLane: WorkspaceSsoEnvironmentLane,\n): boolean {\n if (origin === canonicalOrigin) return true;\n if (environmentLane !== \"beta\") return false;\n\n try {\n const targets = resolveEnvironmentTargets(\n new URL(canonicalOrigin).hostname,\n );\n return targets ? origin === `https://${targets.betaHost}` : false;\n } catch {\n // coercion-ok: canonical origins are static metadata, so malformed values\n // are rejected as non-canonical.\n return false;\n }\n}\n\n/**\n * Keep the server-side app catalog and the browser's action choice on the\n * same exact-origin rules. `registryRaw` is deliberately supplied by the\n * caller so this shared module never reads process or browser environment.\n */\nexport function isWorkspaceSsoAppUrl(\n app: { id: string; url?: unknown },\n options: {\n nodeEnv?: string;\n registryRaw?: unknown;\n environmentLane?: WorkspaceSsoEnvironmentLane;\n } = {},\n): boolean {\n const origin = appOrigin(app.url);\n if (!origin) return false;\n\n const appId = app.id.trim().toLowerCase();\n const canonicalOrigin =\n CANONICAL_WORKSPACE_SSO_APP_ORIGINS[\n appId as keyof typeof CANONICAL_WORKSPACE_SSO_APP_ORIGINS\n ];\n if (\n canonicalOrigin &&\n isCanonicalWorkspaceSsoOrigin(\n origin,\n canonicalOrigin,\n options.environmentLane ?? \"production\",\n )\n ) {\n return true;\n }\n\n if (\n options.nodeEnv !== \"production\" &&\n canonicalOrigin &&\n isLoopbackOrigin(origin)\n ) {\n return true;\n }\n\n return parseWorkspaceSsoAppRegistrations(options.registryRaw).some(\n (registration) =>\n registration.appId === appId && registration.origin === origin,\n );\n}\n"]}
1
+ {"version":3,"file":"workspace-sso.js","sourceRoot":"","sources":["../../src/shared/workspace-sso.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveEnvironmentTargets } from \"@agent-native/core/shared\";\n\nimport { DISPATCH_WORKSPACE_SSO_FLAG } from \"./feature-flags.js\";\n\nexport { DISPATCH_WORKSPACE_SSO_FLAG };\n\n/** Exact first-party origins. Never replace this with a suffix or wildcard. */\nexport const CANONICAL_WORKSPACE_SSO_APP_ORIGINS = {\n analytics: \"https://analytics.agent-native.com\",\n assets: \"https://assets.agent-native.com\",\n brain: \"https://brain.agent-native.com\",\n calendar: \"https://calendar.agent-native.com\",\n chat: \"https://chat.agent-native.com\",\n clips: \"https://clips.agent-native.com\",\n content: \"https://content.agent-native.com\",\n crm: \"https://crm.agent-native.com\",\n design: \"https://design.agent-native.com\",\n dispatch: \"https://dispatch.agent-native.com\",\n factory: \"https://factory.agent-native.com\",\n forms: \"https://forms.agent-native.com\",\n mail: \"https://mail.agent-native.com\",\n plan: \"https://plan.agent-native.com\",\n slides: \"https://slides.agent-native.com\",\n tasks: \"https://tasks.agent-native.com\",\n} as const;\n\nexport const WORKSPACE_SSO_CALLBACK_PATH =\n \"/_agent-native/identity/callback\" as const;\n\nconst CONTROL_CHARS = /[\\u0000-\\u001f\\u007f]/;\nconst APP_ID = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;\nconst CLIENT_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;\nconst LOCALHOST_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"]);\ntype WorkspaceSsoEnvironmentLane = \"production\" | \"beta\";\n\nexport interface WorkspaceSsoAppRegistration {\n appId: string;\n clientId: string;\n origin: string;\n callbackPath: typeof WORKSPACE_SSO_CALLBACK_PATH;\n}\n\nexport function exactWorkspaceSsoOrigin(raw: unknown): string | null {\n if (typeof raw !== \"string\" || !raw || CONTROL_CHARS.test(raw)) return null;\n try {\n const url = new URL(raw);\n if (\n url.protocol !== \"https:\" &&\n !(url.protocol === \"http:\" && LOCALHOST_HOSTS.has(url.hostname))\n ) {\n return null;\n }\n if (\n url.username ||\n url.password ||\n url.pathname !== \"/\" ||\n url.search ||\n url.hash\n ) {\n return null;\n }\n return url.origin;\n } catch {\n // coercion-ok: malformed registry origins are rejected as absent.\n return null;\n }\n}\n\n/**\n * Parse the same additive custom registry used by browser identity SSO. The\n * raw value is passed in so this shared module stays safe to import in the\n * browser bundle.\n */\nexport function parseWorkspaceSsoAppRegistrations(\n raw: unknown,\n): WorkspaceSsoAppRegistration[] {\n if (typeof raw !== \"string\" || !raw.trim()) return [];\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch {\n // coercion-ok: malformed custom registry data must fail closed.\n return [];\n }\n if (!Array.isArray(value)) return [];\n\n const registrations: WorkspaceSsoAppRegistration[] = [];\n const seen = new Set<string>();\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue;\n const candidate = entry as Record<string, unknown>;\n const appId = candidate.appId;\n const clientId = candidate.clientId;\n const origin = exactWorkspaceSsoOrigin(candidate.origin);\n if (\n typeof appId !== \"string\" ||\n !APP_ID.test(appId) ||\n typeof clientId !== \"string\" ||\n !CLIENT_ID.test(clientId) ||\n !origin ||\n candidate.callbackPath !== WORKSPACE_SSO_CALLBACK_PATH ||\n !Array.isArray(candidate.capabilities) ||\n !candidate.capabilities.includes(\"identity-sso\") ||\n seen.has(appId) ||\n Object.prototype.hasOwnProperty.call(\n CANONICAL_WORKSPACE_SSO_APP_ORIGINS,\n appId,\n )\n ) {\n continue;\n }\n seen.add(appId);\n registrations.push({\n appId,\n clientId,\n origin,\n callbackPath: WORKSPACE_SSO_CALLBACK_PATH,\n });\n }\n return registrations;\n}\n\nfunction appOrigin(rawUrl: unknown): string | null {\n if (typeof rawUrl !== \"string\" || !rawUrl.trim()) return null;\n try {\n const url = new URL(rawUrl.trim());\n return url.protocol === \"http:\" || url.protocol === \"https:\"\n ? url.origin\n : null;\n } catch {\n // coercion-ok: malformed app metadata is not eligible for workspace SSO.\n return null;\n }\n}\n\nfunction isLoopbackOrigin(origin: string): boolean {\n try {\n return LOCALHOST_HOSTS.has(new URL(origin).hostname);\n } catch {\n // coercion-ok: only a previously parsed origin reaches this helper.\n return false;\n }\n}\n\nfunction isCanonicalWorkspaceSsoOrigin(\n origin: string,\n canonicalOrigin: string,\n environmentLane: WorkspaceSsoEnvironmentLane,\n): boolean {\n if (origin === canonicalOrigin) return true;\n if (environmentLane !== \"beta\") return false;\n\n try {\n const targets = resolveEnvironmentTargets(\n new URL(canonicalOrigin).hostname,\n );\n return targets ? origin === `https://${targets.betaHost}` : false;\n } catch {\n // coercion-ok: canonical origins are static metadata, so malformed values\n // are rejected as non-canonical.\n return false;\n }\n}\n\n/**\n * Keep the server-side app catalog and the browser's action choice on the\n * same exact-origin rules. `registryRaw` is deliberately supplied by the\n * caller so this shared module never reads process or browser environment.\n */\nexport function isWorkspaceSsoAppUrl(\n app: { id: string; url?: unknown },\n options: {\n nodeEnv?: string;\n registryRaw?: unknown;\n environmentLane?: WorkspaceSsoEnvironmentLane;\n } = {},\n): boolean {\n const origin = appOrigin(app.url);\n if (!origin) return false;\n\n const appId = app.id.trim().toLowerCase();\n const canonicalOrigin =\n CANONICAL_WORKSPACE_SSO_APP_ORIGINS[\n appId as keyof typeof CANONICAL_WORKSPACE_SSO_APP_ORIGINS\n ];\n if (\n canonicalOrigin &&\n isCanonicalWorkspaceSsoOrigin(\n origin,\n canonicalOrigin,\n options.environmentLane ?? \"production\",\n )\n ) {\n return true;\n }\n\n if (\n options.nodeEnv !== \"production\" &&\n canonicalOrigin &&\n isLoopbackOrigin(origin)\n ) {\n return true;\n }\n\n return parseWorkspaceSsoAppRegistrations(options.registryRaw).some(\n (registration) =>\n registration.appId === appId && registration.origin === origin,\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/dispatch",
3
- "version": "0.37.0",
3
+ "version": "0.37.1-nightly-20260916021205",
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": {
@@ -88,7 +88,7 @@
88
88
  "tailwind-merge": "^3.5.0",
89
89
  "vaul": "^1.1.2",
90
90
  "zod": "^4.3.6",
91
- "@agent-native/toolkit": "0.20.1"
91
+ "@agent-native/toolkit": "0.20.2-nightly-20260916021205"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@react-router/dev": "^8.1.0",
@@ -102,10 +102,10 @@
102
102
  "typescript-7": "npm:typescript@^7.0.2",
103
103
  "vite": "8.1.0",
104
104
  "vitest": "^4.1.5",
105
- "@agent-native/core": "0.180.0"
105
+ "@agent-native/core": "0.181.0-nightly-20260916021205"
106
106
  },
107
107
  "peerDependencies": {
108
- "@agent-native/core": ">=0.8.0",
108
+ "@agent-native/core": "0.181.0-nightly-20260916021205",
109
109
  "react": ">=19.2.7",
110
110
  "react-dom": ">=19.2.7",
111
111
  "react-router": ">=8"
@@ -68,7 +68,6 @@ const APP_VISUALS_BY_ID: Record<
68
68
  dispatch: { icon: IconRoute, colorRgb: "20 184 166" },
69
69
  factory: { icon: IconUsers, colorRgb: "124 58 237" },
70
70
  forms: { icon: IconClipboardList, colorRgb: "6 182 212" },
71
- macros: { icon: IconCode, colorRgb: "113 113 122" },
72
71
  mail: { icon: IconMail, colorRgb: "59 130 246" },
73
72
  plan: { icon: IconLayoutBoard, colorRgb: "47 111 237" },
74
73
  slides: { icon: IconPresentation, colorRgb: "236 72 153" },
package/src/config.ts CHANGED
@@ -34,7 +34,7 @@ export interface DispatchConfig {
34
34
  browserExtensionIds?: readonly string[];
35
35
  /**
36
36
  * App IDs to hide from `list-connected-agents` results. Used to filter
37
- * out first-party Builder apps (calls, issues, macros, …) from the
37
+ * out first-party Builder apps (calls, issues, …) from the
38
38
  * Connected Agents list when those apps aren't shipped to end users.
39
39
  */
40
40
  hiddenAgentIds?: string[];
@@ -1,3 +1,7 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
1
5
  import { ActionContractError } from "@agent-native/core/action";
2
6
  import {
3
7
  CredentialStoreUnavailableError,
@@ -76,6 +80,7 @@ const mocks = vi.hoisted(() => {
76
80
  createBuilderProject: vi.fn(),
77
81
  runBuilderAgent: vi.fn(),
78
82
  getBuilderBranchProjectId: vi.fn(() => ""),
83
+ readConfiguredWorkspaceAppHomePath: vi.fn(async () => undefined),
79
84
  writeAppSecret: vi.fn(async () => "secret-id"),
80
85
  deleteAppSecret: vi.fn(async () => true),
81
86
  };
@@ -137,6 +142,8 @@ vi.mock("@agent-native/core/server", async (importOriginal) => {
137
142
  runBuilderAgent: (...args: any[]) => mocks.runBuilderAgent(...args),
138
143
  getBuilderBranchProjectId: (...args: any[]) =>
139
144
  mocks.getBuilderBranchProjectId(...args),
145
+ readConfiguredWorkspaceAppHomePath: (...args: any[]) =>
146
+ mocks.readConfiguredWorkspaceAppHomePath(...args),
140
147
  resolveAppRuntimeUrl: (...args: any[]) =>
141
148
  actual.resolveAppRuntimeUrl(...args),
142
149
  resolveVercelDeploymentProtectionHeaders: (...args: any[]) =>
@@ -203,6 +210,8 @@ afterEach(() => {
203
210
  lookupFailed: false,
204
211
  });
205
212
  mocks.getBuilderBranchProjectId.mockReturnValue("");
213
+ mocks.readConfiguredWorkspaceAppHomePath.mockReset();
214
+ mocks.readConfiguredWorkspaceAppHomePath.mockResolvedValue(undefined);
206
215
  mocks.createBuilderProject.mockReset();
207
216
  mocks.createBuilderProject.mockResolvedValue({
208
217
  projectId: "project-created",
@@ -595,6 +604,61 @@ describe("listWorkspaceApps", () => {
595
604
  expect(apps.map((app) => app.id)).toEqual(["dispatch"]);
596
605
  });
597
606
 
607
+ it("keeps healthy filesystem apps discoverable when config or routes cannot load", async () => {
608
+ const workspaceRoot = fs.mkdtempSync(
609
+ path.join(os.tmpdir(), "dispatch-workspace-"),
610
+ );
611
+ const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceRoot);
612
+ try {
613
+ fs.writeFileSync(
614
+ path.join(workspaceRoot, "package.json"),
615
+ JSON.stringify({
616
+ name: "test-workspace",
617
+ "agent-native": { workspaceCore: "workspace-core" },
618
+ }),
619
+ );
620
+ for (const app of [
621
+ "dispatch",
622
+ "healthy",
623
+ "config-broken",
624
+ "routes-broken",
625
+ ]) {
626
+ const appDir = path.join(workspaceRoot, "apps", app);
627
+ fs.mkdirSync(appDir, { recursive: true });
628
+ fs.writeFileSync(
629
+ path.join(appDir, "package.json"),
630
+ JSON.stringify({ name: app, displayName: app }),
631
+ );
632
+ if (app === "routes-broken") {
633
+ const brokenRoutes = path.join(appDir, "app", "routes");
634
+ fs.mkdirSync(path.dirname(brokenRoutes), { recursive: true });
635
+ fs.writeFileSync(brokenRoutes, "not a directory");
636
+ }
637
+ }
638
+ stubNoPendingContext();
639
+ vi.stubEnv("NODE_ENV", "test");
640
+ mocks.readConfiguredWorkspaceAppHomePath.mockImplementation(
641
+ async (appDir: string) => {
642
+ if (appDir.endsWith(path.join("apps", "config-broken"))) {
643
+ throw new Error("missing app-only dependency");
644
+ }
645
+ return undefined;
646
+ },
647
+ );
648
+
649
+ const apps = await runWithRequestContext(
650
+ { userEmail: "dev@example.test" },
651
+ () => listWorkspaceApps({ includeAgentCards: false }),
652
+ );
653
+
654
+ expect(apps.map((app) => app.id)).toEqual(["dispatch", "healthy"]);
655
+ expect(mocks.readConfiguredWorkspaceAppHomePath).toHaveBeenCalledTimes(4);
656
+ } finally {
657
+ cwdSpy.mockRestore();
658
+ fs.rmSync(workspaceRoot, { recursive: true, force: true });
659
+ }
660
+ });
661
+
598
662
  it("keeps legacy apps organization-visible when the org default cannot be read", async () => {
599
663
  stubManifest([
600
664
  { id: "dispatch", name: "Dispatch", path: "/dispatch" },
@@ -17,7 +17,9 @@ import {
17
17
  createBuilderProject,
18
18
  getBuilderBranchProjectId,
19
19
  getRequestContext,
20
+ inferWorkspaceAppRootHomePath,
20
21
  isIntegrationCallerRequest,
22
+ readConfiguredWorkspaceAppHomePath,
21
23
  resolveAppRuntimeUrl,
22
24
  resolveVercelDeploymentProtectionHeaders,
23
25
  runBuilderAgent,
@@ -1794,40 +1796,57 @@ function readWorkspaceAppsFromManifestFile(): WorkspaceAppSummary[] | null {
1794
1796
  return null;
1795
1797
  }
1796
1798
 
1797
- function readWorkspaceAppsFromFilesystem(
1799
+ async function readWorkspaceAppsFromFilesystem(
1798
1800
  workspaceRoot: string,
1799
- ): WorkspaceAppSummary[] | null {
1801
+ ): Promise<WorkspaceAppSummary[] | null> {
1800
1802
  const appsDir = path.join(workspaceRoot, "apps");
1801
1803
  if (!fs.existsSync(appsDir)) return null;
1802
1804
 
1803
- const apps = fs
1805
+ const apps: WorkspaceAppSummary[] = [];
1806
+ for (const entry of fs
1804
1807
  .readdirSync(appsDir, { withFileTypes: true })
1805
- .filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
1806
- .map((entry): WorkspaceAppSummary | null => {
1807
- const appDir = path.join(appsDir, entry.name);
1808
- const pkg = readJson(path.join(appDir, "package.json"));
1809
- if (!pkg) return null;
1810
- const routeAccess = workspaceAppRouteAccessFromPackageJson(pkg);
1811
- const metadata = workspaceAppMetadataFromRecord(pkg);
1812
- return {
1813
- id: entry.name,
1814
- name: pkg.displayName || titleCase(entry.name),
1815
- description: pkg.description || "",
1816
- path: `/${entry.name}`,
1817
- homePath: "/home",
1818
- url: workspaceAppUrl(`/${entry.name}`),
1819
- isDispatch: entry.name === "dispatch",
1820
- audience:
1821
- workspaceAppAudienceFromPackageJson(pkg) ??
1822
- DEFAULT_WORKSPACE_APP_AUDIENCE,
1823
- publicPaths: routeAccess.publicPaths,
1824
- protectedPaths: routeAccess.protectedPaths,
1825
- status: "ready",
1826
- ...metadata,
1827
- } satisfies WorkspaceAppSummary;
1828
- })
1829
- .filter((app): app is WorkspaceAppSummary => !!app)
1830
- .sort(sortWorkspaceApps);
1808
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))) {
1809
+ const appDir = path.join(appsDir, entry.name);
1810
+ const pkg = readJson(path.join(appDir, "package.json"));
1811
+ if (!pkg) continue;
1812
+ const routeAccess = workspaceAppRouteAccessFromPackageJson(pkg);
1813
+ const metadata = workspaceAppMetadataFromRecord(pkg);
1814
+ let configuredHomePath: string | undefined;
1815
+ let inferredHomePath: "/" | undefined;
1816
+ try {
1817
+ configuredHomePath = await readConfiguredWorkspaceAppHomePath(appDir);
1818
+ if (configuredHomePath === undefined) {
1819
+ inferredHomePath = inferWorkspaceAppRootHomePath(appDir);
1820
+ }
1821
+ } catch (error) {
1822
+ // A broken app or route tree must not hide healthy sibling apps from
1823
+ // Dispatch. The next discovery pass can recover after it is fixed.
1824
+ console.warn(
1825
+ `[dispatch] Could not discover workspace app ${entry.name}; skipping app`,
1826
+ error,
1827
+ );
1828
+ continue;
1829
+ }
1830
+ apps.push({
1831
+ id: entry.name,
1832
+ name: pkg.displayName || titleCase(entry.name),
1833
+ description: pkg.description || "",
1834
+ path: `/${entry.name}`,
1835
+ homePath: normalizeWorkspaceAppHomePath(
1836
+ configuredHomePath ?? inferredHomePath,
1837
+ ),
1838
+ url: workspaceAppUrl(`/${entry.name}`),
1839
+ isDispatch: entry.name === "dispatch",
1840
+ audience:
1841
+ workspaceAppAudienceFromPackageJson(pkg) ??
1842
+ DEFAULT_WORKSPACE_APP_AUDIENCE,
1843
+ publicPaths: routeAccess.publicPaths,
1844
+ protectedPaths: routeAccess.protectedPaths,
1845
+ status: "ready",
1846
+ ...metadata,
1847
+ });
1848
+ }
1849
+ apps.sort(sortWorkspaceApps);
1831
1850
 
1832
1851
  return apps.length ? apps : null;
1833
1852
  }
@@ -1997,7 +2016,7 @@ export async function listWorkspaceApps(
1997
2016
  const workspaceRoot = findWorkspaceRoot();
1998
2017
  const localFilesystemApps =
1999
2018
  workspaceRoot && isLocalAppCreationRuntime()
2000
- ? readWorkspaceAppsFromFilesystem(workspaceRoot)
2019
+ ? await readWorkspaceAppsFromFilesystem(workspaceRoot)
2001
2020
  : null;
2002
2021
  if (localFilesystemApps) {
2003
2022
  return finalize(localFilesystemApps, true);
@@ -2027,8 +2046,9 @@ export async function listWorkspaceApps(
2027
2046
  ]);
2028
2047
  }
2029
2048
 
2030
- const apps = readWorkspaceAppsFromFilesystem(workspaceRoot) ?? [];
2031
- return finalize(apps);
2049
+ const apps = await readWorkspaceAppsFromFilesystem(workspaceRoot);
2050
+ if (apps) return finalize(apps);
2051
+ return finalize([]);
2032
2052
  }
2033
2053
 
2034
2054
  /**
@@ -2602,6 +2622,7 @@ function buildWorkspaceAppPrompt(input: {
2602
2622
  "",
2603
2623
  `Use the workspace app layout: create it under apps/${appId}, mount it at /${appId}, keep it on the shared workspace database/hosting model, and avoid table-name collisions by namespacing any new domain tables to the app.`,
2604
2624
  `Important routing rule: from outside the app, link to /${appId}; inside apps/${appId}, React Router routes are app-local. Use <Link to="/review"> and navigate("/review"), not "/${appId}/review"; APP_BASE_PATH supplies the mounted prefix, and hardcoding it causes doubled URLs like /${appId}/${appId}/review.`,
2625
+ `Home route contract: Dispatch opens the registered app.homePath. If the main screen is app/routes/_index.tsx and there is no app/routes/home.tsx or app/routes/_app.home.tsx, set app.homePath to "/" in server/plugins/config.ts. If the app uses a /home route, keep the default "/home". Never leave the registry pointing at /home when that route does not exist.`,
2605
2626
  "Existing first-party apps are neighbors, not implementation details for this app. If the user prompt mentions Mail, Calendar, Analytics, Dispatch, or other templates, treat them as existing hosted/connected apps that this app can link to or call through A2A/default connected agents. For example, Mail, Calendar, and Analytics already exist at https://mail.agent-native.com, https://calendar.agent-native.com, and https://analytics.agent-native.com.",
2606
2627
  `Do not create wrapper apps or scaffold child apps/routes for Mail, Calendar, Analytics, etc. inside apps/${appId} just so this app can access them. If the request is a cross-app dashboard or overview, build only the new dashboard/overview app and delegate to the existing apps for domain work.`,
2607
2628
  "Only create another first-party app when the user explicitly asks for a customized app from that template; otherwise keep using the hosted/shared app so improvements to the base app keep flowing to users.",
@@ -18,7 +18,6 @@ export const CANONICAL_WORKSPACE_SSO_APP_ORIGINS = {
18
18
  dispatch: "https://dispatch.agent-native.com",
19
19
  factory: "https://factory.agent-native.com",
20
20
  forms: "https://forms.agent-native.com",
21
- macros: "https://macros.agent-native.com",
22
21
  mail: "https://mail.agent-native.com",
23
22
  plan: "https://plan.agent-native.com",
24
23
  slides: "https://slides.agent-native.com",