@agent-native/dispatch 0.20.1 → 0.20.3
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/dist/actions/start-workspace-app-creation.js +1 -1
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +1 -0
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/layout/Header.d.ts.map +1 -1
- package/dist/components/layout/Header.js +1 -2
- package/dist/components/layout/Header.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +108 -16
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/plugins/integrations.js +1 -1
- package/dist/server/plugins/integrations.js.map +1 -1
- package/package.json +3 -3
- package/src/actions/start-workspace-app-creation.ts +1 -1
- package/src/components/create-app-popover.tsx +1 -0
- package/src/components/layout/Header.tsx +0 -2
- package/src/server/lib/app-creation-store.spec.ts +71 -5
- package/src/server/lib/app-creation-store.ts +128 -19
- package/src/server/plugins/integrations.ts +1 -1
|
@@ -36,7 +36,7 @@ When a user asks for something:
|
|
|
36
36
|
- 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.
|
|
37
37
|
- 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 app from that template.
|
|
38
38
|
- If the chat 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 "Chat", "Starter", "Blank app", or "New app" UI behind.
|
|
39
|
-
- 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/chat 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.
|
|
39
|
+
- 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/chat 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. Dispatch reuses the connected workspace Builder project or provisions it through the Builder Projects API when an organization owner/admin starts the first app; members need an owner/admin to configure the project first. Do not ask the user to use Builder org/beta settings. If it returns mode "builder-unavailable", the action also returns a \`reason\` code plus a user-facing \`message\` and (for some reasons) an operator-facing \`detail\`. Relay the \`message\` to the user as-is; only surface \`detail\` when the user is clearly an operator debugging the deployment (e.g. troubleshooting a broken Builder connection for the workspace), never as routine detail for an end user.
|
|
40
40
|
- For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.
|
|
41
41
|
- Keep responses concise and operational — messaging platforms have character limits.
|
|
42
42
|
- Use markdown sparingly (bold and lists are fine, avoid complex formatting).
|
|
@@ -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;AAErE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,+BAA+B,GAChC,MAAM,iCAAiC,CAAC;AAEzC,MAAM,0BAA0B,GAAG;IACjC,GAAG,eAAe;IAClB,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,OAAO,EAAE;QACP,GAAG,eAAe,CAAC,OAAO;QAC1B,SAAS,EAAE,KAAK;KACjB;CACF,CAAC;AAEF,MAAM,kCAAkC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;4FA4BiD,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,0BAA0B;QACnC,uBAAuB,EAAE,+BAA+B;QACxD,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\";\n\nimport { dispatchActions } from \"../../actions/index.js\";\nimport { getDispatchConfig } from \"../index.js\";\nimport {\n beforeDispatchProcess,\n resolveDispatchExecutionContext,\n} from \"../lib/dispatch-integrations.js\";\n\nconst dispatchIntegrationActions = {\n ...dispatchActions,\n // Messaging integrations should use the core call-agent tool for cross-app\n // delegation because it queues A2A continuations when serverless budgets are\n // tight. The MCP-facing ask_app action is still available outside this path.\n ask_app: {\n ...dispatchActions.ask_app,\n agentTool: false,\n },\n};\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), 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- Hosted/connected A2A neighbors such as Analytics and Content come from the available-apps context or list-connected-agents. list-workspace-apps only inventories apps mounted inside this workspace deployment; never use a missing row there to conclude that a connected agent is unavailable.\n- When asked whether a mounted workspace app exposes an agent card or A2A endpoint, 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- Integration grants are not provider capability limits. For ad hoc provider inspection, querying, reporting, or troubleshooting, call provider-api-catalog/provider-api-docs, then provider-api-request against the provider's real HTTP API. Use connectionId for a specific shared grant and accountId for a specific OAuth account. Never expose secret values or silently widen app access while doing this.\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, clips, assets, etc., delegate via call-agent — do not re-implement the domain logic in dispatch.\n- Synthetic uptime, health-check, and URL-availability monitors belong to Analytics, even when the monitored target is another app such as Clips. Delegate creation to Analytics and relay its exact monitor URL. Dispatch-native recurring jobs are for reminders, digests, and agent workflows, not HTTP uptime probes.\n- Route by the requested artifact type, not by organization-specific names stored in code. For structured records, databases, tables, queues, boards, and intake forms, resolve the owning app and canonical destination from loaded workspace instructions/resources plus discovered app capabilities; do not assume Content, a database ID, schema, owner, or required fields. Visual designs, mockups, wireframes, screens, and interfaces belong to Design. A trusted Required target agent hint in integration context is authoritative.\n- When delegating structured intake to the resolved owning app, preserve the exact Source thread URL and the workspace instruction context, inspect the destination's current required fields, ask only for missing values, submit once, verify the saved record, and return the exact link.\n- In messaging integrations, use call-agent for cross-app delegation; do not use ask_app.\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 app from that template.\n- If the chat 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 \"Chat\", \"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/chat 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\", say this requires a code change and they can edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way they like; do not send them to Builder org/beta settings. If it returns mode \"builder-unavailable\", the action also returns a \\`reason\\` code plus a user-facing \\`message\\` and (for some reasons) an operator-facing \\`detail\\`. Relay the \\`message\\` to the user as-is; only surface \\`detail\\` when the user is clearly an operator debugging the deployment (e.g. troubleshooting a broken Builder connection for the workspace), never as routine detail for an end user.\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: dispatchIntegrationActions,\n resolveExecutionContext: resolveDispatchExecutionContext,\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;AAErE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,+BAA+B,GAChC,MAAM,iCAAiC,CAAC;AAEzC,MAAM,0BAA0B,GAAG;IACjC,GAAG,eAAe;IAClB,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,OAAO,EAAE;QACP,GAAG,eAAe,CAAC,OAAO;QAC1B,SAAS,EAAE,KAAK;KACjB;CACF,CAAC;AAEF,MAAM,kCAAkC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;4FA4BiD,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,0BAA0B;QACnC,uBAAuB,EAAE,+BAA+B;QACxD,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\";\n\nimport { dispatchActions } from \"../../actions/index.js\";\nimport { getDispatchConfig } from \"../index.js\";\nimport {\n beforeDispatchProcess,\n resolveDispatchExecutionContext,\n} from \"../lib/dispatch-integrations.js\";\n\nconst dispatchIntegrationActions = {\n ...dispatchActions,\n // Messaging integrations should use the core call-agent tool for cross-app\n // delegation because it queues A2A continuations when serverless budgets are\n // tight. The MCP-facing ask_app action is still available outside this path.\n ask_app: {\n ...dispatchActions.ask_app,\n agentTool: false,\n },\n};\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), 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- Hosted/connected A2A neighbors such as Analytics and Content come from the available-apps context or list-connected-agents. list-workspace-apps only inventories apps mounted inside this workspace deployment; never use a missing row there to conclude that a connected agent is unavailable.\n- When asked whether a mounted workspace app exposes an agent card or A2A endpoint, 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- Integration grants are not provider capability limits. For ad hoc provider inspection, querying, reporting, or troubleshooting, call provider-api-catalog/provider-api-docs, then provider-api-request against the provider's real HTTP API. Use connectionId for a specific shared grant and accountId for a specific OAuth account. Never expose secret values or silently widen app access while doing this.\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, clips, assets, etc., delegate via call-agent — do not re-implement the domain logic in dispatch.\n- Synthetic uptime, health-check, and URL-availability monitors belong to Analytics, even when the monitored target is another app such as Clips. Delegate creation to Analytics and relay its exact monitor URL. Dispatch-native recurring jobs are for reminders, digests, and agent workflows, not HTTP uptime probes.\n- Route by the requested artifact type, not by organization-specific names stored in code. For structured records, databases, tables, queues, boards, and intake forms, resolve the owning app and canonical destination from loaded workspace instructions/resources plus discovered app capabilities; do not assume Content, a database ID, schema, owner, or required fields. Visual designs, mockups, wireframes, screens, and interfaces belong to Design. A trusted Required target agent hint in integration context is authoritative.\n- When delegating structured intake to the resolved owning app, preserve the exact Source thread URL and the workspace instruction context, inspect the destination's current required fields, ask only for missing values, submit once, verify the saved record, and return the exact link.\n- In messaging integrations, use call-agent for cross-app delegation; do not use ask_app.\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 app from that template.\n- If the chat 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 \"Chat\", \"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/chat 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. Dispatch reuses the connected workspace Builder project or provisions it through the Builder Projects API when an organization owner/admin starts the first app; members need an owner/admin to configure the project first. Do not ask the user to use Builder org/beta settings. If it returns mode \"builder-unavailable\", the action also returns a \\`reason\\` code plus a user-facing \\`message\\` and (for some reasons) an operator-facing \\`detail\\`. Relay the \\`message\\` to the user as-is; only surface \\`detail\\` when the user is clearly an operator debugging the deployment (e.g. troubleshooting a broken Builder connection for the workspace), never as routine detail for an end user.\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: dispatchIntegrationActions,\n resolveExecutionContext: resolveDispatchExecutionContext,\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.20.
|
|
3
|
+
"version": "0.20.3",
|
|
4
4
|
"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.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"tailwind-merge": "^3.5.0",
|
|
85
85
|
"vaul": "^1.1.2",
|
|
86
86
|
"zod": "^4.3.6",
|
|
87
|
-
"@agent-native/toolkit": "0.13.
|
|
87
|
+
"@agent-native/toolkit": "0.13.5"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@react-router/dev": "^8.1.0",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
99
99
|
"vite": "8.1.0",
|
|
100
100
|
"vitest": "^4.1.5",
|
|
101
|
-
"@agent-native/core": "0.146.
|
|
101
|
+
"@agent-native/core": "0.146.2"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
104
|
"@agent-native/core": ">=0.8.0",
|
|
@@ -6,7 +6,7 @@ import { startWorkspaceAppCreation } from "../server/lib/app-creation-store.js";
|
|
|
6
6
|
|
|
7
7
|
export default defineAction({
|
|
8
8
|
description:
|
|
9
|
-
'Start creating a new workspace app from Dispatch when the request truly needs its own app. For Claude Web, ChatGPT Web, or either host\'s web Project, this action is the Builder handoff: call it after forming a bounded source brief and do not scaffold or edit files in the host sandbox. 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
|
|
9
|
+
'Start creating a new workspace app from Dispatch when the request truly needs its own app. For Claude Web, ChatGPT Web, or either host\'s web Project, this action is the Builder handoff: call it after forming a bounded source brief and do not scaffold or edit files in the host sandbox. 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 reuses the connected Agent-Native workspace project or, for an organization owner/admin, provisions it through the Builder Projects API before starting the Builder Cloud Agent branch. If no project is configured, organization members must ask an owner/admin to configure it first. The result must be a separate workspace app under apps/<app-id>, not a new route or file in apps/chat. If chat is used as the source template, the finished app must be branded as the requested app and must not leave visible "Chat", "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 wrap or nest those apps inside the new app unless the user explicitly asks for a customized app from that template.',
|
|
10
10
|
schema: z.object({
|
|
11
11
|
prompt: z.string().min(1).describe("The user's app creation request"),
|
|
12
12
|
appId: z
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AgentToggleButton } from "@agent-native/core/client/agent-chat";
|
|
2
|
-
import { LanguagePicker } from "@agent-native/core/client/i18n";
|
|
3
2
|
import { RunsTray } from "@agent-native/core/client/progress";
|
|
4
3
|
import { IconLayoutSidebar } from "@tabler/icons-react";
|
|
5
4
|
import { useLocation, useNavigate } from "react-router";
|
|
@@ -91,7 +90,6 @@ export function Header({
|
|
|
91
90
|
</div>
|
|
92
91
|
<div className="flex items-center gap-2 shrink-0">
|
|
93
92
|
{actions}
|
|
94
|
-
<LanguagePicker variant="icon" />
|
|
95
93
|
<RunsTray limit={8} onOpenThread={openRunThread} />
|
|
96
94
|
{showAgentToggle ? (
|
|
97
95
|
<AgentToggleButton className="h-8 w-8 rounded-md hover:bg-accent" />
|
|
@@ -44,6 +44,7 @@ const mocks = vi.hoisted(() => {
|
|
|
44
44
|
source: null,
|
|
45
45
|
lookupFailed: false,
|
|
46
46
|
})),
|
|
47
|
+
ensureBuilderProject: vi.fn(),
|
|
47
48
|
runBuilderAgent: vi.fn(),
|
|
48
49
|
resolveBuilderBranchProjectId: vi.fn(async () => ""),
|
|
49
50
|
getBuilderBranchProjectId: vi.fn(() => ""),
|
|
@@ -82,6 +83,8 @@ vi.mock("@agent-native/core/server", async (importOriginal) => {
|
|
|
82
83
|
...actual,
|
|
83
84
|
resolveBuilderCredentialsDetailed: (...args: any[]) =>
|
|
84
85
|
mocks.resolveBuilderCredentialsDetailed(...args),
|
|
86
|
+
ensureBuilderProject: (...args: any[]) =>
|
|
87
|
+
mocks.ensureBuilderProject(...args),
|
|
85
88
|
runBuilderAgent: (...args: any[]) => mocks.runBuilderAgent(...args),
|
|
86
89
|
resolveBuilderBranchProjectId: (...args: any[]) =>
|
|
87
90
|
mocks.resolveBuilderBranchProjectId(...args),
|
|
@@ -120,6 +123,7 @@ afterEach(() => {
|
|
|
120
123
|
});
|
|
121
124
|
mocks.resolveBuilderBranchProjectId.mockResolvedValue("");
|
|
122
125
|
mocks.getBuilderBranchProjectId.mockReturnValue("");
|
|
126
|
+
mocks.ensureBuilderProject.mockReset();
|
|
123
127
|
globalThis.fetch = originalFetch;
|
|
124
128
|
});
|
|
125
129
|
|
|
@@ -469,8 +473,13 @@ describe("startWorkspaceAppCreation", () => {
|
|
|
469
473
|
};
|
|
470
474
|
}
|
|
471
475
|
|
|
472
|
-
function create(
|
|
473
|
-
|
|
476
|
+
function create(
|
|
477
|
+
appId = "onboarding",
|
|
478
|
+
ctx: { userEmail: string; orgId?: string } = {
|
|
479
|
+
userEmail: "dev@example.test",
|
|
480
|
+
},
|
|
481
|
+
) {
|
|
482
|
+
return runWithRequestContext(ctx, () =>
|
|
474
483
|
startWorkspaceAppCreation({ prompt: "Track onboarding tasks", appId }),
|
|
475
484
|
);
|
|
476
485
|
}
|
|
@@ -548,13 +557,70 @@ describe("startWorkspaceAppCreation", () => {
|
|
|
548
557
|
);
|
|
549
558
|
});
|
|
550
559
|
|
|
551
|
-
it("
|
|
560
|
+
it("provisions and remembers the workspace Builder project when none is configured", async () => {
|
|
552
561
|
stubHostedRuntime();
|
|
562
|
+
mocks.resolveBuilderCredentialsDetailed.mockResolvedValue(
|
|
563
|
+
credentials({
|
|
564
|
+
privateKey: "priv",
|
|
565
|
+
publicKey: "pub",
|
|
566
|
+
userId: "builder-user-42",
|
|
567
|
+
}),
|
|
568
|
+
);
|
|
569
|
+
mocks.ensureBuilderProject.mockResolvedValue({
|
|
570
|
+
projectId: "project-provisioned",
|
|
571
|
+
name: "Agent-Native Workspace",
|
|
572
|
+
repoUrl: "https://github.com/BuilderIO/builder-agent-native-workspace",
|
|
573
|
+
browserUrl: "https://builder.io/app/projects/project-provisioned",
|
|
574
|
+
created: true,
|
|
575
|
+
});
|
|
576
|
+
mocks.runBuilderAgent.mockResolvedValue({
|
|
577
|
+
branchName: "onboarding1",
|
|
578
|
+
url: "https://builder.io/app/projects/project-provisioned/onboarding1",
|
|
579
|
+
status: "processing",
|
|
580
|
+
});
|
|
553
581
|
|
|
554
582
|
const result = (await create()) as any;
|
|
555
583
|
|
|
556
|
-
expect(result.mode).toBe("
|
|
557
|
-
expect(
|
|
584
|
+
expect(result.mode).toBe("builder");
|
|
585
|
+
expect(result.projectId).toBe("project-provisioned");
|
|
586
|
+
expect(mocks.ensureBuilderProject).toHaveBeenCalledWith({
|
|
587
|
+
name: "Agent-Native Workspace",
|
|
588
|
+
repoUrl: "https://github.com/BuilderIO/builder-agent-native-workspace",
|
|
589
|
+
});
|
|
590
|
+
expect(mocks.runBuilderAgent).toHaveBeenCalledWith(
|
|
591
|
+
expect.objectContaining({ projectId: "project-provisioned" }),
|
|
592
|
+
);
|
|
593
|
+
expect(mocks.writeAppSecret).toHaveBeenCalledWith(
|
|
594
|
+
expect.objectContaining({
|
|
595
|
+
key: "BUILDER_BRANCH_PROJECT_ID",
|
|
596
|
+
value: "project-provisioned",
|
|
597
|
+
}),
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it("does not let an organization member persist an auto-provisioned project", async () => {
|
|
602
|
+
stubHostedRuntime();
|
|
603
|
+
mocks.state.orgRole = "member";
|
|
604
|
+
mocks.resolveBuilderCredentialsDetailed.mockResolvedValue(
|
|
605
|
+
credentials({
|
|
606
|
+
privateKey: "priv",
|
|
607
|
+
publicKey: "pub",
|
|
608
|
+
userId: "builder-user-42",
|
|
609
|
+
}),
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
const result = (await create("onboarding", {
|
|
613
|
+
userEmail: "dev@example.test",
|
|
614
|
+
orgId: "builder_io",
|
|
615
|
+
})) as any;
|
|
616
|
+
|
|
617
|
+
expect(result).toMatchObject({
|
|
618
|
+
mode: "builder-unavailable",
|
|
619
|
+
reason: "settings-management-required",
|
|
620
|
+
});
|
|
621
|
+
expect(mocks.ensureBuilderProject).not.toHaveBeenCalled();
|
|
622
|
+
expect(mocks.putSetting).not.toHaveBeenCalled();
|
|
623
|
+
expect(mocks.writeAppSecret).not.toHaveBeenCalled();
|
|
558
624
|
});
|
|
559
625
|
});
|
|
560
626
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type SecretScope,
|
|
11
11
|
} from "@agent-native/core/secrets";
|
|
12
12
|
import {
|
|
13
|
+
ensureBuilderProject,
|
|
13
14
|
getBuilderBranchProjectId,
|
|
14
15
|
getRequestContext,
|
|
15
16
|
isIntegrationCallerRequest,
|
|
@@ -38,6 +39,14 @@ const SETTINGS_KEY = "dispatch-app-creation-settings";
|
|
|
38
39
|
const BUILDER_BRANCH_PROJECT_SECRET_KEY = "BUILDER_BRANCH_PROJECT_ID";
|
|
39
40
|
const BUILDER_BRANCH_PROJECT_SECRET_DESCRIPTION =
|
|
40
41
|
"Builder project for cloud code-change branches (set in Dispatch)";
|
|
42
|
+
const BUILDER_WORKSPACE_REPO_URL_ENV = "AGENT_NATIVE_WORKSPACE_REPO_URL";
|
|
43
|
+
const DEFAULT_BUILDER_WORKSPACE_REPO_URL =
|
|
44
|
+
"https://github.com/BuilderIO/builder-agent-native-workspace";
|
|
45
|
+
const DEFAULT_BUILDER_WORKSPACE_PROJECT_NAME = "Agent-Native Workspace";
|
|
46
|
+
const APP_CREATION_SETTINGS_AUTHORIZATION_MESSAGE =
|
|
47
|
+
"Only organization owners and admins can update app creation settings.";
|
|
48
|
+
const APP_CREATION_SETTINGS_REQUIRED_MESSAGE =
|
|
49
|
+
"An organization owner or admin must configure the Builder workspace project before members can create apps.";
|
|
41
50
|
const WORKSPACE_APP_METADATA_SETTINGS_KEY = "workspace-app-metadata";
|
|
42
51
|
const WORKSPACE_APPS_ENV_KEY = "AGENT_NATIVE_WORKSPACE_APPS_JSON";
|
|
43
52
|
const WORKSPACE_APPS_MANIFEST_FILE = "workspace-apps.json";
|
|
@@ -48,6 +57,17 @@ const PENDING_WORKSPACE_APP_TTL_MS = 7 * 24 * 60 * 60 * 1_000;
|
|
|
48
57
|
const AGENT_CARD_PATH = "/.well-known/agent-card.json";
|
|
49
58
|
const AGENT_CARD_FETCH_TIMEOUT_MS = 1_500;
|
|
50
59
|
const DEFAULT_WORKSPACE_APP_AUDIENCE = "internal";
|
|
60
|
+
const pendingBuilderProjectProvisioning = new Map<
|
|
61
|
+
string,
|
|
62
|
+
Promise<{ projectId: string }>
|
|
63
|
+
>();
|
|
64
|
+
|
|
65
|
+
class AppCreationSettingsAuthorizationError extends Error {
|
|
66
|
+
constructor() {
|
|
67
|
+
super(APP_CREATION_SETTINGS_AUTHORIZATION_MESSAGE);
|
|
68
|
+
this.name = "AppCreationSettingsAuthorizationError";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
51
71
|
|
|
52
72
|
type WorkspaceAppAudience = "internal" | "public";
|
|
53
73
|
|
|
@@ -1605,6 +1625,75 @@ export async function getAppCreationSettings(): Promise<AppCreationSettings> {
|
|
|
1605
1625
|
};
|
|
1606
1626
|
}
|
|
1607
1627
|
|
|
1628
|
+
function builderWorkspaceRepoUrl(): string {
|
|
1629
|
+
return (
|
|
1630
|
+
process.env[BUILDER_WORKSPACE_REPO_URL_ENV]?.trim() ||
|
|
1631
|
+
DEFAULT_BUILDER_WORKSPACE_REPO_URL
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
async function persistProvisionedBuilderProjectId(
|
|
1636
|
+
builderProjectId: string,
|
|
1637
|
+
): Promise<void> {
|
|
1638
|
+
const raw = await readSettingsRecord();
|
|
1639
|
+
const secretTarget = builderProjectSecretTarget();
|
|
1640
|
+
|
|
1641
|
+
// This is an internal consequence of an authorized app-creation request,
|
|
1642
|
+
// not a user-controlled settings update. Persist the project before the
|
|
1643
|
+
// branch run so later members reuse the same Builder project.
|
|
1644
|
+
if (secretTarget) {
|
|
1645
|
+
await writeAppSecret({
|
|
1646
|
+
key: BUILDER_BRANCH_PROJECT_SECRET_KEY,
|
|
1647
|
+
scope: secretTarget.scope,
|
|
1648
|
+
scopeId: secretTarget.scopeId,
|
|
1649
|
+
value: builderProjectId,
|
|
1650
|
+
description: BUILDER_BRANCH_PROJECT_SECRET_DESCRIPTION,
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
await putSetting(scopedSettingsKey(), { ...raw, builderProjectId });
|
|
1654
|
+
await recordAudit({
|
|
1655
|
+
action: "settings.updated",
|
|
1656
|
+
targetType: "dispatch-app-creation-settings",
|
|
1657
|
+
targetId: SETTINGS_KEY,
|
|
1658
|
+
summary: "Provisioned the Builder project for workspace app creation",
|
|
1659
|
+
metadata: {
|
|
1660
|
+
builderProjectIdConfigured: true,
|
|
1661
|
+
source: "builder-project-create-api",
|
|
1662
|
+
},
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
async function ensureBuilderProjectForWorkspace(): Promise<{
|
|
1667
|
+
projectId: string;
|
|
1668
|
+
}> {
|
|
1669
|
+
const key = scopedSettingsKey();
|
|
1670
|
+
const existing = pendingBuilderProjectProvisioning.get(key);
|
|
1671
|
+
if (existing) return existing;
|
|
1672
|
+
|
|
1673
|
+
const pending = (async () => {
|
|
1674
|
+
const current = await getAppCreationSettings();
|
|
1675
|
+
if (current.builderProjectId) {
|
|
1676
|
+
return { projectId: current.builderProjectId };
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
await assertCanManageAppCreationSettings();
|
|
1680
|
+
const project = await ensureBuilderProject({
|
|
1681
|
+
name: DEFAULT_BUILDER_WORKSPACE_PROJECT_NAME,
|
|
1682
|
+
repoUrl: builderWorkspaceRepoUrl(),
|
|
1683
|
+
});
|
|
1684
|
+
await persistProvisionedBuilderProjectId(project.projectId);
|
|
1685
|
+
return { projectId: project.projectId };
|
|
1686
|
+
})();
|
|
1687
|
+
pendingBuilderProjectProvisioning.set(key, pending);
|
|
1688
|
+
try {
|
|
1689
|
+
return await pending;
|
|
1690
|
+
} finally {
|
|
1691
|
+
if (pendingBuilderProjectProvisioning.get(key) === pending) {
|
|
1692
|
+
pendingBuilderProjectProvisioning.delete(key);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1608
1697
|
export async function setAppCreationSettings(input: {
|
|
1609
1698
|
builderProjectId?: string | null;
|
|
1610
1699
|
}): Promise<AppCreationSettings> {
|
|
@@ -1700,9 +1789,7 @@ async function assertCanManageAppCreationSettings(): Promise<void> {
|
|
|
1700
1789
|
if (!orgId) return;
|
|
1701
1790
|
const role = await requestOwnerRole();
|
|
1702
1791
|
if (role !== "owner" && role !== "admin") {
|
|
1703
|
-
throw new
|
|
1704
|
-
"Only organization owners and admins can update app creation settings.",
|
|
1705
|
-
);
|
|
1792
|
+
throw new AppCreationSettingsAuthorizationError();
|
|
1706
1793
|
}
|
|
1707
1794
|
}
|
|
1708
1795
|
|
|
@@ -1930,6 +2017,7 @@ async function grantSelectedWorkspaceResources(input: {
|
|
|
1930
2017
|
*/
|
|
1931
2018
|
export type AppCreationUnavailableReason =
|
|
1932
2019
|
| "identity-not-linked"
|
|
2020
|
+
| "settings-management-required"
|
|
1933
2021
|
| "builder-not-connected"
|
|
1934
2022
|
| "credential-store-unavailable"
|
|
1935
2023
|
| "builder-error";
|
|
@@ -2052,15 +2140,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2052
2140
|
}
|
|
2053
2141
|
|
|
2054
2142
|
const settings = await getAppCreationSettings();
|
|
2055
|
-
|
|
2056
|
-
if (!settings.builderProjectId) {
|
|
2057
|
-
return {
|
|
2058
|
-
mode: "coming-soon",
|
|
2059
|
-
appId: built.appId,
|
|
2060
|
-
message:
|
|
2061
|
-
"This requires a code change. Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.",
|
|
2062
|
-
};
|
|
2063
|
-
}
|
|
2143
|
+
let builderProjectId = settings.builderProjectId;
|
|
2064
2144
|
|
|
2065
2145
|
let builderCreds: Awaited<
|
|
2066
2146
|
ReturnType<typeof resolveBuilderCredentialsDetailed>
|
|
@@ -2072,7 +2152,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2072
2152
|
mode: "builder-unavailable",
|
|
2073
2153
|
appId: built.appId,
|
|
2074
2154
|
reason: "credential-store-unavailable",
|
|
2075
|
-
projectId:
|
|
2155
|
+
projectId: builderProjectId ?? "",
|
|
2076
2156
|
message:
|
|
2077
2157
|
"Could not read your Builder connection just now. Try creating the app again in a moment.",
|
|
2078
2158
|
};
|
|
@@ -2083,7 +2163,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2083
2163
|
mode: "builder-unavailable",
|
|
2084
2164
|
appId: built.appId,
|
|
2085
2165
|
reason: "credential-store-unavailable",
|
|
2086
|
-
projectId:
|
|
2166
|
+
projectId: builderProjectId ?? "",
|
|
2087
2167
|
message:
|
|
2088
2168
|
"Could not read your Builder connection just now. Try creating the app again in a moment.",
|
|
2089
2169
|
};
|
|
@@ -2094,12 +2174,41 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2094
2174
|
mode: "builder-unavailable",
|
|
2095
2175
|
appId: built.appId,
|
|
2096
2176
|
reason: "builder-not-connected",
|
|
2097
|
-
projectId:
|
|
2177
|
+
projectId: builderProjectId ?? "",
|
|
2098
2178
|
message:
|
|
2099
2179
|
"Connect your Builder account (free tier available) to create apps from Dispatch.",
|
|
2100
2180
|
};
|
|
2101
2181
|
}
|
|
2102
2182
|
|
|
2183
|
+
if (!builderProjectId) {
|
|
2184
|
+
try {
|
|
2185
|
+
builderProjectId = (await ensureBuilderProjectForWorkspace()).projectId;
|
|
2186
|
+
} catch (err) {
|
|
2187
|
+
if (err instanceof AppCreationSettingsAuthorizationError) {
|
|
2188
|
+
return {
|
|
2189
|
+
mode: "builder-unavailable",
|
|
2190
|
+
appId: built.appId,
|
|
2191
|
+
reason: "settings-management-required",
|
|
2192
|
+
projectId: "",
|
|
2193
|
+
message: APP_CREATION_SETTINGS_REQUIRED_MESSAGE,
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
const detail =
|
|
2197
|
+
err instanceof Error && err.message
|
|
2198
|
+
? err.message
|
|
2199
|
+
: "Builder could not provision the workspace project";
|
|
2200
|
+
return {
|
|
2201
|
+
mode: "builder-unavailable",
|
|
2202
|
+
appId: built.appId,
|
|
2203
|
+
reason: "builder-error",
|
|
2204
|
+
projectId: "",
|
|
2205
|
+
detail,
|
|
2206
|
+
message:
|
|
2207
|
+
"Builder could not prepare the connected Agent-Native workspace. Try again in a moment.",
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2103
2212
|
const builderUserId = builderCreds.userId || undefined;
|
|
2104
2213
|
|
|
2105
2214
|
let result: {
|
|
@@ -2111,7 +2220,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2111
2220
|
result = normalizeBuilderRunResult(
|
|
2112
2221
|
await runBuilderAgent({
|
|
2113
2222
|
prompt,
|
|
2114
|
-
projectId:
|
|
2223
|
+
projectId: builderProjectId,
|
|
2115
2224
|
...(builderUserId
|
|
2116
2225
|
? { userId: builderUserId }
|
|
2117
2226
|
: { userEmail: currentOwnerEmail() }),
|
|
@@ -2126,7 +2235,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2126
2235
|
mode: "builder-unavailable",
|
|
2127
2236
|
appId: built.appId,
|
|
2128
2237
|
reason: "builder-error",
|
|
2129
|
-
projectId:
|
|
2238
|
+
projectId: builderProjectId,
|
|
2130
2239
|
detail,
|
|
2131
2240
|
message:
|
|
2132
2241
|
"Builder could not start the app branch. This is usually temporary — try again.",
|
|
@@ -2135,7 +2244,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2135
2244
|
|
|
2136
2245
|
await recordPendingWorkspaceApp({
|
|
2137
2246
|
appId: built.appId,
|
|
2138
|
-
projectId:
|
|
2247
|
+
projectId: builderProjectId,
|
|
2139
2248
|
description: appDescription,
|
|
2140
2249
|
sourcePrompt: input.prompt,
|
|
2141
2250
|
branchName: result.branchName,
|
|
@@ -2155,7 +2264,7 @@ export async function startWorkspaceAppCreation(input: {
|
|
|
2155
2264
|
mode: "builder",
|
|
2156
2265
|
appId: built.appId,
|
|
2157
2266
|
path: `/${built.appId}`,
|
|
2158
|
-
projectId:
|
|
2267
|
+
projectId: builderProjectId,
|
|
2159
2268
|
branchName: result.branchName,
|
|
2160
2269
|
url: result.url,
|
|
2161
2270
|
workspaceUrl: workspaceAppUrl(`/${built.appId}`),
|
|
@@ -42,7 +42,7 @@ When a user asks for something:
|
|
|
42
42
|
- 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.
|
|
43
43
|
- 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 app from that template.
|
|
44
44
|
- If the chat 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 "Chat", "Starter", "Blank app", or "New app" UI behind.
|
|
45
|
-
- 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/chat 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.
|
|
45
|
+
- 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/chat 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. Dispatch reuses the connected workspace Builder project or provisions it through the Builder Projects API when an organization owner/admin starts the first app; members need an owner/admin to configure the project first. Do not ask the user to use Builder org/beta settings. If it returns mode "builder-unavailable", the action also returns a \`reason\` code plus a user-facing \`message\` and (for some reasons) an operator-facing \`detail\`. Relay the \`message\` to the user as-is; only surface \`detail\` when the user is clearly an operator debugging the deployment (e.g. troubleshooting a broken Builder connection for the workspace), never as routine detail for an end user.
|
|
46
46
|
- For digests, reminders, or saved behavior, prefer recurring jobs, resources, or destinations over chat replies.
|
|
47
47
|
- Keep responses concise and operational — messaging platforms have character limits.
|
|
48
48
|
- Use markdown sparingly (bold and lists are fine, avoid complex formatting).
|