@agent-native/dispatch 0.36.0-nightly-20260909183543 → 0.36.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/dist/actions/apply-dream-proposal.js +2 -1
- package/dist/actions/apply-dream-proposal.js.map +1 -1
- package/dist/actions/approve-dispatch-change.js +2 -0
- package/dist/actions/approve-dispatch-change.js.map +1 -1
- package/dist/actions/create-pylon-ticket.js +2 -0
- package/dist/actions/create-pylon-ticket.js.map +1 -1
- package/dist/actions/create-workspace-resource-grant.js +2 -0
- package/dist/actions/create-workspace-resource-grant.js.map +1 -1
- package/dist/actions/create-workspace-resource.js +2 -0
- package/dist/actions/create-workspace-resource.js.map +1 -1
- package/dist/actions/delete-destination.js +2 -0
- package/dist/actions/delete-destination.js.map +1 -1
- package/dist/actions/delete-workspace-resource.js +2 -0
- package/dist/actions/delete-workspace-resource.js.map +1 -1
- package/dist/actions/ensure-dream-job.js +2 -0
- package/dist/actions/ensure-dream-job.js.map +1 -1
- package/dist/actions/grant-workspace-resources-to-app.js +2 -0
- package/dist/actions/grant-workspace-resources-to-app.js.map +1 -1
- package/dist/actions/import-agent-pack.d.ts.map +1 -1
- package/dist/actions/import-agent-pack.js +2 -0
- package/dist/actions/import-agent-pack.js.map +1 -1
- package/dist/actions/import-agent.d.ts.map +1 -1
- package/dist/actions/import-agent.js +2 -0
- package/dist/actions/import-agent.js.map +1 -1
- package/dist/actions/reject-dispatch-change.js +2 -0
- package/dist/actions/reject-dispatch-change.js.map +1 -1
- package/dist/actions/reject-dream-proposal.js +2 -1
- package/dist/actions/reject-dream-proposal.js.map +1 -1
- package/dist/actions/restore-starter-workspace-resources.js +2 -0
- package/dist/actions/restore-starter-workspace-resources.js.map +1 -1
- package/dist/actions/revoke-workspace-resource-grant.js +2 -0
- package/dist/actions/revoke-workspace-resource-grant.js.map +1 -1
- package/dist/actions/send-platform-message.js +2 -0
- package/dist/actions/send-platform-message.js.map +1 -1
- package/dist/actions/set-dispatch-approval-policy.js +2 -0
- package/dist/actions/set-dispatch-approval-policy.js.map +1 -1
- package/dist/actions/set-dream-settings.js +2 -0
- package/dist/actions/set-dream-settings.js.map +1 -1
- package/dist/actions/update-workspace-resource.js +2 -0
- package/dist/actions/update-workspace-resource.js.map +1 -1
- package/dist/actions/upsert-destination.d.ts +12 -12
- package/dist/actions/upsert-destination.js +2 -0
- package/dist/actions/upsert-destination.js.map +1 -1
- package/dist/components/dispatch-access.d.ts +1 -1
- package/dist/components/dispatch-access.d.ts.map +1 -1
- package/dist/components/dispatch-access.js +0 -9
- package/dist/components/dispatch-access.js.map +1 -1
- package/dist/server/lib/app-creation-store.js +1 -1
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/app-roles.d.ts +7 -0
- package/dist/server/lib/app-roles.d.ts.map +1 -0
- package/dist/server/lib/app-roles.js +45 -0
- package/dist/server/lib/app-roles.js.map +1 -0
- package/dist/server/lib/dispatch-store.d.ts.map +1 -1
- package/dist/server/lib/dispatch-store.js +1 -1
- package/dist/server/lib/dispatch-store.js.map +1 -1
- package/dist/server/lib/dreams-store.d.ts +4 -0
- package/dist/server/lib/dreams-store.d.ts.map +1 -1
- package/dist/server/lib/dreams-store.js +22 -1
- package/dist/server/lib/dreams-store.js.map +1 -1
- package/package.json +4 -4
- package/src/actions/apply-dream-proposal.ts +5 -1
- package/src/actions/approve-dispatch-change.ts +2 -0
- package/src/actions/create-pylon-ticket.ts +2 -0
- package/src/actions/create-workspace-resource-grant.ts +2 -0
- package/src/actions/create-workspace-resource.ts +2 -0
- package/src/actions/delete-destination.ts +2 -0
- package/src/actions/delete-workspace-resource.ts +2 -0
- package/src/actions/dispatch-admin-authorization.spec.ts +114 -0
- package/src/actions/ensure-dream-job.ts +2 -0
- package/src/actions/grant-workspace-resources-to-app.ts +2 -0
- package/src/actions/import-agent-pack.ts +2 -0
- package/src/actions/import-agent.ts +2 -0
- package/src/actions/reject-dispatch-change.ts +2 -0
- package/src/actions/reject-dream-proposal.ts +5 -1
- package/src/actions/restore-starter-workspace-resources.ts +2 -0
- package/src/actions/revoke-workspace-resource-grant.ts +2 -0
- package/src/actions/send-platform-message.spec.ts +4 -0
- package/src/actions/send-platform-message.ts +2 -0
- package/src/actions/set-dispatch-approval-policy.ts +2 -0
- package/src/actions/set-dream-settings.spec.ts +5 -0
- package/src/actions/set-dream-settings.ts +2 -0
- package/src/actions/update-workspace-resource.ts +2 -0
- package/src/actions/upsert-destination.ts +2 -0
- package/src/components/dispatch-access.spec.tsx +1 -63
- package/src/components/dispatch-access.tsx +0 -8
- package/src/server/lib/app-creation-store.spec.ts +21 -22
- package/src/server/lib/app-creation-store.ts +1 -1
- package/src/server/lib/app-roles.spec.ts +120 -0
- package/src/server/lib/app-roles.ts +67 -0
- package/src/server/lib/approval-fencing.spec.ts +39 -0
- package/src/server/lib/dispatch-store.ts +4 -1
- package/src/server/lib/dreams-store.spec.ts +78 -0
- package/src/server/lib/dreams-store.ts +37 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.36.0
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"tailwind-merge": "^3.5.0",
|
|
89
89
|
"vaul": "^1.1.2",
|
|
90
90
|
"zod": "^4.3.6",
|
|
91
|
-
"@agent-native/toolkit": "0.19.6
|
|
91
|
+
"@agent-native/toolkit": "0.19.6"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
94
|
"@react-router/dev": "^8.1.0",
|
|
@@ -102,10 +102,10 @@
|
|
|
102
102
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
103
103
|
"vite": "8.1.0",
|
|
104
104
|
"vitest": "^4.1.5",
|
|
105
|
-
"@agent-native/core": "0.178.0
|
|
105
|
+
"@agent-native/core": "0.178.0"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
|
-
"@agent-native/core": "0.
|
|
108
|
+
"@agent-native/core": ">=0.8.0",
|
|
109
109
|
"react": ">=19.2.7",
|
|
110
110
|
"react-dom": ">=19.2.7",
|
|
111
111
|
"react-router": ">=8"
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
applyDreamProposal,
|
|
6
|
+
authorizeDreamProposalMutation,
|
|
7
|
+
} from "../server/lib/dreams-store.js";
|
|
5
8
|
|
|
6
9
|
export default defineAction({
|
|
7
10
|
description:
|
|
8
11
|
"Apply one pending Dispatch dream proposal. Personal memory applies directly; shared LEARNINGS.md and workspace resource proposals queue an approval request when approval policy is enabled.",
|
|
12
|
+
authorize: authorizeDreamProposalMutation,
|
|
9
13
|
schema: z.object({
|
|
10
14
|
id: z.string().min(1).describe("Dream proposal id."),
|
|
11
15
|
}),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { approveRequest } from "../server/lib/dispatch-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description: "Approve a pending dispatch change request and apply it.",
|
|
9
|
+
authorize: authorizeDispatchAdmin,
|
|
8
10
|
schema: z.object({
|
|
9
11
|
id: z.string().describe("Approval request id"),
|
|
10
12
|
}),
|
|
@@ -2,6 +2,7 @@ import { defineAction } from "@agent-native/core/action";
|
|
|
2
2
|
import { resolveSecret } from "@agent-native/core/server";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
5
6
|
import { recordAudit } from "../server/lib/dispatch-store.js";
|
|
6
7
|
|
|
7
8
|
const PYLON_API_BASE =
|
|
@@ -10,6 +11,7 @@ const PYLON_API_BASE =
|
|
|
10
11
|
export default defineAction({
|
|
11
12
|
description:
|
|
12
13
|
"Create a Pylon ticket. Use to escalate blockers from client meetings, route unmatched #customer-* posts that have no Slack channel, or open a follow-up that needs tracking. Requires PYLON_API_KEY in the Vault.",
|
|
14
|
+
authorize: authorizeDispatchAdmin,
|
|
13
15
|
schema: z.object({
|
|
14
16
|
title: z.string().min(1).describe("Short ticket title"),
|
|
15
17
|
bodyHtml: z
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { createResourceGrant } from "../server/lib/workspace-resources-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
"Grant an app access to a workspace resource (skill, instruction, agent, knowledge pack, or MCP server). Admin only.",
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
resourceId: z.string().describe("Workspace resource ID"),
|
|
11
13
|
appId: z
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { createWorkspaceResource } from "../server/lib/workspace-resources-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
'Create a workspace-wide skill, instruction, agent profile, reference resource, or MCP server. Set scope to "all" for runtime inheritance by every app, or "selected" to grant per-app. When Dispatch approval policy is enabled, All-app creates queue an approval request before taking effect.',
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
kind: z
|
|
11
13
|
.enum([
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { deleteDestination } from "../server/lib/dispatch-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description: "Delete a saved dispatch destination.",
|
|
9
|
+
authorize: authorizeDispatchAdmin,
|
|
8
10
|
schema: z.object({
|
|
9
11
|
id: z.string().describe("Destination id"),
|
|
10
12
|
}),
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { deleteWorkspaceResource } from "../server/lib/workspace-resources-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
"Delete a workspace resource and revoke all its grants. When Dispatch approval policy is enabled, deleting an All-app resource queues an approval request before taking effect.",
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
id: z.string().describe("Resource ID to delete"),
|
|
11
13
|
}),
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
assertAny: vi.fn(),
|
|
5
|
+
deleteDestination: vi.fn(),
|
|
6
|
+
getDestinationById: vi.fn(),
|
|
7
|
+
resolveSecret: vi.fn(),
|
|
8
|
+
upsertDestination: vi.fn(),
|
|
9
|
+
validateFederatedOrganizationMembershipForCurrentRequest: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock("@agent-native/core/org", () => ({
|
|
13
|
+
defineAppRoles: () => ({ assertAny: mocks.assertAny }),
|
|
14
|
+
validateFederatedOrganizationMembershipForCurrentRequest:
|
|
15
|
+
mocks.validateFederatedOrganizationMembershipForCurrentRequest,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock("@agent-native/core/server", () => ({
|
|
19
|
+
getRequestOrgId: () => "org-a",
|
|
20
|
+
getRequestUserEmail: () => "member@example.com",
|
|
21
|
+
resolveSecret: mocks.resolveSecret,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock("@agent-native/core/integrations", () => ({
|
|
25
|
+
listIntegrationInstallations: vi.fn(),
|
|
26
|
+
resolveIntegrationTokenBundle: vi.fn(),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock("../server/lib/dispatch-store.js", () => ({
|
|
30
|
+
deleteDestination: mocks.deleteDestination,
|
|
31
|
+
getDestinationById: mocks.getDestinationById,
|
|
32
|
+
recordAudit: vi.fn(),
|
|
33
|
+
upsertDestination: mocks.upsertDestination,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
import { ForbiddenError } from "@agent-native/core/sharing";
|
|
37
|
+
|
|
38
|
+
const sendPlatformMessage = (await import("./send-platform-message.js"))
|
|
39
|
+
.default;
|
|
40
|
+
const createPylonTicket = (await import("./create-pylon-ticket.js")).default;
|
|
41
|
+
const deleteDestination = (await import("./delete-destination.js")).default;
|
|
42
|
+
const upsertDestination = (await import("./upsert-destination.js")).default;
|
|
43
|
+
|
|
44
|
+
const memberContext = {
|
|
45
|
+
caller: "http" as const,
|
|
46
|
+
orgId: "org-a",
|
|
47
|
+
userEmail: "member@example.com",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe("Dispatch admin authorization", () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
vi.clearAllMocks();
|
|
53
|
+
mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
|
|
54
|
+
{ active: true, role: "member" },
|
|
55
|
+
);
|
|
56
|
+
mocks.assertAny.mockRejectedValue(
|
|
57
|
+
new ForbiddenError("Requires dispatch role admin"),
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("denies organization members before sending a platform message", async () => {
|
|
62
|
+
await expect(
|
|
63
|
+
sendPlatformMessage.run(
|
|
64
|
+
{
|
|
65
|
+
platform: "slack",
|
|
66
|
+
destination: "C1",
|
|
67
|
+
text: "hello",
|
|
68
|
+
},
|
|
69
|
+
memberContext,
|
|
70
|
+
),
|
|
71
|
+
).rejects.toThrow("Requires dispatch role admin");
|
|
72
|
+
|
|
73
|
+
expect(mocks.getDestinationById).not.toHaveBeenCalled();
|
|
74
|
+
expect(mocks.resolveSecret).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("denies organization members before creating a Pylon ticket", async () => {
|
|
78
|
+
await expect(
|
|
79
|
+
createPylonTicket.run(
|
|
80
|
+
{
|
|
81
|
+
title: "Follow-up",
|
|
82
|
+
bodyHtml: "<p>hello</p>",
|
|
83
|
+
requesterEmail: "requester@example.com",
|
|
84
|
+
},
|
|
85
|
+
memberContext,
|
|
86
|
+
),
|
|
87
|
+
).rejects.toThrow("Requires dispatch role admin");
|
|
88
|
+
|
|
89
|
+
expect(mocks.resolveSecret).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("denies organization members before changing a destination", async () => {
|
|
93
|
+
await expect(
|
|
94
|
+
upsertDestination.run(
|
|
95
|
+
{
|
|
96
|
+
name: "Support",
|
|
97
|
+
platform: "slack",
|
|
98
|
+
destination: "C1",
|
|
99
|
+
},
|
|
100
|
+
memberContext,
|
|
101
|
+
),
|
|
102
|
+
).rejects.toThrow("Requires dispatch role admin");
|
|
103
|
+
|
|
104
|
+
expect(mocks.upsertDestination).not.toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("denies organization members before deleting a destination", async () => {
|
|
108
|
+
await expect(
|
|
109
|
+
deleteDestination.run({ id: "destination-1" }, memberContext),
|
|
110
|
+
).rejects.toThrow("Requires dispatch role admin");
|
|
111
|
+
|
|
112
|
+
expect(mocks.deleteDestination).not.toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { ensureDreamJob } from "../server/lib/dreams-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
"Create or update the personal recurring Dispatch dream job resource at jobs/dispatch-dream.md.",
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
schedule: z
|
|
11
13
|
.string()
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { grantWorkspaceResourcesToApp } from "../server/lib/workspace-resources-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
"Grant several selected workspace resources, knowledge packs, or MCP servers to an app, skipping existing active grants.",
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
appId: z.string().describe("App ID receiving the resources"),
|
|
11
13
|
resourceIds: z
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../lib/agent-pack.js";
|
|
14
14
|
import { validateImportedAgentTools } from "../lib/simple-agent-profile.js";
|
|
15
15
|
import { applyAgentPackCreate } from "../server/lib/agent-pack-store.js";
|
|
16
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
16
17
|
import {
|
|
17
18
|
createApprovalRequest,
|
|
18
19
|
getApprovalPolicy,
|
|
@@ -60,6 +61,7 @@ function availableToolNames(dispatchToolNames: string[]): Set<string> {
|
|
|
60
61
|
export default defineAction({
|
|
61
62
|
description:
|
|
62
63
|
"Import a folder-backed agent pack from Claude, Cowork, or another agent tool. The pack can include a Markdown/JSON profile, context, references, and skills. Credentials, hooks, shell commands, and local environment settings are never imported.",
|
|
64
|
+
authorize: authorizeDispatchAdmin,
|
|
63
65
|
schema,
|
|
64
66
|
run: async ({ files, scope }) => {
|
|
65
67
|
const normalized = normalizeAgentPack(files as AgentPackFileInput[]);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
normalizeImportedAgent,
|
|
9
9
|
validateImportedAgentTools,
|
|
10
10
|
} from "../lib/simple-agent-profile.js";
|
|
11
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
11
12
|
import {
|
|
12
13
|
createWorkspaceResource,
|
|
13
14
|
listWorkspaceResources,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
export default defineAction({
|
|
17
18
|
description:
|
|
18
19
|
"Import a Claude-style Markdown agent or a generic JSON agent definition into a reusable Dispatch agent profile. Credentials, shell commands, hooks, and local environment settings are never imported. Use connect-external-agent for an HTTP/A2A endpoint.",
|
|
20
|
+
authorize: authorizeDispatchAdmin,
|
|
19
21
|
schema: z.object({
|
|
20
22
|
source: z
|
|
21
23
|
.string()
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { rejectRequest } from "../server/lib/dispatch-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description: "Reject a pending dispatch change request.",
|
|
9
|
+
authorize: authorizeDispatchAdmin,
|
|
8
10
|
schema: z.object({
|
|
9
11
|
id: z.string().describe("Approval request id"),
|
|
10
12
|
reason: z.string().optional().describe("Optional rejection reason"),
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
authorizeDreamProposalMutation,
|
|
6
|
+
rejectDreamProposal,
|
|
7
|
+
} from "../server/lib/dreams-store.js";
|
|
5
8
|
|
|
6
9
|
export default defineAction({
|
|
7
10
|
description: "Reject one pending Dispatch dream proposal.",
|
|
11
|
+
authorize: authorizeDreamProposalMutation,
|
|
8
12
|
schema: z.object({
|
|
9
13
|
id: z.string().min(1).describe("Dream proposal id."),
|
|
10
14
|
reason: z.string().optional().describe("Optional rejection reason."),
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { restoreStarterWorkspaceResources } from "../server/lib/workspace-resources-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
"Restore missing starter global workspace resources such as company, brand, messaging, guardrails, and company voice. Existing resources are left unchanged.",
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
paths: z
|
|
11
13
|
.array(z.string())
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import {
|
|
5
6
|
requireWorkspaceResourceCtx,
|
|
6
7
|
revokeResourceGrant,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
|
|
9
10
|
export default defineAction({
|
|
10
11
|
description: "Revoke an app's access to a workspace resource. Admin only.",
|
|
12
|
+
authorize: authorizeDispatchAdmin,
|
|
11
13
|
schema: z.object({
|
|
12
14
|
grantId: z.string().describe("Grant ID to revoke"),
|
|
13
15
|
}),
|
|
@@ -28,6 +28,10 @@ vi.mock("../server/lib/dispatch-store.js", () => ({
|
|
|
28
28
|
getDestinationById: mocks.getDestinationById,
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
|
+
vi.mock("../server/lib/app-roles.js", () => ({
|
|
32
|
+
authorizeDispatchAdmin: vi.fn(),
|
|
33
|
+
}));
|
|
34
|
+
|
|
31
35
|
const action = (await import("./send-platform-message.js")).default;
|
|
32
36
|
|
|
33
37
|
describe("send-platform-message tenant scoping", () => {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "@agent-native/core/server";
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
|
|
17
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
17
18
|
import { getDestinationById } from "../server/lib/dispatch-store.js";
|
|
18
19
|
|
|
19
20
|
function getAdapter(
|
|
@@ -78,6 +79,7 @@ async function assertOutboundConfigured(
|
|
|
78
79
|
export default defineAction({
|
|
79
80
|
description:
|
|
80
81
|
"Send a proactive message to a saved Slack, Telegram, or email destination.",
|
|
82
|
+
authorize: authorizeDispatchAdmin,
|
|
81
83
|
schema: z.object({
|
|
82
84
|
platform: z.enum(["slack", "telegram", "email"]).optional(),
|
|
83
85
|
destinationId: z.string().optional().describe("Saved destination id"),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import {
|
|
5
6
|
getApprovalPolicy,
|
|
6
7
|
setApprovalPolicy,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
|
|
9
10
|
export default defineAction({
|
|
10
11
|
description: "Enable or disable dispatch approval flow for durable changes.",
|
|
12
|
+
authorize: authorizeDispatchAdmin,
|
|
11
13
|
schema: z.object({
|
|
12
14
|
enabled: z.boolean(),
|
|
13
15
|
approverEmails: z.array(z.string().email()).default([]),
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
const mocks = vi.hoisted(() => ({
|
|
4
|
+
authorizeDispatchAdmin: vi.fn(),
|
|
4
5
|
setDreamSettings: vi.fn(),
|
|
5
6
|
}));
|
|
6
7
|
|
|
8
|
+
vi.mock("../server/lib/app-roles.js", () => ({
|
|
9
|
+
authorizeDispatchAdmin: mocks.authorizeDispatchAdmin,
|
|
10
|
+
}));
|
|
11
|
+
|
|
7
12
|
vi.mock("../server/lib/dreams-store.js", () => ({
|
|
8
13
|
setDreamSettings: mocks.setDreamSettings,
|
|
9
14
|
}));
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { setDreamSettings } from "../server/lib/dreams-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
"Update recurring Dispatch dream settings without immediately running or applying a dream pass.",
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
enabled: z
|
|
11
13
|
.boolean()
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { updateWorkspaceResource } from "../server/lib/workspace-resources-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
"Update a workspace resource's name, description, content, or scope. When Dispatch approval policy is enabled, changes that affect All-app resources queue an approval request before taking effect.",
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
id: z.string().describe("Resource ID"),
|
|
11
13
|
name: z.string().optional().describe("New name"),
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
import { authorizeDispatchAdmin } from "../server/lib/app-roles.js";
|
|
4
5
|
import { upsertDestination } from "../server/lib/dispatch-store.js";
|
|
5
6
|
|
|
6
7
|
export default defineAction({
|
|
7
8
|
description:
|
|
8
9
|
"Create or update a saved messaging destination for proactive Slack, Telegram, or email sends.",
|
|
10
|
+
authorize: authorizeDispatchAdmin,
|
|
9
11
|
schema: z.object({
|
|
10
12
|
id: z.string().optional(),
|
|
11
13
|
name: z.string().describe("Friendly destination name"),
|
|
@@ -4,19 +4,6 @@ import React, { act } from "react";
|
|
|
4
4
|
import { createRoot, type Root } from "react-dom/client";
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
|
|
7
|
-
const mocks = vi.hoisted(() => ({
|
|
8
|
-
useOrgRole: vi.fn(),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
vi.mock("@agent-native/core/client/org", async (importOriginal) => {
|
|
12
|
-
const actual =
|
|
13
|
-
await importOriginal<typeof import("@agent-native/core/client/org")>();
|
|
14
|
-
return {
|
|
15
|
-
...actual,
|
|
16
|
-
useOrgRole: mocks.useOrgRole,
|
|
17
|
-
};
|
|
18
|
-
});
|
|
19
|
-
|
|
20
7
|
import { RequireDispatchAccess } from "./dispatch-access.js";
|
|
21
8
|
|
|
22
9
|
describe("RequireDispatchAccess", () => {
|
|
@@ -33,40 +20,10 @@ describe("RequireDispatchAccess", () => {
|
|
|
33
20
|
afterEach(() => {
|
|
34
21
|
act(() => root.unmount());
|
|
35
22
|
container.remove();
|
|
36
|
-
mocks.useOrgRole.mockReset();
|
|
37
23
|
vi.unstubAllGlobals();
|
|
38
24
|
});
|
|
39
25
|
|
|
40
|
-
it
|
|
41
|
-
"renders the Dispatch shell for an organization %s",
|
|
42
|
-
(role) => {
|
|
43
|
-
mocks.useOrgRole.mockReturnValue({
|
|
44
|
-
org: { orgId: "org-1" },
|
|
45
|
-
role,
|
|
46
|
-
isLoading: false,
|
|
47
|
-
error: null,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
act(() => {
|
|
51
|
-
root.render(
|
|
52
|
-
<RequireDispatchAccess>
|
|
53
|
-
<div data-dispatch-shell>Dispatch shell</div>
|
|
54
|
-
</RequireDispatchAccess>,
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
expect(container.querySelector("[data-dispatch-shell]")).not.toBeNull();
|
|
59
|
-
},
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
it("renders the Dispatch shell without an active organization", () => {
|
|
63
|
-
mocks.useOrgRole.mockReturnValue({
|
|
64
|
-
org: undefined,
|
|
65
|
-
role: null,
|
|
66
|
-
isLoading: false,
|
|
67
|
-
error: null,
|
|
68
|
-
});
|
|
69
|
-
|
|
26
|
+
it("renders the Dispatch shell for every authenticated user", () => {
|
|
70
27
|
act(() => {
|
|
71
28
|
root.render(
|
|
72
29
|
<RequireDispatchAccess>
|
|
@@ -77,23 +34,4 @@ describe("RequireDispatchAccess", () => {
|
|
|
77
34
|
|
|
78
35
|
expect(container.querySelector("[data-dispatch-shell]")).not.toBeNull();
|
|
79
36
|
});
|
|
80
|
-
|
|
81
|
-
it("does not render the Dispatch shell for an organization member", () => {
|
|
82
|
-
mocks.useOrgRole.mockReturnValue({
|
|
83
|
-
org: { orgId: "org-1" },
|
|
84
|
-
role: "member",
|
|
85
|
-
isLoading: false,
|
|
86
|
-
error: null,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
act(() => {
|
|
90
|
-
root.render(
|
|
91
|
-
<RequireDispatchAccess>
|
|
92
|
-
<div data-dispatch-shell>Dispatch shell</div>
|
|
93
|
-
</RequireDispatchAccess>,
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
expect(container.querySelector("[data-dispatch-shell]")).toBeNull();
|
|
98
|
-
});
|
|
99
37
|
});
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
import { canManageOrg, useOrgRole } from "@agent-native/core/client/org";
|
|
2
|
-
import { DefaultSpinner } from "@agent-native/core/client/ui";
|
|
3
1
|
import { type ReactNode } from "react";
|
|
4
2
|
|
|
5
3
|
export function RequireDispatchAccess({ children }: { children: ReactNode }) {
|
|
6
|
-
const { org, role, isLoading, error } = useOrgRole();
|
|
7
|
-
|
|
8
|
-
if (isLoading) return <DefaultSpinner />;
|
|
9
|
-
if (error) return null;
|
|
10
|
-
if (org?.orgId && !canManageOrg(role)) return null;
|
|
11
|
-
|
|
12
4
|
return <>{children}</>;
|
|
13
5
|
}
|
|
@@ -35,6 +35,7 @@ const mocks = vi.hoisted(() => {
|
|
|
35
35
|
settings.set(key, value);
|
|
36
36
|
}),
|
|
37
37
|
getOrgSetting: vi.fn(async () => null),
|
|
38
|
+
isWorkspaceAppAccessAllowed: vi.fn(async () => true),
|
|
38
39
|
resolveAccess: vi.fn(async () => ({
|
|
39
40
|
role: "viewer",
|
|
40
41
|
resource: {},
|
|
@@ -106,12 +107,8 @@ vi.mock("@agent-native/core/org", async (importOriginal) => {
|
|
|
106
107
|
await importOriginal<typeof import("@agent-native/core/org")>();
|
|
107
108
|
return {
|
|
108
109
|
...actual,
|
|
109
|
-
isWorkspaceAppAccessAllowed:
|
|
110
|
-
|
|
111
|
-
!context.orgId ||
|
|
112
|
-
mocks.state.orgRole === "owner" ||
|
|
113
|
-
mocks.state.orgRole === "admin",
|
|
114
|
-
),
|
|
110
|
+
isWorkspaceAppAccessAllowed: (...args: any[]) =>
|
|
111
|
+
mocks.isWorkspaceAppAccessAllowed(...args),
|
|
115
112
|
};
|
|
116
113
|
});
|
|
117
114
|
|
|
@@ -158,6 +155,8 @@ afterEach(() => {
|
|
|
158
155
|
mocks.settings.clear();
|
|
159
156
|
mocks.getOrgSetting.mockReset();
|
|
160
157
|
mocks.getOrgSetting.mockResolvedValue(null);
|
|
158
|
+
mocks.isWorkspaceAppAccessAllowed.mockReset();
|
|
159
|
+
mocks.isWorkspaceAppAccessAllowed.mockResolvedValue(true);
|
|
161
160
|
mocks.mutateSetting.mockReset();
|
|
162
161
|
mocks.mutateSetting.mockImplementation(
|
|
163
162
|
async (key: string, updater: (current: any) => any) => {
|
|
@@ -296,22 +295,6 @@ describe("listWorkspaceApps", () => {
|
|
|
296
295
|
};
|
|
297
296
|
}
|
|
298
297
|
|
|
299
|
-
it.each(["owner", "admin", "member"] as const)(
|
|
300
|
-
"applies the organization role gate to Dispatch registry links for %s",
|
|
301
|
-
async (role) => {
|
|
302
|
-
stubNoPendingContext();
|
|
303
|
-
stubManifest();
|
|
304
|
-
mocks.state.orgRole = role;
|
|
305
|
-
|
|
306
|
-
const apps = await runWithRequestContext(
|
|
307
|
-
{ userEmail: `${role}@example.test`, orgId: "org-123" },
|
|
308
|
-
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
expect(apps.some((app) => app.id === "dispatch")).toBe(role !== "member");
|
|
312
|
-
},
|
|
313
|
-
);
|
|
314
|
-
|
|
315
298
|
it("prefers the live workspace gateway manifest when available", async () => {
|
|
316
299
|
const fetchMock = vi.fn(async () => {
|
|
317
300
|
return new Response(
|
|
@@ -598,6 +581,22 @@ describe("listWorkspaceApps", () => {
|
|
|
598
581
|
expect(apps.map((app) => app.id)).toEqual(["dispatch"]);
|
|
599
582
|
});
|
|
600
583
|
|
|
584
|
+
it("does not expose Dispatch after federated membership is revoked", async () => {
|
|
585
|
+
stubManifest();
|
|
586
|
+
mocks.isWorkspaceAppAccessAllowed.mockResolvedValueOnce(false);
|
|
587
|
+
|
|
588
|
+
const apps = await runWithRequestContext(
|
|
589
|
+
{ userEmail: "member@example.test", orgId: "org-123" },
|
|
590
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
expect(apps).toEqual([]);
|
|
594
|
+
expect(mocks.isWorkspaceAppAccessAllowed).toHaveBeenCalledWith("dispatch", {
|
|
595
|
+
email: "member@example.test",
|
|
596
|
+
orgId: "org-123",
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
|
|
601
600
|
it("does not expose the workspace app registry without an authenticated user", async () => {
|
|
602
601
|
stubNoPendingContext();
|
|
603
602
|
stubManifest([
|