@agent-native/dispatch 0.2.11 → 0.2.13

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.
Files changed (84) hide show
  1. package/dist/actions/create-workspace-resource.js +4 -4
  2. package/dist/actions/create-workspace-resource.js.map +1 -1
  3. package/dist/actions/grant-workspace-resources-to-app.d.ts +3 -0
  4. package/dist/actions/grant-workspace-resources-to-app.d.ts.map +1 -0
  5. package/dist/actions/grant-workspace-resources-to-app.js +15 -0
  6. package/dist/actions/grant-workspace-resources-to-app.js.map +1 -0
  7. package/dist/actions/index.d.ts.map +1 -1
  8. package/dist/actions/index.js +4 -0
  9. package/dist/actions/index.js.map +1 -1
  10. package/dist/actions/list-workspace-resource-options.d.ts +3 -0
  11. package/dist/actions/list-workspace-resource-options.d.ts.map +1 -0
  12. package/dist/actions/list-workspace-resource-options.js +15 -0
  13. package/dist/actions/list-workspace-resource-options.js.map +1 -0
  14. package/dist/actions/list-workspace-resources.js +2 -2
  15. package/dist/actions/list-workspace-resources.js.map +1 -1
  16. package/dist/actions/start-workspace-app-creation.d.ts.map +1 -1
  17. package/dist/actions/start-workspace-app-creation.js +6 -1
  18. package/dist/actions/start-workspace-app-creation.js.map +1 -1
  19. package/dist/actions/view-screen.d.ts.map +1 -1
  20. package/dist/actions/view-screen.js +4 -0
  21. package/dist/actions/view-screen.js.map +1 -1
  22. package/dist/components/app-keys-popover.js +3 -3
  23. package/dist/components/app-keys-popover.js.map +1 -1
  24. package/dist/components/create-app-popover.d.ts +1 -1
  25. package/dist/components/create-app-popover.d.ts.map +1 -1
  26. package/dist/components/create-app-popover.js +66 -21
  27. package/dist/components/create-app-popover.js.map +1 -1
  28. package/dist/components/workspace-app-card.d.ts +6 -0
  29. package/dist/components/workspace-app-card.d.ts.map +1 -0
  30. package/dist/components/workspace-app-card.js +12 -0
  31. package/dist/components/workspace-app-card.js.map +1 -0
  32. package/dist/db/schema.js +2 -2
  33. package/dist/db/schema.js.map +1 -1
  34. package/dist/lib/workspace-apps.d.ts +15 -0
  35. package/dist/lib/workspace-apps.d.ts.map +1 -0
  36. package/dist/lib/workspace-apps.js +9 -0
  37. package/dist/lib/workspace-apps.js.map +1 -0
  38. package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
  39. package/dist/routes/pages/apps.$appId.js +1 -5
  40. package/dist/routes/pages/apps.$appId.js.map +1 -1
  41. package/dist/routes/pages/apps.d.ts.map +1 -1
  42. package/dist/routes/pages/apps.js +3 -20
  43. package/dist/routes/pages/apps.js.map +1 -1
  44. package/dist/routes/pages/overview.d.ts.map +1 -1
  45. package/dist/routes/pages/overview.js +2 -20
  46. package/dist/routes/pages/overview.js.map +1 -1
  47. package/dist/routes/pages/workspace.d.ts.map +1 -1
  48. package/dist/routes/pages/workspace.js +17 -6
  49. package/dist/routes/pages/workspace.js.map +1 -1
  50. package/dist/server/lib/app-creation-store.d.ts +1 -0
  51. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  52. package/dist/server/lib/app-creation-store.js +36 -0
  53. package/dist/server/lib/app-creation-store.js.map +1 -1
  54. package/dist/server/lib/workspace-resources-store.d.ts +29 -1
  55. package/dist/server/lib/workspace-resources-store.d.ts.map +1 -1
  56. package/dist/server/lib/workspace-resources-store.js +46 -0
  57. package/dist/server/lib/workspace-resources-store.js.map +1 -1
  58. package/dist/server/plugins/agent-chat.d.ts.map +1 -1
  59. package/dist/server/plugins/agent-chat.js +1 -0
  60. package/dist/server/plugins/agent-chat.js.map +1 -1
  61. package/dist/server/plugins/integrations.d.ts.map +1 -1
  62. package/dist/server/plugins/integrations.js +2 -0
  63. package/dist/server/plugins/integrations.js.map +1 -1
  64. package/package.json +2 -2
  65. package/src/actions/create-workspace-resource.ts +4 -4
  66. package/src/actions/grant-workspace-resources-to-app.ts +16 -0
  67. package/src/actions/index.ts +4 -0
  68. package/src/actions/list-workspace-resource-options.ts +16 -0
  69. package/src/actions/list-workspace-resources.ts +2 -2
  70. package/src/actions/start-workspace-app-creation.ts +8 -1
  71. package/src/actions/view-screen.ts +4 -0
  72. package/src/components/app-keys-popover.tsx +3 -3
  73. package/src/components/create-app-popover.tsx +155 -22
  74. package/src/components/workspace-app-card.tsx +85 -0
  75. package/src/db/schema.ts +2 -2
  76. package/src/lib/workspace-apps.ts +21 -0
  77. package/src/routes/pages/apps.$appId.tsx +4 -17
  78. package/src/routes/pages/apps.tsx +6 -89
  79. package/src/routes/pages/overview.tsx +5 -84
  80. package/src/routes/pages/workspace.tsx +31 -5
  81. package/src/server/lib/app-creation-store.ts +52 -0
  82. package/src/server/lib/workspace-resources-store.ts +75 -1
  83. package/src/server/plugins/agent-chat.ts +1 -0
  84. package/src/server/plugins/integrations.ts +2 -0
