@agent-native/dispatch 0.6.1 → 0.7.0
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/README.md +1 -1
- package/dist/actions/create-pylon-ticket.d.ts +3 -0
- package/dist/actions/create-pylon-ticket.d.ts.map +1 -0
- package/dist/actions/create-pylon-ticket.js +94 -0
- package/dist/actions/create-pylon-ticket.js.map +1 -0
- package/dist/actions/create-vault-grant.js +1 -1
- package/dist/actions/create-vault-grant.js.map +1 -1
- package/dist/actions/create-vault-secret.d.ts.map +1 -1
- package/dist/actions/create-vault-secret.js +4 -3
- package/dist/actions/create-vault-secret.js.map +1 -1
- package/dist/actions/get-vault-access-settings.d.ts +3 -0
- package/dist/actions/get-vault-access-settings.d.ts.map +1 -0
- package/dist/actions/get-vault-access-settings.js +10 -0
- package/dist/actions/get-vault-access-settings.js.map +1 -0
- package/dist/actions/grant-vault-secrets-to-app.js +1 -1
- package/dist/actions/grant-vault-secrets-to-app.js.map +1 -1
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +8 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-integrations-catalog.js +1 -1
- package/dist/actions/list-integrations-catalog.js.map +1 -1
- package/dist/actions/list-vault-grants.js +1 -1
- package/dist/actions/list-vault-grants.js.map +1 -1
- package/dist/actions/list-workspace-apps.d.ts.map +1 -1
- package/dist/actions/list-workspace-apps.js +5 -1
- package/dist/actions/list-workspace-apps.js.map +1 -1
- package/dist/actions/set-vault-access-settings.d.ts +3 -0
- package/dist/actions/set-vault-access-settings.d.ts.map +1 -0
- package/dist/actions/set-vault-access-settings.js +13 -0
- package/dist/actions/set-vault-access-settings.js.map +1 -0
- package/dist/actions/start-workspace-app-creation.d.ts.map +1 -1
- package/dist/actions/start-workspace-app-creation.js +6 -0
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/actions/sync-vault-to-app.js +1 -1
- package/dist/actions/sync-vault-to-app.js.map +1 -1
- package/dist/actions/update-workspace-app-metadata.d.ts +3 -0
- package/dist/actions/update-workspace-app-metadata.d.ts.map +1 -0
- package/dist/actions/update-workspace-app-metadata.js +30 -0
- package/dist/actions/update-workspace-app-metadata.js.map +1 -0
- package/dist/actions/view-screen.d.ts.map +1 -1
- package/dist/actions/view-screen.js +4 -2
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/app-keys-popover.js +16 -5
- package/dist/components/app-keys-popover.js.map +1 -1
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +38 -14
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/dispatch-shell.d.ts +4 -4
- package/dist/components/dispatch-shell.d.ts.map +1 -1
- package/dist/components/dispatch-shell.js +6 -6
- package/dist/components/dispatch-shell.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +10 -3
- 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 +2 -2
- package/dist/components/messaging-setup-panel.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +41 -2
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/hooks/use-navigation-state.js +12 -5
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/catch-all-target.d.ts +2 -0
- package/dist/lib/catch-all-target.d.ts.map +1 -0
- package/dist/lib/catch-all-target.js +95 -0
- package/dist/lib/catch-all-target.js.map +1 -0
- package/dist/lib/workspace-apps.d.ts +9 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/pages/$appId.d.ts +2 -2
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +17 -8
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +20 -15
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/new-app.js +1 -1
- package/dist/routes/pages/new-app.js.map +1 -1
- package/dist/routes/pages/overview.d.ts.map +1 -1
- package/dist/routes/pages/overview.js +5 -1
- package/dist/routes/pages/overview.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +23 -5
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts +13 -0
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +295 -9
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/env-config.d.ts.map +1 -1
- package/dist/server/lib/env-config.js +5 -0
- package/dist/server/lib/env-config.js.map +1 -1
- package/dist/server/lib/onboarding-steps.d.ts +12 -0
- package/dist/server/lib/onboarding-steps.d.ts.map +1 -0
- package/dist/server/lib/onboarding-steps.js +47 -0
- package/dist/server/lib/onboarding-steps.js.map +1 -0
- package/dist/server/lib/vault-store.d.ts +55 -0
- package/dist/server/lib/vault-store.d.ts.map +1 -1
- package/dist/server/lib/vault-store.js +210 -41
- package/dist/server/lib/vault-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.d.ts.map +1 -1
- package/dist/server/plugins/agent-chat.js +2 -1
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/server/plugins/core-routes.d.ts.map +1 -1
- package/dist/server/plugins/core-routes.js +4 -0
- package/dist/server/plugins/core-routes.js.map +1 -1
- package/dist/server/plugins/integrations.js +2 -2
- package/dist/server/plugins/integrations.js.map +1 -1
- package/package.json +13 -11
- package/src/actions/create-pylon-ticket.ts +109 -0
- package/src/actions/create-vault-grant.ts +1 -1
- package/src/actions/create-vault-secret.ts +4 -3
- package/src/actions/get-vault-access-settings.ts +11 -0
- package/src/actions/grant-vault-secrets-to-app.ts +1 -1
- package/src/actions/index.ts +8 -0
- package/src/actions/list-integrations-catalog.ts +1 -1
- package/src/actions/list-vault-grants.ts +1 -1
- package/src/actions/list-workspace-apps.ts +5 -1
- package/src/actions/set-vault-access-settings.ts +16 -0
- package/src/actions/start-workspace-app-creation.ts +8 -0
- package/src/actions/sync-vault-to-app.ts +1 -1
- package/src/actions/update-workspace-app-metadata.ts +32 -0
- package/src/actions/view-screen.ts +4 -1
- package/src/components/app-keys-popover.tsx +23 -7
- package/src/components/create-app-popover.tsx +47 -14
- package/src/components/dispatch-shell.tsx +16 -15
- package/src/components/layout/Layout.tsx +11 -5
- package/src/components/messaging-setup-panel.tsx +54 -39
- package/src/components/workspace-app-card.tsx +102 -0
- package/src/hooks/use-navigation-state.ts +10 -4
- package/src/lib/catch-all-target.spec.ts +218 -0
- package/src/lib/catch-all-target.ts +99 -0
- package/src/lib/workspace-apps.ts +9 -0
- package/src/routes/pages/$appId.tsx +21 -8
- package/src/routes/pages/integrations.tsx +57 -18
- package/src/routes/pages/new-app.tsx +1 -1
- package/src/routes/pages/overview.tsx +11 -3
- package/src/routes/pages/vault.tsx +76 -9
- package/src/server/lib/app-creation-store.spec.ts +61 -2
- package/src/server/lib/app-creation-store.ts +386 -11
- package/src/server/lib/env-config.ts +5 -0
- package/src/server/lib/onboarding-steps.ts +49 -0
- package/src/server/lib/vault-store.spec.ts +69 -0
- package/src/server/lib/vault-store.ts +266 -49
- package/src/server/plugins/agent-chat.ts +2 -1
- package/src/server/plugins/core-routes.ts +5 -0
- package/src/server/plugins/integrations.ts +2 -2
package/src/actions/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import approveDispatchChange from "./approve-dispatch-change.js";
|
|
|
3
3
|
import approveVaultRequest from "./approve-vault-request.js";
|
|
4
4
|
import archiveWorkspaceApp from "./archive-workspace-app.js";
|
|
5
5
|
import createLinkToken from "./create-link-token.js";
|
|
6
|
+
import createPylonTicket from "./create-pylon-ticket.js";
|
|
6
7
|
import createVaultGrant from "./create-vault-grant.js";
|
|
7
8
|
import createVaultSecret from "./create-vault-secret.js";
|
|
8
9
|
import createWorkspaceResourceGrant from "./create-workspace-resource-grant.js";
|
|
@@ -14,6 +15,7 @@ import denyVaultRequest from "./deny-vault-request.js";
|
|
|
14
15
|
import getAppCreationSettings from "./get-app-creation-settings.js";
|
|
15
16
|
import getAgentThreadDebug from "./get-agent-thread-debug.js";
|
|
16
17
|
import getDispatchSettings from "./get-dispatch-settings.js";
|
|
18
|
+
import getVaultAccessSettings from "./get-vault-access-settings.js";
|
|
17
19
|
import getWorkspaceInfo from "./get-workspace-info.js";
|
|
18
20
|
import grantWorkspaceResourcesToApp from "./grant-workspace-resources-to-app.js";
|
|
19
21
|
import grantVaultSecretsToApp from "./grant-vault-secrets-to-app.js";
|
|
@@ -47,11 +49,13 @@ import searchAgentThreads from "./search-agent-threads.js";
|
|
|
47
49
|
import sendPlatformMessage from "./send-platform-message.js";
|
|
48
50
|
import setAppCreationSettings from "./set-app-creation-settings.js";
|
|
49
51
|
import setDispatchApprovalPolicy from "./set-dispatch-approval-policy.js";
|
|
52
|
+
import setVaultAccessSettings from "./set-vault-access-settings.js";
|
|
50
53
|
import startWorkspaceAppCreation from "./start-workspace-app-creation.js";
|
|
51
54
|
import syncVaultToApp from "./sync-vault-to-app.js";
|
|
52
55
|
import syncWorkspaceResourcesToAll from "./sync-workspace-resources-to-all.js";
|
|
53
56
|
import syncWorkspaceResourcesToApp from "./sync-workspace-resources-to-app.js";
|
|
54
57
|
import unarchiveWorkspaceApp from "./unarchive-workspace-app.js";
|
|
58
|
+
import updateWorkspaceAppMetadata from "./update-workspace-app-metadata.js";
|
|
55
59
|
import updateVaultSecret from "./update-vault-secret.js";
|
|
56
60
|
import updateWorkspaceResource from "./update-workspace-resource.js";
|
|
57
61
|
import upsertDestination from "./upsert-destination.js";
|
|
@@ -68,6 +72,7 @@ export const dispatchActions: Record<string, ActionEntry> = {
|
|
|
68
72
|
"approve-vault-request": approveVaultRequest,
|
|
69
73
|
"archive-workspace-app": archiveWorkspaceApp,
|
|
70
74
|
"create-link-token": createLinkToken,
|
|
75
|
+
"create-pylon-ticket": createPylonTicket,
|
|
71
76
|
"create-vault-grant": createVaultGrant,
|
|
72
77
|
"create-vault-secret": createVaultSecret,
|
|
73
78
|
"create-workspace-resource-grant": createWorkspaceResourceGrant,
|
|
@@ -79,6 +84,7 @@ export const dispatchActions: Record<string, ActionEntry> = {
|
|
|
79
84
|
"get-app-creation-settings": getAppCreationSettings,
|
|
80
85
|
"get-agent-thread-debug": getAgentThreadDebug,
|
|
81
86
|
"get-dispatch-settings": getDispatchSettings,
|
|
87
|
+
"get-vault-access-settings": getVaultAccessSettings,
|
|
82
88
|
"get-workspace-info": getWorkspaceInfo,
|
|
83
89
|
"grant-workspace-resources-to-app": grantWorkspaceResourcesToApp,
|
|
84
90
|
"grant-vault-secrets-to-app": grantVaultSecretsToApp,
|
|
@@ -112,11 +118,13 @@ export const dispatchActions: Record<string, ActionEntry> = {
|
|
|
112
118
|
"send-platform-message": sendPlatformMessage,
|
|
113
119
|
"set-app-creation-settings": setAppCreationSettings,
|
|
114
120
|
"set-dispatch-approval-policy": setDispatchApprovalPolicy,
|
|
121
|
+
"set-vault-access-settings": setVaultAccessSettings,
|
|
115
122
|
"start-workspace-app-creation": startWorkspaceAppCreation,
|
|
116
123
|
"sync-vault-to-app": syncVaultToApp,
|
|
117
124
|
"sync-workspace-resources-to-all": syncWorkspaceResourcesToAll,
|
|
118
125
|
"sync-workspace-resources-to-app": syncWorkspaceResourcesToApp,
|
|
119
126
|
"unarchive-workspace-app": unarchiveWorkspaceApp,
|
|
127
|
+
"update-workspace-app-metadata": updateWorkspaceAppMetadata,
|
|
120
128
|
"update-vault-secret": updateVaultSecret,
|
|
121
129
|
"update-workspace-resource": updateWorkspaceResource,
|
|
122
130
|
"upsert-destination": upsertDestination,
|
|
@@ -4,7 +4,7 @@ import { listIntegrationsCatalog } from "../server/lib/vault-store.js";
|
|
|
4
4
|
|
|
5
5
|
export default defineAction({
|
|
6
6
|
description:
|
|
7
|
-
"List all workspace apps and their credential/integration requirements. Shows
|
|
7
|
+
"List all workspace apps and their credential/integration requirements. Shows configured credentials and effective Dispatch vault access.",
|
|
8
8
|
schema: z.object({}),
|
|
9
9
|
http: { method: "GET" },
|
|
10
10
|
run: async () => listIntegrationsCatalog(),
|
|
@@ -4,7 +4,7 @@ import { listGrants } from "../server/lib/vault-store.js";
|
|
|
4
4
|
|
|
5
5
|
export default defineAction({
|
|
6
6
|
description:
|
|
7
|
-
"List vault grants
|
|
7
|
+
"List explicit vault grants used by manual vault access mode. Optionally filter by app or secret.",
|
|
8
8
|
schema: z.object({
|
|
9
9
|
appId: z.string().optional().describe("Filter by app ID"),
|
|
10
10
|
secretId: z.string().optional().describe("Filter by secret ID"),
|
|
@@ -12,13 +12,17 @@ const httpBoolean = z.preprocess((value) => {
|
|
|
12
12
|
|
|
13
13
|
export default defineAction({
|
|
14
14
|
description:
|
|
15
|
-
"List apps installed in this workspace, including mounted paths, absolute URLs, and agent-card/A2A metadata for ready apps by default. UI polling callers can pass includeAgentCards=false to skip network probes.",
|
|
15
|
+
"List apps installed in this workspace, including mounted paths, absolute URLs, audience (internal/public), page route access overrides, and agent-card/A2A metadata for ready apps by default. UI polling callers can pass includeAgentCards=false to skip network probes.",
|
|
16
16
|
schema: z.object({
|
|
17
17
|
includeAgentCards: httpBoolean
|
|
18
18
|
.default(true)
|
|
19
19
|
.describe(
|
|
20
20
|
"Fetch each ready app's /.well-known/agent-card.json with a short non-throwing timeout and include agentCardUrl, agentCardReachable, a2aEndpointUrl, agentName, and agentSkillsCount. Defaults to true for agent calls; UI polling should pass false. Pending Builder apps are not probed.",
|
|
21
21
|
),
|
|
22
|
+
audience: z
|
|
23
|
+
.enum(["all", "internal", "public"])
|
|
24
|
+
.default("all")
|
|
25
|
+
.describe("Filter by workspace app audience."),
|
|
22
26
|
}),
|
|
23
27
|
http: { method: "GET" },
|
|
24
28
|
run: async (input) => listWorkspaceApps(input),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { setVaultAccessSettings } from "../server/lib/vault-store.js";
|
|
4
|
+
|
|
5
|
+
export default defineAction({
|
|
6
|
+
description:
|
|
7
|
+
"Set the Dispatch vault access mode. Use all-apps for the default workspace-wide mode or manual to require explicit per-app grants.",
|
|
8
|
+
schema: z.object({
|
|
9
|
+
mode: z
|
|
10
|
+
.enum(["all-apps", "manual"])
|
|
11
|
+
.describe(
|
|
12
|
+
"all-apps shares every vault key with every app; manual requires grants",
|
|
13
|
+
),
|
|
14
|
+
}),
|
|
15
|
+
run: async (args) => setVaultAccessSettings(args),
|
|
16
|
+
});
|
|
@@ -23,6 +23,14 @@ export default defineAction({
|
|
|
23
23
|
.optional()
|
|
24
24
|
.nullable()
|
|
25
25
|
.describe("Template to start from"),
|
|
26
|
+
description: z
|
|
27
|
+
.string()
|
|
28
|
+
.max(500)
|
|
29
|
+
.optional()
|
|
30
|
+
.nullable()
|
|
31
|
+
.describe(
|
|
32
|
+
"Concise AI-generated description of the app based on the user's prompt. Dispatch saves this while the app is being created.",
|
|
33
|
+
),
|
|
26
34
|
secretIds: z
|
|
27
35
|
.array(z.string())
|
|
28
36
|
.max(100)
|
|
@@ -4,7 +4,7 @@ import { syncGrantsToApp } from "../server/lib/vault-store.js";
|
|
|
4
4
|
|
|
5
5
|
export default defineAction({
|
|
6
6
|
description:
|
|
7
|
-
"Push
|
|
7
|
+
"Push vault secrets to an app by calling its env-vars endpoint. In all-apps mode this syncs every vault key; in manual mode it syncs active grants.",
|
|
8
8
|
schema: z.object({
|
|
9
9
|
appId: z
|
|
10
10
|
.string()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { getWorkspaceAppIdValidationError } from "@agent-native/core/shared";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { updateWorkspaceAppMetadata } from "../server/lib/app-creation-store.js";
|
|
5
|
+
|
|
6
|
+
export default defineAction({
|
|
7
|
+
description:
|
|
8
|
+
"Update the human-editable display name and description Dispatch uses for a workspace app. These details are also used as connected-agent/A2A context.",
|
|
9
|
+
schema: z.object({
|
|
10
|
+
appId: z
|
|
11
|
+
.string()
|
|
12
|
+
.max(64)
|
|
13
|
+
.refine((appId) => !getWorkspaceAppIdValidationError(appId), {
|
|
14
|
+
message:
|
|
15
|
+
"Use a non-reserved app id with lowercase letters, numbers, and hyphens.",
|
|
16
|
+
})
|
|
17
|
+
.describe("Workspace app id, matching the apps/<id> folder"),
|
|
18
|
+
name: z
|
|
19
|
+
.string()
|
|
20
|
+
.max(120)
|
|
21
|
+
.optional()
|
|
22
|
+
.nullable()
|
|
23
|
+
.describe("Human-readable app name"),
|
|
24
|
+
description: z
|
|
25
|
+
.string()
|
|
26
|
+
.max(500)
|
|
27
|
+
.optional()
|
|
28
|
+
.nullable()
|
|
29
|
+
.describe("Human-readable app description"),
|
|
30
|
+
}),
|
|
31
|
+
run: async (args) => updateWorkspaceAppMetadata(args),
|
|
32
|
+
});
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
listSecrets,
|
|
17
17
|
listGrants,
|
|
18
18
|
listRequests,
|
|
19
|
+
getVaultAccessSettings,
|
|
19
20
|
} from "../server/lib/vault-store.js";
|
|
20
21
|
import { listWorkspaceApps } from "../server/lib/app-creation-store.js";
|
|
21
22
|
import { listDispatchUsageMetrics } from "../server/lib/usage-metrics-store.js";
|
|
@@ -78,11 +79,13 @@ export default defineAction({
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
if (navigation?.view === "vault" || navigation?.view === "new-app") {
|
|
81
|
-
const [secrets, grants, requests] = await Promise.all([
|
|
82
|
+
const [secrets, grants, requests, access] = await Promise.all([
|
|
82
83
|
listSecrets(),
|
|
83
84
|
listGrants(),
|
|
84
85
|
listRequests({ status: "pending" }),
|
|
86
|
+
getVaultAccessSettings(),
|
|
85
87
|
]);
|
|
88
|
+
screen.vaultAccessMode = access.mode;
|
|
86
89
|
screen.vaultSecrets = secrets.map((s) => ({
|
|
87
90
|
id: s.id,
|
|
88
91
|
name: s.name,
|
|
@@ -89,6 +89,12 @@ function AppKeysPanel({ appId, appName }: { appId: string; appName: string }) {
|
|
|
89
89
|
isLoading: grantsLoading,
|
|
90
90
|
refetch: refetchGrants,
|
|
91
91
|
} = useActionQuery("list-vault-grants", { appId });
|
|
92
|
+
const { data: accessSettings, isLoading: accessLoading } = useActionQuery(
|
|
93
|
+
"get-vault-access-settings",
|
|
94
|
+
{},
|
|
95
|
+
);
|
|
96
|
+
const accessMode =
|
|
97
|
+
(accessSettings as any)?.mode === "manual" ? "manual" : "all-apps";
|
|
92
98
|
|
|
93
99
|
const grantBySecretId = useMemo(() => {
|
|
94
100
|
const map = new Map<string, VaultGrant>();
|
|
@@ -135,11 +141,13 @@ function AppKeysPanel({ appId, appName }: { appId: string; appName: string }) {
|
|
|
135
141
|
onError: (err) => toast.error(`Sync failed: ${String(err)}`),
|
|
136
142
|
});
|
|
137
143
|
|
|
138
|
-
const isLoading = secretsLoading || grantsLoading;
|
|
144
|
+
const isLoading = secretsLoading || grantsLoading || accessLoading;
|
|
139
145
|
const grantedCount = grantBySecretId.size;
|
|
140
146
|
const typedSecrets = secrets as VaultSecret[];
|
|
147
|
+
const allApps = accessMode !== "manual";
|
|
141
148
|
|
|
142
149
|
const toggleSecret = (secret: VaultSecret) => {
|
|
150
|
+
if (allApps) return;
|
|
143
151
|
if (pendingSecretIds.has(secret.id)) return;
|
|
144
152
|
const existing = grantBySecretId.get(secret.id);
|
|
145
153
|
markPending(secret.id, true);
|
|
@@ -159,14 +167,20 @@ function AppKeysPanel({ appId, appName }: { appId: string; appName: string }) {
|
|
|
159
167
|
Keys for {appName}
|
|
160
168
|
</p>
|
|
161
169
|
<p className="text-[11px] text-muted-foreground">
|
|
162
|
-
{
|
|
170
|
+
{allApps
|
|
171
|
+
? `${typedSecrets.length} available`
|
|
172
|
+
: `${grantedCount} of ${typedSecrets.length} granted`}
|
|
163
173
|
</p>
|
|
164
174
|
</div>
|
|
165
175
|
<Button
|
|
166
176
|
type="button"
|
|
167
177
|
variant="outline"
|
|
168
178
|
size="sm"
|
|
169
|
-
disabled={
|
|
179
|
+
disabled={
|
|
180
|
+
syncMutation.isPending ||
|
|
181
|
+
typedSecrets.length === 0 ||
|
|
182
|
+
(!allApps && grantedCount === 0)
|
|
183
|
+
}
|
|
170
184
|
onClick={() => syncMutation.mutate({ appId })}
|
|
171
185
|
className="h-7 px-2"
|
|
172
186
|
>
|
|
@@ -201,17 +215,17 @@ function AppKeysPanel({ appId, appName }: { appId: string; appName: string }) {
|
|
|
201
215
|
</p>
|
|
202
216
|
) : (
|
|
203
217
|
typedSecrets.map((secret) => {
|
|
204
|
-
const granted = grantBySecretId.has(secret.id);
|
|
218
|
+
const granted = allApps || grantBySecretId.has(secret.id);
|
|
205
219
|
const pending = pendingSecretIds.has(secret.id);
|
|
206
220
|
return (
|
|
207
221
|
<button
|
|
208
222
|
key={secret.id}
|
|
209
223
|
type="button"
|
|
210
224
|
aria-pressed={granted}
|
|
211
|
-
disabled={pending}
|
|
225
|
+
disabled={pending || allApps}
|
|
212
226
|
onClick={() => toggleSecret(secret)}
|
|
213
227
|
className={`flex w-full items-start gap-3 rounded-md px-2.5 py-2 text-left text-sm disabled:cursor-not-allowed disabled:opacity-60 ${
|
|
214
|
-
pending ? "" : "cursor-pointer"
|
|
228
|
+
pending || allApps ? "" : "cursor-pointer"
|
|
215
229
|
} ${
|
|
216
230
|
granted
|
|
217
231
|
? "border border-primary/45 bg-primary/5"
|
|
@@ -232,7 +246,9 @@ function AppKeysPanel({ appId, appName }: { appId: string; appName: string }) {
|
|
|
232
246
|
{secret.credentialKey}
|
|
233
247
|
</span>
|
|
234
248
|
<span className="block truncate text-xs text-muted-foreground/70">
|
|
235
|
-
{
|
|
249
|
+
{allApps
|
|
250
|
+
? "Available to this app"
|
|
251
|
+
: secret.provider || secret.name || "Vault secret"}
|
|
236
252
|
</span>
|
|
237
253
|
</span>
|
|
238
254
|
</button>
|
|
@@ -44,6 +44,8 @@ interface WorkspaceResourceOption {
|
|
|
44
44
|
updatedAt?: number;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
type VaultAccessMode = "all-apps" | "manual";
|
|
48
|
+
|
|
47
49
|
interface CreateAppPopoverProps {
|
|
48
50
|
/**
|
|
49
51
|
* Custom trigger element. Defaults to a dashed-border tile that matches the
|
|
@@ -78,11 +80,15 @@ function buildAppCreationPrompt(input: {
|
|
|
78
80
|
prompt: string;
|
|
79
81
|
selectedKeys: string[];
|
|
80
82
|
selectedResources: WorkspaceResourceOption[];
|
|
83
|
+
vaultAccessMode: VaultAccessMode;
|
|
81
84
|
}): string {
|
|
82
85
|
const keyList = input.selectedKeys.join(", ");
|
|
83
|
-
const grantRequest =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
const grantRequest =
|
|
87
|
+
input.vaultAccessMode === "all-apps"
|
|
88
|
+
? `Dispatch vault access: all saved vault keys are available to every workspace app by default. No per-app vault grants are needed.`
|
|
89
|
+
: keyList
|
|
90
|
+
? `Requested Dispatch vault key grants for this app: ${keyList}`
|
|
91
|
+
: `Requested Dispatch vault key grants for this app: none`;
|
|
86
92
|
const resourceList = input.selectedResources.length
|
|
87
93
|
? input.selectedResources
|
|
88
94
|
.map(
|
|
@@ -98,6 +104,7 @@ function buildAppCreationPrompt(input: {
|
|
|
98
104
|
``,
|
|
99
105
|
`Suggested app name: ${input.appId} (you may adjust the slug if it conflicts)`,
|
|
100
106
|
`User prompt: ${input.prompt.trim()}`,
|
|
107
|
+
`Generate a concise one-sentence app description from the user prompt before coding; save it in apps/${input.appId}/package.json "description" so Dispatch and A2A can describe the app.`,
|
|
101
108
|
`If the user mentions a product or company such as Granola, Loom, Superhuman, Linear, or Notion, treat it as product inspiration unless they explicitly ask to connect to that service. Do not invent or require third-party API keys like GRANOLA_API_KEY just because a product is named.`,
|
|
102
109
|
grantRequest,
|
|
103
110
|
`Requested Dispatch workspace resources for this app:\n${resourceList}`,
|
|
@@ -112,18 +119,20 @@ function buildAppCreationPrompt(input: {
|
|
|
112
119
|
`Do not clone first-party templates, create wrapper apps, or scaffold child apps/routes for Mail, Calendar, Analytics, etc. inside apps/${input.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.`,
|
|
113
120
|
`Only create another first-party app copy when the user explicitly asks for a customized fork/copy of that app; otherwise keep using the hosted/shared app so improvements to the base template keep flowing to users.`,
|
|
114
121
|
`Do not satisfy this by adding a route, page, component, or file inside apps/starter or another existing app unless the user explicitly asks to modify that existing app.`,
|
|
115
|
-
|
|
116
|
-
? `
|
|
117
|
-
:
|
|
122
|
+
input.vaultAccessMode === "all-apps"
|
|
123
|
+
? `Do not create per-app Dispatch vault grants unless the workspace switches vault access to manual or the user explicitly asks for manual grants.`
|
|
124
|
+
: keyList
|
|
125
|
+
? `After the app exists, grant the selected Dispatch vault keys to appId "${input.appId}" and sync them once the app server is available. Treat these as requested grants, not active grants before creation succeeds.`
|
|
126
|
+
: `Do not grant any Dispatch vault keys unless the user asks later.`,
|
|
118
127
|
input.selectedResources.length
|
|
119
128
|
? `After the app exists, grant the selected Dispatch workspace resources to appId "${input.appId}" and sync them once the app server is available. Add a short note to apps/${input.appId}/AGENTS.md telling the app agent to read relevant shared resources under context/ or the selected resource paths before doing GTM/domain work.`
|
|
120
129
|
: `Do not grant any Dispatch workspace resources unless the user asks later.`,
|
|
121
130
|
``,
|
|
122
131
|
`App readiness requirements before handing off:`,
|
|
123
|
-
`- Ensure apps/${input.appId}/package.json exists; Dispatch discovers workspace apps from apps/<app-id>/package.json, not a separate app registry.`,
|
|
132
|
+
`- Ensure apps/${input.appId}/package.json exists with displayName/name and a concise description; Dispatch discovers workspace apps from apps/<app-id>/package.json, not a separate app registry.`,
|
|
124
133
|
`- Update the app manifest/package/deploy metadata needed by the existing workspace deployment model.`,
|
|
125
134
|
`- Ensure the React Router client entry preserves APP_BASE_PATH/VITE_APP_BASE_PATH via appBasePath() so /${input.appId} hydrates correctly.`,
|
|
126
|
-
`- Verify the app's agent card/A2A metadata is ready so Dispatch can discover and delegate to the app after deployment.`,
|
|
135
|
+
`- Verify the app's agent card/A2A metadata is ready so Dispatch can discover and delegate to the app after deployment. Every sibling workspace app is available over A2A by default through call-agent, with names and descriptions from the workspace app registry.`,
|
|
127
136
|
`When it is ready, start or update the workspace dev server and navigate the user to the absolute path /${input.appId} on the workspace origin. Do not prefix with /dispatch/, /apps/, /workspace/, or any other Dispatch tab — the new app is mounted at the workspace root, not under Dispatch. If you have a navigate tool available, pass /${input.appId} verbatim; if you only have a window.location-style escape hatch, set it to /${input.appId}.`,
|
|
128
137
|
].join("\n");
|
|
129
138
|
}
|
|
@@ -170,6 +179,8 @@ export function CreateAppFlow({
|
|
|
170
179
|
const [selectedResourceIds, setSelectedResourceIds] = useState<string[]>([]);
|
|
171
180
|
const [secrets, setSecrets] = useState<VaultSecretOption[]>([]);
|
|
172
181
|
const [resources, setResources] = useState<WorkspaceResourceOption[]>([]);
|
|
182
|
+
const [vaultAccessMode, setVaultAccessMode] =
|
|
183
|
+
useState<VaultAccessMode>("all-apps");
|
|
173
184
|
const [secretsError, setSecretsError] = useState<string | null>(null);
|
|
174
185
|
const [resourcesError, setResourcesError] = useState<string | null>(null);
|
|
175
186
|
const [statusMessage, setStatusMessage] = useState<string | null>(null);
|
|
@@ -193,6 +204,15 @@ export function CreateAppFlow({
|
|
|
193
204
|
setSecrets([]);
|
|
194
205
|
setSecretsError(err?.message || "Could not load Dispatch keys");
|
|
195
206
|
});
|
|
207
|
+
fetchJson(actionUrl(basePath, "get-vault-access-settings"))
|
|
208
|
+
.then((data) => {
|
|
209
|
+
if (cancelled) return;
|
|
210
|
+
setVaultAccessMode(data?.mode === "manual" ? "manual" : "all-apps");
|
|
211
|
+
})
|
|
212
|
+
.catch(() => {
|
|
213
|
+
if (cancelled) return;
|
|
214
|
+
setVaultAccessMode("manual");
|
|
215
|
+
});
|
|
196
216
|
fetchJson(actionUrl(basePath, "list-workspace-resource-options"))
|
|
197
217
|
.then((data) => {
|
|
198
218
|
if (cancelled) return;
|
|
@@ -218,9 +238,11 @@ export function CreateAppFlow({
|
|
|
218
238
|
[resources, selectedResourceIds],
|
|
219
239
|
);
|
|
220
240
|
const selectedSecretLabel =
|
|
221
|
-
|
|
222
|
-
? "
|
|
223
|
-
:
|
|
241
|
+
vaultAccessMode === "all-apps"
|
|
242
|
+
? "all keys"
|
|
243
|
+
: selectedSecretIds.length === 0
|
|
244
|
+
? "no keys"
|
|
245
|
+
: `${selectedSecretIds.length} key${selectedSecretIds.length === 1 ? "" : "s"}`;
|
|
224
246
|
const selectedResourceLabel =
|
|
225
247
|
selectedResourceIds.length === 0
|
|
226
248
|
? "no resources"
|
|
@@ -254,8 +276,12 @@ export function CreateAppFlow({
|
|
|
254
276
|
const message = buildAppCreationPrompt({
|
|
255
277
|
appId,
|
|
256
278
|
prompt: trimmed,
|
|
257
|
-
selectedKeys:
|
|
279
|
+
selectedKeys:
|
|
280
|
+
vaultAccessMode === "manual"
|
|
281
|
+
? selectedSecrets.map((s) => s.credentialKey)
|
|
282
|
+
: [],
|
|
258
283
|
selectedResources,
|
|
284
|
+
vaultAccessMode,
|
|
259
285
|
});
|
|
260
286
|
setIsSubmitting(true);
|
|
261
287
|
setStatusMessage(null);
|
|
@@ -279,7 +305,10 @@ export function CreateAppFlow({
|
|
|
279
305
|
body: JSON.stringify({
|
|
280
306
|
prompt: trimmed,
|
|
281
307
|
appId,
|
|
282
|
-
secretIds:
|
|
308
|
+
secretIds:
|
|
309
|
+
vaultAccessMode === "manual" && selectedSecretIds.length > 0
|
|
310
|
+
? selectedSecretIds
|
|
311
|
+
: [],
|
|
283
312
|
resourceIds:
|
|
284
313
|
selectedResourceIds.length > 0 ? selectedResourceIds : [],
|
|
285
314
|
}),
|
|
@@ -351,7 +380,11 @@ export function CreateAppFlow({
|
|
|
351
380
|
<IconKey size={12} />
|
|
352
381
|
Dispatch keys
|
|
353
382
|
</div>
|
|
354
|
-
{
|
|
383
|
+
{vaultAccessMode === "all-apps" ? (
|
|
384
|
+
<p className="rounded-md border border-dashed border-border px-3 py-3 text-xs text-muted-foreground">
|
|
385
|
+
Every saved Dispatch vault key is available to new apps.
|
|
386
|
+
</p>
|
|
387
|
+
) : secretsError ? (
|
|
355
388
|
<p className="rounded-md border border-dashed border-border px-3 py-3 text-xs text-muted-foreground">
|
|
356
389
|
{secretsError}
|
|
357
390
|
</p>
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "@/components/ui/
|
|
3
|
+
Popover,
|
|
4
|
+
PopoverContent,
|
|
5
|
+
PopoverTrigger,
|
|
6
|
+
} from "@/components/ui/popover";
|
|
7
7
|
import { IconInfoCircle } from "@tabler/icons-react";
|
|
8
8
|
import { useSetPageTitle } from "@/components/layout/HeaderActions";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* DispatchShell renders the per-page title (with optional
|
|
12
|
-
* into the global header via the HeaderActions store.
|
|
13
|
-
* (sidebar, AgentSidebar, header bar with AgentToggleButton)
|
|
14
|
-
* `Layout` mounted in `root.tsx`.
|
|
11
|
+
* DispatchShell renders the per-page title (with an optional click-to-open
|
|
12
|
+
* description popover) into the global header via the HeaderActions store.
|
|
13
|
+
* The actual chrome (sidebar, AgentSidebar, header bar with AgentToggleButton)
|
|
14
|
+
* is provided by `Layout` mounted in `root.tsx`.
|
|
15
15
|
*/
|
|
16
16
|
export function DispatchShell({
|
|
17
17
|
title,
|
|
@@ -28,23 +28,24 @@ export function DispatchShell({
|
|
|
28
28
|
{title}
|
|
29
29
|
</h1>
|
|
30
30
|
{description ? (
|
|
31
|
-
<
|
|
32
|
-
<
|
|
31
|
+
<Popover>
|
|
32
|
+
<PopoverTrigger asChild>
|
|
33
33
|
<button
|
|
34
34
|
type="button"
|
|
35
|
-
className="text-muted-foreground/
|
|
35
|
+
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground/70 hover:bg-accent hover:text-foreground cursor-pointer"
|
|
36
36
|
aria-label={`About ${title}`}
|
|
37
37
|
>
|
|
38
38
|
<IconInfoCircle className="h-3.5 w-3.5" />
|
|
39
39
|
</button>
|
|
40
|
-
</
|
|
41
|
-
<
|
|
40
|
+
</PopoverTrigger>
|
|
41
|
+
<PopoverContent
|
|
42
42
|
side="bottom"
|
|
43
|
+
align="start"
|
|
43
44
|
className="max-w-72 text-xs leading-relaxed"
|
|
44
45
|
>
|
|
45
46
|
{description}
|
|
46
|
-
</
|
|
47
|
-
</
|
|
47
|
+
</PopoverContent>
|
|
48
|
+
</Popover>
|
|
48
49
|
) : null}
|
|
49
50
|
</div>,
|
|
50
51
|
);
|
|
@@ -229,10 +229,16 @@ function dispatchNavLinkTarget(path: string): string {
|
|
|
229
229
|
if (typeof window === "undefined") return path;
|
|
230
230
|
const basePath = appBasePath();
|
|
231
231
|
if (!basePath) return path;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
232
|
+
// Mirror the basename calculation entry.client.tsx uses to configure the
|
|
233
|
+
// router (basePath iff the current URL is under that mount, "" otherwise).
|
|
234
|
+
// Reading the live URL directly avoids races with the previous check on
|
|
235
|
+
// `__reactRouterContext.basename`, which could read undefined before the
|
|
236
|
+
// entry script set it — that race produced /dispatch/dispatch/<route>
|
|
237
|
+
// history entries that 404'd on back-button navigation.
|
|
238
|
+
const pathname = window.location.pathname;
|
|
239
|
+
const routerHasBasename =
|
|
240
|
+
pathname === basePath || pathname.startsWith(`${basePath}/`);
|
|
241
|
+
return routerHasBasename ? path : appPath(path);
|
|
236
242
|
}
|
|
237
243
|
|
|
238
244
|
export function NavContent({
|
|
@@ -424,7 +430,7 @@ export function Layout({
|
|
|
424
430
|
<AgentSidebar
|
|
425
431
|
position="right"
|
|
426
432
|
defaultOpen={false}
|
|
427
|
-
emptyStateText="Create apps,
|
|
433
|
+
emptyStateText="Create apps, manage vault keys, and route work across the workspace."
|
|
428
434
|
suggestions={SIDEBAR_SUGGESTIONS}
|
|
429
435
|
>
|
|
430
436
|
{appContent}
|
|
@@ -449,23 +449,34 @@ export function MessagingSetupPanel() {
|
|
|
449
449
|
</p>
|
|
450
450
|
</div>
|
|
451
451
|
</div>
|
|
452
|
-
<div className="flex items-center gap-
|
|
453
|
-
<
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
<div className="flex shrink-0 items-center gap-1">
|
|
453
|
+
<Button
|
|
454
|
+
asChild
|
|
455
|
+
variant="ghost"
|
|
456
|
+
size="sm"
|
|
457
|
+
className="h-7 px-2 text-xs text-muted-foreground"
|
|
456
458
|
>
|
|
457
|
-
|
|
458
|
-
|
|
459
|
+
<a href={platform.docsUrl} target="_blank" rel="noreferrer">
|
|
460
|
+
Docs
|
|
461
|
+
<IconExternalLink className="ml-1 h-3 w-3" />
|
|
462
|
+
</a>
|
|
463
|
+
</Button>
|
|
459
464
|
{platform.externalUrl ? (
|
|
460
|
-
<
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
className="
|
|
465
|
+
<Button
|
|
466
|
+
asChild
|
|
467
|
+
variant="ghost"
|
|
468
|
+
size="sm"
|
|
469
|
+
className="h-7 px-2 text-xs text-muted-foreground"
|
|
465
470
|
>
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
471
|
+
<a
|
|
472
|
+
href={platform.externalUrl}
|
|
473
|
+
target="_blank"
|
|
474
|
+
rel="noreferrer"
|
|
475
|
+
>
|
|
476
|
+
{platform.externalLabel ?? "Open"}
|
|
477
|
+
<IconExternalLink className="ml-1 h-3 w-3" />
|
|
478
|
+
</a>
|
|
479
|
+
</Button>
|
|
469
480
|
) : null}
|
|
470
481
|
</div>
|
|
471
482
|
</div>
|
|
@@ -609,7 +620,7 @@ export function MessagingSetupPanel() {
|
|
|
609
620
|
</div>
|
|
610
621
|
) : null}
|
|
611
622
|
|
|
612
|
-
<div className="mt-5 flex flex-wrap gap-2">
|
|
623
|
+
<div className="mt-5 flex flex-wrap items-center justify-end gap-2 border-t border-border pt-4">
|
|
613
624
|
{platform.id === "telegram" && configured ? (
|
|
614
625
|
<Button
|
|
615
626
|
variant="outline"
|
|
@@ -626,31 +637,35 @@ export function MessagingSetupPanel() {
|
|
|
626
637
|
)}
|
|
627
638
|
</Button>
|
|
628
639
|
) : null}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
640
|
+
{!configured && !enabled ? (
|
|
641
|
+
<Tooltip>
|
|
642
|
+
<TooltipTrigger asChild>
|
|
643
|
+
<span tabIndex={0}>
|
|
644
|
+
<Button disabled>Enable</Button>
|
|
645
|
+
</span>
|
|
646
|
+
</TooltipTrigger>
|
|
647
|
+
<TooltipContent>
|
|
648
|
+
Save the required credentials first.
|
|
649
|
+
</TooltipContent>
|
|
650
|
+
</Tooltip>
|
|
651
|
+
) : (
|
|
652
|
+
<Button
|
|
653
|
+
onClick={() => togglePlatform(platform, enabled)}
|
|
654
|
+
disabled={togglingPlatform === platform.id}
|
|
655
|
+
>
|
|
656
|
+
{togglingPlatform === platform.id ? (
|
|
657
|
+
<>
|
|
658
|
+
<IconLoader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
659
|
+
Saving...
|
|
660
|
+
</>
|
|
661
|
+
) : enabled ? (
|
|
662
|
+
"Disable"
|
|
663
|
+
) : (
|
|
664
|
+
"Enable"
|
|
665
|
+
)}
|
|
666
|
+
</Button>
|
|
667
|
+
)}
|
|
646
668
|
</div>
|
|
647
|
-
|
|
648
|
-
{!configured ? (
|
|
649
|
-
<p className="mt-3 text-xs text-muted-foreground">
|
|
650
|
-
Save the required credentials before enabling {platform.label}
|
|
651
|
-
.
|
|
652
|
-
</p>
|
|
653
|
-
) : null}
|
|
654
669
|
</section>
|
|
655
670
|
);
|
|
656
671
|
})}
|