@agent-native/dispatch 0.8.18 → 0.8.20

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 (62) hide show
  1. package/README.md +22 -2
  2. package/dist/actions/navigate.js +1 -1
  3. package/dist/actions/navigate.js.map +1 -1
  4. package/dist/actions/start-workspace-app-creation.js +1 -1
  5. package/dist/actions/start-workspace-app-creation.js.map +1 -1
  6. package/dist/actions/view-screen.d.ts.map +1 -1
  7. package/dist/actions/view-screen.js +6 -0
  8. package/dist/actions/view-screen.js.map +1 -1
  9. package/dist/components/create-app-popover.js +3 -3
  10. package/dist/components/create-app-popover.js.map +1 -1
  11. package/dist/components/layout/Layout.d.ts.map +1 -1
  12. package/dist/components/layout/Layout.js +149 -14
  13. package/dist/components/layout/Layout.js.map +1 -1
  14. package/dist/hooks/use-navigation-state.js +5 -0
  15. package/dist/hooks/use-navigation-state.js.map +1 -1
  16. package/dist/lib/overview-chat.d.ts +3 -1
  17. package/dist/lib/overview-chat.d.ts.map +1 -1
  18. package/dist/lib/overview-chat.js +2 -1
  19. package/dist/lib/overview-chat.js.map +1 -1
  20. package/dist/routes/index.d.ts.map +1 -1
  21. package/dist/routes/index.js +1 -0
  22. package/dist/routes/index.js.map +1 -1
  23. package/dist/routes/pages/_index.js +1 -1
  24. package/dist/routes/pages/_index.js.map +1 -1
  25. package/dist/routes/pages/apps.d.ts.map +1 -1
  26. package/dist/routes/pages/apps.js +3 -1
  27. package/dist/routes/pages/apps.js.map +1 -1
  28. package/dist/routes/pages/chat.d.ts +5 -0
  29. package/dist/routes/pages/chat.d.ts.map +1 -0
  30. package/dist/routes/pages/chat.js +55 -0
  31. package/dist/routes/pages/chat.js.map +1 -0
  32. package/dist/routes/pages/overview.d.ts.map +1 -1
  33. package/dist/routes/pages/overview.js +20 -7
  34. package/dist/routes/pages/overview.js.map +1 -1
  35. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  36. package/dist/server/lib/app-creation-store.js +18 -0
  37. package/dist/server/lib/app-creation-store.js.map +1 -1
  38. package/dist/server/plugins/agent-chat.js +1 -1
  39. package/dist/server/plugins/agent-chat.js.map +1 -1
  40. package/dist/server/plugins/integrations.js +2 -2
  41. package/dist/server/plugins/integrations.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/actions/navigate.ts +1 -1
  44. package/src/actions/start-workspace-app-creation.ts +1 -1
  45. package/src/actions/view-screen.ts +7 -0
  46. package/src/components/create-app-popover.tsx +3 -3
  47. package/src/components/layout/Layout.tsx +307 -19
  48. package/src/hooks/use-navigation-state.spec.ts +7 -0
  49. package/src/hooks/use-navigation-state.ts +4 -0
  50. package/src/lib/overview-chat.spec.ts +15 -0
  51. package/src/lib/overview-chat.ts +2 -0
  52. package/src/routes/index.ts +1 -0
  53. package/src/routes/pages/_index.tsx +1 -1
  54. package/src/routes/pages/apps.tsx +4 -0
  55. package/src/routes/pages/chat.tsx +99 -0
  56. package/src/routes/pages/overview.tsx +21 -5
  57. package/src/server/lib/app-creation-store.spec.ts +15 -0
  58. package/src/server/lib/app-creation-store.ts +18 -0
  59. package/src/server/plugins/agent-chat.ts +1 -1
  60. package/src/server/plugins/integrations.ts +2 -2
  61. package/src/styles/dispatch-css.spec.ts +9 -0
  62. package/src/styles/dispatch.css +103 -0
