@agent-native/dispatch 0.24.0 → 0.24.2
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/provider-api-register.d.ts +12 -12
- package/dist/components/admin-navigation.d.ts.map +1 -1
- package/dist/components/admin-navigation.js +21 -5
- package/dist/components/admin-navigation.js.map +1 -1
- package/dist/components/app-list-row.d.ts +1 -1
- package/dist/components/app-list-row.d.ts.map +1 -1
- package/dist/components/app-list-row.js +1 -1
- package/dist/components/app-list-row.js.map +1 -1
- package/dist/components/app-open-actions.d.ts +1 -17
- package/dist/components/app-open-actions.d.ts.map +1 -1
- package/dist/components/app-open-actions.js +1 -20
- package/dist/components/app-open-actions.js.map +1 -1
- package/dist/components/app-row-settings.d.ts +6 -0
- package/dist/components/app-row-settings.d.ts.map +1 -0
- package/dist/components/app-row-settings.js +11 -0
- package/dist/components/app-row-settings.js.map +1 -0
- package/dist/components/automation-run-history-dialog.d.ts.map +1 -1
- package/dist/components/automation-run-history-dialog.js +3 -2
- package/dist/components/automation-run-history-dialog.js.map +1 -1
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +1 -1
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/create-app-popover.js +2 -2
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +23 -7
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/dispatch-shell.d.ts.map +1 -1
- package/dist/components/dispatch-shell.js +5 -1
- package/dist/components/dispatch-shell.js.map +1 -1
- package/dist/components/docs-link.d.ts +16 -0
- package/dist/components/docs-link.d.ts.map +1 -0
- package/dist/components/docs-link.js +34 -0
- package/dist/components/docs-link.js.map +1 -0
- package/dist/components/layout/Layout.d.ts +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +37 -28
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/messaging-setup-panel.d.ts.map +1 -1
- package/dist/components/messaging-setup-panel.js +3 -2
- package/dist/components/messaging-setup-panel.js.map +1 -1
- package/dist/components/usage-alerts-panel.d.ts +11 -0
- package/dist/components/usage-alerts-panel.d.ts.map +1 -0
- package/dist/components/usage-alerts-panel.js +125 -0
- package/dist/components/usage-alerts-panel.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts +3 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +70 -29
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-app-search.d.ts +10 -0
- package/dist/components/workspace-app-search.d.ts.map +1 -0
- package/dist/components/workspace-app-search.js +27 -0
- package/dist/components/workspace-app-search.js.map +1 -0
- package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
- package/dist/components/workspace-resource-effective-stack.js +3 -2
- package/dist/components/workspace-resource-effective-stack.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +2 -1
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/lib/workspace-app-layout.d.ts +19 -0
- package/dist/lib/workspace-app-layout.d.ts.map +1 -0
- package/dist/lib/workspace-app-layout.js +75 -0
- package/dist/lib/workspace-app-layout.js.map +1 -0
- package/dist/lib/workspace-apps.d.ts +10 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +59 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/lib/workspace-apps.test.js +18 -1
- package/dist/lib/workspace-apps.test.js.map +1 -1
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +4 -2
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/agents.d.ts.map +1 -1
- package/dist/routes/pages/agents.js +2 -3
- package/dist/routes/pages/agents.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +8 -3
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +31 -16
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/audit.d.ts.map +1 -1
- package/dist/routes/pages/audit.js +2 -1
- package/dist/routes/pages/audit.js.map +1 -1
- package/dist/routes/pages/dreams.d.ts.map +1 -1
- package/dist/routes/pages/dreams.js +14 -18
- package/dist/routes/pages/dreams.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +51 -14
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +4 -1
- package/dist/routes/pages/metrics.js.map +1 -1
- package/dist/routes/pages/operations.js +1 -1
- package/dist/routes/pages/operations.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +7 -2
- package/dist/routes/pages/settings.js.map +1 -1
- package/dist/routes/pages/thread-debug.d.ts.map +1 -1
- package/dist/routes/pages/thread-debug.js +5 -8
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +1 -1
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/routes/pages/workspace.d.ts.map +1 -1
- package/dist/routes/pages/workspace.js +13 -10
- package/dist/routes/pages/workspace.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +12 -52
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/shared/app-roles.d.ts +9 -0
- package/dist/shared/app-roles.d.ts.map +1 -0
- package/dist/shared/app-roles.js +7 -0
- package/dist/shared/app-roles.js.map +1 -0
- package/package.json +4 -3
- package/src/components/admin-navigation.tsx +57 -11
- package/src/components/app-list-row.tsx +1 -2
- package/src/components/app-open-actions.tsx +5 -114
- package/src/components/app-row-settings.tsx +54 -0
- package/src/components/automation-run-history-dialog.tsx +8 -4
- package/src/components/connected-app-card.tsx +0 -1
- package/src/components/create-app-popover.spec.tsx +1 -1
- package/src/components/create-app-popover.tsx +2 -2
- package/src/components/dispatch-control-plane.spec.tsx +36 -2
- package/src/components/dispatch-control-plane.tsx +83 -27
- package/src/components/dispatch-shell.tsx +7 -1
- package/src/components/docs-link.tsx +52 -0
- package/src/components/layout/Layout.spec.tsx +10 -0
- package/src/components/layout/Layout.tsx +50 -23
- package/src/components/messaging-setup-panel.tsx +10 -3
- package/src/components/usage-alerts-panel.tsx +445 -0
- package/src/components/workspace-app-card.spec.tsx +130 -9
- package/src/components/workspace-app-card.tsx +128 -71
- package/src/components/workspace-app-search.tsx +96 -0
- package/src/components/workspace-resource-effective-stack.tsx +10 -5
- package/src/components/workspace-template-card.spec.tsx +52 -23
- package/src/components/workspace-template-card.tsx +7 -10
- package/src/lib/workspace-app-layout.spec.ts +47 -0
- package/src/lib/workspace-app-layout.ts +121 -0
- package/src/lib/workspace-apps.test.ts +45 -1
- package/src/lib/workspace-apps.ts +72 -0
- package/src/routes/pages/$appId.tsx +3 -6
- package/src/routes/pages/agents.tsx +6 -3
- package/src/routes/pages/apps.$appId.tsx +6 -6
- package/src/routes/pages/apps.tsx +116 -62
- package/src/routes/pages/audit.tsx +28 -8
- package/src/routes/pages/dreams.tsx +92 -142
- package/src/routes/pages/integrations.tsx +147 -68
- package/src/routes/pages/metrics.tsx +8 -0
- package/src/routes/pages/operations.tsx +2 -2
- package/src/routes/pages/settings.tsx +14 -9
- package/src/routes/pages/thread-debug.tsx +18 -36
- package/src/routes/pages/vault.tsx +7 -2
- package/src/routes/pages/workspace.tsx +92 -56
- package/src/server/index.ts +4 -0
- package/src/server/lib/app-creation-store.spec.ts +90 -43
- package/src/server/lib/app-creation-store.ts +15 -61
- package/src/shared/app-roles.ts +8 -0
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
} from "../../components/workspace-resource-impact-preview";
|
|
68
68
|
|
|
69
69
|
export function meta() {
|
|
70
|
-
return [{ title: "
|
|
70
|
+
return [{ title: "Resources — Dispatch" }];
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const KIND_CONFIG = {
|
|
@@ -600,7 +600,11 @@ function EffectiveContextPreview({ resource }: { resource: any }) {
|
|
|
600
600
|
organization/app override, then personal override.
|
|
601
601
|
</p>
|
|
602
602
|
</div>
|
|
603
|
-
|
|
603
|
+
{isLoading ? (
|
|
604
|
+
<Skeleton className="h-6 w-24 rounded-full" />
|
|
605
|
+
) : (
|
|
606
|
+
<Badge variant="outline">{availabilityLabel(availability)}</Badge>
|
|
607
|
+
)}
|
|
604
608
|
</div>
|
|
605
609
|
|
|
606
610
|
<div className="mt-3 grid gap-3 md:grid-cols-2">
|
|
@@ -881,14 +885,13 @@ function ResourceRow({ resource, grants }: { resource: any; grants: any[] }) {
|
|
|
881
885
|
);
|
|
882
886
|
}
|
|
883
887
|
|
|
884
|
-
function GlobalContextSection({
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const presentCount = STARTER_GLOBAL_CONTEXT.length - missingPaths.length;
|
|
888
|
+
function GlobalContextSection({
|
|
889
|
+
resources,
|
|
890
|
+
isLoading,
|
|
891
|
+
}: {
|
|
892
|
+
resources: any[];
|
|
893
|
+
isLoading: boolean;
|
|
894
|
+
}) {
|
|
892
895
|
const restoreStarter = useActionMutation(
|
|
893
896
|
"restore-starter-workspace-resources",
|
|
894
897
|
{
|
|
@@ -905,6 +908,44 @@ function GlobalContextSection({ resources }: { resources: any[] }) {
|
|
|
905
908
|
},
|
|
906
909
|
);
|
|
907
910
|
|
|
911
|
+
if (isLoading && resources.length === 0) {
|
|
912
|
+
return (
|
|
913
|
+
<section className="space-y-3">
|
|
914
|
+
<div>
|
|
915
|
+
<Skeleton className="h-4 w-28" />
|
|
916
|
+
<Skeleton className="mt-2 h-3 w-full max-w-xl" />
|
|
917
|
+
<Skeleton className="mt-1.5 h-3 w-2/3 max-w-lg" />
|
|
918
|
+
</div>
|
|
919
|
+
<div className="overflow-hidden rounded-lg bg-card">
|
|
920
|
+
{Array.from({ length: STARTER_GLOBAL_CONTEXT.length }).map(
|
|
921
|
+
(_, index) => (
|
|
922
|
+
<div
|
|
923
|
+
key={index}
|
|
924
|
+
className="flex items-center gap-3 border-t px-4 py-3 first:border-t-0"
|
|
925
|
+
>
|
|
926
|
+
<Skeleton className="size-4 rounded-full" />
|
|
927
|
+
<div className="min-w-0 flex-1 space-y-1.5">
|
|
928
|
+
<Skeleton className="h-4 w-28" />
|
|
929
|
+
<Skeleton className="h-3 w-64 max-w-full" />
|
|
930
|
+
<Skeleton className="h-3 w-40 max-w-full" />
|
|
931
|
+
</div>
|
|
932
|
+
<Skeleton className="h-6 w-16 rounded-full" />
|
|
933
|
+
</div>
|
|
934
|
+
),
|
|
935
|
+
)}
|
|
936
|
+
</div>
|
|
937
|
+
</section>
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const byPath = new Map(
|
|
942
|
+
resources.map((resource) => [resource.path, resource]),
|
|
943
|
+
);
|
|
944
|
+
const missingPaths = STARTER_GLOBAL_CONTEXT.filter(
|
|
945
|
+
(item) => !byPath.has(item.path),
|
|
946
|
+
).map((item) => item.path);
|
|
947
|
+
const presentCount = STARTER_GLOBAL_CONTEXT.length - missingPaths.length;
|
|
948
|
+
|
|
908
949
|
return (
|
|
909
950
|
<section className="space-y-3">
|
|
910
951
|
<div className="flex items-center justify-between gap-3">
|
|
@@ -938,35 +979,44 @@ function GlobalContextSection({ resources }: { resources: any[] }) {
|
|
|
938
979
|
</Badge>
|
|
939
980
|
</div>
|
|
940
981
|
</div>
|
|
941
|
-
<div className="
|
|
982
|
+
<div className="overflow-hidden rounded-lg bg-card">
|
|
942
983
|
{STARTER_GLOBAL_CONTEXT.map((item) => {
|
|
943
984
|
const resource = byPath.get(item.path);
|
|
944
985
|
const exists = !!resource;
|
|
945
986
|
const global = resource?.scope === "all";
|
|
946
987
|
return (
|
|
947
|
-
<div
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
</p>
|
|
988
|
+
<div
|
|
989
|
+
key={item.path}
|
|
990
|
+
className="flex flex-wrap items-center gap-3 border-t px-4 py-3 first:border-t-0"
|
|
991
|
+
>
|
|
992
|
+
{exists ? (
|
|
993
|
+
<IconCircleCheck size={16} className="shrink-0 text-primary" />
|
|
994
|
+
) : (
|
|
995
|
+
<IconAlertCircle
|
|
996
|
+
size={16}
|
|
997
|
+
className="shrink-0 text-amber-600 dark:text-amber-400"
|
|
998
|
+
/>
|
|
999
|
+
)}
|
|
1000
|
+
<div className="min-w-0 flex-1">
|
|
1001
|
+
<div className="truncate text-sm font-medium text-foreground">
|
|
1002
|
+
{item.label}
|
|
1003
|
+
</div>
|
|
1004
|
+
<div className="mt-0.5 truncate text-xs text-muted-foreground">
|
|
1005
|
+
{item.description}
|
|
1006
|
+
</div>
|
|
1007
|
+
<div className="mt-1 truncate font-mono text-[11px] text-muted-foreground/80">
|
|
1008
|
+
{item.path}
|
|
969
1009
|
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
<div className="flex items-center gap-1.5">
|
|
1012
|
+
{exists ? (
|
|
1013
|
+
<Badge variant="outline">
|
|
1014
|
+
{global ? "All apps" : "Selected"}
|
|
1015
|
+
</Badge>
|
|
1016
|
+
) : null}
|
|
1017
|
+
{resource && isAutoLoadedInstruction(resource) ? (
|
|
1018
|
+
<Badge variant="outline">Auto-loaded</Badge>
|
|
1019
|
+
) : null}
|
|
970
1020
|
{resource ? (
|
|
971
1021
|
<EditResourceDialog
|
|
972
1022
|
resource={resource}
|
|
@@ -992,24 +1042,6 @@ function GlobalContextSection({ resources }: { resources: any[] }) {
|
|
|
992
1042
|
</Button>
|
|
993
1043
|
)}
|
|
994
1044
|
</div>
|
|
995
|
-
<div className="mt-3 space-y-2">
|
|
996
|
-
<div className="truncate font-mono text-[11px] text-muted-foreground">
|
|
997
|
-
{item.path}
|
|
998
|
-
</div>
|
|
999
|
-
<div className="flex flex-wrap gap-1.5">
|
|
1000
|
-
<Badge variant={exists ? "secondary" : "outline"}>
|
|
1001
|
-
{exists ? "Present" : "Missing"}
|
|
1002
|
-
</Badge>
|
|
1003
|
-
{exists ? (
|
|
1004
|
-
<Badge variant="outline">
|
|
1005
|
-
{global ? "All apps" : "Selected"}
|
|
1006
|
-
</Badge>
|
|
1007
|
-
) : null}
|
|
1008
|
-
{resource && isAutoLoadedInstruction(resource) ? (
|
|
1009
|
-
<Badge variant="outline">Auto-loaded</Badge>
|
|
1010
|
-
) : null}
|
|
1011
|
-
</div>
|
|
1012
|
-
</div>
|
|
1013
1045
|
</div>
|
|
1014
1046
|
);
|
|
1015
1047
|
})}
|
|
@@ -1086,7 +1118,7 @@ export default function WorkspaceRoute() {
|
|
|
1086
1118
|
|
|
1087
1119
|
return (
|
|
1088
1120
|
<DispatchShell
|
|
1089
|
-
title="
|
|
1121
|
+
title="Resources"
|
|
1090
1122
|
description="Manage inherited skills, guardrail instructions, agent profiles, reference resources, and MCP servers. All-app resources are available to every app without syncing."
|
|
1091
1123
|
>
|
|
1092
1124
|
{resourcesQuery.isError || grantsQuery.isError ? (
|
|
@@ -1111,13 +1143,10 @@ export default function WorkspaceRoute() {
|
|
|
1111
1143
|
</div>
|
|
1112
1144
|
</div>
|
|
1113
1145
|
|
|
1114
|
-
{!resourcesQuery.isError ? (
|
|
1115
|
-
<GlobalContextSection resources={resources || []} />
|
|
1116
|
-
) : null}
|
|
1117
|
-
|
|
1118
1146
|
{!resourcesQuery.isError && !grantsQuery.isError ? (
|
|
1119
1147
|
<Tabs defaultValue="skills">
|
|
1120
1148
|
<TabsList>
|
|
1149
|
+
<TabsTrigger value="context">Global context</TabsTrigger>
|
|
1121
1150
|
<TabsTrigger value="skills">
|
|
1122
1151
|
Skills {skills.length > 0 && `(${skills.length})`}
|
|
1123
1152
|
</TabsTrigger>
|
|
@@ -1136,6 +1165,13 @@ export default function WorkspaceRoute() {
|
|
|
1136
1165
|
</TabsTrigger>
|
|
1137
1166
|
</TabsList>
|
|
1138
1167
|
|
|
1168
|
+
<TabsContent value="context" className="mt-4">
|
|
1169
|
+
<GlobalContextSection
|
|
1170
|
+
resources={resources || []}
|
|
1171
|
+
isLoading={isLoading}
|
|
1172
|
+
/>
|
|
1173
|
+
</TabsContent>
|
|
1174
|
+
|
|
1139
1175
|
<TabsContent value="skills" className="mt-4">
|
|
1140
1176
|
<ResourceList
|
|
1141
1177
|
items={skills}
|
package/src/server/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defineAppRoles } from "@agent-native/core/org";
|
|
1
2
|
import {
|
|
2
3
|
registerPackageActions,
|
|
3
4
|
type NitroPluginDef,
|
|
@@ -5,6 +6,9 @@ import {
|
|
|
5
6
|
|
|
6
7
|
import { dispatchActions } from "../actions/index.js";
|
|
7
8
|
import type { DispatchConfig } from "../config.js";
|
|
9
|
+
import { dispatchAccessDescriptor } from "../shared/app-roles.js";
|
|
10
|
+
|
|
11
|
+
defineAppRoles(dispatchAccessDescriptor);
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Register dispatch's package-contributed actions on import. The framework's
|
|
@@ -3,6 +3,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
generateWorkspaceAppDescription,
|
|
6
|
+
getAppCreationSettings,
|
|
6
7
|
listAvailableWorkspaceTemplates,
|
|
7
8
|
listWorkspaceApps,
|
|
8
9
|
setAppCreationSettings,
|
|
@@ -46,7 +47,6 @@ const mocks = vi.hoisted(() => {
|
|
|
46
47
|
})),
|
|
47
48
|
ensureBuilderProject: vi.fn(),
|
|
48
49
|
runBuilderAgent: vi.fn(),
|
|
49
|
-
resolveBuilderBranchProjectId: vi.fn(async () => ""),
|
|
50
50
|
getBuilderBranchProjectId: vi.fn(() => ""),
|
|
51
51
|
writeAppSecret: vi.fn(async () => "secret-id"),
|
|
52
52
|
deleteAppSecret: vi.fn(async () => true),
|
|
@@ -86,8 +86,6 @@ vi.mock("@agent-native/core/server", async (importOriginal) => {
|
|
|
86
86
|
ensureBuilderProject: (...args: any[]) =>
|
|
87
87
|
mocks.ensureBuilderProject(...args),
|
|
88
88
|
runBuilderAgent: (...args: any[]) => mocks.runBuilderAgent(...args),
|
|
89
|
-
resolveBuilderBranchProjectId: (...args: any[]) =>
|
|
90
|
-
mocks.resolveBuilderBranchProjectId(...args),
|
|
91
89
|
getBuilderBranchProjectId: (...args: any[]) =>
|
|
92
90
|
mocks.getBuilderBranchProjectId(...args),
|
|
93
91
|
};
|
|
@@ -121,12 +119,50 @@ afterEach(() => {
|
|
|
121
119
|
source: null,
|
|
122
120
|
lookupFailed: false,
|
|
123
121
|
});
|
|
124
|
-
mocks.resolveBuilderBranchProjectId.mockResolvedValue("");
|
|
125
122
|
mocks.getBuilderBranchProjectId.mockReturnValue("");
|
|
126
123
|
mocks.ensureBuilderProject.mockReset();
|
|
127
124
|
globalThis.fetch = originalFetch;
|
|
128
125
|
});
|
|
129
126
|
|
|
127
|
+
describe("getAppCreationSettings", () => {
|
|
128
|
+
it("treats the Dispatch project as authoritative over the environment", async () => {
|
|
129
|
+
mocks.settings.set("dispatch-app-creation-settings:org:builder_io", {
|
|
130
|
+
builderProjectId: "dispatch-project",
|
|
131
|
+
});
|
|
132
|
+
vi.stubEnv("DISPATCH_BUILDER_PROJECT_ID", "stale-env-project");
|
|
133
|
+
|
|
134
|
+
const result = await runWithRequestContext(
|
|
135
|
+
{ userEmail: "dev@example.test", orgId: "builder_io" },
|
|
136
|
+
() => getAppCreationSettings(),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(result).toMatchObject({
|
|
140
|
+
builderProjectId: "dispatch-project",
|
|
141
|
+
builderProjectIdSource: "dispatch",
|
|
142
|
+
envBuilderProjectId: "stale-env-project",
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("treats an explicit Dispatch null as disabled over the environment", async () => {
|
|
147
|
+
mocks.settings.set("dispatch-app-creation-settings:org:builder_io", {
|
|
148
|
+
builderProjectId: null,
|
|
149
|
+
});
|
|
150
|
+
vi.stubEnv("DISPATCH_BUILDER_PROJECT_ID", "stale-env-project");
|
|
151
|
+
|
|
152
|
+
const result = await runWithRequestContext(
|
|
153
|
+
{ userEmail: "dev@example.test", orgId: "builder_io" },
|
|
154
|
+
() => getAppCreationSettings(),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(result).toMatchObject({
|
|
158
|
+
builderProjectId: null,
|
|
159
|
+
builderProjectIdSource: "dispatch",
|
|
160
|
+
envBuilderProjectId: "stale-env-project",
|
|
161
|
+
builderBranchingEnabled: false,
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
130
166
|
describe("listWorkspaceApps", () => {
|
|
131
167
|
function stubNoPendingContext() {
|
|
132
168
|
for (const key of [
|
|
@@ -399,9 +435,11 @@ describe("listWorkspaceApps", () => {
|
|
|
399
435
|
name: "Todo Board",
|
|
400
436
|
}),
|
|
401
437
|
),
|
|
402
|
-
).rejects.
|
|
403
|
-
|
|
404
|
-
|
|
438
|
+
).rejects.toMatchObject({
|
|
439
|
+
message:
|
|
440
|
+
"Only organization owners and admins can update app creation settings.",
|
|
441
|
+
statusCode: 403,
|
|
442
|
+
});
|
|
405
443
|
expect(
|
|
406
444
|
mocks.settings.get("workspace-app-metadata:org:org-123"),
|
|
407
445
|
).toBeUndefined();
|
|
@@ -603,12 +641,11 @@ describe("startWorkspaceAppCreation", () => {
|
|
|
603
641
|
expect(mocks.runBuilderAgent).toHaveBeenCalledWith(
|
|
604
642
|
expect.objectContaining({ projectId: "project-provisioned" }),
|
|
605
643
|
);
|
|
606
|
-
expect(mocks.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
);
|
|
644
|
+
expect(mocks.settings.get(settingsKey)).toMatchObject({
|
|
645
|
+
builderProjectId: "project-provisioned",
|
|
646
|
+
});
|
|
647
|
+
expect(mocks.writeAppSecret).not.toHaveBeenCalled();
|
|
648
|
+
expect(mocks.deleteAppSecret).not.toHaveBeenCalled();
|
|
612
649
|
});
|
|
613
650
|
|
|
614
651
|
it("does not let an organization member persist an auto-provisioned project", async () => {
|
|
@@ -653,58 +690,68 @@ describe("setAppCreationSettings", () => {
|
|
|
653
690
|
);
|
|
654
691
|
}
|
|
655
692
|
|
|
656
|
-
it("stores the project id
|
|
693
|
+
it("stores the project id in the org-scoped Dispatch settings row", async () => {
|
|
657
694
|
await save(projectId);
|
|
658
695
|
|
|
659
|
-
expect(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}),
|
|
696
|
+
expect(
|
|
697
|
+
mocks.settings.get("dispatch-app-creation-settings:org:builder_io"),
|
|
698
|
+
).toEqual({ builderProjectId: projectId });
|
|
699
|
+
expect(mocks.putSetting).toHaveBeenCalledWith(
|
|
700
|
+
"dispatch-app-creation-settings:org:builder_io",
|
|
701
|
+
{ builderProjectId: projectId },
|
|
666
702
|
);
|
|
667
703
|
expect(mocks.deleteAppSecret).not.toHaveBeenCalled();
|
|
704
|
+
expect(mocks.writeAppSecret).not.toHaveBeenCalled();
|
|
668
705
|
});
|
|
669
706
|
|
|
670
|
-
it("scopes the
|
|
707
|
+
it("scopes the settings row to one organization rather than every tenant", async () => {
|
|
671
708
|
await save(projectId);
|
|
672
709
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
expect(
|
|
677
|
-
expect(
|
|
710
|
+
expect(mocks.putSetting.mock.calls.at(-1)?.[0]).toBe(
|
|
711
|
+
"dispatch-app-creation-settings:org:builder_io",
|
|
712
|
+
);
|
|
713
|
+
expect(mocks.putSetting.mock.calls.at(-1)?.[0]).not.toContain(":user:");
|
|
714
|
+
expect(mocks.writeAppSecret).not.toHaveBeenCalled();
|
|
678
715
|
});
|
|
679
716
|
|
|
680
|
-
it("
|
|
717
|
+
it("uses a user-scoped settings row when there is no active org", async () => {
|
|
681
718
|
await save(projectId, { userEmail: "dev@example.test" });
|
|
682
719
|
|
|
683
|
-
expect(mocks.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
);
|
|
720
|
+
expect(mocks.settings.get(settingsKey)).toEqual({
|
|
721
|
+
builderProjectId: projectId,
|
|
722
|
+
});
|
|
723
|
+
expect(mocks.putSetting).toHaveBeenCalledWith(settingsKey, {
|
|
724
|
+
builderProjectId: projectId,
|
|
725
|
+
});
|
|
726
|
+
expect(mocks.writeAppSecret).not.toHaveBeenCalled();
|
|
689
727
|
});
|
|
690
728
|
|
|
691
|
-
it("
|
|
729
|
+
it("persists an explicit null when the project id is cleared", async () => {
|
|
692
730
|
await save(null);
|
|
693
731
|
|
|
694
|
-
expect(
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
732
|
+
expect(
|
|
733
|
+
mocks.settings.get("dispatch-app-creation-settings:org:builder_io"),
|
|
734
|
+
).toEqual({ builderProjectId: null });
|
|
735
|
+
expect(mocks.putSetting).toHaveBeenCalledWith(
|
|
736
|
+
"dispatch-app-creation-settings:org:builder_io",
|
|
737
|
+
{ builderProjectId: null },
|
|
738
|
+
);
|
|
739
|
+
expect(mocks.deleteAppSecret).not.toHaveBeenCalled();
|
|
699
740
|
expect(mocks.writeAppSecret).not.toHaveBeenCalled();
|
|
700
741
|
});
|
|
701
742
|
|
|
702
|
-
it("
|
|
743
|
+
it("never consults the project secret store when saving settings", async () => {
|
|
703
744
|
mocks.writeAppSecret.mockRejectedValueOnce(
|
|
704
745
|
new Error("credential store down"),
|
|
705
746
|
);
|
|
706
747
|
|
|
707
|
-
await expect(save(projectId)).
|
|
708
|
-
|
|
748
|
+
await expect(save(projectId)).resolves.toMatchObject({
|
|
749
|
+
builderProjectId: projectId,
|
|
750
|
+
});
|
|
751
|
+
expect(
|
|
752
|
+
mocks.settings.get("dispatch-app-creation-settings:org:builder_io"),
|
|
753
|
+
).toEqual({ builderProjectId: projectId });
|
|
754
|
+
expect(mocks.writeAppSecret).not.toHaveBeenCalled();
|
|
755
|
+
expect(mocks.deleteAppSecret).not.toHaveBeenCalled();
|
|
709
756
|
});
|
|
710
757
|
});
|
|
@@ -4,17 +4,11 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
import { getDbExec } from "@agent-native/core/db";
|
|
7
|
-
import {
|
|
8
|
-
deleteAppSecret,
|
|
9
|
-
writeAppSecret,
|
|
10
|
-
type SecretScope,
|
|
11
|
-
} from "@agent-native/core/secrets";
|
|
12
7
|
import {
|
|
13
8
|
ensureBuilderProject,
|
|
14
9
|
getBuilderBranchProjectId,
|
|
15
10
|
getRequestContext,
|
|
16
11
|
isIntegrationCallerRequest,
|
|
17
|
-
resolveBuilderBranchProjectId,
|
|
18
12
|
resolveBuilderCredentialsDetailed,
|
|
19
13
|
runBuilderAgent,
|
|
20
14
|
} from "@agent-native/core/server";
|
|
@@ -36,9 +30,6 @@ import {
|
|
|
36
30
|
} from "./workspace-resources-store.js";
|
|
37
31
|
|
|
38
32
|
const SETTINGS_KEY = "dispatch-app-creation-settings";
|
|
39
|
-
const BUILDER_BRANCH_PROJECT_SECRET_KEY = "BUILDER_BRANCH_PROJECT_ID";
|
|
40
|
-
const BUILDER_BRANCH_PROJECT_SECRET_DESCRIPTION =
|
|
41
|
-
"Builder project for cloud code-change branches (set in Dispatch)";
|
|
42
33
|
const BUILDER_WORKSPACE_REPO_URL_ENV = "AGENT_NATIVE_WORKSPACE_REPO_URL";
|
|
43
34
|
const DEFAULT_BUILDER_WORKSPACE_REPO_URL =
|
|
44
35
|
"https://github.com/BuilderIO/builder-agent-native-workspace";
|
|
@@ -61,6 +52,7 @@ const AUTONOMOUS_WORKSPACE_APP_CREATION_CONTRACT = [
|
|
|
61
52
|
"Autonomous Builder handoff contract:",
|
|
62
53
|
"- This is a background implementation run launched by the turn-into-app workflow. Treat the source brief and latest user request as authorization to build the app now; do not return a proposal or wait for another turn.",
|
|
63
54
|
"- Do not ask the user questions during the initial build and do not invoke a clarification, guided-question, or choice flow for a non-blocking decision.",
|
|
55
|
+
"- If the confirmed source brief describes a spreadsheet source-review or input/output confirmation surface, implement that review UI as part of the first-run app experience and seed it with the bounded candidates and mapping; keep the background build autonomous and do not send a question back from the Builder run.",
|
|
64
56
|
"- When the source or a tool presents a recommended option, choose it and continue. When no recommendation is present, choose the most direct, conservative default supported by the source and normal Agent-Native conventions.",
|
|
65
57
|
"- Resolve product, visual, copy, layout, route, data-model, dependency, and integration choices yourself. If an input is missing, use an empty state or clearly labeled representative sample so the workflow is demonstrable; never invent private facts or credentials.",
|
|
66
58
|
"- Treat the source brief's unknowns and follow-up items as assumptions to record in the app README or a visible Assumptions / Review section, not as questions to send back to the user.",
|
|
@@ -74,6 +66,8 @@ const pendingBuilderProjectProvisioning = new Map<
|
|
|
74
66
|
>();
|
|
75
67
|
|
|
76
68
|
class AppCreationSettingsAuthorizationError extends Error {
|
|
69
|
+
statusCode = 403;
|
|
70
|
+
|
|
77
71
|
constructor() {
|
|
78
72
|
super(APP_CREATION_SETTINGS_AUTHORIZATION_MESSAGE);
|
|
79
73
|
this.name = "AppCreationSettingsAuthorizationError";
|
|
@@ -254,16 +248,6 @@ function scopedSettingsKey(): string {
|
|
|
254
248
|
return `${SETTINGS_KEY}:user:${currentOwnerEmail()}`;
|
|
255
249
|
}
|
|
256
250
|
|
|
257
|
-
function builderProjectSecretTarget(): {
|
|
258
|
-
scope: Extract<SecretScope, "org" | "workspace">;
|
|
259
|
-
scopeId: string;
|
|
260
|
-
} | null {
|
|
261
|
-
const orgId = currentOrgId();
|
|
262
|
-
if (orgId) return { scope: "org", scopeId: orgId };
|
|
263
|
-
const email = currentOwnerEmail();
|
|
264
|
-
return email ? { scope: "workspace", scopeId: `solo:${email}` } : null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
251
|
function workspaceAppMetadataSettingsKey(): string {
|
|
268
252
|
const orgId = currentOrgId();
|
|
269
253
|
if (orgId) return `${WORKSPACE_APP_METADATA_SETTINGS_KEY}:org:${orgId}`;
|
|
@@ -1607,26 +1591,28 @@ function runScaffoldCli(input: {
|
|
|
1607
1591
|
|
|
1608
1592
|
export async function getAppCreationSettings(): Promise<AppCreationSettings> {
|
|
1609
1593
|
const envBuilderProjectId = getEnvBuilderProjectId();
|
|
1610
|
-
const resolvedBuilderProjectId = await resolveBuilderBranchProjectId();
|
|
1611
1594
|
const raw = await readSettingsRecord();
|
|
1595
|
+
const hasSavedBuilderProjectId = Object.prototype.hasOwnProperty.call(
|
|
1596
|
+
raw,
|
|
1597
|
+
"builderProjectId",
|
|
1598
|
+
);
|
|
1612
1599
|
const savedBuilderProjectId =
|
|
1613
1600
|
typeof raw?.builderProjectId === "string" && raw.builderProjectId.trim()
|
|
1614
1601
|
? raw.builderProjectId.trim()
|
|
1615
1602
|
: null;
|
|
1616
|
-
const builderProjectId = envBuilderProjectId || savedBuilderProjectId;
|
|
1617
1603
|
const enableBuilder =
|
|
1618
1604
|
process.env.ENABLE_BUILDER === "true" || process.env.ENABLE_BUILDER === "1";
|
|
1619
|
-
const effectiveBuilderProjectId =
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1605
|
+
const effectiveBuilderProjectId = hasSavedBuilderProjectId
|
|
1606
|
+
? savedBuilderProjectId
|
|
1607
|
+
: envBuilderProjectId ||
|
|
1608
|
+
(enableBuilder ? getBuilderBranchProjectId() : null);
|
|
1623
1609
|
|
|
1624
1610
|
return {
|
|
1625
1611
|
builderProjectId: effectiveBuilderProjectId,
|
|
1626
|
-
builderProjectIdSource:
|
|
1627
|
-
? "
|
|
1628
|
-
:
|
|
1629
|
-
? "
|
|
1612
|
+
builderProjectIdSource: hasSavedBuilderProjectId
|
|
1613
|
+
? "dispatch"
|
|
1614
|
+
: envBuilderProjectId
|
|
1615
|
+
? "env"
|
|
1630
1616
|
: effectiveBuilderProjectId
|
|
1631
1617
|
? "default"
|
|
1632
1618
|
: "unset",
|
|
@@ -1647,20 +1633,10 @@ async function persistProvisionedBuilderProjectId(
|
|
|
1647
1633
|
builderProjectId: string,
|
|
1648
1634
|
): Promise<void> {
|
|
1649
1635
|
const raw = await readSettingsRecord();
|
|
1650
|
-
const secretTarget = builderProjectSecretTarget();
|
|
1651
1636
|
|
|
1652
1637
|
// This is an internal consequence of an authorized app-creation request,
|
|
1653
1638
|
// not a user-controlled settings update. Persist the project before the
|
|
1654
1639
|
// branch run so later members reuse the same Builder project.
|
|
1655
|
-
if (secretTarget) {
|
|
1656
|
-
await writeAppSecret({
|
|
1657
|
-
key: BUILDER_BRANCH_PROJECT_SECRET_KEY,
|
|
1658
|
-
scope: secretTarget.scope,
|
|
1659
|
-
scopeId: secretTarget.scopeId,
|
|
1660
|
-
value: builderProjectId,
|
|
1661
|
-
description: BUILDER_BRANCH_PROJECT_SECRET_DESCRIPTION,
|
|
1662
|
-
});
|
|
1663
|
-
}
|
|
1664
1640
|
await putSetting(scopedSettingsKey(), { ...raw, builderProjectId });
|
|
1665
1641
|
await recordAudit({
|
|
1666
1642
|
action: "settings.updated",
|
|
@@ -1712,28 +1688,6 @@ export async function setAppCreationSettings(input: {
|
|
|
1712
1688
|
const builderProjectId = input.builderProjectId?.trim() || null;
|
|
1713
1689
|
const raw = await readSettingsRecord();
|
|
1714
1690
|
|
|
1715
|
-
// The credential store, not this settings row, is what
|
|
1716
|
-
// `resolveBuilderBranchProjectId()` reads. Write it first: a saved setting
|
|
1717
|
-
// whose secret never landed reports the project as configured while cloud
|
|
1718
|
-
// code changes stay silently disabled.
|
|
1719
|
-
const secretTarget = builderProjectSecretTarget();
|
|
1720
|
-
if (secretTarget) {
|
|
1721
|
-
const ref = {
|
|
1722
|
-
key: BUILDER_BRANCH_PROJECT_SECRET_KEY,
|
|
1723
|
-
scope: secretTarget.scope,
|
|
1724
|
-
scopeId: secretTarget.scopeId,
|
|
1725
|
-
};
|
|
1726
|
-
if (builderProjectId) {
|
|
1727
|
-
await writeAppSecret({
|
|
1728
|
-
...ref,
|
|
1729
|
-
value: builderProjectId,
|
|
1730
|
-
description: BUILDER_BRANCH_PROJECT_SECRET_DESCRIPTION,
|
|
1731
|
-
});
|
|
1732
|
-
} else {
|
|
1733
|
-
await deleteAppSecret(ref);
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
1691
|
await putSetting(scopedSettingsKey(), { ...raw, builderProjectId });
|
|
1738
1692
|
await recordAudit({
|
|
1739
1693
|
action: "settings.updated",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppRolesDescriptor } from "@agent-native/core/client/org";
|
|
2
|
+
|
|
3
|
+
export const dispatchAccessDescriptor = {
|
|
4
|
+
appId: "dispatch",
|
|
5
|
+
roles: ["admin"] as const,
|
|
6
|
+
roleLabels: { admin: "Dispatch admin" },
|
|
7
|
+
label: "Dispatch access",
|
|
8
|
+
} satisfies AppRolesDescriptor<"admin">;
|