@agent-native/dispatch 0.2.3 → 0.2.5

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 (31) hide show
  1. package/README.md +4 -4
  2. package/dist/actions/start-workspace-app-creation.js +1 -1
  3. package/dist/actions/start-workspace-app-creation.js.map +1 -1
  4. package/dist/components/agents-panel.d.ts.map +1 -1
  5. package/dist/components/agents-panel.js +68 -4
  6. package/dist/components/agents-panel.js.map +1 -1
  7. package/dist/components/create-app-popover.d.ts.map +1 -1
  8. package/dist/components/create-app-popover.js +2 -0
  9. package/dist/components/create-app-popover.js.map +1 -1
  10. package/dist/lib/overview-chat.d.ts +2 -0
  11. package/dist/lib/overview-chat.d.ts.map +1 -0
  12. package/dist/lib/overview-chat.js +20 -0
  13. package/dist/lib/overview-chat.js.map +1 -0
  14. package/dist/routes/pages/overview.d.ts.map +1 -1
  15. package/dist/routes/pages/overview.js +4 -8
  16. package/dist/routes/pages/overview.js.map +1 -1
  17. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  18. package/dist/server/lib/app-creation-store.js +5 -2
  19. package/dist/server/lib/app-creation-store.js.map +1 -1
  20. package/dist/server/plugins/integrations.d.ts.map +1 -1
  21. package/dist/server/plugins/integrations.js +2 -1
  22. package/dist/server/plugins/integrations.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/actions/start-workspace-app-creation.ts +1 -1
  25. package/src/components/agents-panel.tsx +117 -22
  26. package/src/components/create-app-popover.tsx +2 -0
  27. package/src/lib/overview-chat.spec.ts +48 -0
  28. package/src/lib/overview-chat.ts +24 -0
  29. package/src/routes/pages/overview.tsx +3 -8
  30. package/src/server/lib/app-creation-store.ts +5 -1
  31. package/src/server/plugins/integrations.ts +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/server/plugins/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,kCAAkC,GAAG;;;;;;;;;;;;;;;;;;4FAkBiD,CAAC;AAE7F;;;;GAIG;AACH,MAAM,0BAA0B,GAAG,KAAK,EAAE,QAAa,EAAE,EAAE;IACzD,MAAM,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAClD,MAAM,cAAc,GAAG,YAAY,CAAC,YAAY,CAAC;IACjD,MAAM,YAAY,GAChB,OAAO,cAAc,KAAK,QAAQ;QAChC,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,OAAO,cAAc,KAAK,UAAU;YACpC,CAAC,CAAC,cAAc,CAAC,kCAAkC,CAAC;YACpD,CAAC,CAAC,kCAAkC,CAAC;IAE3C,MAAM,MAAM,GAAG,wBAAwB,CAAC;QACtC,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,eAAe;QACxB,YAAY,EAAE,oBAAoB;QAClC,aAAa,EAAE,qBAAqB;QACpC,YAAY;QACZ,wDAAwD;QACxD,yEAAyE;QACzE,+DAA+D;QAC/D,6EAA6E;KAC9E,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,eAAe,0BAA0B,CAAC","sourcesContent":["import { createIntegrationsPlugin } from \"@agent-native/core/server\";\nimport {\n beforeDispatchProcess,\n resolveDispatchOwner,\n} from \"../lib/dispatch-integrations.js\";\nimport { getDispatchConfig } from \"../index.js\";\nimport { dispatchActions } from \"../../actions/index.js\";\n\nconst DISPATCH_INTEGRATION_SYSTEM_PROMPT = `You are the central dispatch for this workspace, responding via a messaging platform integration (Slack, Telegram, email, etc.).\n\nDefault posture:\n- Treat Slack, Telegram, and email as shared entrypoints into the workspace.\n- 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), and design (visual designs).\n- Use list-connected-agents to see what agents are available before assuming a request must be handled locally.\n- 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.\n- Keep durable memory and operating instructions in resources rather than ephemeral chat.\n- Reply in the originating thread unless the user explicitly asks you to send to a saved destination.\n\nWhen a user asks for something:\n- If it belongs to analytics, content, slides, videos, etc., delegate via call-agent — do not re-implement the domain logic in dispatch.\n- 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.\n- 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.\n- If the user asks to create, build, make, scaffold, or generate a new workspace app or agent, call start-workspace-app-creation with their prompt. In this codebase, \"agent\" and \"app\" are synonyms every agent-native app is an agent, so \"build me an agent\" and \"build me an app\" mean the same thing. Route both to start-workspace-app-creation. If the request is too vague to produce an app, ask one concise follow-up. If the action returns mode \"builder\", reply with the Builder branch URL. 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.\n- For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.\n- Keep responses concise and operational — messaging platforms have character limits.\n- Use markdown sparingly (bold and lists are fine, avoid complex formatting).\n- If a task requires many steps, summarize what you did rather than streaming every detail.`;\n\n/**\n * Defer plugin construction until the Nitro plugin actually fires so the\n * config-aware system prompt resolves AFTER `setupDispatch(config)` has\n * stamped the active config (plugin module load order is not guaranteed).\n */\nconst dispatchIntegrationsPlugin = async (nitroApp: any) => {\n const { integrations = {} } = getDispatchConfig();\n const promptOverride = integrations.systemPrompt;\n const systemPrompt =\n typeof promptOverride === \"string\"\n ? promptOverride\n : typeof promptOverride === \"function\"\n ? promptOverride(DISPATCH_INTEGRATION_SYSTEM_PROMPT)\n : DISPATCH_INTEGRATION_SYSTEM_PROMPT;\n\n const plugin = createIntegrationsPlugin({\n appId: \"dispatch\",\n actions: dispatchActions,\n resolveOwner: resolveDispatchOwner,\n beforeProcess: beforeDispatchProcess,\n systemPrompt,\n // Inherit the framework default (claude-sonnet-4-6 from\n // packages/core/src/integrations/plugin.ts). Haiku was tried for latency\n // but hallucinated URLs/IDs after delegated call-agent results\n // (e.g. inventing `https://slides.workspace.com/deck/builder-io-deck-2024`).\n });\n\n return plugin(nitroApp);\n};\n\nexport default dispatchIntegrationsPlugin;\n"]}
