@agent-native/dispatch 0.14.14 → 0.15.1
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 +3 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +4 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-curated-workspace-templates.d.ts +3 -0
- package/dist/actions/list-curated-workspace-templates.d.ts.map +1 -0
- package/dist/actions/list-curated-workspace-templates.js +10 -0
- package/dist/actions/list-curated-workspace-templates.js.map +1 -0
- package/dist/actions/remix-workspace-template.d.ts +60 -0
- package/dist/actions/remix-workspace-template.d.ts.map +1 -0
- package/dist/actions/remix-workspace-template.js +78 -0
- package/dist/actions/remix-workspace-template.js.map +1 -0
- package/dist/actions/upsert-destination.d.ts +12 -12
- package/dist/components/workspace-template-card.d.ts +52 -0
- package/dist/components/workspace-template-card.d.ts.map +1 -0
- package/dist/components/workspace-template-card.js +104 -0
- package/dist/components/workspace-template-card.js.map +1 -0
- package/dist/lib/other-apps.d.ts +14 -0
- package/dist/lib/other-apps.d.ts.map +1 -0
- package/dist/lib/other-apps.js +30 -0
- package/dist/lib/other-apps.js.map +1 -0
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +37 -2
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts +1 -0
- package/dist/server/lib/app-creation-store.d.ts.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/curated-workspace-templates.d.ts +22 -0
- package/dist/server/lib/curated-workspace-templates.d.ts.map +1 -0
- package/dist/server/lib/curated-workspace-templates.js +137 -0
- package/dist/server/lib/curated-workspace-templates.js.map +1 -0
- package/package.json +8 -2
- package/src/actions/index.ts +4 -0
- package/src/actions/list-curated-workspace-templates.spec.ts +36 -0
- package/src/actions/list-curated-workspace-templates.ts +12 -0
- package/src/actions/remix-workspace-template.spec.ts +116 -0
- package/src/actions/remix-workspace-template.ts +99 -0
- package/src/components/workspace-template-card.spec.tsx +174 -0
- package/src/components/workspace-template-card.tsx +361 -0
- package/src/lib/other-apps.spec.ts +76 -0
- package/src/lib/other-apps.ts +44 -0
- package/src/routes/pages/apps.tsx +165 -0
- package/src/server/lib/app-creation-store.ts +1 -1
- package/src/server/lib/curated-workspace-templates.spec.ts +56 -0
- package/src/server/lib/curated-workspace-templates.ts +187 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { listWorkspaceApps } from "./app-creation-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* Stable first-party template metadata for the initial remix catalog.
|
|
4
|
+
* `liveUrl` identifies the product URL; it is not a public-demo claim.
|
|
5
|
+
*/
|
|
6
|
+
export const CURATED_WORKSPACE_TEMPLATES = [
|
|
7
|
+
{
|
|
8
|
+
id: "mail",
|
|
9
|
+
name: "Mail",
|
|
10
|
+
description: "Agent-native email client with keyboard shortcuts and AI triage.",
|
|
11
|
+
icon: "Mail",
|
|
12
|
+
color: "#3B82F6",
|
|
13
|
+
template: "mail",
|
|
14
|
+
liveUrl: "https://mail.agent-native.com",
|
|
15
|
+
category: "communication",
|
|
16
|
+
setupNote: "Connect the workspace email integration before working with live mail.",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "calendar",
|
|
20
|
+
name: "Calendar",
|
|
21
|
+
description: "Manage events, synchronization, and public booking with an agent-native calendar.",
|
|
22
|
+
icon: "CalendarDays",
|
|
23
|
+
color: "#00B5FF",
|
|
24
|
+
template: "calendar",
|
|
25
|
+
liveUrl: "https://calendar.agent-native.com",
|
|
26
|
+
category: "productivity",
|
|
27
|
+
setupNote: "Connect the workspace calendar integration before working with live events.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "analytics",
|
|
31
|
+
name: "Analytics",
|
|
32
|
+
description: "Connect data sources and prompt for charts, reports, and product insights.",
|
|
33
|
+
icon: "BarChart2",
|
|
34
|
+
color: "#F59E0B",
|
|
35
|
+
template: "analytics",
|
|
36
|
+
liveUrl: "https://analytics.agent-native.com",
|
|
37
|
+
category: "insights",
|
|
38
|
+
setupNote: "Connect a data source or import a representative dataset before analysis.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "slides",
|
|
42
|
+
name: "Slides",
|
|
43
|
+
description: "Generate and edit React presentations with agent assistance.",
|
|
44
|
+
icon: "GalleryHorizontal",
|
|
45
|
+
color: "#EC4899",
|
|
46
|
+
template: "slides",
|
|
47
|
+
liveUrl: "https://slides.agent-native.com",
|
|
48
|
+
category: "creative",
|
|
49
|
+
setupNote: "Start with a blank deck or connect the workspace presentation integration.",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "content",
|
|
53
|
+
name: "Content",
|
|
54
|
+
description: "Write and organize local MDX content with agent assistance.",
|
|
55
|
+
icon: "FileText",
|
|
56
|
+
color: "#10B981",
|
|
57
|
+
template: "content",
|
|
58
|
+
liveUrl: "https://content.agent-native.com",
|
|
59
|
+
category: "content",
|
|
60
|
+
setupNote: "Choose a content folder or create a first document for the private remix.",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "clips",
|
|
64
|
+
name: "Clips",
|
|
65
|
+
description: "Record screens, capture meeting notes, and use voice dictation with AI.",
|
|
66
|
+
icon: "ScreenShare",
|
|
67
|
+
color: "#0EA5E9",
|
|
68
|
+
template: "clips",
|
|
69
|
+
liveUrl: "https://clips.agent-native.com",
|
|
70
|
+
category: "media",
|
|
71
|
+
setupNote: "Grant screen and audio permissions only when the workspace needs recording.",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "brain",
|
|
75
|
+
name: "Brain",
|
|
76
|
+
description: "Search cited company knowledge from conversations, meetings, and decisions.",
|
|
77
|
+
icon: "Brain",
|
|
78
|
+
color: "#8B5CF6",
|
|
79
|
+
template: "brain",
|
|
80
|
+
liveUrl: "https://brain.agent-native.com",
|
|
81
|
+
category: "knowledge",
|
|
82
|
+
setupNote: "Connect approved knowledge sources before indexing workspace information.",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "assets",
|
|
86
|
+
name: "Assets",
|
|
87
|
+
description: "Upload, organize, search, and generate on-brand images and videos.",
|
|
88
|
+
icon: "Photo",
|
|
89
|
+
color: "#0F766E",
|
|
90
|
+
template: "assets",
|
|
91
|
+
liveUrl: "https://assets.agent-native.com",
|
|
92
|
+
category: "content",
|
|
93
|
+
setupNote: "Upload a representative asset or connect approved asset storage to begin.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "forms",
|
|
97
|
+
name: "Forms",
|
|
98
|
+
description: "Create, edit, and manage forms with agent assistance.",
|
|
99
|
+
icon: "ClipboardList",
|
|
100
|
+
color: "#06B6D4",
|
|
101
|
+
template: "forms",
|
|
102
|
+
liveUrl: "https://forms.agent-native.com",
|
|
103
|
+
category: "operations",
|
|
104
|
+
setupNote: "Create a form and choose its submission destination before inviting respondents.",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "design",
|
|
108
|
+
name: "Design",
|
|
109
|
+
description: "Create and edit visual designs with agent-assisted exploration.",
|
|
110
|
+
icon: "Brush",
|
|
111
|
+
color: "#F472B6",
|
|
112
|
+
template: "design",
|
|
113
|
+
liveUrl: "https://design.agent-native.com",
|
|
114
|
+
category: "creative",
|
|
115
|
+
setupNote: "Create a private project and optionally connect approved asset sources.",
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
const curatedTemplateById = new Map(CURATED_WORKSPACE_TEMPLATES.map((template) => [template.id, template]));
|
|
119
|
+
export function getCuratedWorkspaceTemplate(templateId) {
|
|
120
|
+
const normalized = templateId.trim().toLowerCase();
|
|
121
|
+
const template = curatedTemplateById.get(normalized);
|
|
122
|
+
if (!template) {
|
|
123
|
+
throw new Error(`Unknown curated workspace template "${templateId}".`);
|
|
124
|
+
}
|
|
125
|
+
return template;
|
|
126
|
+
}
|
|
127
|
+
export async function listCuratedWorkspaceTemplates() {
|
|
128
|
+
const installedIds = new Set((await listWorkspaceApps({
|
|
129
|
+
includeAgentCards: false,
|
|
130
|
+
includeArchived: true,
|
|
131
|
+
})).map((app) => app.id.trim().toLowerCase()));
|
|
132
|
+
return CURATED_WORKSPACE_TEMPLATES.map((template) => ({
|
|
133
|
+
...template,
|
|
134
|
+
installed: installedIds.has(template.id),
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=curated-workspace-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"curated-workspace-templates.js","sourceRoot":"","sources":["../../../src/server/lib/curated-workspace-templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { listWorkspaceApps } from \"./app-creation-store.js\";\n\nexport interface CuratedWorkspaceTemplate {\n id: string;\n name: string;\n description: string;\n icon: string;\n color: string;\n template: string;\n liveUrl: string;\n category: string;\n setupNote: string;\n}\n\nexport interface CuratedWorkspaceTemplateStatus extends CuratedWorkspaceTemplate {\n installed: boolean;\n}\n\n/**\n * Stable first-party template metadata for the initial remix catalog.\n * `liveUrl` identifies the product URL; it is not a public-demo claim.\n */\nexport const CURATED_WORKSPACE_TEMPLATES: readonly CuratedWorkspaceTemplate[] =\n [\n {\n id: \"mail\",\n name: \"Mail\",\n description:\n \"Agent-native email client with keyboard shortcuts and AI triage.\",\n icon: \"Mail\",\n color: \"#3B82F6\",\n template: \"mail\",\n liveUrl: \"https://mail.agent-native.com\",\n category: \"communication\",\n setupNote:\n \"Connect the workspace email integration before working with live mail.\",\n },\n {\n id: \"calendar\",\n name: \"Calendar\",\n description:\n \"Manage events, synchronization, and public booking with an agent-native calendar.\",\n icon: \"CalendarDays\",\n color: \"#00B5FF\",\n template: \"calendar\",\n liveUrl: \"https://calendar.agent-native.com\",\n category: \"productivity\",\n setupNote:\n \"Connect the workspace calendar integration before working with live events.\",\n },\n {\n id: \"analytics\",\n name: \"Analytics\",\n description:\n \"Connect data sources and prompt for charts, reports, and product insights.\",\n icon: \"BarChart2\",\n color: \"#F59E0B\",\n template: \"analytics\",\n liveUrl: \"https://analytics.agent-native.com\",\n category: \"insights\",\n setupNote:\n \"Connect a data source or import a representative dataset before analysis.\",\n },\n {\n id: \"slides\",\n name: \"Slides\",\n description:\n \"Generate and edit React presentations with agent assistance.\",\n icon: \"GalleryHorizontal\",\n color: \"#EC4899\",\n template: \"slides\",\n liveUrl: \"https://slides.agent-native.com\",\n category: \"creative\",\n setupNote:\n \"Start with a blank deck or connect the workspace presentation integration.\",\n },\n {\n id: \"content\",\n name: \"Content\",\n description:\n \"Write and organize local MDX content with agent assistance.\",\n icon: \"FileText\",\n color: \"#10B981\",\n template: \"content\",\n liveUrl: \"https://content.agent-native.com\",\n category: \"content\",\n setupNote:\n \"Choose a content folder or create a first document for the private remix.\",\n },\n {\n id: \"clips\",\n name: \"Clips\",\n description:\n \"Record screens, capture meeting notes, and use voice dictation with AI.\",\n icon: \"ScreenShare\",\n color: \"#0EA5E9\",\n template: \"clips\",\n liveUrl: \"https://clips.agent-native.com\",\n category: \"media\",\n setupNote:\n \"Grant screen and audio permissions only when the workspace needs recording.\",\n },\n {\n id: \"brain\",\n name: \"Brain\",\n description:\n \"Search cited company knowledge from conversations, meetings, and decisions.\",\n icon: \"Brain\",\n color: \"#8B5CF6\",\n template: \"brain\",\n liveUrl: \"https://brain.agent-native.com\",\n category: \"knowledge\",\n setupNote:\n \"Connect approved knowledge sources before indexing workspace information.\",\n },\n {\n id: \"assets\",\n name: \"Assets\",\n description:\n \"Upload, organize, search, and generate on-brand images and videos.\",\n icon: \"Photo\",\n color: \"#0F766E\",\n template: \"assets\",\n liveUrl: \"https://assets.agent-native.com\",\n category: \"content\",\n setupNote:\n \"Upload a representative asset or connect approved asset storage to begin.\",\n },\n {\n id: \"forms\",\n name: \"Forms\",\n description: \"Create, edit, and manage forms with agent assistance.\",\n icon: \"ClipboardList\",\n color: \"#06B6D4\",\n template: \"forms\",\n liveUrl: \"https://forms.agent-native.com\",\n category: \"operations\",\n setupNote:\n \"Create a form and choose its submission destination before inviting respondents.\",\n },\n {\n id: \"design\",\n name: \"Design\",\n description:\n \"Create and edit visual designs with agent-assisted exploration.\",\n icon: \"Brush\",\n color: \"#F472B6\",\n template: \"design\",\n liveUrl: \"https://design.agent-native.com\",\n category: \"creative\",\n setupNote:\n \"Create a private project and optionally connect approved asset sources.\",\n },\n ] as const;\n\nconst curatedTemplateById = new Map(\n CURATED_WORKSPACE_TEMPLATES.map((template) => [template.id, template]),\n);\n\nexport function getCuratedWorkspaceTemplate(\n templateId: string,\n): CuratedWorkspaceTemplate {\n const normalized = templateId.trim().toLowerCase();\n const template = curatedTemplateById.get(normalized);\n if (!template) {\n throw new Error(`Unknown curated workspace template \"${templateId}\".`);\n }\n return template;\n}\n\nexport async function listCuratedWorkspaceTemplates(): Promise<\n CuratedWorkspaceTemplateStatus[]\n> {\n const installedIds = new Set(\n (\n await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n })\n ).map((app) => app.id.trim().toLowerCase()),\n );\n\n return CURATED_WORKSPACE_TEMPLATES.map((template) => ({\n ...template,\n installed: installedIds.has(template.id),\n }));\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
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": {
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
"src"
|
|
18
18
|
],
|
|
19
19
|
"type": "module",
|
|
20
|
+
"sideEffects": [
|
|
21
|
+
"*.css",
|
|
22
|
+
"dist/server/index.js",
|
|
23
|
+
"dist/server/lib/usage-metrics-store.js",
|
|
24
|
+
"dist/server/plugins/core-routes.js"
|
|
25
|
+
],
|
|
20
26
|
"exports": {
|
|
21
27
|
".": "./dist/index.js",
|
|
22
28
|
"./routes": "./dist/routes/index.js",
|
|
@@ -93,7 +99,7 @@
|
|
|
93
99
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
94
100
|
"vite": "8.1.0",
|
|
95
101
|
"vitest": "^4.1.5",
|
|
96
|
-
"@agent-native/core": "0.
|
|
102
|
+
"@agent-native/core": "0.109.4"
|
|
97
103
|
},
|
|
98
104
|
"peerDependencies": {
|
|
99
105
|
"@agent-native/core": ">=0.8.0",
|
package/src/actions/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ import grantWorkspaceResourcesToApp from "./grant-workspace-resources-to-app.js"
|
|
|
33
33
|
import listAgentThreadSources from "./list-agent-thread-sources.js";
|
|
34
34
|
import listAvailableWorkspaceTemplates from "./list-available-workspace-templates.js";
|
|
35
35
|
import listConnectedAgents from "./list-connected-agents.js";
|
|
36
|
+
import listCuratedWorkspaceTemplates from "./list-curated-workspace-templates.js";
|
|
36
37
|
import listDestinations from "./list-destinations.js";
|
|
37
38
|
import listDispatchApprovals from "./list-dispatch-approvals.js";
|
|
38
39
|
import listDispatchAudit from "./list-dispatch-audit.js";
|
|
@@ -66,6 +67,7 @@ import providerApiRequest from "./provider-api-request.js";
|
|
|
66
67
|
import queryStagedDataset from "./query-staged-dataset.js";
|
|
67
68
|
import rejectDispatchChange from "./reject-dispatch-change.js";
|
|
68
69
|
import rejectDreamProposal from "./reject-dream-proposal.js";
|
|
70
|
+
import remixWorkspaceTemplate from "./remix-workspace-template.js";
|
|
69
71
|
import removePendingWorkspaceApp from "./remove-pending-workspace-app.js";
|
|
70
72
|
import requestVaultSecret from "./request-vault-secret.js";
|
|
71
73
|
import restoreStarterWorkspaceResources from "./restore-starter-workspace-resources.js";
|
|
@@ -128,6 +130,7 @@ export const dispatchActions: Record<string, ActionEntry> = {
|
|
|
128
130
|
"grant-vault-secrets-to-app": grantVaultSecretsToApp,
|
|
129
131
|
"list-agent-thread-sources": listAgentThreadSources,
|
|
130
132
|
"list-available-workspace-templates": listAvailableWorkspaceTemplates,
|
|
133
|
+
"list-curated-workspace-templates": listCuratedWorkspaceTemplates,
|
|
131
134
|
"list-connected-agents": listConnectedAgents,
|
|
132
135
|
"list-destinations": listDestinations,
|
|
133
136
|
"list-dispatch-approvals": listDispatchApprovals,
|
|
@@ -164,6 +167,7 @@ export const dispatchActions: Record<string, ActionEntry> = {
|
|
|
164
167
|
"reject-dispatch-change": rejectDispatchChange,
|
|
165
168
|
"reject-dream-proposal": rejectDreamProposal,
|
|
166
169
|
"remove-pending-workspace-app": removePendingWorkspaceApp,
|
|
170
|
+
"remix-workspace-template": remixWorkspaceTemplate,
|
|
167
171
|
"request-vault-secret": requestVaultSecret,
|
|
168
172
|
"revoke-vault-grant": revokeVaultGrant,
|
|
169
173
|
"revoke-workspace-resource-grant": revokeWorkspaceResourceGrant,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
listCuratedWorkspaceTemplates: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock("../server/lib/curated-workspace-templates.js", () => ({
|
|
8
|
+
listCuratedWorkspaceTemplates: mocks.listCuratedWorkspaceTemplates,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import action from "./list-curated-workspace-templates.js";
|
|
12
|
+
|
|
13
|
+
describe("list-curated-workspace-templates action", () => {
|
|
14
|
+
beforeEach(() => vi.clearAllMocks());
|
|
15
|
+
|
|
16
|
+
it("delegates to the catalog store", async () => {
|
|
17
|
+
const templates = [
|
|
18
|
+
{
|
|
19
|
+
id: "mail",
|
|
20
|
+
name: "Mail",
|
|
21
|
+
description: "Email",
|
|
22
|
+
icon: "Mail",
|
|
23
|
+
color: "#3B82F6",
|
|
24
|
+
template: "mail",
|
|
25
|
+
liveUrl: "https://mail.agent-native.com",
|
|
26
|
+
category: "communication",
|
|
27
|
+
setupNote: "Connect email.",
|
|
28
|
+
installed: false,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
mocks.listCuratedWorkspaceTemplates.mockResolvedValue(templates);
|
|
32
|
+
|
|
33
|
+
await expect(action.run({})).resolves.toEqual(templates);
|
|
34
|
+
expect(mocks.listCuratedWorkspaceTemplates).toHaveBeenCalledOnce();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { listCuratedWorkspaceTemplates } from "../server/lib/curated-workspace-templates.js";
|
|
5
|
+
|
|
6
|
+
export default defineAction({
|
|
7
|
+
description:
|
|
8
|
+
"List the curated first-party workspace templates available for private remixing. Returns stable template metadata, the product/live URL (not a public-demo claim), setup guidance, and whether each source template is already installed in the current workspace.",
|
|
9
|
+
schema: z.object({}),
|
|
10
|
+
http: { method: "GET" },
|
|
11
|
+
run: async () => listCuratedWorkspaceTemplates(),
|
|
12
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
isLocalAppCreationRuntime: vi.fn(() => process.env.NODE_ENV !== "production"),
|
|
5
|
+
scaffoldWorkspaceAppFromTemplate: vi.fn(),
|
|
6
|
+
startWorkspaceAppCreation: vi.fn(),
|
|
7
|
+
recordAudit: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("../server/lib/app-creation-store.js", () => ({
|
|
11
|
+
isLocalAppCreationRuntime: mocks.isLocalAppCreationRuntime,
|
|
12
|
+
scaffoldWorkspaceAppFromTemplate: mocks.scaffoldWorkspaceAppFromTemplate,
|
|
13
|
+
startWorkspaceAppCreation: mocks.startWorkspaceAppCreation,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("../server/lib/dispatch-store.js", () => ({
|
|
17
|
+
recordAudit: mocks.recordAudit,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
import action from "./remix-workspace-template.js";
|
|
21
|
+
|
|
22
|
+
describe("remix-workspace-template action", () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
mocks.recordAudit.mockResolvedValue(undefined);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
vi.unstubAllEnvs();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("scaffolds the curated source template in local app-creation runtime", async () => {
|
|
33
|
+
vi.stubEnv("NODE_ENV", "test");
|
|
34
|
+
vi.stubEnv("NETLIFY", "");
|
|
35
|
+
mocks.scaffoldWorkspaceAppFromTemplate.mockResolvedValue({
|
|
36
|
+
appId: "mail-remix",
|
|
37
|
+
template: "mail",
|
|
38
|
+
output: "scaffolded",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const result = await action.run({
|
|
42
|
+
templateId: "mail",
|
|
43
|
+
appId: "mail-remix",
|
|
44
|
+
description: "A private support inbox.",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(mocks.scaffoldWorkspaceAppFromTemplate).toHaveBeenCalledWith({
|
|
48
|
+
template: "mail",
|
|
49
|
+
appId: "mail-remix",
|
|
50
|
+
});
|
|
51
|
+
expect(mocks.startWorkspaceAppCreation).not.toHaveBeenCalled();
|
|
52
|
+
expect(result).toEqual(
|
|
53
|
+
expect.objectContaining({
|
|
54
|
+
appId: "mail-remix",
|
|
55
|
+
sourceTemplate: expect.objectContaining({
|
|
56
|
+
id: "mail",
|
|
57
|
+
template: "mail",
|
|
58
|
+
setupNote: expect.any(String),
|
|
59
|
+
}),
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
expect(mocks.recordAudit).toHaveBeenCalledWith(
|
|
63
|
+
expect.objectContaining({
|
|
64
|
+
action: "workspace-app.remix-requested",
|
|
65
|
+
targetId: "mail-remix",
|
|
66
|
+
metadata: expect.objectContaining({ sourceTemplate: "mail" }),
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("starts a hosted private remix without copying data or secrets", async () => {
|
|
72
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
73
|
+
mocks.startWorkspaceAppCreation.mockResolvedValue({
|
|
74
|
+
mode: "builder",
|
|
75
|
+
appId: "calendar-remix",
|
|
76
|
+
status: "processing",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const result = await action.run({
|
|
80
|
+
templateId: "calendar",
|
|
81
|
+
description: "A private scheduling workflow.",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(mocks.scaffoldWorkspaceAppFromTemplate).not.toHaveBeenCalled();
|
|
85
|
+
expect(mocks.startWorkspaceAppCreation).toHaveBeenCalledWith(
|
|
86
|
+
expect.objectContaining({
|
|
87
|
+
appId: "calendar-remix",
|
|
88
|
+
template: "calendar",
|
|
89
|
+
description: "A private scheduling workflow.",
|
|
90
|
+
prompt: expect.stringContaining(
|
|
91
|
+
"private workspace remix of a curated first-party template",
|
|
92
|
+
),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
const prompt = mocks.startWorkspaceAppCreation.mock.calls[0][0].prompt;
|
|
96
|
+
expect(prompt).toContain("Source template: Calendar (calendar)");
|
|
97
|
+
expect(prompt).toContain("Never copy source-app data");
|
|
98
|
+
expect(prompt).toContain("secrets");
|
|
99
|
+
expect(result).toEqual(
|
|
100
|
+
expect.objectContaining({
|
|
101
|
+
mode: "builder",
|
|
102
|
+
sourceTemplate: expect.objectContaining({ id: "calendar" }),
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("rejects templates outside the curated catalog before creating an app", async () => {
|
|
108
|
+
await expect(
|
|
109
|
+
action.run({ templateId: "not-a-curated-template", appId: null }),
|
|
110
|
+
).rejects.toThrow(
|
|
111
|
+
'Unknown curated workspace template "not-a-curated-template".',
|
|
112
|
+
);
|
|
113
|
+
expect(mocks.scaffoldWorkspaceAppFromTemplate).not.toHaveBeenCalled();
|
|
114
|
+
expect(mocks.startWorkspaceAppCreation).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { getWorkspaceAppIdValidationError } from "@agent-native/core/shared";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
isLocalAppCreationRuntime,
|
|
7
|
+
scaffoldWorkspaceAppFromTemplate,
|
|
8
|
+
startWorkspaceAppCreation,
|
|
9
|
+
} from "../server/lib/app-creation-store.js";
|
|
10
|
+
import { getCuratedWorkspaceTemplate } from "../server/lib/curated-workspace-templates.js";
|
|
11
|
+
import { recordAudit } from "../server/lib/dispatch-store.js";
|
|
12
|
+
|
|
13
|
+
function buildRemixPrompt(input: {
|
|
14
|
+
templateName: string;
|
|
15
|
+
templateId: string;
|
|
16
|
+
setupNote: string;
|
|
17
|
+
description?: string | null;
|
|
18
|
+
}): string {
|
|
19
|
+
return [
|
|
20
|
+
"Create a private workspace remix of a curated first-party template.",
|
|
21
|
+
`Source template: ${input.templateName} (${input.templateId}).`,
|
|
22
|
+
"Recreate the source template's product shape and capabilities as an independent workspace app.",
|
|
23
|
+
"Never copy source-app data, records, user content, secrets, credentials, tokens, API keys, or private configuration.",
|
|
24
|
+
"Use empty or synthetic seed data only, keep the remix private to the current workspace, and do not create a public demo.",
|
|
25
|
+
`Setup note: ${input.setupNote}`,
|
|
26
|
+
input.description?.trim()
|
|
27
|
+
? `Requested customization: ${input.description.trim()}`
|
|
28
|
+
: "Keep the first version close to the curated source template while preserving independent ownership.",
|
|
29
|
+
].join(" ");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default defineAction({
|
|
33
|
+
description:
|
|
34
|
+
"Create a private, independent remix of one of Dispatch's curated first-party workspace templates. Valid templates are mail, calendar, analytics, slides, content, clips, brain, assets, forms, and design. The remix must not copy source-app data, secrets, credentials, or private configuration; local workspaces scaffold the template, while hosted workspaces start a Builder app-creation branch.",
|
|
35
|
+
schema: z.object({
|
|
36
|
+
templateId: z
|
|
37
|
+
.string()
|
|
38
|
+
.min(1)
|
|
39
|
+
.describe(
|
|
40
|
+
"Curated source template id: mail, calendar, analytics, slides, content, clips, brain, assets, forms, or design.",
|
|
41
|
+
),
|
|
42
|
+
appId: z
|
|
43
|
+
.string()
|
|
44
|
+
.max(64)
|
|
45
|
+
.optional()
|
|
46
|
+
.nullable()
|
|
47
|
+
.refine((appId) => !appId || !getWorkspaceAppIdValidationError(appId), {
|
|
48
|
+
message:
|
|
49
|
+
"Use a non-reserved app id with lowercase letters, numbers, and hyphens.",
|
|
50
|
+
})
|
|
51
|
+
.describe(
|
|
52
|
+
"Optional target workspace app id. Defaults to <template>-remix so the source app remains independent.",
|
|
53
|
+
),
|
|
54
|
+
description: z
|
|
55
|
+
.string()
|
|
56
|
+
.max(500)
|
|
57
|
+
.optional()
|
|
58
|
+
.nullable()
|
|
59
|
+
.describe("Optional customization or purpose for the private remix."),
|
|
60
|
+
}),
|
|
61
|
+
run: async (input) => {
|
|
62
|
+
const sourceTemplate = getCuratedWorkspaceTemplate(input.templateId);
|
|
63
|
+
const appId = input.appId?.trim() || `${sourceTemplate.template}-remix`;
|
|
64
|
+
|
|
65
|
+
const result = isLocalAppCreationRuntime()
|
|
66
|
+
? await scaffoldWorkspaceAppFromTemplate({
|
|
67
|
+
template: sourceTemplate.template,
|
|
68
|
+
appId,
|
|
69
|
+
})
|
|
70
|
+
: await startWorkspaceAppCreation({
|
|
71
|
+
prompt: buildRemixPrompt({
|
|
72
|
+
templateName: sourceTemplate.name,
|
|
73
|
+
templateId: sourceTemplate.template,
|
|
74
|
+
setupNote: sourceTemplate.setupNote,
|
|
75
|
+
description: input.description,
|
|
76
|
+
}),
|
|
77
|
+
appId,
|
|
78
|
+
description: input.description,
|
|
79
|
+
template: sourceTemplate.template,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await recordAudit({
|
|
83
|
+
action: "workspace-app.remix-requested",
|
|
84
|
+
targetType: "workspace-app",
|
|
85
|
+
targetId: appId,
|
|
86
|
+
summary: `Requested private remix of ${sourceTemplate.name}`,
|
|
87
|
+
metadata: {
|
|
88
|
+
sourceTemplate: sourceTemplate.template,
|
|
89
|
+
mode:
|
|
90
|
+
result && typeof result === "object" && "mode" in result
|
|
91
|
+
? result.mode
|
|
92
|
+
: "local",
|
|
93
|
+
descriptionConfigured: !!input.description?.trim(),
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return { ...result, sourceTemplate };
|
|
98
|
+
},
|
|
99
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import React, { act } from "react";
|
|
3
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
WorkspaceTemplateCard,
|
|
8
|
+
WorkspaceTemplatesSection,
|
|
9
|
+
type CuratedWorkspaceTemplate,
|
|
10
|
+
} from "./workspace-template-card";
|
|
11
|
+
|
|
12
|
+
const clientState = vi.hoisted(() => ({
|
|
13
|
+
mutation: vi.fn(),
|
|
14
|
+
options: null as Record<string, unknown> | null,
|
|
15
|
+
isPending: false,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const toast = vi.hoisted(() => ({
|
|
19
|
+
error: vi.fn(),
|
|
20
|
+
success: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock("@agent-native/core/client", () => ({
|
|
24
|
+
useActionMutation: (_name: string, options: Record<string, unknown>) => {
|
|
25
|
+
clientState.options = options;
|
|
26
|
+
return {
|
|
27
|
+
mutate: clientState.mutation,
|
|
28
|
+
isPending: clientState.isPending,
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock("sonner", () => ({ toast }));
|
|
34
|
+
|
|
35
|
+
const template: CuratedWorkspaceTemplate = {
|
|
36
|
+
id: "weekly-report",
|
|
37
|
+
name: "Weekly report",
|
|
38
|
+
description: "Turn workspace activity into a concise weekly report.",
|
|
39
|
+
source: "RevOps",
|
|
40
|
+
integrationSetup: "Connect your CRM after installing the app.",
|
|
41
|
+
liveUrl: "https://reports.example.test",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
describe("WorkspaceTemplateCard", () => {
|
|
45
|
+
let container: HTMLDivElement;
|
|
46
|
+
let root: Root;
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
50
|
+
clientState.mutation.mockReset();
|
|
51
|
+
clientState.options = null;
|
|
52
|
+
clientState.isPending = false;
|
|
53
|
+
toast.error.mockReset();
|
|
54
|
+
toast.success.mockReset();
|
|
55
|
+
container = document.createElement("div");
|
|
56
|
+
document.body.appendChild(container);
|
|
57
|
+
root = createRoot(container);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
act(() => root.unmount());
|
|
62
|
+
container.remove();
|
|
63
|
+
document.body
|
|
64
|
+
.querySelectorAll("[data-radix-portal]")
|
|
65
|
+
.forEach((portal) => portal.remove());
|
|
66
|
+
vi.unstubAllGlobals();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("shows template context and only links to a live app when supplied", async () => {
|
|
70
|
+
await act(async () => {
|
|
71
|
+
root.render(<WorkspaceTemplateCard template={template} />);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(container.textContent).toContain("Weekly report");
|
|
75
|
+
expect(container.textContent).toContain("RevOps");
|
|
76
|
+
expect(container.textContent).toContain(
|
|
77
|
+
"Connect your CRM after installing the app.",
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const liveLink = container.querySelector<HTMLAnchorElement>(
|
|
81
|
+
'a[href="https://reports.example.test"]',
|
|
82
|
+
);
|
|
83
|
+
expect(liveLink?.textContent).toContain("View the live app");
|
|
84
|
+
expect(liveLink?.target).toBe("_blank");
|
|
85
|
+
|
|
86
|
+
await act(async () => {
|
|
87
|
+
root.render(
|
|
88
|
+
<WorkspaceTemplateCard
|
|
89
|
+
template={{ ...template, liveUrl: null, productUrl: null }}
|
|
90
|
+
/>,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
expect(
|
|
94
|
+
container.querySelector('a[href="https://reports.example.test"]'),
|
|
95
|
+
).toBe(null);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("remixes with the default app id and allows an override", async () => {
|
|
99
|
+
await act(async () => {
|
|
100
|
+
root.render(
|
|
101
|
+
<WorkspaceTemplateCard template={template} defaultAppId="pipeline" />,
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const trigger = Array.from(container.querySelectorAll("button")).find(
|
|
106
|
+
(button) => button.textContent?.includes("Remix into workspace"),
|
|
107
|
+
);
|
|
108
|
+
expect(trigger).not.toBeUndefined();
|
|
109
|
+
|
|
110
|
+
await act(async () => {
|
|
111
|
+
trigger?.click();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const input = document.body.querySelector<HTMLInputElement>(
|
|
115
|
+
"#workspace-template-app-id-weekly-report",
|
|
116
|
+
);
|
|
117
|
+
expect(input?.value).toBe("pipeline");
|
|
118
|
+
|
|
119
|
+
await act(async () => {
|
|
120
|
+
if (!input) throw new Error("Expected app id input");
|
|
121
|
+
Object.getOwnPropertyDescriptor(
|
|
122
|
+
HTMLInputElement.prototype,
|
|
123
|
+
"value",
|
|
124
|
+
)?.set?.call(input, "sales-ops");
|
|
125
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await act(async () => {
|
|
129
|
+
document.body.querySelector<HTMLFormElement>("form")?.requestSubmit();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(clientState.mutation).toHaveBeenCalledWith({
|
|
133
|
+
templateId: "weekly-report",
|
|
134
|
+
appId: "sales-ops",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await act(async () => {
|
|
138
|
+
await (clientState.options?.onSuccess as (result: unknown) => unknown)({
|
|
139
|
+
appId: "sales-ops",
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
expect(toast.success).toHaveBeenCalledWith(
|
|
143
|
+
"Template remixed into your workspace.",
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("accepts the list action envelope and renders installed state", async () => {
|
|
148
|
+
await act(async () => {
|
|
149
|
+
root.render(
|
|
150
|
+
<WorkspaceTemplatesSection
|
|
151
|
+
templates={{
|
|
152
|
+
templates: [
|
|
153
|
+
{
|
|
154
|
+
...template,
|
|
155
|
+
installed: true,
|
|
156
|
+
liveUrl: null,
|
|
157
|
+
productUrl: null,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
}}
|
|
161
|
+
title="Curated templates"
|
|
162
|
+
/>,
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(container.textContent).toContain("Curated templates");
|
|
167
|
+
expect(container.textContent).toContain("Installed");
|
|
168
|
+
expect(container.querySelector('a[href^="https://"]')).toBeNull();
|
|
169
|
+
const remixButton = Array.from(container.querySelectorAll("button")).find(
|
|
170
|
+
(button) => button.textContent?.includes("Remix into workspace"),
|
|
171
|
+
);
|
|
172
|
+
expect(remixButton).not.toHaveProperty("disabled", true);
|
|
173
|
+
});
|
|
174
|
+
});
|