@agent-native/dispatch 0.15.19 → 0.15.21
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/provider-api-catalog.d.ts +1 -0
- package/dist/actions/provider-api-register.d.ts +14 -14
- package/dist/actions/remix-workspace-template.d.ts +20 -32
- package/dist/actions/set-app-creation-settings.js +2 -2
- package/dist/actions/set-app-creation-settings.js.map +1 -1
- package/dist/actions/start-workspace-app-creation.d.ts +1 -45
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +30 -2
- 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 +1 -1
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +14 -1
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts +36 -37
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +71 -6
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/provider-api.d.ts +1 -0
- package/dist/server/lib/provider-api.d.ts.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/set-app-creation-settings.ts +4 -2
- package/src/components/create-app-popover.spec.tsx +249 -0
- package/src/components/create-app-popover.tsx +77 -10
- package/src/components/layout/Layout.tsx +9 -6
- package/src/components/workspace-template-card.tsx +11 -1
- package/src/server/lib/app-creation-store.spec.ts +252 -0
- package/src/server/lib/app-creation-store.ts +148 -8
- package/src/server/plugins/integrations.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-api.d.ts","sourceRoot":"","sources":["../../../src/server/lib/provider-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,sBAAsB,EAE3B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAGzC,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,yBAAyB,kVAAmB,CAAC;AAC1D,MAAM,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;AA0C1D,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"provider-api.d.ts","sourceRoot":"","sources":["../../../src/server/lib/provider-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,sBAAsB,EAE3B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAGzC,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,yBAAyB,kVAAmB,CAAC;AAC1D,MAAM,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;AA0C1D,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;yBAEvD;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,oBAGvD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,sBAAsB,oBAErE"}
|
|
@@ -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 fork/copy.
|
|
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. 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",
|
|
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. 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.
|
|
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 fork/copy.\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\", report the action's message exactly enough to preserve the missing identity/credential detail.\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 fork/copy.\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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.21",
|
|
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": {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"tailwind-merge": "^3.5.0",
|
|
87
87
|
"vaul": "^1.1.2",
|
|
88
88
|
"zod": "^4.3.6",
|
|
89
|
-
"@agent-native/toolkit": "0.10.
|
|
89
|
+
"@agent-native/toolkit": "0.10.6"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@react-router/dev": "^8.1.0",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
101
101
|
"vite": "8.1.0",
|
|
102
102
|
"vitest": "^4.1.5",
|
|
103
|
-
"@agent-native/core": "0.
|
|
103
|
+
"@agent-native/core": "0.122.0"
|
|
104
104
|
},
|
|
105
105
|
"peerDependencies": {
|
|
106
106
|
"@agent-native/core": ">=0.8.0",
|
|
@@ -5,7 +5,7 @@ import { setAppCreationSettings } from "../server/lib/app-creation-store.js";
|
|
|
5
5
|
|
|
6
6
|
export default defineAction({
|
|
7
7
|
description:
|
|
8
|
-
"Set Dispatch settings for creating new workspace apps.
|
|
8
|
+
"Set Dispatch settings for creating new workspace apps, and the Builder project this organization uses for cloud code changes. Stores the project id as an organization-scoped credential; does not write env vars or files.",
|
|
9
9
|
schema: z.object({
|
|
10
10
|
builderProjectId: z
|
|
11
11
|
.string()
|
|
@@ -14,7 +14,9 @@ export default defineAction({
|
|
|
14
14
|
.max(128)
|
|
15
15
|
.optional()
|
|
16
16
|
.nullable()
|
|
17
|
-
.describe(
|
|
17
|
+
.describe(
|
|
18
|
+
"Builder project ID used for app-creation branches and for cloud code-change branches across this organization's workspace apps. Pass null to clear it and return those apps to the connect/waitlist prompt.",
|
|
19
|
+
),
|
|
18
20
|
}),
|
|
19
21
|
run: async (args) => setAppCreationSettings(args),
|
|
20
22
|
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import React, { act } from "react";
|
|
3
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { CreateAppFlow } from "./create-app-popover";
|
|
7
|
+
|
|
8
|
+
const sendToAgentChatMock = vi.hoisted(() => vi.fn());
|
|
9
|
+
const devState = vi.hoisted(() => ({ isDevMode: false }));
|
|
10
|
+
const frameState = vi.hoisted(() => ({ inBuilderFrame: false }));
|
|
11
|
+
const builderConnectFlowState = vi.hoisted(() => ({
|
|
12
|
+
connecting: false,
|
|
13
|
+
start: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
const startWorkspaceAppCreationResponse = vi.hoisted<{ result: unknown }>(
|
|
16
|
+
() => ({
|
|
17
|
+
result: {
|
|
18
|
+
mode: "builder",
|
|
19
|
+
appId: "qa-dashboard",
|
|
20
|
+
url: "https://branch.example.test",
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
vi.mock("@agent-native/core/client/agent-chat", () => ({
|
|
26
|
+
sendToAgentChat: sendToAgentChatMock,
|
|
27
|
+
useDevMode: () => ({ isDevMode: devState.isDevMode }),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("@agent-native/core/client/host", () => ({
|
|
31
|
+
isInBuilderFrame: () => frameState.inBuilderFrame,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock("@agent-native/core/client/settings/useBuilderStatus", () => ({
|
|
35
|
+
useBuilderConnectFlow: () => ({
|
|
36
|
+
configured: false,
|
|
37
|
+
connecting: builderConnectFlowState.connecting,
|
|
38
|
+
error: null,
|
|
39
|
+
start: builderConnectFlowState.start,
|
|
40
|
+
}),
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
vi.mock("@agent-native/core/client/composer", async () => {
|
|
44
|
+
const ReactModule = await import("react");
|
|
45
|
+
return {
|
|
46
|
+
PromptComposer: ({
|
|
47
|
+
onSubmit,
|
|
48
|
+
placeholder,
|
|
49
|
+
disabled,
|
|
50
|
+
}: {
|
|
51
|
+
onSubmit: (text: string, files: File[], references: unknown[]) => void;
|
|
52
|
+
placeholder?: string;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
}) => {
|
|
55
|
+
const [value, setValue] = ReactModule.useState("");
|
|
56
|
+
return ReactModule.createElement(
|
|
57
|
+
"div",
|
|
58
|
+
null,
|
|
59
|
+
ReactModule.createElement("textarea", {
|
|
60
|
+
"aria-label": "Prompt",
|
|
61
|
+
placeholder,
|
|
62
|
+
value,
|
|
63
|
+
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) =>
|
|
64
|
+
setValue(event.target.value),
|
|
65
|
+
}),
|
|
66
|
+
ReactModule.createElement(
|
|
67
|
+
"button",
|
|
68
|
+
{
|
|
69
|
+
disabled: disabled || !value.trim(),
|
|
70
|
+
onClick: () => onSubmit(value, [], []),
|
|
71
|
+
type: "button",
|
|
72
|
+
},
|
|
73
|
+
"Create app",
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function jsonResponse(data: unknown, status = 200): Response {
|
|
81
|
+
return new Response(JSON.stringify(data), {
|
|
82
|
+
status,
|
|
83
|
+
headers: { "Content-Type": "application/json" },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function findButton(container: HTMLElement, text: string): HTMLButtonElement {
|
|
88
|
+
const button = Array.from(container.querySelectorAll("button")).find(
|
|
89
|
+
(candidate) => candidate.textContent?.includes(text),
|
|
90
|
+
);
|
|
91
|
+
if (!button) throw new Error(`Button not found: ${text}`);
|
|
92
|
+
return button as HTMLButtonElement;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function changeValue(
|
|
96
|
+
element: HTMLInputElement | HTMLTextAreaElement,
|
|
97
|
+
value: string,
|
|
98
|
+
) {
|
|
99
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
100
|
+
Object.getPrototypeOf(element),
|
|
101
|
+
"value",
|
|
102
|
+
)?.set;
|
|
103
|
+
act(() => {
|
|
104
|
+
setter?.call(element, value);
|
|
105
|
+
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
106
|
+
element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
describe("CreateAppFlow", () => {
|
|
111
|
+
let container: HTMLDivElement;
|
|
112
|
+
let root: Root;
|
|
113
|
+
let fetchSpy: ReturnType<typeof vi.fn>;
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
117
|
+
devState.isDevMode = false;
|
|
118
|
+
frameState.inBuilderFrame = false;
|
|
119
|
+
builderConnectFlowState.connecting = false;
|
|
120
|
+
builderConnectFlowState.start.mockReset();
|
|
121
|
+
startWorkspaceAppCreationResponse.result = {
|
|
122
|
+
mode: "builder",
|
|
123
|
+
appId: "qa-dashboard",
|
|
124
|
+
url: "https://branch.example.test",
|
|
125
|
+
};
|
|
126
|
+
sendToAgentChatMock.mockReset();
|
|
127
|
+
fetchSpy = vi.fn(async (input: RequestInfo | URL) => {
|
|
128
|
+
const url = String(input);
|
|
129
|
+
if (url.includes("get-vault-access-settings")) {
|
|
130
|
+
return jsonResponse({ mode: "all-apps" });
|
|
131
|
+
}
|
|
132
|
+
if (url.includes("list-vault-secret-options")) {
|
|
133
|
+
return jsonResponse([]);
|
|
134
|
+
}
|
|
135
|
+
if (url.includes("list-workspace-resource-options")) {
|
|
136
|
+
return jsonResponse([]);
|
|
137
|
+
}
|
|
138
|
+
if (url.includes("start-workspace-app-creation")) {
|
|
139
|
+
return jsonResponse(startWorkspaceAppCreationResponse.result);
|
|
140
|
+
}
|
|
141
|
+
return jsonResponse({ error: `Unexpected URL: ${url}` }, 404);
|
|
142
|
+
});
|
|
143
|
+
vi.stubGlobal("fetch", fetchSpy);
|
|
144
|
+
container = document.createElement("div");
|
|
145
|
+
document.body.appendChild(container);
|
|
146
|
+
root = createRoot(container);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
afterEach(() => {
|
|
150
|
+
act(() => root.unmount());
|
|
151
|
+
container.remove();
|
|
152
|
+
vi.unstubAllGlobals();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
async function renderAndSubmit(prompt: string) {
|
|
156
|
+
await act(async () => {
|
|
157
|
+
root.render(React.createElement(CreateAppFlow, {}));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
changeValue(
|
|
161
|
+
container.querySelector('textarea[aria-label="Prompt"]')!,
|
|
162
|
+
prompt,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
await act(async () => {
|
|
166
|
+
findButton(container, "Create app").click();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
it("renders a Connect Builder control when Builder is not connected", async () => {
|
|
171
|
+
startWorkspaceAppCreationResponse.result = {
|
|
172
|
+
mode: "builder-unavailable",
|
|
173
|
+
reason: "builder-not-connected",
|
|
174
|
+
message: "Connect Builder for this user",
|
|
175
|
+
appId: "quality",
|
|
176
|
+
};
|
|
177
|
+
await renderAndSubmit("Build a quality dashboard");
|
|
178
|
+
|
|
179
|
+
await act(async () => {
|
|
180
|
+
await vi.waitFor(() =>
|
|
181
|
+
expect(container.textContent).toContain(
|
|
182
|
+
"Connect Builder for this user",
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const connectButton = findButton(container, "Connect Builder");
|
|
188
|
+
act(() => {
|
|
189
|
+
connectButton.click();
|
|
190
|
+
});
|
|
191
|
+
expect(builderConnectFlowState.start).toHaveBeenCalledTimes(1);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("renders the error affordance and a Try again control for builder-error, without a Connect Builder control", async () => {
|
|
195
|
+
startWorkspaceAppCreationResponse.result = {
|
|
196
|
+
mode: "builder-unavailable",
|
|
197
|
+
reason: "builder-error",
|
|
198
|
+
message: "Builder rejected the request.",
|
|
199
|
+
appId: "quality",
|
|
200
|
+
detail: "500 from Builder",
|
|
201
|
+
projectId: "proj_1",
|
|
202
|
+
};
|
|
203
|
+
await renderAndSubmit("Build a quality dashboard");
|
|
204
|
+
|
|
205
|
+
await act(async () => {
|
|
206
|
+
await vi.waitFor(() =>
|
|
207
|
+
expect(container.textContent).toContain(
|
|
208
|
+
"Builder rejected the request.",
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const matchingDivs = Array.from(container.querySelectorAll("div")).filter(
|
|
214
|
+
(el) => el.textContent?.includes("Builder rejected the request."),
|
|
215
|
+
);
|
|
216
|
+
expect(
|
|
217
|
+
matchingDivs.some((el) => el.className.includes("border-destructive")),
|
|
218
|
+
).toBe(true);
|
|
219
|
+
|
|
220
|
+
expect(() => findButton(container, "Connect Builder")).toThrow();
|
|
221
|
+
expect(findButton(container, "Try again")).toBeTruthy();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("renders coming-soon messages neutrally with no Connect Builder control", async () => {
|
|
225
|
+
startWorkspaceAppCreationResponse.result = {
|
|
226
|
+
mode: "coming-soon",
|
|
227
|
+
message: "This template is coming soon.",
|
|
228
|
+
appId: "quality",
|
|
229
|
+
};
|
|
230
|
+
await renderAndSubmit("Build a quality dashboard");
|
|
231
|
+
|
|
232
|
+
await act(async () => {
|
|
233
|
+
await vi.waitFor(() =>
|
|
234
|
+
expect(container.textContent).toContain(
|
|
235
|
+
"This template is coming soon.",
|
|
236
|
+
),
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const matchingDivs = Array.from(container.querySelectorAll("div")).filter(
|
|
241
|
+
(el) => el.textContent?.includes("This template is coming soon."),
|
|
242
|
+
);
|
|
243
|
+
expect(
|
|
244
|
+
matchingDivs.some((el) => el.className.includes("border-destructive")),
|
|
245
|
+
).toBe(false);
|
|
246
|
+
expect(() => findButton(container, "Connect Builder")).toThrow();
|
|
247
|
+
expect(() => findButton(container, "Try again")).toThrow();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
} from "@agent-native/core/client/api-path";
|
|
7
7
|
import { PromptComposer } from "@agent-native/core/client/composer";
|
|
8
8
|
import { isInBuilderFrame } from "@agent-native/core/client/host";
|
|
9
|
+
import { useBuilderConnectFlow } from "@agent-native/core/client/settings/useBuilderStatus";
|
|
9
10
|
import { getWorkspaceAppIdValidationError } from "@agent-native/core/shared";
|
|
10
11
|
import {
|
|
12
|
+
IconAlertTriangle,
|
|
11
13
|
IconArrowLeft,
|
|
12
14
|
IconArrowUpRight,
|
|
13
15
|
IconBook,
|
|
@@ -160,6 +162,16 @@ function actionUrl(basePath: string | null, action: string): string {
|
|
|
160
162
|
return `${normalized}${path}`;
|
|
161
163
|
}
|
|
162
164
|
|
|
165
|
+
const ERROR_FAILURE_REASONS = new Set([
|
|
166
|
+
"builder-error",
|
|
167
|
+
"builder-not-connected",
|
|
168
|
+
"credential-store-unavailable",
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
function isErrorFailureReason(reason: string | null): boolean {
|
|
172
|
+
return !!reason && ERROR_FAILURE_REASONS.has(reason);
|
|
173
|
+
}
|
|
174
|
+
|
|
163
175
|
/**
|
|
164
176
|
* Inline two-step app-creation flow: prompt → optional access picker → submit.
|
|
165
177
|
* Used both in the popover form and in the dedicated `/new-app` page so the
|
|
@@ -184,11 +196,25 @@ export function CreateAppFlow({
|
|
|
184
196
|
const [resourcesError, setResourcesError] = useState<string | null>(null);
|
|
185
197
|
const [statusMessage, setStatusMessage] = useState<string | null>(null);
|
|
186
198
|
const [branchUrl, setBranchUrl] = useState<string | null>(null);
|
|
199
|
+
const [failureReason, setFailureReason] = useState<string | null>(null);
|
|
187
200
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
188
201
|
const { isDevMode } = useDevMode();
|
|
189
202
|
|
|
190
203
|
const basePath = useMemo(() => defaultDispatchBasePath(), []);
|
|
191
204
|
|
|
205
|
+
// Enabled only while the connect CTA is on screen. Left always-on, the hook
|
|
206
|
+
// would poll Builder status on every popover mount and fire onConnected on
|
|
207
|
+
// its first status read for anyone already connected.
|
|
208
|
+
const connectFlow = useBuilderConnectFlow({
|
|
209
|
+
enabled: failureReason === "builder-not-connected",
|
|
210
|
+
trackingSource: "dispatch_create_app",
|
|
211
|
+
trackingFlow: "create_app",
|
|
212
|
+
onConnected: () => {
|
|
213
|
+
setFailureReason(null);
|
|
214
|
+
setStatusMessage("Builder connected. Press Create app to try again.");
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
192
218
|
// Fetch access options eagerly so step 2 has them ready immediately.
|
|
193
219
|
useEffect(() => {
|
|
194
220
|
let cancelled = false;
|
|
@@ -285,6 +311,7 @@ export function CreateAppFlow({
|
|
|
285
311
|
setIsSubmitting(true);
|
|
286
312
|
setStatusMessage(null);
|
|
287
313
|
setBranchUrl(null);
|
|
314
|
+
setFailureReason(null);
|
|
288
315
|
|
|
289
316
|
try {
|
|
290
317
|
if (isInBuilderFrame()) {
|
|
@@ -321,10 +348,14 @@ export function CreateAppFlow({
|
|
|
321
348
|
result?.message ||
|
|
322
349
|
"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.",
|
|
323
350
|
);
|
|
351
|
+
setFailureReason(
|
|
352
|
+
result?.mode === "builder-unavailable" ? result.reason : null,
|
|
353
|
+
);
|
|
324
354
|
}
|
|
325
355
|
}
|
|
326
356
|
} catch (err: any) {
|
|
327
357
|
setStatusMessage(err?.message || "Could not start the new app flow.");
|
|
358
|
+
setFailureReason(null);
|
|
328
359
|
} finally {
|
|
329
360
|
setIsSubmitting(false);
|
|
330
361
|
}
|
|
@@ -548,17 +579,53 @@ export function CreateAppFlow({
|
|
|
548
579
|
)}
|
|
549
580
|
|
|
550
581
|
{statusMessage ? (
|
|
551
|
-
<div
|
|
552
|
-
{
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
582
|
+
<div
|
|
583
|
+
className={`flex flex-col gap-2 rounded-md border px-3 py-2 text-xs ${
|
|
584
|
+
isErrorFailureReason(failureReason)
|
|
585
|
+
? "border-destructive/40 bg-destructive/10 text-destructive"
|
|
586
|
+
: "border-border bg-muted/40 text-muted-foreground"
|
|
587
|
+
}`}
|
|
588
|
+
>
|
|
589
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
590
|
+
{isErrorFailureReason(failureReason) ? (
|
|
591
|
+
<IconAlertTriangle className="h-3.5 w-3.5 shrink-0" />
|
|
592
|
+
) : null}
|
|
593
|
+
<span>{statusMessage}</span>
|
|
594
|
+
{branchUrl ? (
|
|
595
|
+
<a
|
|
596
|
+
href={branchUrl}
|
|
597
|
+
target="_blank"
|
|
598
|
+
rel="noreferrer"
|
|
599
|
+
className="inline-flex items-center gap-1 font-medium text-foreground underline"
|
|
600
|
+
>
|
|
601
|
+
Open branch <IconArrowUpRight className="h-3 w-3" />
|
|
602
|
+
</a>
|
|
603
|
+
) : null}
|
|
604
|
+
</div>
|
|
605
|
+
{failureReason === "builder-not-connected" ? (
|
|
606
|
+
<Button
|
|
607
|
+
type="button"
|
|
608
|
+
size="sm"
|
|
609
|
+
variant="outline"
|
|
610
|
+
onClick={() => connectFlow.start()}
|
|
611
|
+
disabled={connectFlow.connecting}
|
|
612
|
+
className="w-fit"
|
|
559
613
|
>
|
|
560
|
-
|
|
561
|
-
</
|
|
614
|
+
{connectFlow.connecting ? "Connecting..." : "Connect Builder"}
|
|
615
|
+
</Button>
|
|
616
|
+
) : null}
|
|
617
|
+
{failureReason === "credential-store-unavailable" ||
|
|
618
|
+
failureReason === "builder-error" ? (
|
|
619
|
+
<Button
|
|
620
|
+
type="button"
|
|
621
|
+
size="sm"
|
|
622
|
+
variant="outline"
|
|
623
|
+
onClick={submitWithSelectedAccess}
|
|
624
|
+
disabled={isSubmitting}
|
|
625
|
+
className="w-fit"
|
|
626
|
+
>
|
|
627
|
+
Try again
|
|
628
|
+
</Button>
|
|
562
629
|
) : null}
|
|
563
630
|
</div>
|
|
564
631
|
) : null}
|
|
@@ -747,13 +747,16 @@ export function NavContent({
|
|
|
747
747
|
</nav>
|
|
748
748
|
</div>
|
|
749
749
|
|
|
750
|
-
|
|
751
|
-
<div
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
750
|
+
<div className="mt-auto shrink-0">
|
|
751
|
+
<div
|
|
752
|
+
className={cn(
|
|
753
|
+
"py-2",
|
|
754
|
+
collapsed ? "flex justify-center px-1" : "px-3",
|
|
755
|
+
)}
|
|
756
|
+
>
|
|
757
|
+
<OrgSwitcher compact={collapsed} />
|
|
755
758
|
</div>
|
|
756
|
-
|
|
759
|
+
</div>
|
|
757
760
|
<SidebarFooterActions
|
|
758
761
|
collapsed={collapsed}
|
|
759
762
|
feedback={feedbackButton}
|
|
@@ -151,7 +151,17 @@ export function WorkspaceTemplateCard({
|
|
|
151
151
|
const setupNote = template.integrationSetup || template.setupNote;
|
|
152
152
|
const remix = useActionMutation("remix-workspace-template", {
|
|
153
153
|
onSuccess: (result) => {
|
|
154
|
-
|
|
154
|
+
const mode = (result as { mode?: string } | null)?.mode;
|
|
155
|
+
const message = (result as { message?: string } | null)?.message;
|
|
156
|
+
if (mode === "builder") {
|
|
157
|
+
toast.success(labels.remixSuccess);
|
|
158
|
+
} else if (mode === "builder-unavailable") {
|
|
159
|
+
toast.error(message || labels.remixError);
|
|
160
|
+
} else if (mode === "coming-soon") {
|
|
161
|
+
toast.info(message || labels.remixSuccess);
|
|
162
|
+
} else {
|
|
163
|
+
toast.success(labels.remixSuccess);
|
|
164
|
+
}
|
|
155
165
|
setOpen(false);
|
|
156
166
|
onRemixSuccess?.(result, template);
|
|
157
167
|
},
|