@@ -0,0 +1,85 @@
1
+ import { IconArrowUpRight, IconClockHour4 } from "@tabler/icons-react";
2
+ import { AppKeysPopover } from "@/components/app-keys-popover";
3
+ import { Badge } from "@/components/ui/badge";
4
+ import { cn } from "@/lib/utils";
5
+ import {
6
+ isPendingBuilderHref,
7
+ workspaceAppHref,
8
+ type WorkspaceAppSummary,
9
+ } from "@/lib/workspace-apps";
10
+
11
+ export function WorkspaceAppCard({
12
+ app,
13
+ className,
14
+ }: {
15
+ app: WorkspaceAppSummary;
16
+ className?: string;
17
+ }) {
18
+ const href = workspaceAppHref(app);
19
+ const openInNewTab = isPendingBuilderHref(app);
20
+
21
+ return (
22
+ <div
23
+ aria-disabled={!href}
24
+ className={cn(
25
+ "group relative rounded-lg border bg-card p-4 transition hover:border-foreground/30 aria-disabled:opacity-60",
26
+ className,
27
+ )}
28
+ >
29
+ {href ? (
30
+ <a
31
+ href={href}
32
+ target={openInNewTab ? "_blank" : undefined}
33
+ rel={openInNewTab ? "noreferrer" : undefined}
34
+ aria-label={`Open ${app.name}`}
35
+ className="absolute inset-0 z-0 rounded-lg"
36
+ />
37
+ ) : null}
38
+
39
+ <div className="pointer-events-none relative z-10 flex h-full items-start justify-between gap-3">
40
+ <div className="min-w-0">
41
+ <div className="flex min-w-0 items-center gap-2">
42
+ <h3 className="truncate text-sm font-semibold text-foreground">
43
+ {app.name}
44
+ </h3>
45
+ {app.status === "pending" ? (
46
+ <Badge
47
+ variant="outline"
48
+ className="shrink-0 gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
49
+ >
50
+ <IconClockHour4 size={12} />
51
+ Building
52
+ </Badge>
53
+ ) : null}
54
+ </div>
55
+ <p className="mt-1 truncate font-mono text-xs text-muted-foreground">
56
+ {app.path}
57
+ </p>
58
+ {app.status === "pending" && app.branchName ? (
59
+ <p className="mt-1 truncate text-xs text-muted-foreground">
60
+ Branch: {app.branchName}
61
+ </p>
62
+ ) : null}
63
+ {app.description ? (
64
+ <p className="mt-2 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
65
+ {app.description}
66
+ </p>
67
+ ) : null}
68
+ </div>
69
+ <div className="flex shrink-0 items-center gap-1">
70
+ {app.status === "ready" ? (
71
+ <div className="pointer-events-auto">
72
+ <AppKeysPopover appId={app.id} appName={app.name} />
73
+ </div>
74
+ ) : null}
75
+ {href ? (
76
+ <IconArrowUpRight
77
+ size={16}
78
+ className="text-muted-foreground transition group-hover:text-foreground"
79
+ />
80
+ ) : null}
81
+ </div>
82
+ </div>
83
+ </div>
84
+ );
85
+ }
package/src/db/schema.ts CHANGED
@@ -130,13 +130,13 @@ export const vaultAuditLog = table("vault_audit_log", {
130
130
  createdAt: integer("created_at").notNull(),
131
131
  });
