@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.
- package/dist/actions/create-workspace-resource.d.ts +13 -13
- package/dist/actions/delete-workspace-resource.d.ts +13 -13
- package/dist/actions/import-agent.d.ts +13 -13
- package/dist/actions/list-connected-agents.d.ts +1 -0
- package/dist/actions/provider-api-register.d.ts +12 -12
- package/dist/components/app-icon.d.ts.map +1 -1
- package/dist/components/app-icon.js +0 -1
- package/dist/components/app-icon.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +31 -15
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/shared/workspace-sso.d.ts +0 -1
- package/dist/shared/workspace-sso.d.ts.map +1 -1
- package/dist/shared/workspace-sso.js +0 -1
- package/dist/shared/workspace-sso.js.map +1 -1
- package/package.json +4 -4
- package/src/components/app-icon.tsx +0 -1
- package/src/config.ts +1 -1
- package/src/server/lib/app-creation-store.spec.ts +64 -0
- package/src/server/lib/app-creation-store.ts +53 -32
- package/src/shared/workspace-sso.ts +0 -1
|
@@ -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,
|
|
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.
|
|
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.
|
|
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.
|
|
105
|
+
"@agent-native/core": "0.181.0-nightly-20260916021205"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
|
-
"@agent-native/core": "
|
|
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,
|
|
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 =
|
|
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
|
-
.
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
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",
|