@agent-native/dispatch 0.30.1 → 0.30.3
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/ask_app.d.ts +1 -0
- package/dist/actions/ask_app_status.d.ts +1 -0
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +4 -3
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/_index.d.ts.map +1 -1
- package/dist/routes/pages/_index.js +7 -2
- package/dist/routes/pages/_index.js.map +1 -1
- package/dist/routes/pages/_index.test.d.ts +2 -0
- package/dist/routes/pages/_index.test.d.ts.map +1 -0
- package/dist/routes/pages/_index.test.js +29 -0
- package/dist/routes/pages/_index.test.js.map +1 -0
- package/dist/server/lib/dispatch-integrations.d.ts.map +1 -1
- package/dist/server/lib/dispatch-integrations.js +149 -7
- package/dist/server/lib/dispatch-integrations.js.map +1 -1
- package/dist/server/lib/dispatch-store.d.ts +1 -0
- package/dist/server/lib/dispatch-store.d.ts.map +1 -1
- package/dist/server/lib/dispatch-store.js +4 -0
- package/dist/server/lib/dispatch-store.js.map +1 -1
- package/dist/server/lib/mcp-gateway.d.ts +8 -2
- package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
- package/dist/server/lib/mcp-gateway.js +39 -6
- package/dist/server/lib/mcp-gateway.js.map +1 -1
- package/package.json +2 -2
- package/src/routes/pages/$appId.tsx +4 -3
- package/src/routes/pages/_index.test.ts +34 -0
- package/src/routes/pages/_index.tsx +7 -2
- package/src/server/lib/dispatch-integrations.spec.ts +578 -7
- package/src/server/lib/dispatch-integrations.ts +186 -9
- package/src/server/lib/dispatch-store.ts +7 -0
- package/src/server/lib/mcp-gateway.spec.ts +328 -17
- package/src/server/lib/mcp-gateway.ts +59 -4
|
@@ -9,6 +9,7 @@ declare const _default: import("@agent-native/core").ActionDefinition<{
|
|
|
9
9
|
taskId: string;
|
|
10
10
|
status: string;
|
|
11
11
|
response?: string;
|
|
12
|
+
receipts?: import("node_modules/@agent-native/core/dist/a2a/artifact-response.js").A2APersistedMutationReceipt[];
|
|
12
13
|
error?: string;
|
|
13
14
|
inputRequired?: string;
|
|
14
15
|
statusRead?: "unavailable";
|
|
@@ -7,6 +7,7 @@ declare const _default: import("@agent-native/core").ActionDefinition<{
|
|
|
7
7
|
taskId: string;
|
|
8
8
|
status: string;
|
|
9
9
|
response?: string;
|
|
10
|
+
receipts?: import("node_modules/@agent-native/core/dist/a2a/artifact-response.js").A2APersistedMutationReceipt[];
|
|
10
11
|
error?: string;
|
|
11
12
|
inputRequired?: string;
|
|
12
13
|
statusRead?: "unavailable";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"$appId.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/$appId.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"$appId.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/$appId.tsx"],"names":[],"mappings":"AAWA,OAAO,EAKL,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AActB,wBAAgB,IAAI;;IAEnB;AA2CD,wBAAsB,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,kBAAkB,iBAQ1D;AAED,wBAAsB,YAAY,CAAC,EACjC,MAAM,EACN,YAAY,GACb,EAAE,wBAAwB,oBAS1B;AAED,MAAM,CAAC,OAAO,UAAU,yBAAyB,oDA0IhD"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
3
3
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
4
4
|
import { useT } from "@agent-native/core/client/i18n";
|
|
5
|
+
import { withSsrHtmlContentType } from "@agent-native/core/shared";
|
|
5
6
|
import { withBuilderUtmTrackingParams } from "@agent-native/core/shared/builder-link-tracking";
|
|
6
7
|
import { IconArrowLeft, IconArrowUpRight, IconClockHour4, } from "@tabler/icons-react";
|
|
7
8
|
import { useEffect, useMemo, useState } from "react";
|
|
@@ -63,16 +64,16 @@ export async function loader({ params }) {
|
|
|
63
64
|
return null;
|
|
64
65
|
const selfTarget = dispatchSelfRedirect(appId);
|
|
65
66
|
if (selfTarget)
|
|
66
|
-
throw redirect(selfTarget);
|
|
67
|
+
throw withSsrHtmlContentType(redirect(selfTarget));
|
|
67
68
|
const target = await resolveServerCatchAllTarget(appId);
|
|
68
69
|
if (target)
|
|
69
|
-
throw redirect(target);
|
|
70
|
+
throw withSsrHtmlContentType(redirect(target));
|
|
70
71
|
return null;
|
|
71
72
|
}
|
|
72
73
|
export async function clientLoader({ params, serverLoader, }) {
|
|
73
74
|
const selfTarget = dispatchSelfRedirect(params.appId);
|
|
74
75
|
if (selfTarget)
|
|
75
|
-
throw redirect(selfTarget);
|
|
76
|
+
throw withSsrHtmlContentType(redirect(selfTarget));
|
|
76
77
|
// Defer to the server loader so the built-in template fallback runs on
|
|
77
78
|
// SPA navigations too (e.g. clicking a `/<template-id>` link inside
|
|
78
79
|
// dispatch). Without this the client side would only check the workspace
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"$appId.js","sourceRoot":"","sources":["../../../src/routes/pages/$appId.tsx"],"names":[],"mappings":"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","sourcesContent":["import { appPath } from \"@agent-native/core/client/api-path\";\nimport { useActionQuery } from \"@agent-native/core/client/hooks\";\nimport { useT } from \"@agent-native/core/client/i18n\";\nimport { withBuilderUtmTrackingParams } from \"@agent-native/core/shared/builder-link-tracking\";\nimport {\n IconArrowLeft,\n IconArrowUpRight,\n IconClockHour4,\n} from \"@tabler/icons-react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport {\n Link,\n Navigate,\n redirect,\n useParams,\n type ClientLoaderFunctionArgs,\n type LoaderFunctionArgs,\n} from \"react-router\";\n\nimport { ActionQueryError } from \"../../components/action-query-error\";\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Badge } from \"../../components/ui/badge\";\nimport { Button } from \"../../components/ui/button\";\nimport { Spinner } from \"../../components/ui/spinner\";\nimport { resolveServerCatchAllTarget } from \"../../lib/catch-all-target\";\nimport {\n navigateToWorkspaceApp,\n workspaceAppHref,\n type WorkspaceAppSummary,\n} from \"../../lib/workspace-apps\";\n\nexport function meta() {\n return [{ title: \"Workspace app - Dispatch\" }];\n}\n\n/**\n * Catch-all for `/dispatch/<segment>` paths that don't match an explicit\n * Dispatch route. When `<segment>` is the id of a workspace app sibling\n * (e.g. `/dispatch/todo` after Builder.io routes a \"navigate to /todo\"\n * call through Dispatch's mount point), bounce to the absolute `/<appId>`\n * so the user lands on the actual app instead of a 404 inside Dispatch.\n *\n * Server-side redirect: we resolve the workspace app manifest via the\n * shared `loadWorkspaceAppsManifest()` helper, which checks the\n * `AGENT_NATIVE_WORKSPACE_APPS_JSON` env var, then the\n * `.agent-native/workspace-apps.json` file written by `workspace-deploy.ts`,\n * then a live filesystem scan of `apps/` for local dev. We then throw\n * `redirect(\"/<appId>\")`. React Router 7 does not prepend the basename to\n * absolute paths returned from a loader, so the redirect escapes Dispatch's\n * `/dispatch` mount cleanly.\n *\n * Why a catch-all instead of fixing the agent prompt: Builder.io currently\n * resolves \"navigate to /todo\" relative to Dispatch's mount, sending the\n * user to /dispatch/todo. The same wrong path then gets captured as the\n * OAuth callbackURL, so Google sign-in completes back at /dispatch/todo\n * and looks broken. This route fixes both the post-creation navigation\n * and the OAuth round-trip from a single place.\n *\n * Built-in template fallback: when no workspace manifest is available\n * (framework dev with each template on its own port, hosted dispatch with\n * no sibling apps), redirect to the matching first-party template's deploy\n * URL — `http://localhost:<devPort>` in dev, `https://<id>.agent-native.com`\n * in production. Without this, a user visiting `/forms` on dispatch is\n * forced to sign in (auth guard) and then lands on this route's \"Page not\n * found\" pane after the post-login reload.\n *\n * `appId === \"dispatch\"` short-circuit: when the segment matches Dispatch\n * itself (e.g. `/dispatch/dispatch`), we go straight to the overview rather\n * than chaining through `/dispatch` (which polled `useActionQuery` re-fired\n * `window.location.assign` against and looped forever in production).\n */\nfunction dispatchSelfRedirect(appId: string | undefined): string | null {\n if (appId === \"dispatch\") return appPath(\"/overview\");\n return null;\n}\n\nexport async function loader({ params }: LoaderFunctionArgs) {\n const appId = params.appId;\n if (!appId) return null;\n const selfTarget = dispatchSelfRedirect(appId);\n if (selfTarget) throw redirect(selfTarget);\n const target = await resolveServerCatchAllTarget(appId);\n if (target) throw redirect(target);\n return null;\n}\n\nexport async function clientLoader({\n params,\n serverLoader,\n}: ClientLoaderFunctionArgs) {\n const selfTarget = dispatchSelfRedirect(params.appId);\n if (selfTarget) throw redirect(selfTarget);\n // Defer to the server loader so the built-in template fallback runs on\n // SPA navigations too (e.g. clicking a `/<template-id>` link inside\n // dispatch). Without this the client side would only check the workspace\n // apps query, which never lists the static first-party templates and so\n // the user would land on the \"Page not found\" pane.\n return serverLoader();\n}\n\nexport default function WorkspaceAppCatchAllRoute() {\n const t = useT();\n const { appId } = useParams();\n const appsQuery = useActionQuery(\"list-workspace-apps\", {\n includeAgentCards: false,\n });\n const { data: apps = [], isLoading } = appsQuery;\n const app = useMemo(\n () =>\n (apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,\n [appId, apps],\n );\n const href = app ? workspaceAppHref(app) : null;\n const isSelfReference = appId === \"dispatch\";\n const hasApp = app !== null;\n const appIsPending = app?.status === \"pending\";\n const [navigationFailed, setNavigationFailed] = useState(false);\n\n useEffect(() => {\n if (isSelfReference) return;\n if (!hasApp || appIsPending || !href) {\n setNavigationFailed(false);\n return;\n }\n setNavigationFailed(!navigateToWorkspaceApp(href));\n }, [appIsPending, hasApp, href, isSelfReference]);\n\n if (isSelfReference) {\n return <Navigate to={appPath(\"/overview\")} replace />;\n }\n\n if (appsQuery.isError) {\n return (\n <DispatchShell\n title={t(\"dispatch.pages.dataLoadFailed\")}\n description={t(\"dispatch.pages.pageNotFoundDescription\")}\n >\n <ActionQueryError\n error={appsQuery.error}\n onRetry={() => void appsQuery.refetch()}\n />\n </DispatchShell>\n );\n }\n\n if (navigationFailed && href) {\n return (\n <DispatchShell\n title={t(\"dispatch.pages.dataLoadFailed\")}\n description={t(\"dispatch.pages.pageNotFoundDescription\")}\n >\n <ActionQueryError\n onRetry={() => setNavigationFailed(!navigateToWorkspaceApp(href))}\n />\n </DispatchShell>\n );\n }\n\n if (\n (isLoading && !app) ||\n (app && app.status !== \"pending\" && href && !navigationFailed)\n ) {\n return (\n <div className=\"flex h-screen w-full items-center justify-center\">\n <Spinner className=\"size-8\" />\n </div>\n );\n }\n\n return (\n <DispatchShell\n title={app?.name || t(\"dispatch.pages.pageNotFound\")}\n description={t(\"dispatch.pages.pageNotFoundDescription\")}\n >\n <div className=\"max-w-2xl rounded-lg bg-card p-5\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to={appPath(\"/overview\")}>\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n {t(\"dispatch.nav.overview\")}\n </Link>\n </Button>\n\n {app?.status === \"pending\" ? (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {app.name}\n </h2>\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n >\n <IconClockHour4 size={12} />\n {t(\"dispatch.pages.building\")}\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {t(\"dispatch.pages.appBuildingPrefix\")}{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>{\" \"}\n {t(\"dispatch.pages.appBuildingSuffix\")}\n </p>\n {app.builderUrl ? (\n <Button asChild>\n <a\n href={withBuilderUtmTrackingParams(app.builderUrl, {\n campaign: \"product\",\n content: \"dispatch_branch\",\n })}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {t(\"dispatch.pages.openBuilderBranch\", {\n defaultValue: \"Open in Builder\",\n })}\n <IconArrowUpRight size={15} className=\"ml-1.5\" />\n </a>\n </Button>\n ) : null}\n </div>\n ) : (\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {t(\"dispatch.pages.pageNotFound\")}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n <span className=\"font-mono text-foreground\">/{appId}</span> isn't\n {t(\"dispatch.pages.notDispatchOrWorkspaceApp\")}\n </p>\n <Button asChild>\n <Link to={appPath(\"/apps\")}>\n {t(\"dispatch.pages.browseApps\")}\n </Link>\n </Button>\n </div>\n )}\n </div>\n </DispatchShell>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"$appId.js","sourceRoot":"","sources":["../../../src/routes/pages/$appId.tsx"],"names":[],"mappings":"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","sourcesContent":["import { appPath } from \"@agent-native/core/client/api-path\";\nimport { useActionQuery } from \"@agent-native/core/client/hooks\";\nimport { useT } from \"@agent-native/core/client/i18n\";\nimport { withSsrHtmlContentType } from \"@agent-native/core/shared\";\nimport { withBuilderUtmTrackingParams } from \"@agent-native/core/shared/builder-link-tracking\";\nimport {\n IconArrowLeft,\n IconArrowUpRight,\n IconClockHour4,\n} from \"@tabler/icons-react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport {\n Link,\n Navigate,\n redirect,\n useParams,\n type ClientLoaderFunctionArgs,\n type LoaderFunctionArgs,\n} from \"react-router\";\n\nimport { ActionQueryError } from \"../../components/action-query-error\";\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Badge } from \"../../components/ui/badge\";\nimport { Button } from \"../../components/ui/button\";\nimport { Spinner } from \"../../components/ui/spinner\";\nimport { resolveServerCatchAllTarget } from \"../../lib/catch-all-target\";\nimport {\n navigateToWorkspaceApp,\n workspaceAppHref,\n type WorkspaceAppSummary,\n} from \"../../lib/workspace-apps\";\n\nexport function meta() {\n return [{ title: \"Workspace app - Dispatch\" }];\n}\n\n/**\n * Catch-all for `/dispatch/<segment>` paths that don't match an explicit\n * Dispatch route. When `<segment>` is the id of a workspace app sibling\n * (e.g. `/dispatch/todo` after Builder.io routes a \"navigate to /todo\"\n * call through Dispatch's mount point), bounce to the absolute `/<appId>`\n * so the user lands on the actual app instead of a 404 inside Dispatch.\n *\n * Server-side redirect: we resolve the workspace app manifest via the\n * shared `loadWorkspaceAppsManifest()` helper, which checks the\n * `AGENT_NATIVE_WORKSPACE_APPS_JSON` env var, then the\n * `.agent-native/workspace-apps.json` file written by `workspace-deploy.ts`,\n * then a live filesystem scan of `apps/` for local dev. We then throw\n * `redirect(\"/<appId>\")`. React Router 7 does not prepend the basename to\n * absolute paths returned from a loader, so the redirect escapes Dispatch's\n * `/dispatch` mount cleanly.\n *\n * Why a catch-all instead of fixing the agent prompt: Builder.io currently\n * resolves \"navigate to /todo\" relative to Dispatch's mount, sending the\n * user to /dispatch/todo. The same wrong path then gets captured as the\n * OAuth callbackURL, so Google sign-in completes back at /dispatch/todo\n * and looks broken. This route fixes both the post-creation navigation\n * and the OAuth round-trip from a single place.\n *\n * Built-in template fallback: when no workspace manifest is available\n * (framework dev with each template on its own port, hosted dispatch with\n * no sibling apps), redirect to the matching first-party template's deploy\n * URL — `http://localhost:<devPort>` in dev, `https://<id>.agent-native.com`\n * in production. Without this, a user visiting `/forms` on dispatch is\n * forced to sign in (auth guard) and then lands on this route's \"Page not\n * found\" pane after the post-login reload.\n *\n * `appId === \"dispatch\"` short-circuit: when the segment matches Dispatch\n * itself (e.g. `/dispatch/dispatch`), we go straight to the overview rather\n * than chaining through `/dispatch` (which polled `useActionQuery` re-fired\n * `window.location.assign` against and looped forever in production).\n */\nfunction dispatchSelfRedirect(appId: string | undefined): string | null {\n if (appId === \"dispatch\") return appPath(\"/overview\");\n return null;\n}\n\nexport async function loader({ params }: LoaderFunctionArgs) {\n const appId = params.appId;\n if (!appId) return null;\n const selfTarget = dispatchSelfRedirect(appId);\n if (selfTarget) throw withSsrHtmlContentType(redirect(selfTarget));\n const target = await resolveServerCatchAllTarget(appId);\n if (target) throw withSsrHtmlContentType(redirect(target));\n return null;\n}\n\nexport async function clientLoader({\n params,\n serverLoader,\n}: ClientLoaderFunctionArgs) {\n const selfTarget = dispatchSelfRedirect(params.appId);\n if (selfTarget) throw withSsrHtmlContentType(redirect(selfTarget));\n // Defer to the server loader so the built-in template fallback runs on\n // SPA navigations too (e.g. clicking a `/<template-id>` link inside\n // dispatch). Without this the client side would only check the workspace\n // apps query, which never lists the static first-party templates and so\n // the user would land on the \"Page not found\" pane.\n return serverLoader();\n}\n\nexport default function WorkspaceAppCatchAllRoute() {\n const t = useT();\n const { appId } = useParams();\n const appsQuery = useActionQuery(\"list-workspace-apps\", {\n includeAgentCards: false,\n });\n const { data: apps = [], isLoading } = appsQuery;\n const app = useMemo(\n () =>\n (apps as WorkspaceAppSummary[]).find((item) => item.id === appId) ?? null,\n [appId, apps],\n );\n const href = app ? workspaceAppHref(app) : null;\n const isSelfReference = appId === \"dispatch\";\n const hasApp = app !== null;\n const appIsPending = app?.status === \"pending\";\n const [navigationFailed, setNavigationFailed] = useState(false);\n\n useEffect(() => {\n if (isSelfReference) return;\n if (!hasApp || appIsPending || !href) {\n setNavigationFailed(false);\n return;\n }\n setNavigationFailed(!navigateToWorkspaceApp(href));\n }, [appIsPending, hasApp, href, isSelfReference]);\n\n if (isSelfReference) {\n return <Navigate to={appPath(\"/overview\")} replace />;\n }\n\n if (appsQuery.isError) {\n return (\n <DispatchShell\n title={t(\"dispatch.pages.dataLoadFailed\")}\n description={t(\"dispatch.pages.pageNotFoundDescription\")}\n >\n <ActionQueryError\n error={appsQuery.error}\n onRetry={() => void appsQuery.refetch()}\n />\n </DispatchShell>\n );\n }\n\n if (navigationFailed && href) {\n return (\n <DispatchShell\n title={t(\"dispatch.pages.dataLoadFailed\")}\n description={t(\"dispatch.pages.pageNotFoundDescription\")}\n >\n <ActionQueryError\n onRetry={() => setNavigationFailed(!navigateToWorkspaceApp(href))}\n />\n </DispatchShell>\n );\n }\n\n if (\n (isLoading && !app) ||\n (app && app.status !== \"pending\" && href && !navigationFailed)\n ) {\n return (\n <div className=\"flex h-screen w-full items-center justify-center\">\n <Spinner className=\"size-8\" />\n </div>\n );\n }\n\n return (\n <DispatchShell\n title={app?.name || t(\"dispatch.pages.pageNotFound\")}\n description={t(\"dispatch.pages.pageNotFoundDescription\")}\n >\n <div className=\"max-w-2xl rounded-lg bg-card p-5\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2 mb-4\">\n <Link to={appPath(\"/overview\")}>\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n {t(\"dispatch.nav.overview\")}\n </Link>\n </Button>\n\n {app?.status === \"pending\" ? (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {app.name}\n </h2>\n <Badge\n variant=\"outline\"\n className=\"gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n >\n <IconClockHour4 size={12} />\n {t(\"dispatch.pages.building\")}\n </Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {t(\"dispatch.pages.appBuildingPrefix\")}{\" \"}\n <span className=\"font-mono text-foreground\">{app.path}</span>{\" \"}\n {t(\"dispatch.pages.appBuildingSuffix\")}\n </p>\n {app.builderUrl ? (\n <Button asChild>\n <a\n href={withBuilderUtmTrackingParams(app.builderUrl, {\n campaign: \"product\",\n content: \"dispatch_branch\",\n })}\n target=\"_blank\"\n rel=\"noreferrer\"\n >\n {t(\"dispatch.pages.openBuilderBranch\", {\n defaultValue: \"Open in Builder\",\n })}\n <IconArrowUpRight size={15} className=\"ml-1.5\" />\n </a>\n </Button>\n ) : null}\n </div>\n ) : (\n <div className=\"space-y-3\">\n <h2 className=\"text-base font-semibold text-foreground\">\n {t(\"dispatch.pages.pageNotFound\")}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n <span className=\"font-mono text-foreground\">/{appId}</span> isn't\n {t(\"dispatch.pages.notDispatchOrWorkspaceApp\")}\n </p>\n <Button asChild>\n <Link to={appPath(\"/apps\")}>\n {t(\"dispatch.pages.browseApps\")}\n </Link>\n </Button>\n </div>\n )}\n </div>\n </DispatchShell>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_index.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/_index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"_index.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/_index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AASjE,wBAAgB,IAAI;;;;;;;;;;;;;;;KAanB;AAkBD,wBAAgB,MAAM,CAAC,EAAE,GAAG,EAAE,EAAE,kBAAkB,QAIjD;AAED,wBAAgB,YAAY,CAAC,EAAE,GAAG,EAAE,EAAE,kBAAkB,QAIvD;AAED,wBAAgB,eAAe,oDAM9B;AAED,MAAM,CAAC,OAAO,UAAU,SAAS,SAEhC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
3
|
+
import { withSsrHtmlContentType } from "@agent-native/core/shared";
|
|
3
4
|
import { redirect } from "react-router";
|
|
4
5
|
import { Spinner } from "../../components/ui/spinner.js";
|
|
5
6
|
const SEO_TITLE = "Dispatch - Open Source workspace control plane for AI agents";
|
|
@@ -34,10 +35,14 @@ function buildTarget(url) {
|
|
|
34
35
|
return appPath(`/overview${url.search}${url.hash}`);
|
|
35
36
|
}
|
|
36
37
|
export function loader({ url }) {
|
|
37
|
-
throw redirect(buildTarget(url))
|
|
38
|
+
throw withSsrHtmlContentType(redirect(buildTarget(url)), {
|
|
39
|
+
varyByQuery: true,
|
|
40
|
+
});
|
|
38
41
|
}
|
|
39
42
|
export function clientLoader({ url }) {
|
|
40
|
-
throw redirect(buildTarget(url))
|
|
43
|
+
throw withSsrHtmlContentType(redirect(buildTarget(url)), {
|
|
44
|
+
varyByQuery: true,
|
|
45
|
+
});
|
|
41
46
|
}
|
|
42
47
|
export function HydrateFallback() {
|
|
43
48
|
return (_jsx("div", { className: "flex items-center justify-center h-screen w-full", children: _jsx(Spinner, { className: "size-8" }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_index.js","sourceRoot":"","sources":["../../../src/routes/pages/_index.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAA2B,MAAM,cAAc,CAAC;AAEjE,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAEtD,MAAM,SAAS,GACb,8DAA8D,CAAC;AACjE,MAAM,eAAe,GACnB,iIAAiI,CAAC;AAEpI,MAAM,UAAU,IAAI;IAClB,OAAO;QACL,EAAE,KAAK,EAAE,SAAS,EAAE;QACpB;YACE,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,eAAe;SACzB;QACD,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE;QAC5C,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,eAAe,EAAE;QACxD,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE;QAC5C,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE;QAC7C,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,eAAe,EAAE;KAC1D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,WAAW,CAAC,GAAQ;IAC3B,OAAO,OAAO,CAAC,YAAY,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,EAAE,GAAG,EAAsB;IAChD,MAAM,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"_index.js","sourceRoot":"","sources":["../../../src/routes/pages/_index.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,QAAQ,EAA2B,MAAM,cAAc,CAAC;AAEjE,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAEtD,MAAM,SAAS,GACb,8DAA8D,CAAC;AACjE,MAAM,eAAe,GACnB,iIAAiI,CAAC;AAEpI,MAAM,UAAU,IAAI;IAClB,OAAO;QACL,EAAE,KAAK,EAAE,SAAS,EAAE;QACpB;YACE,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,eAAe;SACzB;QACD,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE;QAC5C,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,eAAe,EAAE;QACxD,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE;QAC5C,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE;QAC7C,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,eAAe,EAAE;KAC1D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,WAAW,CAAC,GAAQ;IAC3B,OAAO,OAAO,CAAC,YAAY,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,EAAE,GAAG,EAAsB;IAChD,MAAM,sBAAsB,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE;QACvD,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAAE,GAAG,EAAsB;IACtD,MAAM,sBAAsB,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE;QACvD,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CACL,cAAK,SAAS,EAAC,kDAAkD,YAC/D,KAAC,OAAO,IAAC,SAAS,EAAC,QAAQ,GAAG,GAC1B,CACP,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,SAAS;IAC/B,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import { appPath } from \"@agent-native/core/client/api-path\";\nimport { withSsrHtmlContentType } from \"@agent-native/core/shared\";\nimport { redirect, type LoaderFunctionArgs } from \"react-router\";\n\nimport { Spinner } from \"../../components/ui/spinner\";\n\nconst SEO_TITLE =\n \"Dispatch - Open Source workspace control plane for AI agents\";\nconst SEO_DESCRIPTION =\n \"Open Source workspace control plane for AI agents to manage apps, secrets, approvals, messages, jobs, and cross-app delegation.\";\n\nexport function meta() {\n return [\n { title: SEO_TITLE },\n {\n name: \"description\",\n content: SEO_DESCRIPTION,\n },\n { property: \"og:title\", content: SEO_TITLE },\n { property: \"og:description\", content: SEO_DESCRIPTION },\n { name: \"twitter:card\", content: \"summary\" },\n { name: \"twitter:title\", content: SEO_TITLE },\n { name: \"twitter:description\", content: SEO_DESCRIPTION },\n ];\n}\n\n/**\n * Run the redirect on both the server and the client. A client-only\n * `<Navigate>` can drop during hydration (before the route tree is fully\n * attached), leaving the user stranded on `/` with a blank main area while\n * the layout chrome around it still renders. A `loader` redirect runs as\n * part of the server response and the navigation completes before the app\n * hydrates; `clientLoader` covers SPA-style navigations to `/`.\n *\n * We preserve `?` and `#` so deep-links like `?thread=<id>` from a Slack\n * \"Open thread\" button survive the bounce — `useThreadDeepLink` in\n * `root.tsx` reads them after the redirect lands and opens `/chat`.\n */\nfunction buildTarget(url: URL): string {\n return appPath(`/overview${url.search}${url.hash}`);\n}\n\nexport function loader({ url }: LoaderFunctionArgs) {\n throw withSsrHtmlContentType(redirect(buildTarget(url)), {\n varyByQuery: true,\n });\n}\n\nexport function clientLoader({ url }: LoaderFunctionArgs) {\n throw withSsrHtmlContentType(redirect(buildTarget(url)), {\n varyByQuery: true,\n });\n}\n\nexport function HydrateFallback() {\n return (\n <div className=\"flex items-center justify-center h-screen w-full\">\n <Spinner className=\"size-8\" />\n </div>\n );\n}\n\nexport default function IndexPage() {\n return null;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_index.test.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/_index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SSR_QUERY_CACHE_KEY_HEADER } from "@agent-native/core/shared";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { clientLoader, loader } from "./_index.js";
|
|
4
|
+
function expectOverviewRedirect(routeLoader) {
|
|
5
|
+
let thrown;
|
|
6
|
+
try {
|
|
7
|
+
routeLoader({
|
|
8
|
+
url: new URL("https://dispatch.example/?thread=thread-1"),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
thrown = error;
|
|
13
|
+
}
|
|
14
|
+
expect(thrown).toBeInstanceOf(Response);
|
|
15
|
+
const response = thrown;
|
|
16
|
+
expect(response.status).toBe(302);
|
|
17
|
+
expect(response.headers.get("location")).toBe("/overview?thread=thread-1");
|
|
18
|
+
expect(response.headers.get("content-type")).toBe("text/html; charset=utf-8");
|
|
19
|
+
expect(response.headers.get(SSR_QUERY_CACHE_KEY_HEADER)).toBe("query");
|
|
20
|
+
}
|
|
21
|
+
describe("Dispatch root route", () => {
|
|
22
|
+
it("marks the server redirect as cacheable HTML", () => {
|
|
23
|
+
expectOverviewRedirect(loader);
|
|
24
|
+
});
|
|
25
|
+
it("marks the client redirect as cacheable HTML", () => {
|
|
26
|
+
expectOverviewRedirect(clientLoader);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=_index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_index.test.js","sourceRoot":"","sources":["../../../src/routes/pages/_index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEhD,SAAS,sBAAsB,CAC7B,WAAgD;IAEhD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,WAAW,CAAC;YACV,GAAG,EAAE,IAAI,GAAG,CAAC,2CAA2C,CAAC;SACjD,CAAC,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAkB,CAAC;IACpC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IAC3E,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC9E,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACzE,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,sBAAsB,CAAC,YAAY,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { SSR_QUERY_CACHE_KEY_HEADER } from \"@agent-native/core/shared\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { clientLoader, loader } from \"./_index\";\n\nfunction expectOverviewRedirect(\n routeLoader: typeof loader | typeof clientLoader,\n) {\n let thrown: unknown;\n try {\n routeLoader({\n url: new URL(\"https://dispatch.example/?thread=thread-1\"),\n } as never);\n } catch (error) {\n thrown = error;\n }\n\n expect(thrown).toBeInstanceOf(Response);\n const response = thrown as Response;\n expect(response.status).toBe(302);\n expect(response.headers.get(\"location\")).toBe(\"/overview?thread=thread-1\");\n expect(response.headers.get(\"content-type\")).toBe(\"text/html; charset=utf-8\");\n expect(response.headers.get(SSR_QUERY_CACHE_KEY_HEADER)).toBe(\"query\");\n}\n\ndescribe(\"Dispatch root route\", () => {\n it(\"marks the server redirect as cacheable HTML\", () => {\n expectOverviewRedirect(loader);\n });\n\n it(\"marks the client redirect as cacheable HTML\", () => {\n expectOverviewRedirect(clientLoader);\n });\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-integrations.d.ts","sourceRoot":"","sources":["../../../src/server/lib/dispatch-integrations.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,eAAe,EACf,2BAA2B,EAC3B,eAAe,EAChB,MAAM,2BAA2B,CAAC;AA2BnC,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,eAAe,GACxB,MAAM,GAAG,IAAI,CAmBf;
|
|
1
|
+
{"version":3,"file":"dispatch-integrations.d.ts","sourceRoot":"","sources":["../../../src/server/lib/dispatch-integrations.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,eAAe,EACf,2BAA2B,EAC3B,eAAe,EAChB,MAAM,2BAA2B,CAAC;AA2BnC,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,eAAe,GACxB,MAAM,GAAG,IAAI,CAmBf;AA+VD,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,MAAM,CAAC,CA8DjB;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,2BAA2B,CAAC,CAwHtC;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC,CA0FxE"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { evaluateIntegrationScopePolicy, getActiveIntegrationInstallationByKey, getIntegrationScope, resolveIntegrationTokenBundle, resolveSlackBotTokenForIncoming, saveIntegrationScope, slackInstallationKey, } from "@agent-native/core/integrations";
|
|
3
|
-
import { resolveOrgIdForEmail } from "@agent-native/core/org";
|
|
3
|
+
import { isOrgMember, resolveOrgIdForEmail } from "@agent-native/core/org";
|
|
4
4
|
import { withConfiguredAppBasePath } from "@agent-native/core/server";
|
|
5
5
|
import { handleRemoteCodeCommand } from "./dispatch-remote-commands.js";
|
|
6
6
|
import { dispatchIntegrationRoutingHint, } from "./dispatch-routing.js";
|
|
@@ -82,14 +82,30 @@ async function resolveManagedSlackInstallation(incoming) {
|
|
|
82
82
|
if (incoming.platform !== "slack")
|
|
83
83
|
return null;
|
|
84
84
|
const teamId = contextString(incoming.platformContext.teamId);
|
|
85
|
+
const enterpriseId = contextString(incoming.platformContext.enterpriseId);
|
|
85
86
|
const apiAppId = contextString(incoming.platformContext.apiAppId);
|
|
86
|
-
|
|
87
|
+
const isEnterpriseInstall = incoming.platformContext.isEnterpriseInstall === true;
|
|
88
|
+
if ((!teamId && !enterpriseId) || (isEnterpriseInstall && !enterpriseId))
|
|
87
89
|
return null;
|
|
90
|
+
return getActiveIntegrationInstallationByKey("slack", slackInstallationKey({
|
|
91
|
+
teamId,
|
|
92
|
+
enterpriseId,
|
|
93
|
+
apiAppId,
|
|
94
|
+
isEnterpriseInstall,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
class ManagedSlackInstallationLookupError extends Error {
|
|
98
|
+
constructor(options) {
|
|
99
|
+
super("Managed Slack installation identity is temporarily unavailable", options);
|
|
100
|
+
this.name = "ManagedSlackInstallationLookupError";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function resolveManagedSlackInstallationOrFail(incoming) {
|
|
88
104
|
try {
|
|
89
|
-
return await
|
|
105
|
+
return await resolveManagedSlackInstallation(incoming);
|
|
90
106
|
}
|
|
91
|
-
catch {
|
|
92
|
-
|
|
107
|
+
catch (cause) {
|
|
108
|
+
throw new ManagedSlackInstallationLookupError({ cause });
|
|
93
109
|
}
|
|
94
110
|
}
|
|
95
111
|
async function resolveManagedSlackToken(incoming) {
|
|
@@ -113,6 +129,12 @@ function formatSlackLinkRequiredMessage() {
|
|
|
113
129
|
: "Open Dispatch while signed in, create a Slack link token, then send `/link <token>` in this DM.";
|
|
114
130
|
return `Agent Native is ready, but this Slack account is not linked to an Agent Native user yet. ${linkStep}`;
|
|
115
131
|
}
|
|
132
|
+
function formatSlackIdentityVerificationFailedMessage() {
|
|
133
|
+
return "I couldn't verify your Slack identity just now, so I can't run this request. Please try again in a moment.";
|
|
134
|
+
}
|
|
135
|
+
function formatSlackIdentityDeniedMessage() {
|
|
136
|
+
return "This assistant is only available to members of this workspace's organization.";
|
|
137
|
+
}
|
|
116
138
|
async function resolveSlackSenderProfile(incoming) {
|
|
117
139
|
if (incoming.platform !== "slack") {
|
|
118
140
|
return { email: null, name: null, trust: "unknown" };
|
|
@@ -147,7 +169,8 @@ async function resolveSlackSenderProfile(incoming) {
|
|
|
147
169
|
data.user?.real_name?.trim() ||
|
|
148
170
|
data.user?.name?.trim() ||
|
|
149
171
|
null,
|
|
150
|
-
trust: data.user?.
|
|
172
|
+
trust: data.user?.is_stranger === true ||
|
|
173
|
+
data.user?.is_ultra_restricted === true
|
|
151
174
|
? "external_shared"
|
|
152
175
|
: data.user?.is_restricted === true
|
|
153
176
|
? "guest"
|
|
@@ -179,6 +202,74 @@ async function resolveSlackOwnerFromVerifiedEmail(incoming) {
|
|
|
179
202
|
const orgId = await resolveOrgIdForEmail(profile.email);
|
|
180
203
|
return orgId ? profile.email : null;
|
|
181
204
|
}
|
|
205
|
+
async function resolveManagedSlackDmExecutionContext(incoming, installation) {
|
|
206
|
+
const profile = await resolveSlackSenderProfile(incoming);
|
|
207
|
+
incoming.actorTrust = {
|
|
208
|
+
memberType: profile.trust === "guest"
|
|
209
|
+
? "guest"
|
|
210
|
+
: profile.trust === "external_shared"
|
|
211
|
+
? "external"
|
|
212
|
+
: profile.trust === "trusted"
|
|
213
|
+
? "member"
|
|
214
|
+
: "unknown",
|
|
215
|
+
verified: profile.trust !== "unknown",
|
|
216
|
+
};
|
|
217
|
+
incoming.senderVerified = profile.email !== null;
|
|
218
|
+
if (profile.email) {
|
|
219
|
+
incoming.senderEmail = profile.email;
|
|
220
|
+
incoming.platformContext.senderEmail = profile.email;
|
|
221
|
+
}
|
|
222
|
+
if (profile.name) {
|
|
223
|
+
incoming.senderName = profile.name;
|
|
224
|
+
incoming.platformContext.senderName = profile.name;
|
|
225
|
+
}
|
|
226
|
+
const deniedContext = () => ({
|
|
227
|
+
ownerEmail: fallbackOwnerForIncoming(incoming),
|
|
228
|
+
orgId: null,
|
|
229
|
+
principalType: "user",
|
|
230
|
+
installationId: installation.id,
|
|
231
|
+
});
|
|
232
|
+
if (profile.trust === "unknown") {
|
|
233
|
+
incoming.platformContext.identityVerificationFailed = true;
|
|
234
|
+
return deniedContext();
|
|
235
|
+
}
|
|
236
|
+
if (profile.trust === "guest" || profile.trust === "external_shared") {
|
|
237
|
+
incoming.platformContext.identityAccessDenied = true;
|
|
238
|
+
return deniedContext();
|
|
239
|
+
}
|
|
240
|
+
if (!installation.orgId) {
|
|
241
|
+
incoming.platformContext.identityLinkRequired = true;
|
|
242
|
+
return deniedContext();
|
|
243
|
+
}
|
|
244
|
+
incoming.platformContext.managedInstallationOrgId = installation.orgId;
|
|
245
|
+
let linkedOwner;
|
|
246
|
+
try {
|
|
247
|
+
linkedOwner = await resolveLinkedOwner("slack", identityKeyForIncoming(incoming), { orgId: installation.orgId });
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
incoming.platformContext.identityVerificationFailed = true;
|
|
251
|
+
return deniedContext();
|
|
252
|
+
}
|
|
253
|
+
const candidates = [profile.email, linkedOwner].filter((email, index, values) => !!email && values.indexOf(email) === index);
|
|
254
|
+
for (const ownerEmail of candidates) {
|
|
255
|
+
try {
|
|
256
|
+
if (await isOrgMember(installation.orgId, ownerEmail)) {
|
|
257
|
+
return {
|
|
258
|
+
ownerEmail,
|
|
259
|
+
orgId: installation.orgId,
|
|
260
|
+
principalType: "user",
|
|
261
|
+
installationId: installation.id,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
incoming.platformContext.identityVerificationFailed = true;
|
|
267
|
+
return deniedContext();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
incoming.platformContext.identityLinkRequired = true;
|
|
271
|
+
return deniedContext();
|
|
272
|
+
}
|
|
182
273
|
async function resolveSlackConversationTrust(incoming, actorTrust) {
|
|
183
274
|
if (incoming.triggerKind === "dm") {
|
|
184
275
|
return { trust: actorTrust, conversationType: "direct_message" };
|
|
@@ -276,6 +367,31 @@ export async function resolveDispatchOwner(incoming) {
|
|
|
276
367
|
* personal credentials or memory.
|
|
277
368
|
*/
|
|
278
369
|
export async function resolveDispatchExecutionContext(incoming) {
|
|
370
|
+
if (incoming.platform === "slack" && incoming.triggerKind === "dm") {
|
|
371
|
+
let installation;
|
|
372
|
+
try {
|
|
373
|
+
installation = await resolveManagedSlackInstallationOrFail(incoming);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
if (!(error instanceof ManagedSlackInstallationLookupError))
|
|
377
|
+
throw error;
|
|
378
|
+
incoming.platformContext.identityVerificationFailed = true;
|
|
379
|
+
return {
|
|
380
|
+
ownerEmail: fallbackOwnerForIncoming(incoming),
|
|
381
|
+
orgId: null,
|
|
382
|
+
principalType: "user",
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (installation) {
|
|
386
|
+
return resolveManagedSlackDmExecutionContext(incoming, installation);
|
|
387
|
+
}
|
|
388
|
+
incoming.platformContext.identityVerificationFailed = true;
|
|
389
|
+
return {
|
|
390
|
+
ownerEmail: fallbackOwnerForIncoming(incoming),
|
|
391
|
+
orgId: null,
|
|
392
|
+
principalType: "user",
|
|
393
|
+
};
|
|
394
|
+
}
|
|
279
395
|
if (incoming.platform !== "slack" || incoming.triggerKind === "dm") {
|
|
280
396
|
const ownerEmail = await resolveDispatchOwner(incoming);
|
|
281
397
|
if (incoming.platform === "slack" &&
|
|
@@ -291,7 +407,7 @@ export async function resolveDispatchExecutionContext(incoming) {
|
|
|
291
407
|
principalType: "user",
|
|
292
408
|
};
|
|
293
409
|
}
|
|
294
|
-
const installation = await
|
|
410
|
+
const installation = await resolveManagedSlackInstallationOrFail(incoming);
|
|
295
411
|
if (!installation) {
|
|
296
412
|
// Preserve the legacy manually configured app path while managed installs
|
|
297
413
|
// roll out. Its behavior remains explicit and visible in Settings.
|
|
@@ -365,6 +481,30 @@ export async function beforeDispatchProcess(incoming, adapter) {
|
|
|
365
481
|
const trimmed = incoming.text.trim();
|
|
366
482
|
const commandText = contextString(incoming.platformContext.rawText) || trimmed;
|
|
367
483
|
const match = commandText.match(/^\/link(?:@\w+)?\s+([a-zA-Z0-9_-]+)$/);
|
|
484
|
+
if (match &&
|
|
485
|
+
incoming.platform === "slack" &&
|
|
486
|
+
incoming.triggerKind === "dm" &&
|
|
487
|
+
!contextString(incoming.platformContext.managedInstallationOrgId) &&
|
|
488
|
+
incoming.platformContext.identityVerificationFailed !== true &&
|
|
489
|
+
incoming.platformContext.identityAccessDenied !== true) {
|
|
490
|
+
await resolveDispatchExecutionContext(incoming);
|
|
491
|
+
}
|
|
492
|
+
if (incoming.platform === "slack" &&
|
|
493
|
+
incoming.triggerKind === "dm" &&
|
|
494
|
+
incoming.platformContext.identityVerificationFailed === true) {
|
|
495
|
+
return {
|
|
496
|
+
handled: true,
|
|
497
|
+
responseText: formatSlackIdentityVerificationFailedMessage(),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (incoming.platform === "slack" &&
|
|
501
|
+
incoming.triggerKind === "dm" &&
|
|
502
|
+
incoming.platformContext.identityAccessDenied === true) {
|
|
503
|
+
return {
|
|
504
|
+
handled: true,
|
|
505
|
+
responseText: formatSlackIdentityDeniedMessage(),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
368
508
|
if (!match) {
|
|
369
509
|
const routedIncoming = incoming;
|
|
370
510
|
routedIncoming.routingHint ??= dispatchIntegrationRoutingHint(trimmed);
|
|
@@ -390,11 +530,13 @@ export async function beforeDispatchProcess(incoming, adapter) {
|
|
|
390
530
|
});
|
|
391
531
|
}
|
|
392
532
|
try {
|
|
533
|
+
const expectedOrgId = contextString(incoming.platformContext.managedInstallationOrgId);
|
|
393
534
|
const owner = await consumeLinkToken({
|
|
394
535
|
platform: incoming.platform,
|
|
395
536
|
token: match[1],
|
|
396
537
|
externalUserId: identityKeyForIncoming(incoming),
|
|
397
538
|
externalUserName: incoming.senderName || null,
|
|
539
|
+
...(expectedOrgId ? { expectedOrgId } : {}),
|
|
398
540
|
});
|
|
399
541
|
return {
|
|
400
542
|
handled: true,
|