1
+ {"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/server/plugins/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,kCAAkC,GAAG;;;;;;;;;;;;;;;;;;;4FAmBiD,CAAC;AAE7F;;;;GAIG;AACH,MAAM,0BAA0B,GAAG,KAAK,EAAE,QAAa,EAAE,EAAE;IACzD,MAAM,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAClD,MAAM,cAAc,GAAG,YAAY,CAAC,YAAY,CAAC;IACjD,MAAM,YAAY,GAChB,OAAO,cAAc,KAAK,QAAQ;QAChC,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,OAAO,cAAc,KAAK,UAAU;YACpC,CAAC,CAAC,cAAc,CAAC,kCAAkC,CAAC;YACpD,CAAC,CAAC,kCAAkC,CAAC;IAE3C,MAAM,MAAM,GAAG,wBAAwB,CAAC;QACtC,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,eAAe;QACxB,YAAY,EAAE,oBAAoB;QAClC,aAAa,EAAE,qBAAqB;QACpC,YAAY;QACZ,wDAAwD;QACxD,yEAAyE;QACzE,+DAA+D;QAC/D,6EAA6E;KAC9E,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,eAAe,0BAA0B,CAAC","sourcesContent":["import { createIntegrationsPlugin } from \"@agent-native/core/server\";\nimport {\n beforeDispatchProcess,\n resolveDispatchOwner,\n} from \"../lib/dispatch-integrations.js\";\nimport { getDispatchConfig } from \"../index.js\";\nimport { dispatchActions } from \"../../actions/index.js\";\n\nconst DISPATCH_INTEGRATION_SYSTEM_PROMPT = `You are the central dispatch for this workspace, responding via a messaging platform integration (Slack, Telegram, email, etc.).\n\nDefault posture:\n- Treat Slack, Telegram, and email as shared entrypoints into the workspace.\n- 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), and design (visual designs).\n- Use list-connected-agents to see what agents are available before assuming a request must be handled locally.\n- 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.\n- Keep durable memory and operating instructions in resources rather than ephemeral chat.\n- Reply in the originating thread unless the user explicitly asks you to send to a saved destination.\n\nWhen a user asks for something:\n- If it belongs to analytics, content, slides, videos, etc., delegate via call-agent — do not re-implement the domain logic in dispatch.\n- 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.\n- 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.\n- 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.\n- If the user explicitly asks for a new app or workspace app, call start-workspace-app-creation with their prompt. 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>, and making it appear in the workspace apps list. 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.\n- For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.\n- Keep responses concise and operational — messaging platforms have character limits.\n- Use markdown sparingly (bold and lists are fine, avoid complex formatting).\n- If a task requires many steps, summarize what you did rather than streaming every detail.`;\n\n/**\n * Defer plugin construction until the Nitro plugin actually fires so the\n * config-aware system prompt resolves AFTER `setupDispatch(config)` has\n * stamped the active config (plugin module load order is not guaranteed).\n */\nconst dispatchIntegrationsPlugin = async (nitroApp: any) => {\n const { integrations = {} } = getDispatchConfig();\n const promptOverride = integrations.systemPrompt;\n const systemPrompt =\n typeof promptOverride === \"string\"\n ? promptOverride\n : typeof promptOverride === \"function\"\n ? promptOverride(DISPATCH_INTEGRATION_SYSTEM_PROMPT)\n : DISPATCH_INTEGRATION_SYSTEM_PROMPT;\n\n const plugin = createIntegrationsPlugin({\n appId: \"dispatch\",\n actions: dispatchActions,\n resolveOwner: resolveDispatchOwner,\n beforeProcess: beforeDispatchProcess,\n systemPrompt,\n // Inherit the framework default (claude-sonnet-4-6 from\n // packages/core/src/integrations/plugin.ts). Haiku was tried for latency\n // but hallucinated URLs/IDs after delegated call-agent results\n // (e.g. inventing `https://slides.workspace.com/deck/builder-io-deck-2024`).\n });\n\n return plugin(nitroApp);\n};\n\nexport default dispatchIntegrationsPlugin;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/dispatch",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
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",
@@ -97,7 +97,7 @@
97
97
  "typescript": "^6.0.3",
98
98
  "vite": "8.0.3",
99
99
  "vitest": "^4.1.5",
100
- "@agent-native/core": "0.9.1"
100
+ "@agent-native/core": "0.11.4"
101
101
  },
102
102
  "scripts": {
103
103
  "build": "tsc && tsc-alias --resolve-full-paths",
@@ -5,7 +5,7 @@ import { startWorkspaceAppCreation } from "../server/lib/app-creation-store.js";
5
5
 
6
6
  export default defineAction({
7
7
  description:
8
- "Start creating a new workspace app from Dispatch. In local dev this returns a code-agent prompt; in production it creates a Builder branch when a Builder project is configured.",
8
+ "Start creating a new workspace app from Dispatch when the request truly needs its own app. In local dev this returns a code-agent prompt; in production it creates a Builder branch when a Builder project is configured. The result must be a separate workspace app under apps/<app-id>, not a new route or file in apps/starter.",
9
9
  schema: z.object({
10
10
  prompt: z.string().min(1).describe("The user's app creation request"),
11
11
  appId: z
@@ -1,4 +1,4 @@
1
- import { useRef, useState } from "react";
1
+ import { useRef, useState, type FormEvent } from "react";
2
2
  import { Button } from "@/components/ui/button";
3
3
  import { Input } from "@/components/ui/input";
4
4
  import {
@@ -27,6 +27,48 @@ export interface ConnectedAgent {
27
27
  scope?: "shared" | "personal";
28
28
  }
29
29
 
30
+ type AgentFormErrors = Partial<Record<"name" | "url" | "form", string>>;
31
+
32
+ function slugifyAgentName(value: string): string {
33
+ return value
34
+ .trim()
35
+ .toLowerCase()
36
+ .replace(/[^a-z0-9]+/g, "-")
37
+ .replace(/^-+|-+$/g, "");
38
+ }
39
+
40
+ function validateAgentForm(name: string, url: string): AgentFormErrors {
41
+ const errors: AgentFormErrors = {};
42
+ const trimmedName = name.trim();
43
+ const trimmedUrl = url.trim();
44
+
45
+ if (!trimmedName) {
46
+ errors.name = "Agent name is required.";
47
+ } else if (!slugifyAgentName(trimmedName)) {
48
+ errors.name = "Agent name must include at least one letter or number.";
49
+ }
50
+
51
+ if (!trimmedUrl) {
52
+ errors.url = "Agent endpoint URL is required.";
53
+ } else {
54
+ try {
55
+ const parsed = new URL(trimmedUrl);
56
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
57
+ errors.url = "Use an http:// or https:// endpoint URL.";
58
+ } else if (!parsed.hostname) {
59
+ errors.url = "Enter a complete endpoint URL with a host.";
60
+ } else if (parsed.username || parsed.password) {
61
+ errors.url = "Do not include credentials in the endpoint URL.";
62
+ }
63
+ } catch {
64
+ errors.url =
65
+ "Enter a valid endpoint URL, such as https://app.example.com.";
66
+ }
67
+ }
68
+
69
+ return errors;
70
+ }
71
+
30
72
  export function AgentsPanel({
31
73
  agents,
32
74
  onRefresh,
@@ -38,6 +80,7 @@ export function AgentsPanel({
38
80
  const [url, setUrl] = useState("");
39
81
  const [description, setDescription] = useState("");
40
82
  const [saving, setSaving] = useState(false);
83
+ const [errors, setErrors] = useState<AgentFormErrors>({});
41
84
  const nameRef = useRef<HTMLInputElement>(null);
42
85
 
43
86
  const customAgents = agents.filter((agent) => agent.source === "custom");
@@ -46,12 +89,17 @@ export function AgentsPanel({
46
89
  );
47
90
  const builtinAgents = agents.filter((agent) => agent.source === "builtin");
48
91
 
49
- const handleAdd = async () => {
92
+ const handleAdd = async (event?: FormEvent<HTMLFormElement>) => {
93
+ event?.preventDefault();
50
94
  const trimmedName = name.trim();
51
95
  const trimmedUrl = url.trim();
52
- if (!trimmedName || !trimmedUrl) return;
96
+ const nextErrors = validateAgentForm(trimmedName, trimmedUrl);
97
+ if (Object.keys(nextErrors).length > 0) {
98
+ setErrors(nextErrors);
99
+ return;
100
+ }
53
101
 
54
- const id = trimmedName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
102
+ const id = slugifyAgentName(trimmedName);
55
103
  const agentJson = JSON.stringify(
56
104
  {
57
105
  id,
@@ -79,9 +127,21 @@ export function AgentsPanel({
79
127
  setName("");
80
128
  setUrl("");
81
129
  setDescription("");
130
+ setErrors({});
82
131
  onRefresh();
83
132
  nameRef.current?.focus();
133
+ } else {
134
+ setErrors({
135
+ form: `Could not add agent. Request failed with ${res.status}.`,
136
+ });
84
137
  }
138
+ } catch (error) {
139
+ setErrors({
140
+ form:
141
+ error instanceof Error
142
+ ? error.message
143
+ : "Could not add agent. Please try again.",
144
+ });
85
145
  } finally {
86
146
  setSaving(false);
87
147
  }
@@ -230,31 +290,66 @@ export function AgentsPanel({
230
290
  <p className="mt-1 text-xs leading-relaxed text-muted-foreground">
231
291
  Add another A2A-compatible app by saving its agent endpoint here.
232
292
  </p>
233
- <div className="mt-4 space-y-3">
234
- <Input
235
- ref={nameRef}
236
- value={name}
237
- onChange={(event) => setName(event.target.value)}
238
- placeholder="Name"
239
- />
240
- <Input
241
- value={url}
242
- onChange={(event) => setUrl(event.target.value)}
243
- placeholder="https://app.example.com"
244
- />
293
+ <form className="mt-4 space-y-3" onSubmit={handleAdd} noValidate>
294
+ <div className="space-y-1.5">
295
+ <Input
296
+ ref={nameRef}
297
+ value={name}
298
+ onChange={(event) => {
299
+ setName(event.target.value);
300
+ setErrors((current) => ({ ...current, name: undefined }));
301
+ }}
302
+ placeholder="Name"
303
+ aria-invalid={Boolean(errors.name)}
304
+ aria-describedby={
305
+ errors.name ? "external-agent-name-error" : undefined
306
+ }
307
+ />
308
+ {errors.name ? (
309
+ <p
310
+ id="external-agent-name-error"
311
+ className="text-xs font-medium text-destructive"
312
+ >
313
+ {errors.name}
314
+ </p>
315
+ ) : null}
316
+ </div>
317
+ <div className="space-y-1.5">
318
+ <Input
319
+ value={url}
320
+ onChange={(event) => {
321
+ setUrl(event.target.value);
322
+ setErrors((current) => ({ ...current, url: undefined }));
323
+ }}
324
+ placeholder="https://app.example.com"
325
+ aria-invalid={Boolean(errors.url)}
326
+ aria-describedby={
327
+ errors.url ? "external-agent-url-error" : undefined
328
+ }
329
+ />
330
+ {errors.url ? (
331
+ <p
332
+ id="external-agent-url-error"
333
+ className="text-xs font-medium text-destructive"
334
+ >
335
+ {errors.url}
336
+ </p>
337
+ ) : null}
338
+ </div>
245
339
  <Input
246
340
  value={description}
247
341
  onChange={(event) => setDescription(event.target.value)}
248
342
  placeholder="Description (optional)"
249
343
  />
250
- <Button
251
- className="w-full"
252
- onClick={handleAdd}
253
- disabled={!name.trim() || !url.trim() || saving}
254
- >
344
+ {errors.form ? (
345
+ <p className="text-xs font-medium text-destructive">
346
+ {errors.form}
347
+ </p>
348
+ ) : null}
349
+ <Button type="submit" className="w-full" disabled={saving}>
255
350
  {saving ? "Saving..." : "Add agent"}
256
351
  </Button>
257
- </div>
352
+ </form>
258
353
  </div>
259
354
  </div>
260
355
  </section>
@@ -73,6 +73,7 @@ function buildAppCreationPrompt(input: {
73
73
 
74
74
  return [
75
75
  `Create a new agent-native app in this workspace.`,
76
+ `This is a new workspace app request, not a feature request for the current app.`,
76
77
  ``,
77
78
  `Suggested app name: ${input.appId} (you may adjust the slug if it conflicts)`,
78
79
  `User prompt: ${input.prompt.trim()}`,
@@ -80,6 +81,7 @@ function buildAppCreationPrompt(input: {
80
81
  ``,
81
82
  `Pick a starter template that fits the user's prompt — analytics, calendar, content, design, dispatch, forms, mail, slides, videos, clips, or starter when none of the others fit.`,
82
83
  `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.`,
84
+ `Do not satisfy this 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.`,
83
85
  keyList
84
86
  ? `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.`
85
87
  : `Do not grant any Dispatch vault keys unless the user asks later.`,
@@ -0,0 +1,48 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const frameState = vi.hoisted(() => ({ inBuilderFrame: false }));
4
+ const sendToAgentChatMock = vi.hoisted(() => vi.fn(() => "chat-tab"));
5
+
6
+ vi.mock("@agent-native/core/client", () => ({
7
+ isInBuilderFrame: () => frameState.inBuilderFrame,
8
+ sendToAgentChat: sendToAgentChatMock,
9
+ }));
10
+
11
+ const { submitOverviewPrompt } = await import("./overview-chat.js");
12
+
13
+ describe("submitOverviewPrompt", () => {
14
+ beforeEach(() => {
15
+ frameState.inBuilderFrame = false;
16
+ sendToAgentChatMock.mockClear();
17
+ });
18
+
19
+ it("sends overview prompts to a new local agent tab outside Builder", () => {
20
+ const tabId = submitOverviewPrompt(" build a metrics app ", "auto");
21
+
22
+ expect(tabId).toBe("chat-tab");
23
+ expect(sendToAgentChatMock).toHaveBeenCalledWith({
24
+ message: "build a metrics app",
25
+ submit: true,
26
+ newTab: true,
27
+ model: "auto",
28
+ });
29
+ });
30
+
31
+ it("routes overview prompts to Builder chat inside Builder", () => {
32
+ frameState.inBuilderFrame = true;
33
+
34
+ const tabId = submitOverviewPrompt("ship the onboarding flow", "auto");
35
+
36
+ expect(tabId).toBe("chat-tab");
37
+ expect(sendToAgentChatMock).toHaveBeenCalledWith({
38
+ message: "ship the onboarding flow",
39
+ submit: true,
40
+ type: "code",
41
+ });
42
+ });
43
+
44
+ it("ignores empty prompts", () => {
45
+ expect(submitOverviewPrompt(" ", "auto")).toBeNull();
46
+ expect(sendToAgentChatMock).not.toHaveBeenCalled();
47
+ });
48
+ });
@@ -0,0 +1,24 @@
1
+ import { isInBuilderFrame, sendToAgentChat } from "@agent-native/core/client";
2
+
3
+ export function submitOverviewPrompt(
4
+ message: string,
5
+ selectedModel?: string | null,
6
+ ): string | null {
7
+ const trimmed = message.trim();
8
+ if (!trimmed) return null;
9
+
10
+ if (isInBuilderFrame()) {
11
+ return sendToAgentChat({
12
+ message: trimmed,
13
+ submit: true,
14
+ type: "code",
15
+ });
16
+ }
17
+
18
+ return sendToAgentChat({
19
+ message: trimmed,
20
+ submit: true,
21
+ newTab: true,
22
+ model: selectedModel || undefined,
23
+ });
24
+ }
@@ -2,7 +2,6 @@ import { useEffect, useMemo, useState } from "react";
2
2
  import { Link } from "react-router";
3
3
  import {
4
4
  PromptComposer,
5
- sendToAgentChat,
6
5
  useActionQuery,
7
6
  useChatModels,
8
7
  agentNativePath,
@@ -34,6 +33,7 @@ import {
34
33
  TooltipContent,
35
34
  TooltipTrigger,
36
35
  } from "@/components/ui/tooltip";
36
+ import { submitOverviewPrompt } from "@/lib/overview-chat";
37
37
 
38
38
  interface IntegrationStatus {
39
39
  platform: string;
@@ -99,17 +99,12 @@ function HomeChatPanel() {
99
99
  const { selectedModel } = useChatModels();
100
100
 
101
101
  const send = (message: string) => {
102
- sendToAgentChat({
103
- message,
104
- submit: true,
105
- newTab: true,
106
- model: selectedModel || undefined,
107
- });
102
+ submitOverviewPrompt(message, selectedModel);
108
103
  };
109
104
 
110
105
  return (
111
106
  <section className="px-2 py-6 sm:py-10">
112
- <div className="mx-auto w-full max-w-2xl space-y-5">
107
+ <div className="mx-auto w-full max-w-2xl space-y-8">
113
108
  <h1 className="text-center text-2xl font-semibold tracking-tight text-foreground sm:text-3xl">
114
109
  What should we do next?
115
110
  </h1>
@@ -6,6 +6,7 @@ import {
6
6
  getBuilderBranchProjectId,
7
7
  getRequestContext,
8
8
  isIntegrationCallerRequest,
9
+ resolveBuilderBranchProjectId,
9
10
  resolveBuilderCredentials,
10
11
  runBuilderAgent,
11
12
  } from "@agent-native/core/server";
@@ -596,6 +597,7 @@ export async function listWorkspaceApps(
596
597
 
597
598
  export async function getAppCreationSettings(): Promise<AppCreationSettings> {
598
599
  const envBuilderProjectId = getEnvBuilderProjectId();
600
+ const resolvedBuilderProjectId = await resolveBuilderBranchProjectId();
599
601
  const raw = await readSettingsRecord();
600
602
  const savedBuilderProjectId =
601
603
  typeof raw?.builderProjectId === "string" && raw.builderProjectId.trim()
@@ -605,7 +607,9 @@ export async function getAppCreationSettings(): Promise<AppCreationSettings> {
605
607
  const enableBuilder =
606
608
  process.env.ENABLE_BUILDER === "true" || process.env.ENABLE_BUILDER === "1";
607
609
  const effectiveBuilderProjectId =
608
- builderProjectId || (enableBuilder ? getBuilderBranchProjectId() : null);
610
+ builderProjectId ||
611
+ resolvedBuilderProjectId ||
612
+ (enableBuilder ? getBuilderBranchProjectId() : null);
609
613
 
610
614
  return {
611
615
  builderProjectId: effectiveBuilderProjectId,
@@ -20,7 +20,8 @@ When a user asks for something:
20
20
  - If it belongs to analytics, content, slides, videos, etc., delegate via call-agent — do not re-implement the domain logic in dispatch.
21
21
  - 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.
22
22
  - 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.
23
- - If the user asks to create, build, make, scaffold, or generate a new workspace app or agent, call start-workspace-app-creation with their prompt. In this codebase, "agent" and "app" are synonyms every agent-native app is an agent, so "build me an agent" and "build me an app" mean the same thing. Route both to start-workspace-app-creation. If the request is too vague to produce an app, ask one concise follow-up. If the action returns mode "builder", reply with the Builder branch URL. 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.
23
+ - 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.
24
+ - If the user explicitly asks for a new app or workspace app, call start-workspace-app-creation with their prompt. 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>, and making it appear in the workspace apps list. 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.
24
25
  - For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.
25
26
  - Keep responses concise and operational — messaging platforms have character limits.
26
27
  - Use markdown sparingly (bold and lists are fine, avoid complex formatting).