@@ -23,7 +23,7 @@ export function meta() {
23
23
  *
24
24
  * We preserve `?` and `#` so deep-links like `?thread=<id>` from a Slack
25
25
  * "Open thread" button survive the bounce — `useThreadDeepLink` in
26
- * `root.tsx` reads them after the redirect lands on `/overview`.
26
+ * `root.tsx` reads them after the redirect lands and opens `/chat`.
27
27
  */
28
28
  function buildTarget(request: Request): string {
29
29
  const url = new URL(request.url);
@@ -2,6 +2,7 @@ import { useState } from "react";
2
2
  import { useActionMutation, useActionQuery } from "@agent-native/core/client";
3
3
  import {
4
4
  IconApps,
5
+ IconBrain,
5
6
  IconBrush,
6
7
  IconCalendarMonth,
7
8
  IconChartBar,
@@ -11,6 +12,7 @@ import {
11
12
  IconFileText,
12
13
  IconLoader2,
13
14
  IconMail,
15
+ IconPhoto,
14
16
  IconPlus,
15
17
  IconPresentation,
16
18
  IconScreenShare,
@@ -58,6 +60,8 @@ const TEMPLATE_ICONS: Record<string, typeof IconMail> = {
58
60
  FileText: IconFileText,
59
61
  Presentation: IconPresentation,
60
62
  ScreenShare: IconScreenShare,
63
+ Brain: IconBrain,
64
+ Photo: IconPhoto,
61
65
  ChartBar: IconChartBar,
62
66
  ClipboardList: IconClipboardList,
63
67
  Brush: IconBrush,
@@ -0,0 +1,99 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useLocation, useNavigate } from "react-router";
3
+ import { AgentChatSurface } from "@agent-native/core/client";
4
+ import { submitOverviewPrompt } from "@/lib/overview-chat";
5
+
6
+ interface DispatchChatLocationState {
7
+ dispatchPrompt?: {
8
+ id?: string | number;
9
+ message?: string;
10
+ selectedModel?: string | null;
11
+ };
12
+ dispatchThread?: {
13
+ id?: string | number;
14
+ threadId?: string;
15
+ };
16
+ }
17
+
18
+ export function meta() {
19
+ return [{ title: "Chat — Dispatch" }];
20
+ }
21
+
22
+ export default function ChatRoute() {
23
+ const location = useLocation();
24
+ const navigate = useNavigate();
25
+ const handledStateIds = useRef(new Set<string>());
26
+ const state = location.state as DispatchChatLocationState | null;
27
+ const prompt = state?.dispatchPrompt;
28
+ const thread = state?.dispatchThread;
29
+
30
+ useEffect(() => {
31
+ const message = prompt?.message?.trim();
32
+ const threadId = thread?.threadId?.trim();
33
+ if (!message && !threadId) return;
34
+
35
+ const stateId = String(
36
+ prompt?.id ?? thread?.id ?? `${message ?? ""}:${threadId ?? ""}`,
37
+ );
38
+ if (handledStateIds.current.has(stateId)) return;
39
+ handledStateIds.current.add(stateId);
40
+
41
+ const timer = window.setTimeout(() => {
42
+ if (threadId) {
43
+ window.dispatchEvent(
44
+ new CustomEvent("agent-chat:open-thread", {
45
+ detail: { threadId },
46
+ }),
47
+ );
48
+ }
49
+ if (message) {
50
+ submitOverviewPrompt(message, prompt?.selectedModel, {
51
+ openSidebar: false,
52
+ });
53
+ }
54
+ navigate(`${location.pathname}${location.search}${location.hash}`, {
55
+ replace: true,
56
+ state: null,
57
+ });
58
+ }, 0);
59
+
60
+ return () => window.clearTimeout(timer);
61
+ }, [
62
+ location.hash,
63
+ location.pathname,
64
+ location.search,
65
+ navigate,
66
+ prompt?.id,
67
+ prompt?.message,
68
+ prompt?.selectedModel,
69
+ thread?.id,
70
+ thread?.threadId,
71
+ ]);
72
+
73
+ return (
74
+ <div className="flex h-full min-h-0 flex-col bg-background">
75
+ <AgentChatSurface
76
+ mode="page"
77
+ className="dispatch-chat-panel"
78
+ defaultMode="chat"
79
+ showHeader={false}
80
+ showTabBar={false}
81
+ dynamicSuggestions={false}
82
+ suggestions={[]}
83
+ emptyStateText="Ask Dispatch to create apps, route work, or manage the workspace."
84
+ emptyStateDisplay="hidden"
85
+ centerComposerWhenEmpty
86
+ composerLayoutVariant="hero"
87
+ composerPlaceholder="Ask Dispatch..."
88
+ composerSlot={
89
+ <div className="dispatch-chat-intro">
90
+ <h1>What should Dispatch do next?</h1>
91
+ <p>
92
+ Create apps, manage shared keys, and route work across agents.
93
+ </p>
94
+ </div>
95
+ }
96
+ />
97
+ </div>
98
+ );
99
+ }
@@ -1,10 +1,11 @@
1
1
  import { useEffect, useMemo, useState } from "react";
2
- import { Link } from "react-router";
2
+ import { Link, useNavigate } from "react-router";
3
3
  import {
4
4
  PromptComposer,
5
5
  useActionQuery,
6
6
  useChatModels,
7
7
  agentNativePath,
8
+ isInBuilderFrame,
8
9
  } from "@agent-native/core/client";
9
10
  import {
10
11
  IconActivity,
@@ -75,9 +76,26 @@ const HOME_CHAT_SUGGESTIONS = [
75
76
 
76
77
  function HomeChatPanel() {
77
78
  const { selectedModel } = useChatModels();
79
+ const navigate = useNavigate();
78
80
 
79
81
  const send = (message: string) => {
80
- submitOverviewPrompt(message, selectedModel);
82
+ const trimmed = message.trim();
83
+ if (!trimmed) return;
84
+
85
+ if (isInBuilderFrame()) {
86
+ submitOverviewPrompt(trimmed, selectedModel);
87
+ return;
88
+ }
89
+
90
+ navigate("/chat", {
91
+ state: {
92
+ dispatchPrompt: {
93
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
94
+ message: trimmed,
95
+ selectedModel,
96
+ },
97
+ },
98
+ });
81
99
  };
82
100
 
83
101
  return (
@@ -90,9 +108,7 @@ function HomeChatPanel() {
90
108
  <PromptComposer
91
109
  placeholder="Message agent…"
92
110
  onSubmit={(text) => {
93
- const trimmed = text.trim();
94
- if (!trimmed) return;
95
- send(trimmed);
111
+ send(text);
96
112
  }}
97
113
  />
98
114
  <div className="flex flex-wrap justify-center gap-2">
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
2
2
  import { runWithRequestContext } from "@agent-native/core/server";
3
3
  import {
4
4
  generateWorkspaceAppDescription,
5
+ listAvailableWorkspaceTemplates,
5
6
  listWorkspaceApps,
6
7
  updateWorkspaceAppMetadata,
7
8
  } from "./app-creation-store.js";
@@ -347,4 +348,18 @@ describe("listWorkspaceApps", () => {
347
348
  ),
348
349
  ).toBe("Tracks customer onboarding risks and handoffs.");
349
350
  });
351
+
352
+ it("offers Brain and Assets as workspace template tiles", async () => {
353
+ stubNoPendingContext();
354
+ stubManifest([{ id: "dispatch", name: "Dispatch", path: "/dispatch" }]);
355
+
356
+ const templates = await runWithRequestContext(
357
+ { userEmail: "dev@example.test" },
358
+ () => listAvailableWorkspaceTemplates(),
359
+ );
360
+
361
+ expect(templates.map((template) => template.name)).toEqual(
362
+ expect.arrayContaining(["brain", "assets"]),
363
+ );
364
+ });
350
365
  });
@@ -1293,6 +1293,24 @@ const ADDABLE_TEMPLATES: AvailableWorkspaceTemplate[] = [
1293
1293
  colorRgb: "98 93 245",
1294
1294
  core: true,
1295
1295
  },
1296
+ {
1297
+ name: "brain",
1298
+ label: "Brain",
1299
+ hint: "Cited company knowledge from Slack, meetings, transcripts, and decisions",
1300
+ icon: "Brain",
1301
+ color: "#8B5CF6",
1302
+ colorRgb: "139 92 246",
1303
+ core: true,
1304
+ },
1305
+ {
1306
+ name: "assets",
1307
+ label: "Assets",
1308
+ hint: "Upload, organize, search, and generate on-brand images and videos",
1309
+ icon: "Photo",
1310
+ color: "#0F766E",
1311
+ colorRgb: "15 118 110",
1312
+ core: true,
1313
+ },
1296
1314
  {
1297
1315
  name: "analytics",
1298
1316
  label: "Analytics",
@@ -31,7 +31,7 @@ Use the standard workspace primitives:
31
31
  - When answering whether workspace apps expose agent cards or A2A endpoints, call list-workspace-apps with includeAgentCards=true. If you have not requested that probe, absence of agent-card fields means unchecked, not unavailable.
32
32
  - When creating a new workspace app, create a separate app under apps/<app-id> with apps/<app-id>/package.json including a concise generated description, mount it at /<app-id>, use relative /<app-id> links, never hardcode localhost or dev ports, use shadcn/ui with @tabler/icons-react rather than lucide-react, and ensure the React Router client entry preserves APP_BASE_PATH/VITE_APP_BASE_PATH via appBasePath(). There is no separate workspace app registry to edit.
33
33
  - If the starter template is used, treat it as scaffolding only: the finished app must be branded as the requested app with its own home screen/navigation/package metadata/manifest, and must not leave visible "Starter", "Blank app", or "New app" UI behind.
34
- - Treat first-party apps such as Mail, Calendar, Analytics, Brain, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.
34
+ - Treat first-party apps such as Mail, Calendar, Analytics, Brain, Assets, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.
35
35
 
36
36
  When a user asks for something like a digest, reminder, routing rule, or saved behavior:
37
37
  - First decide whether it should be a resource, a recurring job, a destination, or a delegated task.
@@ -13,7 +13,7 @@ Default posture:
13
13
  - Heavily delegate domain work to specialized agents through A2A (call-agent) when another app owns the job. Apps you can delegate to include slides (decks/presentations), analytics (data/dashboards), content (docs/articles), videos (Remotion compositions), forms (form builder), clips (screen recordings), design (visual designs), and assets (brand libraries plus generated images/videos).
14
14
  - Use the available-apps prompt context first, then list-connected-agents when you need fresh details, to see what agents are available before assuming a request must be handled locally.
15
15
  - When asked whether workspace apps expose agent cards or A2A endpoints, call list-workspace-apps with includeAgentCards=true. Without that probe, missing agent-card fields mean unchecked, not unavailable.
16
- - Treat first-party apps such as Mail, Calendar, Analytics, Brain, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.
16
+ - Treat first-party apps such as Mail, Calendar, Analytics, Brain, Assets, and Dispatch as existing hosted/connected neighbors available through links and A2A/default connected agents. Do not create wrapper apps, child apps, nested routes, or cloned template copies just to give a new app access to them; build only the genuinely new workflow and delegate cross-app work to those existing apps.
17
17
  - Keep durable memory and operating instructions in resources rather than ephemeral chat.
18
18
  - Reply in the originating thread unless the user explicitly asks you to send to a saved destination.
19
19
 
@@ -22,7 +22,7 @@ When a user asks for something:
22
22
  - After call-agent returns an answer, RELAY IT DIRECTLY to the user with at most a one-line preface — do not rephrase, summarize, or add commentary. The downstream agent already crafted the answer; your job is delivery, not editing. This minimizes round-trips and keeps the user-visible reply fast.
23
23
  - Exception: if the downstream agent reports a missing model/provider credential, do not name exact env vars, Vault keys, tokens, or secrets. Say the target app needs an LLM connection and recommend connecting Builder/managed LLM for that app; keep bring-your-own provider keys as a secondary option only if the user asks.
24
24
  - If the user asks to create, build, make, scaffold, or generate an "agent" from Dispatch chat or by tagging @agent-native in Slack, email, or Telegram, first classify the ask. If it is a simple Dispatch-native behavior like a reminder, digest, monitor, routing rule, saved instruction, or recurring workflow, create or update the recurring job/resource/destination in Dispatch. If it is a robust unique product or teammate that needs its own UI, data model, actions, integrations, or domain workflow, treat it as a new workspace app and call start-workspace-app-creation.
25
- - If a new-app prompt asks for access to Mail, Calendar, Analytics, Brain, or similar first-party app data/agents, keep using the existing hosted/connected app and A2A path. Do not ask Builder to scaffold those apps as children of the new app unless the user explicitly asks for a customized fork/copy.
25
+ - If a new-app prompt asks for access to Mail, Calendar, Analytics, Brain, Assets, or similar first-party app data/agents, keep using the existing hosted/connected app and A2A path. Do not ask Builder to scaffold those apps as children of the new app unless the user explicitly asks for a customized fork/copy.
26
26
  - If the starter template is used, treat it as scaffolding only: the finished app must be branded as the requested app with its own home screen/navigation/package metadata/manifest, and must not leave visible "Starter", "Blank app", or "New app" UI behind.
27
27
  - If the user explicitly asks for a new app or workspace app, call start-workspace-app-creation with their prompt and include a concise generated description by default. Do not satisfy a new-app request by adding a route, page, component, or file inside apps/starter or another existing app unless the user explicitly asks to modify that existing app. If the request is too vague to classify, ask one concise follow-up. If the action returns mode "builder", reply with the Builder branch URL; Builder is responsible for creating the separate workspace app under apps/<app-id>, mounting it at /<app-id>, ensuring apps/<app-id>/package.json exists with name/displayName and description so Dispatch discovers it, using relative /<app-id> links instead of hardcoded localhost/dev ports, and preserving APP_BASE_PATH/VITE_APP_BASE_PATH via appBasePath() in the React Router client entry. The new app lives at the workspace root /<app-id>, NOT under /dispatch/<app-id>, /apps/<app-id>, or any other Dispatch tab — when telling the user where to find it, link to /<app-id> only. There is no separate workspace app registry to edit. If it returns mode "local-agent", tell the user it is ready for the local code agent and include the returned app path/prompt summary. If it returns mode "coming-soon" or "builder-unavailable", explain the missing Builder setup and ask them to connect/configure Builder.
28
28
  - For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.
@@ -52,4 +52,13 @@ describe("dispatch route shells", () => {
52
52
  expect(indexRoute).toContain("HydrateFallback");
53
53
  expect(indexRoute).toContain("@agent-native/dispatch/routes/pages/_index");
54
54
  });
55
+
56
+ it("re-exports the chat route from the Dispatch template", () => {
57
+ const chatRoute = fs.readFileSync(
58
+ path.join(repoRoot, "templates/dispatch/app/routes/chat.tsx"),
59
+ "utf-8",
60
+ );
61
+
62
+ expect(chatRoute).toContain("@agent-native/dispatch/routes/pages/chat");
63
+ });
55
64
  });
@@ -7,3 +7,106 @@
7
7
  @source "../components/**/*.{js,mjs,ts,tsx}";
8
8
  @source "../hooks/**/*.{js,mjs,ts,tsx}";
9
9
  @source "../routes/**/*.{js,mjs,ts,tsx}";
10
+
11
+ .dispatch-chat-panel [data-agent-empty-state="centered"] {
12
+ justify-content: center;
13
+ padding-bottom: clamp(4rem, 12vh, 8rem);
14
+ }
15
+
16
+ .dispatch-chat-intro {
17
+ display: none;
18
+ }
19
+
20
+ .dispatch-chat-panel [data-agent-empty-state="centered"] .dispatch-chat-intro {
21
+ display: grid;
22
+ width: 100%;
23
+ max-width: min(760px, 90%);
24
+ gap: 0.5rem;
25
+ margin: 0 auto 1rem;
26
+ text-align: center;
27
+ }
28
+
29
+ .dispatch-chat-intro h1 {
30
+ margin: 0;
31
+ color: hsl(var(--foreground));
32
+ font-size: 2.5rem;
33
+ font-weight: 600;
34
+ letter-spacing: 0;
35
+ line-height: 1.12;
36
+ }
37
+
38
+ .dispatch-chat-intro p {
39
+ margin: 0;
40
+ color: hsl(var(--muted-foreground));
41
+ font-size: 0.9375rem;
42
+ line-height: 1.5;
43
+ }
44
+
45
+ .dispatch-chat-panel [data-agent-empty-state="centered"] .agent-chat-scroll {
46
+ flex: 0 0 auto;
47
+ min-height: 0;
48
+ overflow: visible;
49
+ }
50
+
51
+ .dispatch-chat-panel [data-agent-empty-state="centered"] .agent-composer-area {
52
+ width: 100%;
53
+ max-width: min(760px, 90%);
54
+ padding: 0;
55
+ }
56
+
57
+ .dispatch-chat-panel [data-agent-empty-state="centered"] .agent-composer-root {
58
+ min-height: 10.5rem;
59
+ border-color: hsl(var(--border));
60
+ border-radius: 1rem;
61
+ background: hsl(var(--card));
62
+ box-shadow:
63
+ 0 20px 60px hsl(220 10% 2% / 0.12),
64
+ 0 0 0 1px hsl(var(--border) / 0.45);
65
+ }
66
+
67
+ .dark
68
+ .dispatch-chat-panel
69
+ [data-agent-empty-state="centered"]
70
+ .agent-composer-root {
71
+ box-shadow:
72
+ 0 24px 80px hsl(220 10% 2% / 0.4),
73
+ 0 0 0 1px hsl(var(--border) / 0.7);
74
+ }
75
+
76
+ .dispatch-chat-panel
77
+ [data-agent-empty-state="centered"]
78
+ .agent-composer-prosemirror {
79
+ min-height: 6.75rem;
80
+ font-size: 1rem;
81
+ line-height: 1.625rem;
82
+ }
83
+
84
+ @media (max-width: 767px) {
85
+ .dispatch-chat-panel [data-agent-empty-state="centered"] {
86
+ padding: 1rem;
87
+ padding-bottom: 5rem;
88
+ }
89
+
90
+ .dispatch-chat-panel
91
+ [data-agent-empty-state="centered"]
92
+ .dispatch-chat-intro {
93
+ width: 100%;
94
+ margin-bottom: 0.875rem;
95
+ }
96
+
97
+ .dispatch-chat-intro h1 {
98
+ font-size: 1.75rem;
99
+ }
100
+
101
+ .dispatch-chat-panel
102
+ [data-agent-empty-state="centered"]
103
+ .agent-composer-area {
104
+ width: 100%;
105
+ }
106
+
107
+ .dispatch-chat-panel
108
+ [data-agent-empty-state="centered"]
109
+ .agent-composer-root {
110
+ min-height: 9rem;
111
+ }
112
+ }