@agent-native/dispatch 0.38.11 → 0.38.12-nightly-20260926081002
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/list-workspace-apps.d.ts +2 -1
- package/dist/actions/list-workspace-apps.d.ts.map +1 -1
- package/dist/actions/list-workspace-apps.js +27 -2
- package/dist/actions/list-workspace-apps.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +11 -11
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +3 -2
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/package.json +4 -4
- package/src/actions/list-workspace-apps.spec.ts +78 -0
- package/src/actions/list-workspace-apps.ts +40 -3
- package/src/server/lib/app-creation-store.spec.ts +15 -1
- package/src/server/lib/app-creation-store.ts +5 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.12-nightly-20260926081002",
|
|
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.
|
|
91
|
+
"@agent-native/toolkit": "0.22.0-nightly-20260926081002"
|
|
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.
|
|
105
|
+
"@agent-native/core": "0.192.0-nightly-20260926081002"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
|
-
"@agent-native/core": "
|
|
108
|
+
"@agent-native/core": "0.192.0-nightly-20260926081002",
|
|
109
109
|
"react": ">=19.2.7",
|
|
110
110
|
"react-dom": ">=19.2.7",
|
|
111
111
|
"react-router": ">=8"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
listWorkspaceApps: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock("../server/lib/app-creation-store.js", () => ({
|
|
8
|
+
listWorkspaceApps: mocks.listWorkspaceApps,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import type { ActionRunContext } from "@agent-native/core/action";
|
|
12
|
+
|
|
13
|
+
import action from "./list-workspace-apps.js";
|
|
14
|
+
|
|
15
|
+
const input = {
|
|
16
|
+
includeAgentCards: false,
|
|
17
|
+
includeArchived: false,
|
|
18
|
+
audience: "all" as const,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function context(userEmail: string, orgId: string | null): ActionRunContext {
|
|
22
|
+
return {
|
|
23
|
+
caller: "http",
|
|
24
|
+
userEmail,
|
|
25
|
+
orgId,
|
|
26
|
+
appId: "dispatch",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("list-workspace-apps", () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("shares only a pending registry read for the same authorization context", async () => {
|
|
36
|
+
mocks.listWorkspaceApps.mockResolvedValue([]);
|
|
37
|
+
let resolve!: (apps: never[]) => void;
|
|
38
|
+
mocks.listWorkspaceApps.mockImplementationOnce(
|
|
39
|
+
() =>
|
|
40
|
+
new Promise((done) => {
|
|
41
|
+
resolve = done;
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const caller = context("member@example.com", "org-1");
|
|
46
|
+
const first = action.run(input, caller);
|
|
47
|
+
const concurrent = action.run(input, caller);
|
|
48
|
+
await vi.waitFor(() =>
|
|
49
|
+
expect(mocks.listWorkspaceApps).toHaveBeenCalledOnce(),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
resolve([]);
|
|
53
|
+
await expect(Promise.all([first, concurrent])).resolves.toEqual([[], []]);
|
|
54
|
+
|
|
55
|
+
await action.run(input, caller);
|
|
56
|
+
expect(mocks.listWorkspaceApps).toHaveBeenCalledTimes(2);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("isolates users, organizations, app contexts, and list options", async () => {
|
|
60
|
+
mocks.listWorkspaceApps.mockResolvedValue([]);
|
|
61
|
+
|
|
62
|
+
await Promise.all([
|
|
63
|
+
action.run(input, context("member@example.com", "org-1")),
|
|
64
|
+
action.run(input, context("another@example.com", "org-1")),
|
|
65
|
+
action.run(input, context("member@example.com", "org-2")),
|
|
66
|
+
action.run(input, {
|
|
67
|
+
...context("member@example.com", "org-1"),
|
|
68
|
+
appId: "other-dispatch",
|
|
69
|
+
}),
|
|
70
|
+
action.run(
|
|
71
|
+
{ ...input, includeArchived: true },
|
|
72
|
+
context("member@example.com", "org-1"),
|
|
73
|
+
),
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
expect(mocks.listWorkspaceApps).toHaveBeenCalledTimes(5);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -1,7 +1,44 @@
|
|
|
1
|
-
import { defineAction } from "@agent-native/core/action";
|
|
1
|
+
import { defineAction, type ActionRunContext } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
listWorkspaceApps,
|
|
6
|
+
type ListWorkspaceAppsOptions,
|
|
7
|
+
type WorkspaceAppSummary,
|
|
8
|
+
} from "../server/lib/app-creation-store.js";
|
|
9
|
+
|
|
10
|
+
const inFlightWorkspaceAppLists = new Map<
|
|
11
|
+
string,
|
|
12
|
+
Promise<WorkspaceAppSummary[]>
|
|
13
|
+
>();
|
|
14
|
+
|
|
15
|
+
function listWorkspaceAppsForRequest(
|
|
16
|
+
input: ListWorkspaceAppsOptions,
|
|
17
|
+
context?: ActionRunContext,
|
|
18
|
+
): Promise<WorkspaceAppSummary[]> {
|
|
19
|
+
const email = context?.userEmail?.trim().toLowerCase();
|
|
20
|
+
if (!context || !email) return listWorkspaceApps(input);
|
|
21
|
+
|
|
22
|
+
const key = JSON.stringify([
|
|
23
|
+
email,
|
|
24
|
+
context.orgId?.trim() || null,
|
|
25
|
+
context.appId?.trim() || null,
|
|
26
|
+
context.caller,
|
|
27
|
+
input,
|
|
28
|
+
]);
|
|
29
|
+
const pending = inFlightWorkspaceAppLists.get(key);
|
|
30
|
+
if (pending) return pending;
|
|
31
|
+
|
|
32
|
+
const request = listWorkspaceApps(input);
|
|
33
|
+
inFlightWorkspaceAppLists.set(key, request);
|
|
34
|
+
const removeSettledRequest = () => {
|
|
35
|
+
if (inFlightWorkspaceAppLists.get(key) === request) {
|
|
36
|
+
inFlightWorkspaceAppLists.delete(key);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
void request.then(removeSettledRequest, removeSettledRequest);
|
|
40
|
+
return request;
|
|
41
|
+
}
|
|
5
42
|
|
|
6
43
|
const httpBoolean = z.preprocess((value) => {
|
|
7
44
|
if (typeof value !== "string") return value;
|
|
@@ -29,5 +66,5 @@ export default defineAction({
|
|
|
29
66
|
.describe("Filter by workspace app audience."),
|
|
30
67
|
}),
|
|
31
68
|
http: { method: "GET" },
|
|
32
|
-
run: async (input) =>
|
|
69
|
+
run: async (input, context) => listWorkspaceAppsForRequest(input, context),
|
|
33
70
|
});
|
|
@@ -45,7 +45,9 @@ const mocks = vi.hoisted(() => {
|
|
|
45
45
|
settings.set(key, value);
|
|
46
46
|
}),
|
|
47
47
|
getOrgSetting: vi.fn(async () => null),
|
|
48
|
-
isWorkspaceAppAccessAllowed: vi.fn(
|
|
48
|
+
isWorkspaceAppAccessAllowed: vi.fn(
|
|
49
|
+
async (): Promise<boolean | "unavailable"> => true,
|
|
50
|
+
),
|
|
49
51
|
resolveAccess: vi.fn(async () => ({
|
|
50
52
|
role: "viewer",
|
|
51
53
|
resource: {},
|
|
@@ -798,6 +800,18 @@ describe("listWorkspaceApps", () => {
|
|
|
798
800
|
});
|
|
799
801
|
});
|
|
800
802
|
|
|
803
|
+
it("does not expose Dispatch when its access check is unavailable", async () => {
|
|
804
|
+
stubManifest();
|
|
805
|
+
mocks.isWorkspaceAppAccessAllowed.mockResolvedValueOnce("unavailable");
|
|
806
|
+
|
|
807
|
+
const apps = await runWithRequestContext(
|
|
808
|
+
{ userEmail: "member@example.test", orgId: "org-123" },
|
|
809
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
expect(apps).toEqual([]);
|
|
813
|
+
});
|
|
814
|
+
|
|
801
815
|
it("does not expose the workspace app registry without an authenticated user", async () => {
|
|
802
816
|
stubNoPendingContext();
|
|
803
817
|
stubManifest([
|
|
@@ -1556,12 +1556,11 @@ async function filterWorkspaceAppsByAccess(
|
|
|
1556
1556
|
continue;
|
|
1557
1557
|
}
|
|
1558
1558
|
if (app.isDispatch) {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
) {
|
|
1559
|
+
const dispatchAccess = await isWorkspaceAppAccessAllowed("dispatch", {
|
|
1560
|
+
email: userEmail,
|
|
1561
|
+
orgId,
|
|
1562
|
+
});
|
|
1563
|
+
if (dispatchAccess === true) {
|
|
1565
1564
|
visibleIds.add(app.id);
|
|
1566
1565
|
}
|
|
1567
1566
|
continue;
|