@agent-native/dispatch 0.2.10 → 0.2.12

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 (55) 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 +5 -0
  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/create-app-popover.d.ts +1 -1
  23. package/dist/components/create-app-popover.d.ts.map +1 -1
  24. package/dist/components/create-app-popover.js +63 -20
  25. package/dist/components/create-app-popover.js.map +1 -1
  26. package/dist/db/schema.js +2 -2
  27. package/dist/db/schema.js.map +1 -1
  28. package/dist/routes/pages/overview.d.ts.map +1 -1
  29. package/dist/routes/pages/overview.js +6 -6
  30. package/dist/routes/pages/overview.js.map +1 -1
  31. package/dist/routes/pages/workspace.d.ts.map +1 -1
  32. package/dist/routes/pages/workspace.js +17 -6
  33. package/dist/routes/pages/workspace.js.map +1 -1
  34. package/dist/server/lib/app-creation-store.d.ts +1 -0
  35. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  36. package/dist/server/lib/app-creation-store.js +33 -0
  37. package/dist/server/lib/app-creation-store.js.map +1 -1
  38. package/dist/server/lib/workspace-resources-store.d.ts +29 -1
  39. package/dist/server/lib/workspace-resources-store.d.ts.map +1 -1
  40. package/dist/server/lib/workspace-resources-store.js +46 -0
  41. package/dist/server/lib/workspace-resources-store.js.map +1 -1
  42. package/package.json +2 -3
  43. package/src/actions/create-workspace-resource.ts +4 -4
  44. package/src/actions/grant-workspace-resources-to-app.ts +16 -0
  45. package/src/actions/index.ts +4 -0
  46. package/src/actions/list-workspace-resource-options.ts +16 -0
  47. package/src/actions/list-workspace-resources.ts +2 -2
  48. package/src/actions/start-workspace-app-creation.ts +7 -0
  49. package/src/actions/view-screen.ts +4 -0
  50. package/src/components/create-app-popover.tsx +152 -21
  51. package/src/db/schema.ts +2 -2
  52. package/src/routes/pages/overview.tsx +21 -19
  53. package/src/routes/pages/workspace.tsx +31 -5
  54. package/src/server/lib/app-creation-store.ts +49 -0
  55. package/src/server/lib/workspace-resources-store.ts +75 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/dispatch",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "type": "module",
5
5
  "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.",
6
6
  "license": "MIT",
@@ -74,7 +74,6 @@
74
74
  "embla-carousel-react": "^8.6.0",
75
75
  "h3": "^2.0.1-rc.20",
76
76
  "input-otp": "^1.4.2",
77
- "lucide-react": "^1.8.0",
78
77
  "next-themes": "^0.4.6",
79
78
  "react-day-picker": "^9.14.0",
80
79
  "react-hook-form": "^7.71.2",
@@ -97,7 +96,7 @@
97
96
  "typescript": "^6.0.3",
98
97
  "vite": "8.0.3",
99
98
  "vitest": "^4.1.5",
100
- "@agent-native/core": "0.12.11"
99
+ "@agent-native/core": "0.12.14"
101
100
  },
