@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.
- package/README.md +22 -2
- package/dist/actions/navigate.js +1 -1
- package/dist/actions/navigate.js.map +1 -1
- package/dist/actions/start-workspace-app-creation.js +1 -1
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/actions/view-screen.d.ts.map +1 -1
- package/dist/actions/view-screen.js +6 -0
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/create-app-popover.js +3 -3
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +149 -14
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/hooks/use-navigation-state.js +5 -0
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/overview-chat.d.ts +3 -1
- package/dist/lib/overview-chat.d.ts.map +1 -1
- package/dist/lib/overview-chat.js +2 -1
- package/dist/lib/overview-chat.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/_index.js +1 -1
- package/dist/routes/pages/_index.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +3 -1
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/chat.d.ts +5 -0
- package/dist/routes/pages/chat.d.ts.map +1 -0
- package/dist/routes/pages/chat.js +55 -0
- package/dist/routes/pages/chat.js.map +1 -0
- package/dist/routes/pages/overview.d.ts.map +1 -1
- package/dist/routes/pages/overview.js +20 -7
- package/dist/routes/pages/overview.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +18 -0
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +1 -1
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/server/plugins/integrations.js +2 -2
- package/dist/server/plugins/integrations.js.map +1 -1
- package/package.json +1 -1
- package/src/actions/navigate.ts +1 -1
- package/src/actions/start-workspace-app-creation.ts +1 -1
- package/src/actions/view-screen.ts +7 -0
- package/src/components/create-app-popover.tsx +3 -3
- package/src/components/layout/Layout.tsx +307 -19
- package/src/hooks/use-navigation-state.spec.ts +7 -0
- package/src/hooks/use-navigation-state.ts +4 -0
- package/src/lib/overview-chat.spec.ts +15 -0
- package/src/lib/overview-chat.ts +2 -0
- package/src/routes/index.ts +1 -0
- package/src/routes/pages/_index.tsx +1 -1
- package/src/routes/pages/apps.tsx +4 -0
- package/src/routes/pages/chat.tsx +99 -0
- package/src/routes/pages/overview.tsx +21 -5
- package/src/server/lib/app-creation-store.spec.ts +15 -0
- package/src/server/lib/app-creation-store.ts +18 -0
- package/src/server/plugins/agent-chat.ts +1 -1
- package/src/server/plugins/integrations.ts +2 -2
- package/src/styles/dispatch-css.spec.ts +9 -0
- package/src/styles/dispatch.css +103 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-chat.js","sourceRoot":"","sources":["../../../src/server/plugins/agent-chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,eAAe,qBAAqB,CAAC;IACnC,KAAK,EAAE,UAAU;IACjB,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAC5B,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO,GAAG,CAAC,KAAK,CAAC;IACnB,CAAC;IACD,2EAA2E;IAC3E,2EAA2E;IAC3E,wEAAwE;IACxE,OAAO,EAAE,eAAe;IACxB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;4EAqB4D;CAC3E,CAAC,CAAC","sourcesContent":["import { createAgentChatPlugin } from \"@agent-native/core/server\";\nimport { getOrgContext } from \"@agent-native/core/org\";\nimport { dispatchActions } from \"../../actions/index.js\";\n\nexport default createAgentChatPlugin({\n appId: \"dispatch\",\n // Without this, AGENT_ORG_ID is never set on agent action calls and every\n // row written through the frontend (vault secrets, destinations, workspace\n // resources) lands with org_id=NULL — breaking data isolation across orgs.\n resolveOrgId: async (event) => {\n const ctx = await getOrgContext(event);\n return ctx.orgId;\n },\n // Read actions directly from the package's own action map rather than from\n // a build-time-generated `.generated/actions-registry.ts` (the latter is a\n // template-only construct that the Vite plugin emits next to actions/).\n actions: dispatchActions,\n systemPrompt: `You are the central dispatch for this workspace.\n\nDefault posture:\n- Treat Slack and Telegram as shared entrypoints into the workspace.\n- Heavily delegate domain work to specialized agents through A2A when another app owns the job.\n- Keep durable memory and operating instructions in resources rather than ephemeral chat.\n- Prefer replying in the current external thread unless the user explicitly asks you to send to a saved destination.\n\nUse the standard workspace primitives:\n- Read and update resources like AGENTS.md, LEARNINGS.md, jobs/*.md, agents/*.md, and remote-agents/*.json when appropriate.\n- Use recurring jobs for scheduled behavior.\n- Use custom agent profiles in agents/*.md for local spawned work and remote-agents/*.json for remote A2A apps.\n- You receive a compact available-apps block with sibling workspace app names and descriptions. Use it to pick the right A2A target, and call list-connected-agents or tool-search only when you need fresh details.\n- 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.\n- 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.\n- 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.\n- 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.\n\nWhen a user asks for something like a digest, reminder, routing rule, or saved behavior:\n- First decide whether it should be a resource, a recurring job, a destination, or a delegated task.\n- Keep responses concise and operational.\n- Avoid inventing integrations or destinations that are not configured yet.`,\n});\n"]}
|
|
1
|
+
{"version":3,"file":"agent-chat.js","sourceRoot":"","sources":["../../../src/server/plugins/agent-chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,eAAe,qBAAqB,CAAC;IACnC,KAAK,EAAE,UAAU;IACjB,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAC5B,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO,GAAG,CAAC,KAAK,CAAC;IACnB,CAAC;IACD,2EAA2E;IAC3E,2EAA2E;IAC3E,wEAAwE;IACxE,OAAO,EAAE,eAAe;IACxB,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;4EAqB4D;CAC3E,CAAC,CAAC","sourcesContent":["import { createAgentChatPlugin } from \"@agent-native/core/server\";\nimport { getOrgContext } from \"@agent-native/core/org\";\nimport { dispatchActions } from \"../../actions/index.js\";\n\nexport default createAgentChatPlugin({\n appId: \"dispatch\",\n // Without this, AGENT_ORG_ID is never set on agent action calls and every\n // row written through the frontend (vault secrets, destinations, workspace\n // resources) lands with org_id=NULL — breaking data isolation across orgs.\n resolveOrgId: async (event) => {\n const ctx = await getOrgContext(event);\n return ctx.orgId;\n },\n // Read actions directly from the package's own action map rather than from\n // a build-time-generated `.generated/actions-registry.ts` (the latter is a\n // template-only construct that the Vite plugin emits next to actions/).\n actions: dispatchActions,\n systemPrompt: `You are the central dispatch for this workspace.\n\nDefault posture:\n- Treat Slack and Telegram as shared entrypoints into the workspace.\n- Heavily delegate domain work to specialized agents through A2A when another app owns the job.\n- Keep durable memory and operating instructions in resources rather than ephemeral chat.\n- Prefer replying in the current external thread unless the user explicitly asks you to send to a saved destination.\n\nUse the standard workspace primitives:\n- Read and update resources like AGENTS.md, LEARNINGS.md, jobs/*.md, agents/*.md, and remote-agents/*.json when appropriate.\n- Use recurring jobs for scheduled behavior.\n- Use custom agent profiles in agents/*.md for local spawned work and remote-agents/*.json for remote A2A apps.\n- You receive a compact available-apps block with sibling workspace app names and descriptions. Use it to pick the right A2A target, and call list-connected-agents or tool-search only when you need fresh details.\n- 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.\n- 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.\n- 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.\n- 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.\n\nWhen a user asks for something like a digest, reminder, routing rule, or saved behavior:\n- First decide whether it should be a resource, a recurring job, a destination, or a delegated task.\n- Keep responses concise and operational.\n- Avoid inventing integrations or destinations that are not configured yet.`,\n});\n"]}
|
|
@@ -9,7 +9,7 @@ Default posture:
|
|
|
9
9
|
- 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).
|
|
10
10
|
- 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.
|
|
11
11
|
- 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.
|
|
12
|
-
- 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.
|
|
12
|
+
- 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.
|
|
13
13
|
- Keep durable memory and operating instructions in resources rather than ephemeral chat.
|
|
14
14
|
- Reply in the originating thread unless the user explicitly asks you to send to a saved destination.
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ When a user asks for something:
|
|
|
18
18
|
- 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.
|
|
19
19
|
- 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.
|
|
20
20
|
- 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.
|
|
21
|
-
- 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.
|
|
21
|
+
- 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.
|
|
22
22
|
- 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.
|
|
23
23
|
- 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.
|
|
24
24
|
- For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;4FAsBiD,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), design (visual designs), and assets (brand libraries plus generated images/videos).\n- 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.\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- 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.\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, assets, 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 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.\n- 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.\n- 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.\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;;;;;;;;;;;;;;;;;;;;;;4FAsBiD,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), design (visual designs), and assets (brand libraries plus generated images/videos).\n- 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.\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- 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.\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, assets, 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 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.\n- 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.\n- 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.\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.8.
|
|
3
|
+
"version": "0.8.20",
|
|
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",
|
package/src/actions/navigate.ts
CHANGED
|
@@ -26,7 +26,7 @@ export default defineAction({
|
|
|
26
26
|
.string()
|
|
27
27
|
.optional()
|
|
28
28
|
.describe(
|
|
29
|
-
"Named dispatch view to navigate to. Built-in views include overview, apps, metrics, new-app, vault, integrations, messaging, workspace, agents, destinations, identities, approvals, audit, thread-debug, dreams, and team. Generated Dispatch extension tabs can also use their nav item id.",
|
|
29
|
+
"Named dispatch view to navigate to. Built-in views include chat, overview, apps, metrics, new-app, vault, integrations, messaging, workspace, agents, destinations, identities, approvals, audit, thread-debug, dreams, and team. Generated Dispatch extension tabs can also use their nav item id.",
|
|
30
30
|
),
|
|
31
31
|
path: z.string().optional().describe("URL path to navigate to"),
|
|
32
32
|
}),
|
|
@@ -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 when the request truly needs its own app. Callers should include a concise generated description by default; Dispatch generates one from the prompt when omitted. 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. If starter is used as the source template, the finished app must be branded as the requested app and must not leave visible "Starter", "Blank app", or "New app" UI behind. If the request needs Mail, Calendar, Analytics, Brain, or another first-party app, use the existing hosted/connected app via links or A2A; do not clone, wrap, or nest those templates inside the new app unless the user explicitly asks for a customized copy.',
|
|
8
|
+
'Start creating a new workspace app from Dispatch when the request truly needs its own app. Callers should include a concise generated description by default; Dispatch generates one from the prompt when omitted. 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. If starter is used as the source template, the finished app must be branded as the requested app and must not leave visible "Starter", "Blank app", or "New app" UI behind. If the request needs Mail, Calendar, Analytics, Brain, Assets, or another first-party app, use the existing hosted/connected app via links or A2A; do not clone, wrap, or nest those templates inside the new app unless the user explicitly asks for a customized copy.',
|
|
9
9
|
schema: z.object({
|
|
10
10
|
prompt: z.string().min(1).describe("The user's app creation request"),
|
|
11
11
|
appId: z
|
|
@@ -67,6 +67,13 @@ export default defineAction({
|
|
|
67
67
|
approvalPolicy: overview.settings,
|
|
68
68
|
};
|
|
69
69
|
if (navigation) screen.navigation = navigation;
|
|
70
|
+
if (navigation?.view === "chat") {
|
|
71
|
+
screen.chatSurface = {
|
|
72
|
+
view: "full-page Dispatch chat",
|
|
73
|
+
purpose:
|
|
74
|
+
"Create apps, manage workspace resources, route work to connected agents, and continue Dispatch conversations.",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
70
77
|
if (navigation?.view === "overview") {
|
|
71
78
|
screen.recentAudit = overview.recentAudit.slice(0, 5);
|
|
72
79
|
screen.recentApprovals = overview.recentApprovals.slice(0, 5);
|
|
@@ -110,15 +110,15 @@ function buildAppCreationPrompt(input: {
|
|
|
110
110
|
`Requested Dispatch workspace resources for this app:\n${resourceList}`,
|
|
111
111
|
`Dispatch workspace resources with scope=all are inherited workspace context. Do not copy or sync them into the new app; every workspace app reads them at runtime and may override with app shared or personal resources.`,
|
|
112
112
|
``,
|
|
113
|
-
`Pick a starter template that fits the user's prompt — analytics, brain, calendar, content, design, dispatch, forms, mail, slides, clips, or starter when none of the others fit.`,
|
|
113
|
+
`Pick a starter template that fits the user's prompt — analytics, assets, brain, calendar, content, design, dispatch, forms, mail, slides, clips, or starter when none of the others fit.`,
|
|
114
114
|
`If you use the starter template, treat it as scaffolding only: the finished app must use the requested app's real name, home screen, navigation, package metadata, and manifest, and it must not leave visible "Starter", "Blank app", or "New app" UI behind.`,
|
|
115
115
|
`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.`,
|
|
116
116
|
`Important routing rule: from outside the app, link to /${input.appId}; inside apps/${input.appId}, React Router routes are app-local. Use <Link to="/review"> and navigate("/review"), not "/${input.appId}/review"; APP_BASE_PATH supplies the mounted prefix, and hardcoding it causes doubled URLs like /${input.appId}/${input.appId}/review.`,
|
|
117
117
|
`Prefer useActionQuery/useActionMutation for actions. If you must raw-fetch framework endpoints, wrap them with agentNativePath("/_agent-native/actions/<name>") so mounted apps call the right URL.`,
|
|
118
118
|
`Use relative workspace links like /${input.appId}. Do not hardcode localhost, 127.0.0.1, 8080, 8100, or any dev port; the active workspace gateway/browser origin owns the port.`,
|
|
119
119
|
`Use the framework/template UI stack: shadcn/ui components and @tabler/icons-react. Do not add lucide-react or another icon library for standard UI.`,
|
|
120
|
-
`Existing first-party apps are neighbors, not implementation details for this app. If the user's prompt mentions Mail, Calendar, Analytics, Brain, 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, Analytics, and
|
|
121
|
-
`Do not clone first-party templates, create wrapper apps, or scaffold child apps/routes for Mail, Calendar, Analytics, Brain, etc. inside apps/${input.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.`,
|
|
120
|
+
`Existing first-party apps are neighbors, not implementation details for this app. If the user's prompt mentions Mail, Calendar, Analytics, Brain, Assets, 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, Analytics, Brain, and Assets already exist at https://mail.agent-native.com, https://calendar.agent-native.com, https://analytics.agent-native.com, https://brain.agent-native.com, and https://assets.agent-native.com.`,
|
|
121
|
+
`Do not clone first-party templates, create wrapper apps, or scaffold child apps/routes for Mail, Calendar, Analytics, Brain, Assets, etc. inside apps/${input.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.`,
|
|
122
122
|
`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.`,
|
|
123
123
|
`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.`,
|
|
124
124
|
input.vaultAccessMode === "all-apps"
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useMemo,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
type ComponentType,
|
|
7
|
+
type FormEvent,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { NavLink, useLocation, useNavigate } from "react-router";
|
|
3
11
|
import {
|
|
4
12
|
AgentSidebar,
|
|
5
13
|
FeedbackButton,
|
|
6
14
|
appBasePath,
|
|
7
15
|
appPath,
|
|
8
16
|
useActionQuery,
|
|
17
|
+
useChatThreads,
|
|
18
|
+
type ChatThreadSummary,
|
|
9
19
|
} from "@agent-native/core/client";
|
|
10
20
|
import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
|
|
11
21
|
import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
|
|
@@ -17,8 +27,12 @@ import {
|
|
|
17
27
|
IconBrandTelegram,
|
|
18
28
|
IconKey,
|
|
19
29
|
IconChevronDown,
|
|
30
|
+
IconDots,
|
|
31
|
+
IconEdit,
|
|
20
32
|
IconLayersSubtract,
|
|
33
|
+
IconMessageQuestion,
|
|
21
34
|
IconMessages,
|
|
35
|
+
IconPlus,
|
|
22
36
|
IconPlugConnected,
|
|
23
37
|
IconBroadcast,
|
|
24
38
|
IconFingerprint,
|
|
@@ -28,12 +42,24 @@ import {
|
|
|
28
42
|
IconUsersGroup,
|
|
29
43
|
} from "@tabler/icons-react";
|
|
30
44
|
import { cn } from "@/lib/utils";
|
|
45
|
+
import {
|
|
46
|
+
DropdownMenu,
|
|
47
|
+
DropdownMenuContent,
|
|
48
|
+
DropdownMenuItem,
|
|
49
|
+
DropdownMenuTrigger,
|
|
50
|
+
} from "@/components/ui/dropdown-menu";
|
|
51
|
+
import { Input } from "@/components/ui/input";
|
|
31
52
|
import {
|
|
32
53
|
Sheet,
|
|
33
54
|
SheetContent,
|
|
34
55
|
SheetDescription,
|
|
35
56
|
SheetTitle,
|
|
36
57
|
} from "@/components/ui/sheet";
|
|
58
|
+
import {
|
|
59
|
+
Tooltip,
|
|
60
|
+
TooltipContent,
|
|
61
|
+
TooltipTrigger,
|
|
62
|
+
} from "@/components/ui/tooltip";
|
|
37
63
|
import { Header } from "./Header";
|
|
38
64
|
import { HeaderActionsProvider } from "./HeaderActions";
|
|
39
65
|
|
|
@@ -65,6 +91,13 @@ export interface DispatchExtensionConfig {
|
|
|
65
91
|
}
|
|
66
92
|
|
|
67
93
|
const PRIMARY_NAV_ITEMS = [
|
|
94
|
+
{
|
|
95
|
+
id: "chat",
|
|
96
|
+
to: "/chat",
|
|
97
|
+
label: "Chat",
|
|
98
|
+
icon: IconMessageQuestion,
|
|
99
|
+
section: "primary",
|
|
100
|
+
},
|
|
68
101
|
{
|
|
69
102
|
id: "overview",
|
|
70
103
|
to: "/overview",
|
|
@@ -249,6 +282,249 @@ function dispatchNavLinkTarget(path: string): string {
|
|
|
249
282
|
return routerHasBasename ? path : appPath(path);
|
|
250
283
|
}
|
|
251
284
|
|
|
285
|
+
function formatThreadAge(updatedAt: number) {
|
|
286
|
+
const diffMs = Math.max(0, Date.now() - updatedAt);
|
|
287
|
+
const minutes = Math.floor(diffMs / 60_000);
|
|
288
|
+
if (minutes < 1) return "now";
|
|
289
|
+
if (minutes < 60) return `${minutes}m`;
|
|
290
|
+
const hours = Math.floor(minutes / 60);
|
|
291
|
+
if (hours < 24) return `${hours}h`;
|
|
292
|
+
const days = Math.floor(hours / 24);
|
|
293
|
+
if (days < 7) return `${days}d`;
|
|
294
|
+
return new Date(updatedAt).toLocaleDateString([], {
|
|
295
|
+
month: "short",
|
|
296
|
+
day: "numeric",
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function threadTitle(thread: ChatThreadSummary) {
|
|
301
|
+
return thread.title || thread.preview || "New chat";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function threadUpdatedAt(thread: ChatThreadSummary) {
|
|
305
|
+
return Number.isFinite(thread.updatedAt)
|
|
306
|
+
? thread.updatedAt
|
|
307
|
+
: Number.isFinite(thread.createdAt)
|
|
308
|
+
? thread.createdAt
|
|
309
|
+
: 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
|
|
313
|
+
const navigate = useNavigate();
|
|
314
|
+
const {
|
|
315
|
+
threads,
|
|
316
|
+
activeThreadId,
|
|
317
|
+
createThread,
|
|
318
|
+
switchThread,
|
|
319
|
+
renameThread,
|
|
320
|
+
refreshThreads,
|
|
321
|
+
} = useChatThreads(undefined, undefined, undefined, { autoCreate: false });
|
|
322
|
+
const [renamingThreadId, setRenamingThreadId] = useState<string | null>(null);
|
|
323
|
+
const [renameDraft, setRenameDraft] = useState("");
|
|
324
|
+
const renameInputRef = useRef<HTMLInputElement | null>(null);
|
|
325
|
+
const committingRenameRef = useRef(false);
|
|
326
|
+
|
|
327
|
+
const visibleThreads = useMemo(
|
|
328
|
+
() =>
|
|
329
|
+
threads
|
|
330
|
+
.filter(
|
|
331
|
+
(thread) => thread.messageCount > 0 || thread.id === activeThreadId,
|
|
332
|
+
)
|
|
333
|
+
.sort((a, b) => threadUpdatedAt(b) - threadUpdatedAt(a))
|
|
334
|
+
.slice(0, 8),
|
|
335
|
+
[activeThreadId, threads],
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
const refresh = () => refreshThreads();
|
|
340
|
+
const handleRunning = (event: Event) => {
|
|
341
|
+
const detail = (event as CustomEvent).detail as
|
|
342
|
+
| { isRunning?: unknown }
|
|
343
|
+
| undefined;
|
|
344
|
+
if (detail?.isRunning === false) refreshThreads();
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
window.addEventListener("agent-chat:threads-updated", refresh);
|
|
348
|
+
window.addEventListener("agentNative.chatRunning", handleRunning);
|
|
349
|
+
window.addEventListener("focus", refresh);
|
|
350
|
+
return () => {
|
|
351
|
+
window.removeEventListener("agent-chat:threads-updated", refresh);
|
|
352
|
+
window.removeEventListener("agentNative.chatRunning", handleRunning);
|
|
353
|
+
window.removeEventListener("focus", refresh);
|
|
354
|
+
};
|
|
355
|
+
}, [refreshThreads]);
|
|
356
|
+
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
if (!renamingThreadId) return;
|
|
359
|
+
requestAnimationFrame(() => {
|
|
360
|
+
renameInputRef.current?.focus();
|
|
361
|
+
renameInputRef.current?.select();
|
|
362
|
+
});
|
|
363
|
+
}, [renamingThreadId]);
|
|
364
|
+
|
|
365
|
+
function openThread(threadId: string, options?: { isNew?: boolean }) {
|
|
366
|
+
switchThread(threadId);
|
|
367
|
+
navigate(dispatchNavLinkTarget("/chat"));
|
|
368
|
+
onNavigate?.();
|
|
369
|
+
window.requestAnimationFrame(() => {
|
|
370
|
+
window.dispatchEvent(
|
|
371
|
+
new CustomEvent("agent-chat:open-thread", {
|
|
372
|
+
detail: { threadId, newThread: options?.isNew === true },
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function handleNewChat() {
|
|
379
|
+
const threadId = await createThread();
|
|
380
|
+
if (threadId) openThread(threadId, { isNew: true });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function startRenameThread(thread: ChatThreadSummary) {
|
|
384
|
+
committingRenameRef.current = false;
|
|
385
|
+
setRenameDraft(threadTitle(thread));
|
|
386
|
+
setRenamingThreadId(thread.id);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function cancelRenameThread() {
|
|
390
|
+
committingRenameRef.current = true;
|
|
391
|
+
setRenamingThreadId(null);
|
|
392
|
+
setRenameDraft("");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function commitRenameThread() {
|
|
396
|
+
if (committingRenameRef.current) return;
|
|
397
|
+
const threadId = renamingThreadId;
|
|
398
|
+
const title = renameDraft.trim();
|
|
399
|
+
if (!threadId) return;
|
|
400
|
+
committingRenameRef.current = true;
|
|
401
|
+
setRenamingThreadId(null);
|
|
402
|
+
setRenameDraft("");
|
|
403
|
+
if (title) await renameThread(threadId, title);
|
|
404
|
+
committingRenameRef.current = false;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function handleRenameSubmit(event: FormEvent<HTMLFormElement>) {
|
|
408
|
+
event.preventDefault();
|
|
409
|
+
void commitRenameThread();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<div className="mt-2 border-l border-sidebar-border/70 pl-3">
|
|
414
|
+
<div className="mb-1 flex h-7 items-center gap-2 pr-1">
|
|
415
|
+
<div className="min-w-0 flex-1 text-xs font-medium text-sidebar-foreground/70">
|
|
416
|
+
Chats
|
|
417
|
+
</div>
|
|
418
|
+
<Tooltip>
|
|
419
|
+
<TooltipTrigger asChild>
|
|
420
|
+
<button
|
|
421
|
+
type="button"
|
|
422
|
+
onClick={handleNewChat}
|
|
423
|
+
className="flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
424
|
+
aria-label="New Dispatch chat"
|
|
425
|
+
>
|
|
426
|
+
<IconPlus className="size-3.5" />
|
|
427
|
+
</button>
|
|
428
|
+
</TooltipTrigger>
|
|
429
|
+
<TooltipContent>New chat</TooltipContent>
|
|
430
|
+
</Tooltip>
|
|
431
|
+
</div>
|
|
432
|
+
<div className="grid gap-0.5">
|
|
433
|
+
{visibleThreads.length > 0 ? (
|
|
434
|
+
visibleThreads.map((thread) => {
|
|
435
|
+
const isActive = thread.id === activeThreadId;
|
|
436
|
+
const isRenaming = thread.id === renamingThreadId;
|
|
437
|
+
return (
|
|
438
|
+
<div
|
|
439
|
+
key={thread.id}
|
|
440
|
+
className={cn(
|
|
441
|
+
"group flex h-8 min-w-0 items-center rounded-md text-sm transition-colors",
|
|
442
|
+
isActive
|
|
443
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
444
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/65 hover:text-sidebar-accent-foreground",
|
|
445
|
+
)}
|
|
446
|
+
>
|
|
447
|
+
{isRenaming ? (
|
|
448
|
+
<form
|
|
449
|
+
onSubmit={handleRenameSubmit}
|
|
450
|
+
className="flex h-full min-w-0 flex-1 items-center px-1.5"
|
|
451
|
+
>
|
|
452
|
+
<Input
|
|
453
|
+
ref={renameInputRef}
|
|
454
|
+
value={renameDraft}
|
|
455
|
+
onChange={(event) => setRenameDraft(event.target.value)}
|
|
456
|
+
onBlur={() => void commitRenameThread()}
|
|
457
|
+
onKeyDown={(event) => {
|
|
458
|
+
if (event.key === "Escape") {
|
|
459
|
+
event.preventDefault();
|
|
460
|
+
cancelRenameThread();
|
|
461
|
+
}
|
|
462
|
+
}}
|
|
463
|
+
maxLength={160}
|
|
464
|
+
aria-label={`Rename ${threadTitle(thread)}`}
|
|
465
|
+
className="h-6 min-w-0 rounded-sm border-sidebar-border bg-background px-1.5 text-xs"
|
|
466
|
+
/>
|
|
467
|
+
</form>
|
|
468
|
+
) : (
|
|
469
|
+
<>
|
|
470
|
+
<button
|
|
471
|
+
type="button"
|
|
472
|
+
onClick={() => openThread(thread.id)}
|
|
473
|
+
className="flex h-full min-w-0 flex-1 cursor-pointer items-center px-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
474
|
+
>
|
|
475
|
+
<span className="min-w-0 flex-1 truncate">
|
|
476
|
+
{threadTitle(thread)}
|
|
477
|
+
</span>
|
|
478
|
+
</button>
|
|
479
|
+
<div className="relative flex size-7 shrink-0 items-center justify-end pr-1">
|
|
480
|
+
<span className="text-[11px] text-sidebar-foreground/50 transition-opacity group-hover:opacity-0 group-focus-within:opacity-0">
|
|
481
|
+
{isActive
|
|
482
|
+
? ""
|
|
483
|
+
: formatThreadAge(threadUpdatedAt(thread))}
|
|
484
|
+
</span>
|
|
485
|
+
<DropdownMenu>
|
|
486
|
+
<DropdownMenuTrigger asChild>
|
|
487
|
+
<button
|
|
488
|
+
type="button"
|
|
489
|
+
aria-label={`Chat options for ${threadTitle(thread)}`}
|
|
490
|
+
className="absolute right-1 flex size-6 cursor-pointer items-center justify-center rounded-md text-sidebar-foreground/65 opacity-0 transition-opacity hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover:opacity-100 group-focus-within:opacity-100 data-[state=open]:opacity-100"
|
|
491
|
+
>
|
|
492
|
+
<IconDots className="size-4" />
|
|
493
|
+
</button>
|
|
494
|
+
</DropdownMenuTrigger>
|
|
495
|
+
<DropdownMenuContent
|
|
496
|
+
align="end"
|
|
497
|
+
side="right"
|
|
498
|
+
sideOffset={6}
|
|
499
|
+
>
|
|
500
|
+
<DropdownMenuItem
|
|
501
|
+
onSelect={() => startRenameThread(thread)}
|
|
502
|
+
>
|
|
503
|
+
<IconEdit className="size-4" />
|
|
504
|
+
Rename chat
|
|
505
|
+
</DropdownMenuItem>
|
|
506
|
+
</DropdownMenuContent>
|
|
507
|
+
</DropdownMenu>
|
|
508
|
+
</div>
|
|
509
|
+
</>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
512
|
+
);
|
|
513
|
+
})
|
|
514
|
+
) : (
|
|
515
|
+
<button
|
|
516
|
+
type="button"
|
|
517
|
+
onClick={handleNewChat}
|
|
518
|
+
className="flex h-8 cursor-pointer items-center rounded-md px-2 text-left text-sm text-sidebar-foreground/70 transition-colors hover:bg-sidebar-accent/65 hover:text-sidebar-accent-foreground"
|
|
519
|
+
>
|
|
520
|
+
<span className="truncate">New chat</span>
|
|
521
|
+
</button>
|
|
522
|
+
)}
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
252
528
|
export function NavContent({
|
|
253
529
|
onNavigate,
|
|
254
530
|
extensions,
|
|
@@ -280,13 +556,14 @@ export function NavContent({
|
|
|
280
556
|
|
|
281
557
|
const renderNavItem = (item: DispatchNavItem) => {
|
|
282
558
|
const Icon = item.icon;
|
|
559
|
+
const itemMatchesLocalPath = navItemMatchesPath(item, localPathname);
|
|
283
560
|
return (
|
|
284
561
|
<li key={item.id}>
|
|
285
562
|
<NavLink
|
|
286
563
|
to={dispatchNavLinkTarget(item.to)}
|
|
287
564
|
onClick={onNavigate}
|
|
288
565
|
className={({ isActive }) => {
|
|
289
|
-
const active = isActive ||
|
|
566
|
+
const active = isActive || itemMatchesLocalPath;
|
|
290
567
|
return cn(
|
|
291
568
|
"flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm",
|
|
292
569
|
active
|
|
@@ -302,6 +579,9 @@ export function NavContent({
|
|
|
302
579
|
)}
|
|
303
580
|
<span className="truncate">{item.label}</span>
|
|
304
581
|
</NavLink>
|
|
582
|
+
{item.id === "chat" && itemMatchesLocalPath ? (
|
|
583
|
+
<DispatchChatsSection onNavigate={onNavigate} />
|
|
584
|
+
) : null}
|
|
305
585
|
</li>
|
|
306
586
|
);
|
|
307
587
|
};
|
|
@@ -384,17 +664,24 @@ export function Layout({
|
|
|
384
664
|
}) {
|
|
385
665
|
const location = useLocation();
|
|
386
666
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
667
|
+
const localPathname = localDispatchPath(location.pathname);
|
|
387
668
|
|
|
388
|
-
if (CHROMELESS_PATHS.some((path) =>
|
|
669
|
+
if (CHROMELESS_PATHS.some((path) => localPathname === path)) {
|
|
389
670
|
return <>{children}</>;
|
|
390
671
|
}
|
|
391
672
|
|
|
392
|
-
const
|
|
673
|
+
const isChatRoute = localPathname === "/chat";
|
|
674
|
+
const showHeader = !isChatRoute && !pageOwnsToolbar(localPathname);
|
|
393
675
|
const appContent = (
|
|
394
|
-
<div className="flex h-full flex-1 flex-col overflow-hidden">
|
|
676
|
+
<div className="flex h-full min-w-0 flex-1 flex-col overflow-hidden">
|
|
395
677
|
{showHeader ? <Header onOpenMobile={() => setMobileOpen(true)} /> : null}
|
|
396
678
|
<InvitationBanner />
|
|
397
|
-
<main
|
|
679
|
+
<main
|
|
680
|
+
className={cn(
|
|
681
|
+
"flex-1",
|
|
682
|
+
isChatRoute ? "min-h-0 overflow-hidden" : "overflow-y-auto",
|
|
683
|
+
)}
|
|
684
|
+
>
|
|
398
685
|
{showHeader ? (
|
|
399
686
|
<div className="mx-auto max-w-7xl space-y-10 px-4 py-6 sm:px-6">
|
|
400
687
|
{children}
|
|
@@ -405,6 +692,18 @@ export function Layout({
|
|
|
405
692
|
</main>
|
|
406
693
|
</div>
|
|
407
694
|
);
|
|
695
|
+
const content = isChatRoute ? (
|
|
696
|
+
appContent
|
|
697
|
+
) : (
|
|
698
|
+
<AgentSidebar
|
|
699
|
+
position="right"
|
|
700
|
+
defaultOpen={false}
|
|
701
|
+
emptyStateText="Create apps, manage vault keys, and route work across the workspace."
|
|
702
|
+
suggestions={SIDEBAR_SUGGESTIONS}
|
|
703
|
+
>
|
|
704
|
+
{appContent}
|
|
705
|
+
</AgentSidebar>
|
|
706
|
+
);
|
|
408
707
|
|
|
409
708
|
return (
|
|
410
709
|
<HeaderActionsProvider>
|
|
@@ -431,18 +730,7 @@ export function Layout({
|
|
|
431
730
|
</SheetContent>
|
|
432
731
|
</Sheet>
|
|
433
732
|
|
|
434
|
-
{
|
|
435
|
-
* Always mount AgentSidebar so home composer's sendToAgentChat
|
|
436
|
-
* fallback can pop it via agent-panel:open.
|
|
437
|
-
*/}
|
|
438
|
-
<AgentSidebar
|
|
439
|
-
position="right"
|
|
440
|
-
defaultOpen={false}
|
|
441
|
-
emptyStateText="Create apps, manage vault keys, and route work across the workspace."
|
|
442
|
-
suggestions={SIDEBAR_SUGGESTIONS}
|
|
443
|
-
>
|
|
444
|
-
{appContent}
|
|
445
|
-
</AgentSidebar>
|
|
733
|
+
{content}
|
|
446
734
|
</div>
|
|
447
735
|
</HeaderActionsProvider>
|
|
448
736
|
);
|
|
@@ -2,6 +2,13 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { buildDispatchNavigationState } from "./use-navigation-state.js";
|
|
3
3
|
|
|
4
4
|
describe("buildDispatchNavigationState", () => {
|
|
5
|
+
it("recognizes the full-page chat route", () => {
|
|
6
|
+
expect(buildDispatchNavigationState("/chat")).toEqual({
|
|
7
|
+
view: "chat",
|
|
8
|
+
path: "/chat",
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
5
12
|
it("exposes the current extension id from extension routes", () => {
|
|
6
13
|
expect(
|
|
7
14
|
buildDispatchNavigationState("/extensions/ext-1/github-stars-over-time"),
|
|
@@ -173,6 +173,7 @@ function resolveView(
|
|
|
173
173
|
if (pathname === "/extensions" || pathname.startsWith("/extensions/")) {
|
|
174
174
|
return "extensions";
|
|
175
175
|
}
|
|
176
|
+
if (pathname.startsWith("/chat")) return "chat";
|
|
176
177
|
if (pathname.startsWith("/apps")) return "apps";
|
|
177
178
|
if (pathname.startsWith("/metrics")) return "metrics";
|
|
178
179
|
if (pathname.startsWith("/new-app")) return "new-app";
|
|
@@ -197,6 +198,9 @@ function resolvePath(
|
|
|
197
198
|
command?: Pick<NavigationState, "extensionId">,
|
|
198
199
|
): string | undefined {
|
|
199
200
|
switch (view) {
|
|
201
|
+
case "chat":
|
|
202
|
+
case "ask":
|
|
203
|
+
return "/chat";
|
|
200
204
|
case "overview":
|
|
201
205
|
return "/overview";
|
|
202
206
|
case "apps":
|
|
@@ -28,6 +28,21 @@ describe("submitOverviewPrompt", () => {
|
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
it("can submit to a mounted page chat without opening the sidebar", () => {
|
|
32
|
+
const tabId = submitOverviewPrompt(" build a metrics app ", "auto", {
|
|
33
|
+
openSidebar: false,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(tabId).toBe("chat-tab");
|
|
37
|
+
expect(sendToAgentChatMock).toHaveBeenCalledWith({
|
|
38
|
+
message: "build a metrics app",
|
|
39
|
+
submit: true,
|
|
40
|
+
newTab: true,
|
|
41
|
+
model: "auto",
|
|
42
|
+
openSidebar: false,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
31
46
|
it("routes overview prompts to Builder chat inside Builder", () => {
|
|
32
47
|
frameState.inBuilderFrame = true;
|
|
33
48
|
|
package/src/lib/overview-chat.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { isInBuilderFrame, sendToAgentChat } from "@agent-native/core/client";
|
|
|
3
3
|
export function submitOverviewPrompt(
|
|
4
4
|
message: string,
|
|
5
5
|
selectedModel?: string | null,
|
|
6
|
+
options?: { openSidebar?: boolean },
|
|
6
7
|
): string | null {
|
|
7
8
|
const trimmed = message.trim();
|
|
8
9
|
if (!trimmed) return null;
|
|
@@ -20,5 +21,6 @@ export function submitOverviewPrompt(
|
|
|
20
21
|
submit: true,
|
|
21
22
|
newTab: true,
|
|
22
23
|
model: selectedModel || undefined,
|
|
24
|
+
...(options?.openSidebar === false ? { openSidebar: false } : {}),
|
|
23
25
|
});
|
|
24
26
|
}
|
package/src/routes/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { type RouteConfig, route, index } from "@react-router/dev/routes";
|
|
|
31
31
|
*/
|
|
32
32
|
export const dispatchRoutes: RouteConfig = [
|
|
33
33
|
index("./pages/_index.js"),
|
|
34
|
+
route("chat", "./pages/chat.js"),
|
|
34
35
|
route("overview", "./pages/overview.js"),
|
|
35
36
|
route("metrics", "./pages/metrics.js"),
|
|
36
37
|
route("apps", "./pages/apps.js"),
|