@agent-native/dispatch 0.40.40 → 0.40.41-beta-dispatch-embed-01a15dc9-20261027050558
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/CHANGELOG.md +42 -0
- package/dist/actions/create_embed_session.js +1 -0
- package/dist/actions/create_embed_session.js.map +1 -1
- package/dist/actions/delete-destination.d.ts +12 -12
- package/dist/actions/provider-api-register.d.ts +13 -13
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +8 -11
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/workspace-app-host.d.ts.map +1 -1
- package/dist/components/workspace-app-host.js +23 -10
- package/dist/components/workspace-app-host.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +2 -2
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +35 -5
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +173 -48
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/community-apps.js +1 -1
- package/dist/server/lib/community-apps.js.map +1 -1
- package/dist/server/lib/mcp-gateway.d.ts +1 -0
- package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
- package/dist/server/lib/mcp-gateway.js +170 -38
- package/dist/server/lib/mcp-gateway.js.map +1 -1
- package/dist/server/lib/provider-api.d.ts +2 -2
- package/dist/shared/workspace-app-route.d.ts +2 -0
- package/dist/shared/workspace-app-route.d.ts.map +1 -0
- package/dist/shared/workspace-app-route.js +4 -0
- package/dist/shared/workspace-app-route.js.map +1 -0
- package/package.json +4 -4
- package/src/actions/create_embed_session.ts +1 -0
- package/src/actions/index.spec.ts +3 -0
- package/src/components/layout/Layout.spec.tsx +39 -9
- package/src/components/layout/Layout.tsx +19 -40
- package/src/components/workspace-app-host.spec.tsx +88 -1
- package/src/components/workspace-app-host.tsx +30 -9
- package/src/lib/workspace-apps.spec.ts +64 -5
- package/src/lib/workspace-apps.ts +36 -6
- package/src/routes/pages/apps.$appId.spec.tsx +21 -3
- package/src/server/lib/app-creation-store.spec.ts +502 -11
- package/src/server/lib/app-creation-store.ts +198 -50
- package/src/server/lib/community-apps.ts +1 -1
- package/src/server/lib/mcp-gateway.spec.ts +396 -9
- package/src/server/lib/mcp-gateway.ts +209 -45
- package/src/shared/workspace-app-route.ts +3 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
isActionContractError,
|
|
14
14
|
} from "@agent-native/core/action";
|
|
15
15
|
import { describeErrorWithCauses } from "@agent-native/core/agent/engine/error-detail";
|
|
16
|
+
import { getAppConfig } from "@agent-native/core/app-config";
|
|
16
17
|
import { getDbExec } from "@agent-native/core/db";
|
|
17
18
|
import { and, eq, inArray } from "@agent-native/core/db/schema";
|
|
18
19
|
import {
|
|
@@ -56,6 +57,7 @@ import {
|
|
|
56
57
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
57
58
|
|
|
58
59
|
import { getDb } from "../../db/index.js";
|
|
60
|
+
import { workspaceAppRoute } from "../../shared/workspace-app-route.js";
|
|
59
61
|
import { isWorkspaceSsoAppUrl } from "../../shared/workspace-sso.js";
|
|
60
62
|
import { getDispatchDefaultOwnerEmail } from "./admin-config.js";
|
|
61
63
|
import { identityKeyForIncoming } from "./dispatch-integrations.js";
|
|
@@ -155,6 +157,15 @@ class WorkspaceAppOrgRequiredError extends Error {
|
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
class WorkspaceGatewayConfigurationError extends Error {
|
|
161
|
+
constructor(cause?: unknown) {
|
|
162
|
+
super("WORKSPACE_GATEWAY_URL must be a valid absolute HTTP(S) URL.", {
|
|
163
|
+
cause,
|
|
164
|
+
});
|
|
165
|
+
this.name = "WorkspaceGatewayConfigurationError";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
158
169
|
function requireWorkspaceAppOrgId(): string {
|
|
159
170
|
const orgId = currentOrgId();
|
|
160
171
|
if (!orgId) throw new WorkspaceAppOrgRequiredError();
|
|
@@ -295,17 +306,22 @@ interface WorkspaceAppMetadataSettings {
|
|
|
295
306
|
}
|
|
296
307
|
|
|
297
308
|
function readJson(file: string): any {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
} catch {
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
309
|
+
if (!fs.existsSync(file)) return null;
|
|
310
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
303
311
|
}
|
|
304
312
|
|
|
305
313
|
function findWorkspaceRoot(startDir = process.cwd()): string | null {
|
|
306
314
|
let dir = path.resolve(startDir);
|
|
307
315
|
for (let i = 0; i < 20; i++) {
|
|
308
|
-
|
|
316
|
+
let pkg: any;
|
|
317
|
+
try {
|
|
318
|
+
pkg = readJson(path.join(dir, "package.json"));
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.warn(
|
|
321
|
+
`[dispatch] Could not read workspace package metadata in ${dir}; continuing parent search`,
|
|
322
|
+
error,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
309
325
|
if (typeof pkg?.["agent-native"]?.workspaceCore === "string") {
|
|
310
326
|
return dir;
|
|
311
327
|
}
|
|
@@ -627,18 +643,87 @@ function applyWorkspaceAppMetadataOverride(
|
|
|
627
643
|
// code belongs here; changes to authorship, content trust, or sharing require
|
|
628
644
|
// an explicit auth, origin, or sandbox boundary before this invariant changes.
|
|
629
645
|
function workspaceAppUrl(appPath: string): string | null {
|
|
646
|
+
const gateway = workspaceGatewayBaseUrl();
|
|
647
|
+
if (gateway) return appendWorkspaceAppPath(gateway, appPath);
|
|
648
|
+
|
|
630
649
|
const base = resolveAppRuntimeUrl();
|
|
631
650
|
if (!base) return null;
|
|
632
651
|
try {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
652
|
+
const baseUrl = new URL(
|
|
653
|
+
addBasePath(projectEnvironmentUrl(base), getConfiguredAppBasePath()),
|
|
654
|
+
);
|
|
655
|
+
return appendWorkspaceAppPath(baseUrl, appPath);
|
|
637
656
|
} catch {
|
|
638
657
|
return null;
|
|
639
658
|
}
|
|
640
659
|
}
|
|
641
660
|
|
|
661
|
+
function appendWorkspaceAppPath(baseUrl: URL, appPath: string): string {
|
|
662
|
+
const parsedPath = new URL(appPath, "http://agent-native.invalid");
|
|
663
|
+
const basePath = baseUrl.pathname.replace(/\/+$/, "");
|
|
664
|
+
let appPathname = parsedPath.pathname;
|
|
665
|
+
if (
|
|
666
|
+
basePath &&
|
|
667
|
+
(appPathname === basePath || appPathname.startsWith(`${basePath}/`))
|
|
668
|
+
) {
|
|
669
|
+
appPathname = appPathname.slice(basePath.length) || "/";
|
|
670
|
+
}
|
|
671
|
+
baseUrl.pathname =
|
|
672
|
+
`${basePath}${appPathname.startsWith("/") ? appPathname : `/${appPathname}`}` ||
|
|
673
|
+
"/";
|
|
674
|
+
baseUrl.search = parsedPath.search;
|
|
675
|
+
baseUrl.hash = parsedPath.hash;
|
|
676
|
+
return baseUrl.toString().replace(/\/$/, "");
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function workspaceGatewayBaseUrl(): URL | null {
|
|
680
|
+
const configured = getAppConfig().workspace.gatewayUrl?.trim();
|
|
681
|
+
if (!configured) return null;
|
|
682
|
+
let configuredUrl: URL;
|
|
683
|
+
try {
|
|
684
|
+
configuredUrl = new URL(configured);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
throw new WorkspaceGatewayConfigurationError(error);
|
|
687
|
+
}
|
|
688
|
+
if (
|
|
689
|
+
configuredUrl.protocol !== "http:" &&
|
|
690
|
+
configuredUrl.protocol !== "https:"
|
|
691
|
+
) {
|
|
692
|
+
throw new WorkspaceGatewayConfigurationError();
|
|
693
|
+
}
|
|
694
|
+
let url: URL;
|
|
695
|
+
try {
|
|
696
|
+
url = new URL(projectEnvironmentUrl(configuredUrl.toString()));
|
|
697
|
+
} catch (error) {
|
|
698
|
+
throw new WorkspaceGatewayConfigurationError(error);
|
|
699
|
+
}
|
|
700
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
701
|
+
throw new WorkspaceGatewayConfigurationError();
|
|
702
|
+
}
|
|
703
|
+
return new URL(addBasePath(url.toString(), getConfiguredAppBasePath()));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function workspaceGatewayAppUrl(appPath: string): string | null {
|
|
707
|
+
const baseUrl = workspaceGatewayBaseUrl();
|
|
708
|
+
if (!baseUrl) return null;
|
|
709
|
+
const requestOrigin = getRequestContext()?.requestOrigin;
|
|
710
|
+
if (requestOrigin && new URL(requestOrigin).origin === baseUrl.origin) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
return appendWorkspaceAppPath(baseUrl, appPath);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function addBasePath(urlValue: string, basePath: string): string {
|
|
717
|
+
if (!basePath) return urlValue;
|
|
718
|
+
const url = new URL(urlValue);
|
|
719
|
+
const pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
720
|
+
if (pathname === basePath || pathname.startsWith(`${basePath}/`)) {
|
|
721
|
+
return url.toString();
|
|
722
|
+
}
|
|
723
|
+
url.pathname = `${basePath}${pathname === "/" ? "" : pathname}` || "/";
|
|
724
|
+
return url.toString();
|
|
725
|
+
}
|
|
726
|
+
|
|
642
727
|
function isLocalWorkspaceGateway(url: URL): boolean {
|
|
643
728
|
const hostname = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
644
729
|
return (
|
|
@@ -649,18 +734,50 @@ function isLocalWorkspaceGateway(url: URL): boolean {
|
|
|
649
734
|
);
|
|
650
735
|
}
|
|
651
736
|
|
|
652
|
-
function
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
737
|
+
function workspaceAppPagePath(appId: string, urlValue?: string): string {
|
|
738
|
+
const route = workspaceAppRoute(appId);
|
|
739
|
+
if (!urlValue) return route;
|
|
740
|
+
const parsed = new URL(urlValue, "https://agent-native.invalid");
|
|
741
|
+
return `${route}${parsed.search}${parsed.hash}`;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function workspaceAppLink(appId: string, explicitUrl?: unknown): string {
|
|
656
745
|
const urlValue = typeof explicitUrl === "string" ? explicitUrl.trim() : "";
|
|
657
|
-
|
|
658
|
-
if (urlValue
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
746
|
+
const routePath = workspaceAppPagePath(appId, urlValue || undefined);
|
|
747
|
+
if (!urlValue) {
|
|
748
|
+
return (
|
|
749
|
+
workspaceGatewayAppUrl(routePath) ??
|
|
750
|
+
workspaceAppUrl(routePath) ??
|
|
751
|
+
routePath
|
|
752
|
+
);
|
|
663
753
|
}
|
|
754
|
+
if (urlValue.startsWith("/")) {
|
|
755
|
+
return (
|
|
756
|
+
workspaceGatewayAppUrl(routePath) ??
|
|
757
|
+
workspaceAppUrl(routePath) ??
|
|
758
|
+
routePath
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
const resolved = projectEnvironmentUrl(new URL(urlValue).toString());
|
|
762
|
+
const gateway = workspaceGatewayBaseUrl();
|
|
763
|
+
if (gateway && new URL(resolved).origin === gateway.origin) {
|
|
764
|
+
return (
|
|
765
|
+
workspaceGatewayAppUrl(routePath) ??
|
|
766
|
+
workspaceAppUrl(routePath) ??
|
|
767
|
+
routePath
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
const runtimeBase = resolveAppRuntimeUrl();
|
|
771
|
+
if (runtimeBase && new URL(resolved).origin === new URL(runtimeBase).origin) {
|
|
772
|
+
const runtimeUrl = new URL(
|
|
773
|
+
addBasePath(
|
|
774
|
+
projectEnvironmentUrl(runtimeBase),
|
|
775
|
+
getConfiguredAppBasePath(),
|
|
776
|
+
),
|
|
777
|
+
);
|
|
778
|
+
return appendWorkspaceAppPath(runtimeUrl, routePath);
|
|
779
|
+
}
|
|
780
|
+
return resolved;
|
|
664
781
|
}
|
|
665
782
|
|
|
666
783
|
function normalizeWorkspaceAppAudience(value: unknown): WorkspaceAppAudience {
|
|
@@ -821,7 +938,10 @@ function workspaceAppRouteAccessFromPackageJson(pkg: unknown): {
|
|
|
821
938
|
};
|
|
822
939
|
}
|
|
823
940
|
|
|
824
|
-
function parseWorkspaceAppsManifest(
|
|
941
|
+
function parseWorkspaceAppsManifest(
|
|
942
|
+
parsed: any,
|
|
943
|
+
onInvalidAppUrl?: () => void,
|
|
944
|
+
): WorkspaceAppSummary[] | null {
|
|
825
945
|
const rawApps = Array.isArray(parsed?.apps)
|
|
826
946
|
? parsed.apps
|
|
827
947
|
: Array.isArray(parsed)
|
|
@@ -840,6 +960,18 @@ function parseWorkspaceAppsManifest(parsed: any): WorkspaceAppSummary[] | null {
|
|
|
840
960
|
});
|
|
841
961
|
if (!profile) return null;
|
|
842
962
|
const metadata = workspaceAppMetadataFromRecord(entry);
|
|
963
|
+
let url: string | null;
|
|
964
|
+
try {
|
|
965
|
+
url = workspaceAppLink(id, entry.url);
|
|
966
|
+
} catch (error) {
|
|
967
|
+
if (error instanceof WorkspaceGatewayConfigurationError) throw error;
|
|
968
|
+
onInvalidAppUrl?.();
|
|
969
|
+
console.warn(
|
|
970
|
+
`[dispatch] Could not resolve Workspace app ${id}; skipping app`,
|
|
971
|
+
error,
|
|
972
|
+
);
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
843
975
|
return {
|
|
844
976
|
id,
|
|
845
977
|
name:
|
|
@@ -851,7 +983,7 @@ function parseWorkspaceAppsManifest(parsed: any): WorkspaceAppSummary[] | null {
|
|
|
851
983
|
...(profile.whenToUse ? { whenToUse: profile.whenToUse } : {}),
|
|
852
984
|
path: pathValue,
|
|
853
985
|
homePath: normalizeWorkspaceAppHomePath(entry.homePath),
|
|
854
|
-
url
|
|
986
|
+
url,
|
|
855
987
|
isDispatch:
|
|
856
988
|
typeof entry.isDispatch === "boolean"
|
|
857
989
|
? entry.isDispatch
|
|
@@ -887,8 +1019,11 @@ function parseWorkspaceAppsGatewayResponse(
|
|
|
887
1019
|
: parsed;
|
|
888
1020
|
if (!Array.isArray(rawApps)) return null;
|
|
889
1021
|
if (rawApps.length === 0) return [];
|
|
890
|
-
|
|
891
|
-
|
|
1022
|
+
let invalidUrlCount = 0;
|
|
1023
|
+
const apps = parseWorkspaceAppsManifest(parsed, () => {
|
|
1024
|
+
invalidUrlCount += 1;
|
|
1025
|
+
});
|
|
1026
|
+
if (!apps || apps.length + invalidUrlCount !== rawApps.length) return null;
|
|
892
1027
|
const ids = new Set(apps.map((app) => app.id.toLowerCase()));
|
|
893
1028
|
return ids.size === apps.length ? apps : null;
|
|
894
1029
|
}
|
|
@@ -1861,28 +1996,25 @@ function readWorkspaceAppsFromEnv(): WorkspaceAppSummary[] | null {
|
|
|
1861
1996
|
const raw = process.env[WORKSPACE_APPS_ENV_KEY];
|
|
1862
1997
|
if (!raw) return null;
|
|
1863
1998
|
try {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1999
|
+
const apps = parseWorkspaceAppsManifest(JSON.parse(raw));
|
|
2000
|
+
if (apps) return apps;
|
|
2001
|
+
console.warn(
|
|
2002
|
+
`[dispatch] Could not parse Workspace app manifest from ${WORKSPACE_APPS_ENV_KEY}; skipping source`,
|
|
2003
|
+
);
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
if (error instanceof WorkspaceGatewayConfigurationError) throw error;
|
|
2006
|
+
console.warn(
|
|
2007
|
+
`[dispatch] Could not read Workspace app manifest from ${WORKSPACE_APPS_ENV_KEY}; skipping source`,
|
|
2008
|
+
);
|
|
1867
2009
|
}
|
|
2010
|
+
return null;
|
|
1868
2011
|
}
|
|
1869
2012
|
|
|
1870
2013
|
async function readWorkspaceAppsFromGateway(): Promise<WorkspaceAppDiscovery | null> {
|
|
1871
|
-
const configuredBase =
|
|
2014
|
+
const configuredBase = getAppConfig().workspace.gatewayUrl?.trim();
|
|
1872
2015
|
if (!configuredBase) return null;
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1875
|
-
let baseUrl: URL;
|
|
1876
|
-
try {
|
|
1877
|
-
baseUrl = new URL(base);
|
|
1878
|
-
} catch {
|
|
1879
|
-
// coercion-ok: malformed gateway configuration is an unavailable registry
|
|
1880
|
-
// and falls back to local sources.
|
|
1881
|
-
return null;
|
|
1882
|
-
}
|
|
1883
|
-
if (baseUrl.protocol !== "http:" && baseUrl.protocol !== "https:") {
|
|
1884
|
-
return null;
|
|
1885
|
-
}
|
|
2016
|
+
const baseUrl = workspaceGatewayBaseUrl();
|
|
2017
|
+
if (!baseUrl) return null;
|
|
1886
2018
|
|
|
1887
2019
|
const requestContext = getRequestContext();
|
|
1888
2020
|
const isLocalGateway = isLocalWorkspaceGateway(baseUrl);
|
|
@@ -2123,8 +2255,15 @@ function workspaceAppsManifestCandidates(): string[] {
|
|
|
2123
2255
|
function readWorkspaceAppsFromManifestFile(): WorkspaceAppSummary[] | null {
|
|
2124
2256
|
for (const file of workspaceAppsManifestCandidates()) {
|
|
2125
2257
|
if (!fs.existsSync(file)) continue;
|
|
2126
|
-
|
|
2127
|
-
|
|
2258
|
+
try {
|
|
2259
|
+
const apps = parseWorkspaceAppsManifest(readJson(file));
|
|
2260
|
+
if (apps) return apps;
|
|
2261
|
+
} catch (error) {
|
|
2262
|
+
if (error instanceof WorkspaceGatewayConfigurationError) throw error;
|
|
2263
|
+
console.warn(
|
|
2264
|
+
`[dispatch] Could not read Workspace app manifest ${file}; skipping source`,
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2128
2267
|
}
|
|
2129
2268
|
return null;
|
|
2130
2269
|
}
|
|
@@ -2140,7 +2279,16 @@ async function readWorkspaceAppsFromFilesystem(
|
|
|
2140
2279
|
.readdirSync(appsDir, { withFileTypes: true })
|
|
2141
2280
|
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))) {
|
|
2142
2281
|
const appDir = path.join(appsDir, entry.name);
|
|
2143
|
-
|
|
2282
|
+
let pkg: any;
|
|
2283
|
+
try {
|
|
2284
|
+
pkg = readJson(path.join(appDir, "package.json"));
|
|
2285
|
+
} catch (error) {
|
|
2286
|
+
console.warn(
|
|
2287
|
+
`[dispatch] Could not read workspace app ${entry.name}; skipping app`,
|
|
2288
|
+
error,
|
|
2289
|
+
);
|
|
2290
|
+
continue;
|
|
2291
|
+
}
|
|
2144
2292
|
if (!pkg) continue;
|
|
2145
2293
|
const routeAccess = workspaceAppRouteAccessFromPackageJson(pkg);
|
|
2146
2294
|
const metadata = workspaceAppMetadataFromRecord(pkg);
|
|
@@ -2166,7 +2314,7 @@ async function readWorkspaceAppsFromFilesystem(
|
|
|
2166
2314
|
homePath: normalizeWorkspaceAppHomePath(
|
|
2167
2315
|
configuredHomePath ?? inferredHomePath,
|
|
2168
2316
|
),
|
|
2169
|
-
url: workspaceAppUrl(
|
|
2317
|
+
url: workspaceAppUrl(workspaceAppRoute(entry.name)),
|
|
2170
2318
|
isDispatch: entry.name === "dispatch",
|
|
2171
2319
|
audience:
|
|
2172
2320
|
workspaceAppAudienceFromPackageJson(pkg) ??
|
|
@@ -2639,7 +2787,7 @@ export async function scaffoldWorkspaceAppFromTemplate(input: {
|
|
|
2639
2787
|
description: "",
|
|
2640
2788
|
path: `/${appId}`,
|
|
2641
2789
|
homePath: "/home",
|
|
2642
|
-
url: workspaceAppUrl(
|
|
2790
|
+
url: workspaceAppUrl(workspaceAppRoute(appId)),
|
|
2643
2791
|
isDispatch: false,
|
|
2644
2792
|
audience: DEFAULT_WORKSPACE_APP_AUDIENCE,
|
|
2645
2793
|
publicPaths: [],
|
|
@@ -3000,8 +3148,8 @@ function buildWorkspaceAppPrompt(input: {
|
|
|
3000
3148
|
`Dispatch workspace resources selected for this app:\n${resourceList}`,
|
|
3001
3149
|
`Dispatch workspace resources with scope=all are global. After the app exists, sync workspace resources to appId "${appId}" so global skills, guardrail instructions, and reference resources reach the new app even when no per-app resources were selected.`,
|
|
3002
3150
|
"",
|
|
3003
|
-
`Use the workspace app layout: create it under apps/${appId}
|
|
3004
|
-
`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
|
|
3151
|
+
`Use the workspace app layout: create it under apps/${appId}; Dispatch mounts it at /apps/${appId}. Keep it on the shared workspace database/hosting model, and avoid table-name collisions by namespacing any new domain tables to the app.`,
|
|
3152
|
+
`Important routing rule: from outside the app, link to /apps/${appId}; inside apps/${appId}, React Router routes are app-local. Use <Link to="/review"> and navigate("/review"), not "/apps/${appId}/review"; APP_BASE_PATH supplies the mounted prefix, and hardcoding it causes doubled URLs.`,
|
|
3005
3153
|
`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.`,
|
|
3006
3154
|
"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.",
|
|
3007
3155
|
`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.`,
|
|
@@ -3020,7 +3168,7 @@ function buildWorkspaceAppPrompt(input: {
|
|
|
3020
3168
|
"- Use Tabler Icons (@tabler/icons-react) for every icon. Never use emojis as icons.",
|
|
3021
3169
|
`- Expose what the user is looking at via application_state (navigation.view, selection, etc.) so the agent has live context. Mirror the patterns in templates/mail or templates/slides.`,
|
|
3022
3170
|
"- Optimistic UI for every mutation: update the React Query cache immediately, navigate immediately, run the mutation in the background, roll back on error. Don't await a server round-trip before re-rendering.",
|
|
3023
|
-
`- Commit an agent-native.json at apps/${appId}/agent-native.json with { "version": 1, "onboarding": { "firstRun": { "development": "
|
|
3171
|
+
`- Commit an agent-native.json at apps/${appId}/agent-native.json with { "version": 1, "onboarding": { "firstRun": { "development": "off", "production": "connect-and-integrations" } } }, as the first-party templates do, so local runs open straight into the app. Keep the shared Use Builder.io / Add your own keys onboarding for production; never build a second, custom credential form or hardcode a provider key.`,
|
|
3024
3172
|
"- Every AI-labeled button must call sendToAgentChat with openSidebar: true — plus submit: true for one-click work, or submit: false when the user should review/edit the proposed prompt first. Keep follow-ups in that same sidebar thread; don't add a second freeform input beside the result. Never use sparkle, wand, magic, robot, or other decorative AI icons on those buttons — a message or neutral action icon, or no icon, instead.",
|
|
3025
3173
|
"- Choose a named visual direction in DESIGN.md before styling the first screen and build to it. Don't inherit a sibling app's palette unbuilt.",
|
|
3026
3174
|
'- Left navigation must name real domain destinations, not "Chat" as the only or default entry.',
|
|
@@ -3482,7 +3630,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
3482
3630
|
projectId: builderProjectId,
|
|
3483
3631
|
branchName: result.branchName,
|
|
3484
3632
|
url: result.url,
|
|
3485
|
-
workspaceUrl: workspaceAppUrl(
|
|
3633
|
+
workspaceUrl: workspaceAppUrl(workspaceAppRoute(built.appId)),
|
|
3486
3634
|
status: result.status,
|
|
3487
3635
|
message:
|
|
3488
3636
|
`Builder started a branch for /${built.appId}. Use the Builder branch URL to track creation now. ` +
|
|
@@ -79,7 +79,7 @@ export const CURATED_COMMUNITY_APP_SOURCES: readonly CuratedCommunityAppSource[]
|
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
path: "actions/generate-qr-code.spec.ts",
|
|
82
|
-
blobSha: "
|
|
82
|
+
blobSha: "42a1a84ecb421e1e6435cc2e7fd00e2f4f2e1ea9",
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
path: "actions/generate-qr-code.ts",
|