102
101
  "scripts": {
103
102
  "build": "tsc && tsc-alias --resolve-full-paths",
@@ -4,17 +4,17 @@ import { createWorkspaceResource } from "../server/lib/workspace-resources-store
4
4
 
5
5
  export default defineAction({
6
6
  description:
7
- 'Create a workspace-wide skill, instruction, or agent profile. Set scope to "all" to push to every app, or "selected" to grant per-app.',
7
+ 'Create a workspace-wide skill, instruction, agent profile, or knowledge pack. Set scope to "all" to push to every app, or "selected" to grant per-app.',
8
8
  schema: z.object({
9
9
  kind: z
10
- .enum(["skill", "instruction", "agent"])
11
- .describe("Resource kind: skill, instruction, or agent"),
10
+ .enum(["skill", "instruction", "agent", "knowledge"])
11
+ .describe("Resource kind: skill, instruction, agent, or knowledge"),
12
12
  name: z.string().describe("Human-readable name"),
13
13
  description: z.string().optional().describe("Short description"),
14
14
  path: z
15
15
  .string()
16
16
  .describe(
17
- 'Resource path, e.g. "skills/designer.md", "agents/researcher.md", or "remote-agents/researcher.json"',
17
+ 'Resource path, e.g. "skills/designer.md", "agents/researcher.md", "context/gtm-messaging.md", or "remote-agents/researcher.json"',
18
18
  ),
19
19
  content: z
20
20
  .string()
@@ -0,0 +1,16 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+ import { grantWorkspaceResourcesToApp } from "../server/lib/workspace-resources-store.js";
4
+
5
+ export default defineAction({
6
+ description:
7
+ "Grant several selected workspace resources or knowledge packs to an app, skipping existing active grants.",
8
+ schema: z.object({
9
+ appId: z.string().describe("App ID receiving the resources"),
10
+ resourceIds: z
11
+ .array(z.string())
12
+ .max(100)
13
+ .describe("Workspace resource IDs to grant"),
14
+ }),
15
+ run: async (args) => grantWorkspaceResourcesToApp(args),
16
+ });
@@ -13,6 +13,7 @@ import denyVaultRequest from "./deny-vault-request.js";
13
13
  import getAppCreationSettings from "./get-app-creation-settings.js";
14
14
  import getDispatchSettings from "./get-dispatch-settings.js";
15
15
  import getWorkspaceInfo from "./get-workspace-info.js";
16
+ import grantWorkspaceResourcesToApp from "./grant-workspace-resources-to-app.js";
16
17
  import grantVaultSecretsToApp from "./grant-vault-secrets-to-app.js";
17
18
  import listConnectedAgents from "./list-connected-agents.js";
18
19
  import listDestinations from "./list-destinations.js";
@@ -28,6 +29,7 @@ import listVaultRequests from "./list-vault-requests.js";
28
29
  import listVaultSecretOptions from "./list-vault-secret-options.js";
29
30
  import listVaultSecrets from "./list-vault-secrets.js";
30
31
  import listWorkspaceApps from "./list-workspace-apps.js";
32
+ import listWorkspaceResourceOptions from "./list-workspace-resource-options.js";
31
33
  import listWorkspaceResourceGrants from "./list-workspace-resource-grants.js";
32
34
  import listWorkspaceResources from "./list-workspace-resources.js";
33
35
  import navigate from "./navigate.js";
@@ -68,6 +70,7 @@ export const dispatchActions: Record<string, ActionEntry> = {
68
70
  "get-app-creation-settings": getAppCreationSettings,
69
71
  "get-dispatch-settings": getDispatchSettings,
70
72
  "get-workspace-info": getWorkspaceInfo,
73
+ "grant-workspace-resources-to-app": grantWorkspaceResourcesToApp,
71
74
  "grant-vault-secrets-to-app": grantVaultSecretsToApp,
72
75
  "list-connected-agents": listConnectedAgents,
73
76
  "list-destinations": listDestinations,
@@ -83,6 +86,7 @@ export const dispatchActions: Record<string, ActionEntry> = {
83
86
  "list-vault-secret-options": listVaultSecretOptions,
84
87
  "list-vault-secrets": listVaultSecrets,
85
88
  "list-workspace-apps": listWorkspaceApps,
89
+ "list-workspace-resource-options": listWorkspaceResourceOptions,
86
90
  "list-workspace-resource-grants": listWorkspaceResourceGrants,
87
91
  "list-workspace-resources": listWorkspaceResources,
88
92
  navigate: navigate,
@@ -0,0 +1,16 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+ import { listWorkspaceResourceOptions } from "../server/lib/workspace-resources-store.js";
4
+
5
+ export default defineAction({
6
+ description:
7
+ "List lightweight workspace resource options for selectors, without returning full content.",
8
+ schema: z.object({
9
+ kind: z
10
+ .enum(["skill", "instruction", "agent", "knowledge"])
11
+ .optional()
12
+ .describe("Filter by resource kind"),
13
+ }),
14
+ http: { method: "GET" },
15
+ run: async (args) => listWorkspaceResourceOptions(args),
16
+ });
@@ -4,10 +4,10 @@ import { listWorkspaceResources } from "../server/lib/workspace-resources-store.
4
4
 
5
5
  export default defineAction({
6
6
  description:
7
- "List all workspace-wide resources (skills, instructions, agents) that can be shared across apps.",
7
+ "List all workspace-wide resources (skills, instructions, agents, and knowledge packs) that can be shared across apps.",
8
8
  schema: z.object({
9
9
  kind: z
10
- .enum(["skill", "instruction", "agent"])
10
+ .enum(["skill", "instruction", "agent", "knowledge"])
11
11
  .optional()
12
12
  .describe("Filter by resource kind"),
13
13
  }),
@@ -28,6 +28,13 @@ export default defineAction({
28
28
  .max(100)
29
29
  .optional()
30
30
  .describe("Dispatch vault secret IDs to grant to the app"),
31
+ resourceIds: z
32
+ .array(z.string())
33
+ .max(100)
34
+ .optional()
35
+ .describe(
36
+ "Dispatch workspace resource IDs or knowledge packs to grant to the app",
37
+ ),
31
38
  }),
32
39
  run: async (args) => startWorkspaceAppCreation(args),
33
40
  });
@@ -19,6 +19,7 @@ import {
19
19
  } from "../server/lib/vault-store.js";
20
20
  import { listWorkspaceApps } from "../server/lib/app-creation-store.js";
21
21
  import { listDispatchUsageMetrics } from "../server/lib/usage-metrics-store.js";
22
+ import { listWorkspaceResourceOptions } from "../server/lib/workspace-resources-store.js";
22
23
 
23
24
  export default defineAction({
24
25
  description:
@@ -88,6 +89,9 @@ export default defineAction({
88
89
  .map((g) => ({ secretId: g.secretId, appId: g.appId }));
89
90
  screen.vaultPendingRequests = requests;
90
91
  }
92
+ if (navigation?.view === "workspace" || navigation?.view === "new-app") {
93
+ screen.workspaceResources = await listWorkspaceResourceOptions();
94
+ }
91
95
 
92
96
  if (Object.keys(screen).length === 0) {
93
97
  return "No application state found. Is the app running?";
@@ -11,8 +11,10 @@ import { getWorkspaceAppIdValidationError } from "@agent-native/core/shared";
11
11
  import {
12
12
  IconArrowLeft,
13
13
  IconArrowUpRight,
14
+ IconBook,
14
15
  IconCheck,
15
16
  IconChevronDown,
17
+ IconFileText,
16
18
  IconKey,
17
19
  IconLoader2,
18
20
  IconPlus,
@@ -32,6 +34,16 @@ interface VaultSecretOption {
32
34
  description?: string | null;
33
35
  }
34
36
 
37
+ interface WorkspaceResourceOption {
38
+ id: string;
39
+ kind: "skill" | "instruction" | "agent" | "knowledge";
40
+ name: string;
41
+ description?: string | null;
42
+ path: string;
43
+ scope: "all" | "selected";
44
+ updatedAt?: number;
45
+ }
46
+
35
47
  interface CreateAppPopoverProps {
36
48
  /**
37
49
  * Custom trigger element. Defaults to a dashed-border tile that matches the
@@ -65,11 +77,20 @@ function buildAppCreationPrompt(input: {
65
77
  appId: string;
66
78
  prompt: string;
67
79
  selectedKeys: string[];
80
+ selectedResources: WorkspaceResourceOption[];
68
81
  }): string {
69
82
  const keyList = input.selectedKeys.join(", ");
70
83
  const grantRequest = keyList
71
84
  ? `Requested Dispatch vault key grants for this app: ${keyList}`
72
85
  : `Requested Dispatch vault key grants for this app: none`;
86
+ const resourceList = input.selectedResources.length
87
+ ? input.selectedResources
88
+ .map(
89
+ (resource) =>
90
+ `- ${resource.name} (${resource.kind}, ${resource.path})`,
91
+ )
92
+ .join("\n")
93
+ : "none";
73
94
 
74
95
  return [
75
96
  `Create a new agent-native app in this workspace.`,
@@ -78,6 +99,7 @@ function buildAppCreationPrompt(input: {
78
99
  `Suggested app name: ${input.appId} (you may adjust the slug if it conflicts)`,
79
100
  `User prompt: ${input.prompt.trim()}`,
80
101
  grantRequest,
102
+ `Requested Dispatch workspace resources for this app:\n${resourceList}`,
81
103
  ``,
82
104
  `Pick a starter template that fits the user's prompt — analytics, calendar, content, design, dispatch, forms, mail, slides, clips, or starter when none of the others fit.`,
83
105
  `Use the workspace app layout: create it under apps/${input.appId}, mount it at /${input.appId}, keep it on the shared workspace database/hosting model, and avoid table-name collisions by namespacing any new domain tables to the app.`,
@@ -88,6 +110,9 @@ function buildAppCreationPrompt(input: {
88
110
  keyList
89
111
  ? `After the app exists, grant the selected Dispatch vault keys to appId "${input.appId}" and sync them once the app server is available. Treat these as requested grants, not active grants before creation succeeds.`
90
112
  : `Do not grant any Dispatch vault keys unless the user asks later.`,
113
+ input.selectedResources.length
114
+ ? `After the app exists, grant the selected Dispatch workspace resources to appId "${input.appId}" and sync them once the app server is available. Add a short note to apps/${input.appId}/AGENTS.md telling the app agent to read relevant shared resources under context/ or the selected resource paths before doing GTM/domain work.`
115
+ : `Do not grant any Dispatch workspace resources unless the user asks later.`,
91
116
  ``,
92
117
  `App readiness requirements before handing off:`,
93
118
  `- Ensure apps/${input.appId}/package.json exists; Dispatch discovers workspace apps from apps/<app-id>/package.json, not a separate app registry.`,
@@ -123,7 +148,7 @@ function actionUrl(basePath: string | null, action: string): string {
123
148
  }
124
149
 
125
150
  /**
126
- * Inline two-step app-creation flow: prompt → optional key picker → submit.
151
+ * Inline two-step app-creation flow: prompt → optional access picker → submit.
127
152
  * Used both in the popover form and in the dedicated `/new-app` page so the
128
153
  * same UX shows up everywhere a teammate kicks off a new workspace app.
129
154
  */
@@ -134,11 +159,14 @@ export function CreateAppFlow({
134
159
  onClose?: () => void;
135
160
  className?: string;
136
161
  }) {
137
- const [step, setStep] = useState<"prompt" | "keys">("prompt");
162
+ const [step, setStep] = useState<"prompt" | "access">("prompt");
138
163
  const [prompt, setPrompt] = useState("");
139
164
  const [selectedSecretIds, setSelectedSecretIds] = useState<string[]>([]);
165
+ const [selectedResourceIds, setSelectedResourceIds] = useState<string[]>([]);
140
166
  const [secrets, setSecrets] = useState<VaultSecretOption[]>([]);
167
+ const [resources, setResources] = useState<WorkspaceResourceOption[]>([]);
141
168
  const [secretsError, setSecretsError] = useState<string | null>(null);
169
+ const [resourcesError, setResourcesError] = useState<string | null>(null);
142
170
  const [statusMessage, setStatusMessage] = useState<string | null>(null);
143
171
  const [branchUrl, setBranchUrl] = useState<string | null>(null);
144
172
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -146,8 +174,7 @@ export function CreateAppFlow({
146
174
 
147
175
  const basePath = useMemo(() => defaultDispatchBasePath(), []);
148
176
 
149
- // Fetch the vault keys eagerly so step 2 has them ready the moment the user
150
- // taps "Choose keys" — no spinner, no pause between steps.
177
+ // Fetch access options eagerly so step 2 has them ready immediately.
151
178
  useEffect(() => {
152
179
  let cancelled = false;
153
180
  fetchJson(actionUrl(basePath, "list-vault-secret-options"))
@@ -161,6 +188,17 @@ export function CreateAppFlow({
161
188
  setSecrets([]);
162
189
  setSecretsError(err?.message || "Could not load Dispatch keys");
163
190
  });
191
+ fetchJson(actionUrl(basePath, "list-workspace-resource-options"))
192
+ .then((data) => {
193
+ if (cancelled) return;
194
+ setResources(Array.isArray(data) ? data : []);
195
+ setResourcesError(null);
196
+ })
197
+ .catch((err) => {
198
+ if (cancelled) return;
199
+ setResources([]);
200
+ setResourcesError(err?.message || "Could not load Dispatch resources");
201
+ });
164
202
  return () => {
165
203
  cancelled = true;
166
204
  };
@@ -170,10 +208,21 @@ export function CreateAppFlow({
170
208
  () => secrets.filter((s) => selectedSecretIds.includes(s.id)),
171
209
  [secrets, selectedSecretIds],
172
210
  );
211
+ const selectedResources = useMemo(
212
+ () => resources.filter((r) => selectedResourceIds.includes(r.id)),
213
+ [resources, selectedResourceIds],
214
+ );
173
215
  const selectedSecretLabel =
174
216
  selectedSecretIds.length === 0
175
217
  ? "no keys"
176
218
  : `${selectedSecretIds.length} key${selectedSecretIds.length === 1 ? "" : "s"}`;
219
+ const selectedResourceLabel =
220
+ selectedResourceIds.length === 0
221
+ ? "no resources"
222
+ : `${selectedResourceIds.length} resource${selectedResourceIds.length === 1 ? "" : "s"}`;
223
+ const selectedAccessLabel = [selectedSecretLabel, selectedResourceLabel].join(
224
+ " · ",
225
+ );
177
226
 
178
227
  function toggleSecret(id: string) {
179
228
  setSelectedSecretIds((cur) =>
@@ -181,7 +230,13 @@ export function CreateAppFlow({
181
230
  );
182
231
  }
183
232
 
184
- async function submit(rawPrompt: string, selectedKeys: string[]) {
233
+ function toggleResource(id: string) {
234
+ setSelectedResourceIds((cur) =>
235
+ cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id],
236
+ );
237
+ }
238
+
239
+ async function submit(rawPrompt: string) {
185
240
  const trimmed = rawPrompt.trim();
186
241
  if (!trimmed || isSubmitting) return;
187
242
  const appId = titleFromPrompt(trimmed);
@@ -194,7 +249,8 @@ export function CreateAppFlow({
194
249
  const message = buildAppCreationPrompt({
195
250
  appId,
196
251
  prompt: trimmed,
197
- selectedKeys,
252
+ selectedKeys: selectedSecrets.map((s) => s.credentialKey),
253
+ selectedResources,
198
254
  });
199
255
  setIsSubmitting(true);
200
256
  setStatusMessage(null);
@@ -218,7 +274,9 @@ export function CreateAppFlow({
218
274
  body: JSON.stringify({
219
275
  prompt: trimmed,
220
276
  appId,
221
- secretIds: selectedKeys.length > 0 ? selectedSecretIds : [],
277
+ secretIds: selectedSecretIds.length > 0 ? selectedSecretIds : [],
278
+ resourceIds:
279
+ selectedResourceIds.length > 0 ? selectedResourceIds : [],
222
280
  }),
223
281
  },
224
282
  );
@@ -239,11 +297,7 @@ export function CreateAppFlow({
239
297
  }
240
298
  }
241
299
 
242
- const submitWithSelectedKeys = () =>
243
- submit(
244
- prompt,
245
- selectedSecrets.map((s) => s.credentialKey),
246
- );
300
+ const submitWithSelectedAccess = () => submit(prompt);
247
301
 
248
302
  return (
249
303
  <div className={`flex flex-col gap-3 ${className}`}>
@@ -253,11 +307,11 @@ export function CreateAppFlow({
253
307
  <p className="text-sm font-semibold text-foreground">Create app</p>
254
308
  <button
255
309
  type="button"
256
- onClick={() => setStep("keys")}
310
+ onClick={() => setStep("access")}
257
311
  className="inline-flex cursor-pointer items-center gap-1 rounded-md border border-border bg-background/40 px-2 py-1 text-[11px] text-muted-foreground hover:text-foreground hover:bg-accent/50"
258
312
  >
259
313
  <IconKey size={11} />
260
- {selectedSecretLabel}
314
+ {selectedAccessLabel}
261
315
  </button>
262
316
  </div>
263
317
  <PromptComposer
@@ -267,10 +321,7 @@ export function CreateAppFlow({
267
321
  draftScope="dispatch:create-app"
268
322
  onSubmit={(text) => {
269
323
  setPrompt(text);
270
- submit(
271
- text,
272
- selectedSecrets.map((s) => s.credentialKey),
273
- );
324
+ submit(text);
274
325
  }}
275
326
  />
276
327
  </>
@@ -286,10 +337,14 @@ export function CreateAppFlow({
286
337
  Back
287
338
  </button>
288
339
  <span className="text-[11px] text-muted-foreground/70">
289
- {selectedSecretLabel} selected
340
+ {selectedAccessLabel}
290
341
  </span>
291
342
  </div>
292
- <div className="max-h-[340px] space-y-2 overflow-y-auto rounded-md border border-border bg-card p-2">
343
+ <div className="max-h-[180px] space-y-2 overflow-y-auto rounded-md border border-border bg-card p-2">
344
+ <div className="flex items-center gap-1.5 px-1 pb-1 text-[11px] font-medium text-muted-foreground">
345
+ <IconKey size={12} />
346
+ Dispatch keys
347
+ </div>
293
348
  {secretsError ? (
294
349
  <p className="rounded-md border border-dashed border-border px-3 py-3 text-xs text-muted-foreground">
295
350
  {secretsError}
@@ -355,11 +410,87 @@ export function CreateAppFlow({
355
410
  })
356
411
  )}
357
412
  </div>
413
+ <div className="max-h-[180px] space-y-2 overflow-y-auto rounded-md border border-border bg-card p-2">
414
+ <div className="flex items-center gap-1.5 px-1 pb-1 text-[11px] font-medium text-muted-foreground">
415
+ <IconBook size={12} />
416
+ Resource packs
417
+ </div>
418
+ {resourcesError ? (
419
+ <p className="rounded-md border border-dashed border-border px-3 py-3 text-xs text-muted-foreground">
420
+ {resourcesError}
421
+ </p>
422
+ ) : resources.length === 0 ? (
423
+ <p className="rounded-md border border-dashed border-border px-3 py-3 text-xs text-muted-foreground">
424
+ No Dispatch resource packs found yet.
425
+ </p>
426
+ ) : (
427
+ resources.map((resource) => {
428
+ const selected = selectedResourceIds.includes(resource.id);
429
+ return (
430
+ <div
431
+ key={resource.id}
432
+ className={`group rounded-md border text-sm ${
433
+ selected
434
+ ? "border-primary/45 bg-primary/5"
435
+ : "border-border hover:border-muted-foreground/40 hover:bg-accent/35"
436
+ }`}
437
+ >
438
+ <button
439
+ type="button"
440
+ aria-pressed={selected}
441
+ onClick={() => toggleResource(resource.id)}
442
+ className="flex w-full cursor-pointer items-start gap-3 rounded-md px-3 py-2 text-left"
443
+ >
444
+ <span
445
+ className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border ${
446
+ selected
447
+ ? "border-primary/60 bg-primary/10 text-primary"
448
+ : "border-muted-foreground/35 text-transparent"
449
+ }`}
450
+ >
451
+ {selected ? <IconCheck className="h-3 w-3" /> : null}
452
+ </span>
453
+ <span className="min-w-0 flex-1">
454
+ <span className="flex min-w-0 items-center gap-1.5">
455
+ <IconFileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" />
456
+ <span className="block truncate font-medium">
457
+ {resource.name}
458
+ </span>
459
+ </span>
460
+ <span className="block truncate text-xs text-muted-foreground/70">
461
+ {resource.kind} · {resource.path}
462
+ </span>
463
+ </span>
464
+ </button>
465
+ <details className="group/details border-t border-border/60 px-3 py-1.5 text-xs text-muted-foreground/75">
466
+ <summary className="flex cursor-pointer list-none items-center gap-1.5 text-[11px] hover:text-muted-foreground [&::-webkit-details-marker]:hidden">
467
+ <IconChevronDown className="h-3 w-3 transition-transform group-open/details:rotate-180" />
468
+ Details
469
+ </summary>
470
+ <div className="mt-1.5 space-y-1 pb-0.5 pl-4">
471
+ <div className="truncate">
472
+ Scope:{" "}
473
+ {resource.scope === "all"
474
+ ? "All apps"
475
+ : "Selected apps"}
476
+ </div>
477
+ {resource.description ? (
478
+ <div className="line-clamp-2">
479
+ {resource.description}
480
+ </div>
481
+ ) : null}
482
+ </div>
483
+ </details>
484
+ </div>
485
+ );
486
+ })
487
+ )}
488
+ </div>
358
489
  <div className="flex items-center justify-end gap-2">
359
490
  <Button
360
491
  type="button"
361
492
  size="sm"
362
- onClick={submitWithSelectedKeys}
493
+ onClick={submitWithSelectedAccess}
363
494
  disabled={!prompt.trim() || isSubmitting}
364
495
  >
365
496
  {isSubmitting ? (
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"
@@ -108,25 +108,27 @@ function HomeChatPanel() {
108
108
  <h1 className="text-center text-2xl font-semibold tracking-tight text-foreground sm:text-3xl">
109
109
  What should we do next?
110
110
  </h1>
111
- <PromptComposer
112
- placeholder="Message agent…"
113
- onSubmit={(text) => {
114
- const trimmed = text.trim();
115
- if (!trimmed) return;
116
- send(trimmed);
117
- }}
118
- />
119
- <div className="flex flex-wrap justify-center gap-2">
120
- {HOME_CHAT_SUGGESTIONS.map((suggestion) => (
121
- <button
122
- key={suggestion}
123
- type="button"
124
- onClick={() => send(suggestion)}
125
- className="cursor-pointer rounded-full border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground transition hover:border-foreground/30 hover:text-foreground"
126
- >
127
- {suggestion}
128
- </button>
129
- ))}
111
+ <div className="flex flex-col gap-4">
112
+ <PromptComposer
113
+ placeholder="Message agent…"
114
+ onSubmit={(text) => {
115
+ const trimmed = text.trim();
116
+ if (!trimmed) return;
117
+ send(trimmed);
118
+ }}
119
+ />
120
+ <div className="flex flex-wrap justify-center gap-2">
121
+ {HOME_CHAT_SUGGESTIONS.map((suggestion) => (
122
+ <button
123
+ key={suggestion}
124
+ type="button"
125
+ onClick={() => send(suggestion)}
126
+ className="cursor-pointer rounded-full border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground transition hover:border-foreground/30 hover:text-foreground"
127
+ >
128
+ {suggestion}
129
+ </button>
130
+ ))}
131
+ </div>
130
132
  </div>
131
133
  </div>
132
134
  </section>
@@ -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
  );