132
132
 
133
- // ─── Workspace Resources: shared skills, instructions, agents ──────
133
+ // ─── Workspace Resources: shared skills, instructions, agents, knowledge ──────
134
134
 
135
135
  export const workspaceResources = table("workspace_resources", {
136
136
  id: text("id").primaryKey(),
137
137
  ownerEmail: text("owner_email").notNull(),
138
138
  orgId: text("org_id"),
139
- kind: text("kind").notNull(), // "skill" | "instruction" | "agent"
139
+ kind: text("kind").notNull(), // "skill" | "instruction" | "agent" | "knowledge"
140
140
  name: text("name").notNull(),
141
141
  description: text("description"),
142
142
  path: text("path").notNull(), // resource path, e.g. "skills/designer.md"
@@ -0,0 +1,21 @@
1
+ export interface WorkspaceAppSummary {
2
+ id: string;
3
+ name: string;
4
+ description?: string;
5
+ path: string;
6
+ url?: string | null;
7
+ isDispatch?: boolean;
8
+ status?: "ready" | "pending";
9
+ statusLabel?: string;
10
+ builderUrl?: string | null;
11
+ branchName?: string | null;
12
+ }
13
+
14
+ export function workspaceAppHref(app: WorkspaceAppSummary): string | null {
15
+ if (app.status === "pending") return app.builderUrl || null;
16
+ return app.path || app.url || null;
17
+ }
18
+
19
+ export function isPendingBuilderHref(app: WorkspaceAppSummary): boolean {
20
+ return app.status === "pending" && !!app.builderUrl;
21
+ }
@@ -9,23 +9,10 @@ import {
9
9
  import { DispatchShell } from "@/components/dispatch-shell";
10
10
  import { Badge } from "@/components/ui/badge";
11
11
  import { Button } from "@/components/ui/button";
12
-
13
- interface WorkspaceAppSummary {
14
- id: string;
15
- name: string;
16
- description?: string;
17
- path: string;
18
- url?: string | null;
19
- status?: "ready" | "pending";
20
- statusLabel?: string;
21
- builderUrl?: string | null;
22
- branchName?: string | null;
23
- }
24
-
25
- function workspaceAppHref(app: WorkspaceAppSummary): string | null {
26
- if (app.status === "pending") return app.builderUrl || null;
27
- return app.path || app.url || null;
28
- }
12
+ import {
13
+ workspaceAppHref,
14
+ type WorkspaceAppSummary,
15
+ } from "@/lib/workspace-apps";
29
16
 
30
17
  export function meta() {
31
18
  return [{ title: "Workspace app - Dispatch" }];
@@ -1,37 +1,10 @@
1
1
  import { useActionQuery } from "@agent-native/core/client";
2
- import {
3
- IconArrowUpRight,
4
- IconApps,
5
- IconClockHour4,
6
- IconPlus,
7
- } from "@tabler/icons-react";
8
- import { AppKeysPopover } from "@/components/app-keys-popover";
2
+ import { IconApps, IconPlus } from "@tabler/icons-react";
9
3
  import { CreateAppPopover } from "@/components/create-app-popover";
10
4
  import { DispatchShell } from "@/components/dispatch-shell";
11
- import { Badge } from "@/components/ui/badge";
5
+ import { WorkspaceAppCard } from "@/components/workspace-app-card";
12
6
  import { Button } from "@/components/ui/button";
13
-
14
- interface WorkspaceAppSummary {
15
- id: string;
16
- name: string;
17
- description?: string;
18
- path: string;
19
- url?: string | null;
20
- isDispatch: boolean;
21
- status?: "ready" | "pending";
22
- statusLabel?: string;
23
- builderUrl?: string | null;
24
- branchName?: string | null;
25
- }
26
-
27
- function workspaceAppHref(app: WorkspaceAppSummary): string | null {
28
- if (app.status === "pending") return app.builderUrl || null;
29
- return app.path || app.url || null;
30
- }
31
-
32
- function isPendingBuilderHref(app: WorkspaceAppSummary): boolean {
33
- return app.status === "pending" && !!app.builderUrl;
34
- }
7
+ import type { WorkspaceAppSummary } from "@/lib/workspace-apps";
35
8
 
36
9
  export function meta() {
37
10
  return [{ title: "Apps — Dispatch" }];
@@ -94,65 +67,9 @@ export default function AppsRoute() {
94
67
  </div>
95
68
 
96
69
  <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
97
- {typedApps.map((app) => {
98
- const href = workspaceAppHref(app);
99
- // Pending Builder branches live on a different host; open
100
- // those in a new tab. Ready workspace apps stay in-window
101
- // so the cards work inside the Builder webview, where new
102
- // tabs would escape to the host browser.
103
- const openInNewTab = isPendingBuilderHref(app);
104
- return (
105
- <a
106
- key={app.id}
107
- href={href ?? undefined}
108
- target={openInNewTab ? "_blank" : undefined}
109
- rel={openInNewTab ? "noreferrer" : undefined}
110
- aria-disabled={!href}
111
- className="group rounded-lg border bg-card p-4 transition hover:border-foreground/30 aria-disabled:pointer-events-none aria-disabled:opacity-60"
112
- >
113
- <div className="flex items-start justify-between gap-3">
114
- <div className="min-w-0">
115
- <div className="flex min-w-0 items-center gap-2">
116
- <h3 className="truncate text-sm font-semibold text-foreground">
117
- {app.name}
118
- </h3>
119
- {app.status === "pending" ? (
120
- <Badge
121
- variant="outline"
122
- className="shrink-0 gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
123
- >
124
- <IconClockHour4 size={12} />
125
- Building
126
- </Badge>
127
- ) : null}
128
- </div>
129
- <p className="mt-1 truncate font-mono text-xs text-muted-foreground">
130
- {app.path}
131
- </p>
132
- {app.status === "pending" && app.branchName ? (
133
- <p className="mt-1 truncate text-xs text-muted-foreground">
134
- Branch: {app.branchName}
135
- </p>
136
- ) : null}
137
- {app.description ? (
138
- <p className="mt-2 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
139
- {app.description}
140
- </p>
141
- ) : null}
142
- </div>
143
- <div className="flex shrink-0 items-center gap-1">
144
- {app.status === "ready" ? (
145
- <AppKeysPopover appId={app.id} appName={app.name} />
146
- ) : null}
147
- <IconArrowUpRight
148
- size={16}
149
- className="text-muted-foreground transition group-hover:text-foreground"
150
- />
151
- </div>
152
- </div>
153
- </a>
154
- );
155
- })}
70
+ {typedApps.map((app) => (
71
+ <WorkspaceAppCard key={app.id} app={app} />
72
+ ))}
156
73
 
157
74
  <CreateAppPopover />
158
75
  </div>
@@ -21,11 +21,10 @@ import {
21
21
  IconShieldCheck,
22
22
  type IconProps,
23
23
  } from "@tabler/icons-react";
24
- import { AppKeysPopover } from "@/components/app-keys-popover";
25
24
  import { CreateAppPopover } from "@/components/create-app-popover";
26
25
  import { DispatchShell } from "@/components/dispatch-shell";
26
+ import { WorkspaceAppCard } from "@/components/workspace-app-card";
27
27
  import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
28
- import { Badge } from "@/components/ui/badge";
29
28
  import { Button } from "@/components/ui/button";
30
29
  import { Skeleton } from "@/components/ui/skeleton";
31
30
  import {
@@ -34,6 +33,7 @@ import {
34
33
  TooltipTrigger,
35
34
  } from "@/components/ui/tooltip";
36
35
  import { submitOverviewPrompt } from "@/lib/overview-chat";
36
+ import type { WorkspaceAppSummary } from "@/lib/workspace-apps";
37
37
 
38
38
  interface IntegrationStatus {
39
39
  platform: string;
@@ -67,28 +67,6 @@ const ZERO_TASK_QUEUE_STATS: TaskQueueStats = {
67
67
  recent_failures: [],
68
68
  };
69
69
 
70
- interface WorkspaceAppSummary {
71
- id: string;
72
- name: string;
73
- description?: string;
74
- path: string;
75
- url?: string | null;
76
- isDispatch: boolean;
77
- status?: "ready" | "pending";
78
- statusLabel?: string;
79
- builderUrl?: string | null;
80
- branchName?: string | null;
81
- }
82
-
83
- function workspaceAppHref(app: WorkspaceAppSummary): string | null {
84
- if (app.status === "pending") return app.builderUrl || null;
85
- return app.path || app.url || null;
86
- }
87
-
88
- function isPendingBuilderHref(app: WorkspaceAppSummary): boolean {
89
- return app.status === "pending" && !!app.builderUrl;
90
- }
91
-
92
70
  const HOME_CHAT_SUGGESTIONS = [
93
71
  "Create a lightweight customer onboarding app",
94
72
  "Ask Slides to draft a board update from our latest metrics",
@@ -186,66 +164,9 @@ function WorkspaceAppsSection({
186
164
  ? Array.from({ length: 6 }).map((_, index) => (
187
165
  <AppCardSkeleton key={index} />
188
166
  ))
189
- : visibleApps.map((app) => {
190
- const href = workspaceAppHref(app);
191
- // Pending Builder branches live on a different host (Builder
192
- // editor URL); open those in a new tab. Ready workspace apps
193
- // navigate the current window so this works inside the
194
- // Builder webview, where new tabs would try to open in the
195
- // host browser and break the embedded session.
196
- const openInNewTab = isPendingBuilderHref(app);
197
- return (
198
- <a
199
- key={app.id}
200
- href={href ?? undefined}
201
- target={openInNewTab ? "_blank" : undefined}
202
- rel={openInNewTab ? "noreferrer" : undefined}
203
- aria-disabled={!href}
204
- className="group min-h-32 rounded-lg border bg-card p-4 transition hover:border-foreground/30 aria-disabled:pointer-events-none aria-disabled:opacity-60"
205
- >
206
- <div className="flex h-full items-start justify-between gap-3">
207
- <div className="min-w-0">
208
- <div className="flex min-w-0 items-center gap-2">
209
- <h3 className="truncate text-sm font-semibold text-foreground">
210
- {app.name}
211
- </h3>
212
- {app.status === "pending" ? (
213
- <Badge
214
- variant="outline"
215
- className="shrink-0 gap-1 border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300"
216
- >
217
- <IconClockHour4 size={12} />
218
- Building
219
- </Badge>
220
- ) : null}
221
- </div>
222
- <p className="mt-1 truncate font-mono text-xs text-muted-foreground">
223
- {app.path}
224
- </p>
225
- {app.status === "pending" && app.branchName ? (
226
- <p className="mt-1 truncate text-xs text-muted-foreground">
227
- Branch: {app.branchName}
228
- </p>
229
- ) : null}
230
- {app.description ? (
231
- <p className="mt-2 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
232
- {app.description}
233
- </p>
234
- ) : null}
235
- </div>
236
- <div className="flex shrink-0 items-center gap-1">
237
- {app.status === "ready" ? (
238
- <AppKeysPopover appId={app.id} appName={app.name} />
239
- ) : null}
240
- <IconArrowUpRight
241
- size={16}
242
- className="text-muted-foreground transition group-hover:text-foreground"
243
- />
244
- </div>
245
- </div>
246
- </a>
247
- );
248
- })}
167
+ : visibleApps.map((app) => (
168
+ <WorkspaceAppCard key={app.id} app={app} className="min-h-32" />
169
+ ))}
249
170
 
250
171
  {!showSkeletons ? <CreateAppPopover /> : null}
251
172
  </div>
@@ -6,6 +6,7 @@ import {
6
6
  IconChevronDown,
7
7
  IconChevronRight,
8
8
  IconCode,
9
+ IconFileText,
9
10
  IconPlus,
10
11
  IconRefresh,
11
12
  IconTrash,
@@ -72,6 +73,13 @@ const KIND_CONFIG = {
72
73
  description:
73
74
  "Reusable agent profiles — specialist agents shared across apps",
74
75
  },
76
+ knowledge: {
77
+ label: "Knowledge",
78
+ icon: IconFileText,
79
+ pathPrefix: "context/",
80
+ description:
81
+ "Knowledge packs — reusable GTM, product, and domain context for apps",
82
+ },
75
83
  } as const;
76
84
 
77
85
  function AddResourceDialog() {
@@ -127,6 +135,7 @@ function AddResourceDialog() {
127
135
  <SelectItem value="skill">Skill</SelectItem>
128
136
  <SelectItem value="instruction">Instruction</SelectItem>
129
137
  <SelectItem value="agent">Agent</SelectItem>
138
+ <SelectItem value="knowledge">Knowledge pack</SelectItem>
130
139
  </SelectContent>
131
140
  </Select>
132
141
  </div>
@@ -151,7 +160,9 @@ function AddResourceDialog() {
151
160
  ? "Frontend Designer"
152
161
  : kind === "agent"
153
162
  ? "Research Specialist"
154
- : "Code Style Guide"
163
+ : kind === "knowledge"
164
+ ? "Core GTM Messaging"
165
+ : "Code Style Guide"
155
166
  }
156
167
  value={name}
157
168
  onChange={(e) => setName(e.target.value)}
@@ -167,7 +178,7 @@ function AddResourceDialog() {
167
178
  />
168
179
  <p className="text-xs text-muted-foreground">
169
180
  Resource path in target apps. Skills go in skills/, agents in
170
- agents/.
181
+ agents/, knowledge packs in context/.
171
182
  </p>
172
183
  </div>
173
184
  <div className="space-y-2">
@@ -186,7 +197,9 @@ function AddResourceDialog() {
186
197
  ? "---\nname: my-skill\ndescription: What this skill teaches\n---\n\n# My Skill\n\n..."
187
198
  : kind === "agent"
188
199
  ? "---\nname: Research Specialist\ndescription: Handles research tasks\n---\n\n# Instructions\n\n..."
189
- : "# Instructions\n\nBehavioral rules and guidance for agents across apps..."
200
+ : kind === "knowledge"
201
+ ? "# Core GTM Messaging\n\n## Positioning\n\n## ICP\n\n## Proof points\n\n## Source\n\n"
202
+ : "# Instructions\n\nBehavioral rules and guidance for agents across apps..."
190
203
  }
191
204
  value={content}
192
205
  onChange={(e) => setContent(e.target.value)}
@@ -199,7 +212,7 @@ function AddResourceDialog() {
199
212
  <Button
200
213
  onClick={() =>
201
214
  create.mutate({
202
- kind: kind as "skill" | "instruction" | "agent",
215
+ kind: kind as "skill" | "instruction" | "agent" | "knowledge",
203
216
  name,
204
217
  description: description || undefined,
205
218
  path:
@@ -504,6 +517,9 @@ export default function WorkspaceRoute() {
504
517
  (r: any) => r.kind === "instruction",
505
518
  );
506
519
  const agents = (resources || []).filter((r: any) => r.kind === "agent");
520
+ const knowledge = (resources || []).filter(
521
+ (r: any) => r.kind === "knowledge",
522
+ );
507
523
 
508
524
  function ResourceList({
509
525
  items,
@@ -535,7 +551,7 @@ export default function WorkspaceRoute() {
535
551
  return (
536
552
  <DispatchShell
537
553
  title="Workspace Resources"
538
- description="Share skills, instructions, and agent profiles across workspace apps. Scope to all apps or grant per-app."
554
+ description="Share skills, instructions, agent profiles, and knowledge packs across workspace apps. Scope to all apps or grant per-app."
539
555
  >
540
556
  <div className="flex items-center justify-between">
541
557
  <div className="text-sm text-muted-foreground">
@@ -570,6 +586,9 @@ export default function WorkspaceRoute() {
570
586
  <TabsTrigger value="agents">
571
587
  Agents {agents.length > 0 && `(${agents.length})`}
572
588
  </TabsTrigger>
589
+ <TabsTrigger value="knowledge">
590
+ Knowledge {knowledge.length > 0 && `(${knowledge.length})`}
591
+ </TabsTrigger>
573
592
  </TabsList>
574
593
 
575
594
  <TabsContent value="skills" className="mt-4">
@@ -592,6 +611,13 @@ export default function WorkspaceRoute() {
592
611
  emptyText="No workspace agents yet. Add a reusable agent profile to share specialist agents across apps."
593
612
  />
594
613
  </TabsContent>
614
+
615
+ <TabsContent value="knowledge" className="mt-4">
616
+ <ResourceList
617
+ items={knowledge}
618
+ emptyText="No knowledge packs yet. Add GTM, product, or domain context that apps can reuse."
619
+ />
620
+ </TabsContent>
595
621
  </Tabs>
596
622
  </DispatchShell>
597
623
  );
@@ -20,6 +20,11 @@ import {
20
20
  } from "./dispatch-store.js";
21
21
  import { identityKeyForIncoming } from "./dispatch-integrations.js";
22
22
  import { createRequest, listSecrets } from "./vault-store.js";
23
+ import {
24
+ grantWorkspaceResourcesToApp,
25
+ listWorkspaceResourceOptions,
26
+ type WorkspaceResourceOption,
27
+ } from "./workspace-resources-store.js";
23
28
 
24
29
  const SETTINGS_KEY = "dispatch-app-creation-settings";
25
30
  const WORKSPACE_APPS_ENV_KEY = "AGENT_NATIVE_WORKSPACE_APPS_JSON";
@@ -827,6 +832,7 @@ function buildWorkspaceAppPrompt(input: {
827
832
  appId?: string | null;
828
833
  template?: string | null;
829
834
  selectedKeys?: string[];
835
+ selectedResources?: WorkspaceResourceOption[];
830
836
  }): { appId: string; prompt: string } {
831
837
  const appId =
832
838
  slugify(input.appId || "") ||
@@ -835,6 +841,15 @@ function buildWorkspaceAppPrompt(input: {
835
841
  ) ||
836
842
  "new-app";
837
843
  const selectedKeys = input.selectedKeys || [];
844
+ const selectedResources = input.selectedResources || [];
845
+ const resourceList = selectedResources.length
846
+ ? selectedResources
847
+ .map(
848
+ (resource) =>
849
+ `- ${resource.name} (${resource.kind}, ${resource.path})`,
850
+ )
851
+ .join("\n")
852
+ : "none";
838
853
  return {
839
854
  appId,
840
855
  prompt: [
@@ -846,11 +861,18 @@ function buildWorkspaceAppPrompt(input: {
846
861
  selectedKeys.length
847
862
  ? `Dispatch vault keys selected for this app: ${selectedKeys.join(", ")}`
848
863
  : "Dispatch vault keys selected for this app: none",
864
+ `Dispatch workspace resources selected for this app:\n${resourceList}`,
849
865
  "",
850
866
  `Use the workspace app layout: create it under apps/${appId}, mount it at /${appId}, keep it on the shared workspace database/hosting model, and avoid table-name collisions by namespacing any new domain tables to the app.`,
867
+ "Existing first-party apps are neighbors, not implementation details for this app. If the user prompt mentions Mail, Calendar, Analytics, Dispatch, or other templates, treat them as existing hosted/connected apps that this app can link to or call through A2A/default connected agents. For example, Mail, Calendar, and Analytics already exist at https://mail.agent-native.com, https://calendar.agent-native.com, and https://analytics.agent-native.com.",
868
+ `Do not clone first-party templates, create wrapper apps, or scaffold child apps/routes for Mail, Calendar, Analytics, etc. inside apps/${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.`,
869
+ "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.",
851
870
  selectedKeys.length
852
871
  ? `Dispatch will create pending vault requests for the selected keys for appId "${appId}" after this app creation request is accepted. Do not grant or sync vault keys directly from the app-creation branch.`
853
872
  : "Do not grant or request any Dispatch vault keys unless the user asks later.",
873
+ selectedResources.length
874
+ ? `Dispatch will create workspace resource grants for the selected resources for appId "${appId}". After the app exists, sync workspace resources so the app receives those shared resources. Add a short note to apps/${appId}/AGENTS.md telling the app agent to read relevant shared resources under context/ or the selected resource paths before doing GTM/domain work.`
875
+ : "Do not grant any Dispatch workspace resources unless the user asks later.",
854
876
  "",
855
877
  "Agent-native rules (these are the framework's contract — not optional):",
856
878
  `- Persist ALL data in SQL via Drizzle. Add tables to apps/${appId}/server/db/schema.ts and migrations to apps/${appId}/server/plugins/db.ts. NEVER use localStorage, sessionStorage, IndexedDB, or in-memory state for anything the user expects to persist — agent and UI must read the same source of truth.`,
@@ -890,11 +912,29 @@ async function requestSelectedVaultKeys(input: {
890
912
  );
891
913
  }
892
914
 
915
+ async function selectedWorkspaceResourcesForIds(
916
+ resourceIds: string[] | undefined,
917
+ ): Promise<WorkspaceResourceOption[]> {
918
+ if (!resourceIds?.length) return [];
919
+ const requested = new Set(resourceIds);
920
+ const resources = await listWorkspaceResourceOptions();
921
+ return resources.filter((resource) => requested.has(resource.id));
922
+ }
923
+
924
+ async function grantSelectedWorkspaceResources(input: {
925
+ appId: string;
926
+ resourceIds: string[];
927
+ }) {
928
+ if (input.resourceIds.length === 0) return;
929
+ await grantWorkspaceResourcesToApp(input);
930
+ }
931
+
893
932
  export async function startWorkspaceAppCreation(input: {
894
933
  prompt: string;
895
934
  appId?: string | null;
896
935
  template?: string | null;
897
936
  secretIds?: string[];
937
+ resourceIds?: string[];
898
938
  }) {
899
939
  const initial = buildWorkspaceAppPrompt({
900
940
  prompt: input.prompt,
@@ -920,11 +960,15 @@ export async function startWorkspaceAppCreation(input: {
920
960
  .filter((secret) => input.secretIds?.includes(secret.id))
921
961
  .map((secret) => secret.credentialKey)
922
962
  : [];
963
+ const selectedResources = await selectedWorkspaceResourcesForIds(
964
+ input.resourceIds,
965
+ );
923
966
  const built = buildWorkspaceAppPrompt({
924
967
  prompt: input.prompt,
925
968
  appId: input.appId,
926
969
  template: input.template,
927
970
  selectedKeys,
971
+ selectedResources,
928
972
  });
929
973
  const prompt = built.prompt;
930
974
 
@@ -933,6 +977,10 @@ export async function startWorkspaceAppCreation(input: {
933
977
  appId: built.appId,
934
978
  selectedKeys,
935
979
  });
980
+ await grantSelectedWorkspaceResources({
981
+ appId: built.appId,
982
+ resourceIds: selectedResources.map((resource) => resource.id),
983
+ });
936
984
  return {
937
985
  mode: "local-agent",
938
986
  appId: built.appId,
@@ -997,6 +1045,10 @@ export async function startWorkspaceAppCreation(input: {
997
1045
  appId: built.appId,
998
1046
  selectedKeys,
999
1047
  });
1048
+ await grantSelectedWorkspaceResources({
1049
+ appId: built.appId,
1050
+ resourceIds: selectedResources.map((resource) => resource.id),
1051
+ });
1000
1052
 
1001
1053
  return {
1002
1054
  mode: "builder",