@agent-native/core 0.107.0 → 0.107.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +10 -0
  3. package/corpus/core/docs/content/integrations.mdx +27 -27
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/production-agent.ts +19 -0
  6. package/corpus/core/src/agent/run-store.ts +98 -2
  7. package/corpus/core/src/client/AssistantChat.tsx +13 -0
  8. package/corpus/core/src/client/CommandMenu.tsx +5 -0
  9. package/corpus/core/src/client/active-run-state.ts +34 -0
  10. package/corpus/core/src/client/agent-chat-adapter.ts +4 -0
  11. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +41 -11
  12. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +27 -9
  13. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +45 -0
  14. package/corpus/core/src/client/sharing/ShareButton.tsx +30 -9
  15. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +2 -2
  16. package/corpus/core/src/localization/default-messages.ts +4 -1
  17. package/corpus/core/src/secrets/register-framework-secrets.ts +28 -0
  18. package/corpus/core/src/server/agent-chat-plugin.ts +29 -1
  19. package/corpus/core/src/vite/client.ts +24 -0
  20. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +29 -21
  21. package/corpus/templates/analytics/AGENTS.md +8 -0
  22. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
  23. package/corpus/templates/analytics/actions/update-dashboard.ts +6 -2
  24. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +35 -7
  25. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -3
  26. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -1
  27. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/extension-slot.ts +11 -0
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +27 -0
  29. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +6 -0
  30. package/corpus/templates/analytics/changelog/2026-07-17-dashboard-extension-boxes.md +6 -0
  31. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +8 -0
  32. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -0
  33. package/corpus/templates/assets/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  34. package/corpus/templates/brain/app/hooks/use-navigation-state.ts +6 -1
  35. package/corpus/templates/brain/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  36. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -1
  37. package/corpus/templates/calendar/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  38. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  39. package/corpus/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  40. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  41. package/corpus/templates/clips/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  42. package/corpus/templates/content/actions/_content-space-access.ts +45 -2
  43. package/corpus/templates/content/actions/create-content-database.ts +41 -7
  44. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  45. package/corpus/templates/content/app/root.tsx +1 -10
  46. package/corpus/templates/content/changelog/2026-07-17-creating-a-database-now-completes-reliably-in-hosted-workspa.md +6 -0
  47. package/corpus/templates/content/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  48. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +4 -1
  49. package/corpus/templates/dispatch/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  50. package/corpus/templates/forms/app/components/layout/Layout.tsx +2 -4
  51. package/corpus/templates/forms/app/components/ui/popover.tsx +25 -1
  52. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +6 -1
  53. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +13 -10
  54. package/corpus/templates/forms/app/root.tsx +6 -1
  55. package/corpus/templates/forms/changelog/2026-07-17-form-builder-popovers-stay-within-the-viewport.md +6 -0
  56. package/corpus/templates/forms/changelog/2026-07-17-form-editor-loading-matches-current-sidebar-free-layout.md +6 -0
  57. package/corpus/templates/forms/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  58. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
  59. package/corpus/templates/mail/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  60. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +14 -3
  61. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +8 -1
  62. package/corpus/templates/plan/app/root.tsx +0 -2
  63. package/corpus/templates/plan/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  64. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  65. package/corpus/templates/slides/app/context/DeckContext.tsx +23 -23
  66. package/corpus/templates/slides/changelog/2026-07-17-duplicated-slides-now-stay-in-the-deck.md +6 -0
  67. package/corpus/templates/slides/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  68. package/corpus/templates/tasks/app/components/layout/Layout.tsx +2 -13
  69. package/corpus/templates/tasks/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it.md +6 -0
  70. package/dist/agent/production-agent.d.ts.map +1 -1
  71. package/dist/agent/production-agent.js +13 -1
  72. package/dist/agent/production-agent.js.map +1 -1
  73. package/dist/agent/run-store.d.ts +17 -0
  74. package/dist/agent/run-store.d.ts.map +1 -1
  75. package/dist/agent/run-store.js +84 -2
  76. package/dist/agent/run-store.js.map +1 -1
  77. package/dist/client/AssistantChat.d.ts.map +1 -1
  78. package/dist/client/AssistantChat.js +10 -1
  79. package/dist/client/AssistantChat.js.map +1 -1
  80. package/dist/client/CommandMenu.d.ts.map +1 -1
  81. package/dist/client/CommandMenu.js +4 -0
  82. package/dist/client/CommandMenu.js.map +1 -1
  83. package/dist/client/active-run-state.d.ts +7 -0
  84. package/dist/client/active-run-state.d.ts.map +1 -1
  85. package/dist/client/active-run-state.js +28 -0
  86. package/dist/client/active-run-state.js.map +1 -1
  87. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  88. package/dist/client/agent-chat-adapter.js +4 -1
  89. package/dist/client/agent-chat-adapter.js.map +1 -1
  90. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  91. package/dist/client/resources/McpConnectionSuggestion.js +27 -8
  92. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  93. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  94. package/dist/client/resources/McpIntegrationDialog.js +4 -2
  95. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  96. package/dist/client/resources/mcp-integration-catalog.d.ts +6 -0
  97. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  98. package/dist/client/resources/mcp-integration-catalog.js +33 -0
  99. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  100. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  101. package/dist/client/sharing/ShareButton.js +15 -12
  102. package/dist/client/sharing/ShareButton.js.map +1 -1
  103. package/dist/client/use-agent-chat-home-handoff.d.ts +1 -1
  104. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  105. package/dist/client/use-agent-chat-home-handoff.js +1 -1
  106. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  107. package/dist/collab/awareness.d.ts +2 -2
  108. package/dist/collab/awareness.d.ts.map +1 -1
  109. package/dist/collab/routes.d.ts +1 -1
  110. package/dist/collab/struct-routes.d.ts +1 -1
  111. package/dist/localization/default-messages.d.ts +2 -0
  112. package/dist/localization/default-messages.d.ts.map +1 -1
  113. package/dist/localization/default-messages.js +3 -1
  114. package/dist/localization/default-messages.js.map +1 -1
  115. package/dist/observability/routes.d.ts +5 -5
  116. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  117. package/dist/secrets/register-framework-secrets.js +25 -0
  118. package/dist/secrets/register-framework-secrets.js.map +1 -1
  119. package/dist/secrets/routes.d.ts +9 -9
  120. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  121. package/dist/server/agent-chat-plugin.js +23 -1
  122. package/dist/server/agent-chat-plugin.js.map +1 -1
  123. package/dist/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  124. package/dist/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  125. package/dist/vite/client.d.ts.map +1 -1
  126. package/dist/vite/client.js +20 -0
  127. package/dist/vite/client.js.map +1 -1
  128. package/docs/content/integrations.mdx +27 -27
  129. package/package.json +1 -1
  130. package/src/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  131. package/src/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
@@ -24,6 +24,7 @@ export type McpIntegrationVerification =
24
24
  declare const __AGENT_NATIVE_MCP_INTEGRATIONS_CONFIG__:
25
25
  | NormalizedMcpIntegrationsConfig
26
26
  | undefined;
27
+ declare const __AGENT_NATIVE_TEMPLATE__: string | undefined;
27
28
 
28
29
  export interface DefaultMcpIntegration {
29
30
  id: string;
@@ -41,6 +42,11 @@ export interface DefaultMcpIntegration {
41
42
  logoUrl: string;
42
43
  docsUrl?: string;
43
44
  setupNoteKey?: string;
45
+ apiFallback?: {
46
+ secretKey: string;
47
+ docsUrl: string;
48
+ templateUses?: readonly string[];
49
+ };
44
50
  headerPlaceholder?: string;
45
51
  keywords: string[];
46
52
  }
@@ -272,6 +278,12 @@ export const DEFAULT_MCP_INTEGRATIONS: DefaultMcpIntegration[] = [
272
278
  logoUrl: mcpIntegrationLogo("figma"),
273
279
  docsUrl: "https://developers.figma.com/docs/figma-mcp-server/",
274
280
  setupNoteKey: "mcpIntegrations.catalog.figma.setupNote",
281
+ apiFallback: {
282
+ secretKey: "FIGMA_ACCESS_TOKEN",
283
+ docsUrl:
284
+ "https://developers.figma.com/docs/rest-api/personal-access-tokens/",
285
+ templateUses: ["design"],
286
+ },
275
287
  keywords: ["design", "figjam", "components", "variables", "canvas"],
276
288
  },
277
289
  {
@@ -532,6 +544,39 @@ function readRuntimeMcpIntegrationsConfig(): NormalizedMcpIntegrationsConfig {
532
544
  return normalizeMcpIntegrationsConfig();
533
545
  }
534
546
 
547
+ function normalizeTemplateName(value: unknown): string | null {
548
+ if (typeof value !== "string") return null;
549
+ const normalized = value.trim().toLowerCase();
550
+ return normalized || null;
551
+ }
552
+
553
+ function getActiveTemplateName(): string | null {
554
+ try {
555
+ const compiledTemplate = normalizeTemplateName(__AGENT_NATIVE_TEMPLATE__);
556
+ if (compiledTemplate) return compiledTemplate;
557
+ } catch {
558
+ // Test and non-Vite contexts may not define the compile-time constant.
559
+ }
560
+
561
+ const runtimeConfig = (
562
+ globalThis as typeof globalThis & {
563
+ __AGENT_NATIVE_CONFIG__?: { template?: unknown };
564
+ }
565
+ ).__AGENT_NATIVE_CONFIG__;
566
+ return normalizeTemplateName(runtimeConfig?.template);
567
+ }
568
+
569
+ export function getMcpIntegrationApiFallback(
570
+ integration: DefaultMcpIntegration,
571
+ templateName = getActiveTemplateName(),
572
+ ): DefaultMcpIntegration["apiFallback"] | null {
573
+ const fallback = integration.apiFallback;
574
+ if (!fallback) return null;
575
+ if (!fallback.templateUses?.length) return fallback;
576
+ if (!templateName) return null;
577
+ return fallback.templateUses.includes(templateName) ? fallback : null;
578
+ }
579
+
535
580
  function normalizePresetConfig(
536
581
  config?: McpIntegrationsConfigInput | NormalizedMcpIntegrationsConfig,
537
582
  ): NormalizedMcpIntegrationsConfig {
@@ -261,6 +261,7 @@ function handleSharePopoverInteractOutside(
261
261
  */
262
262
  export function ShareButton(props: ShareButtonProps) {
263
263
  const [open, setOpen] = useState(false);
264
+ const [inviteEmail, setInviteEmail] = useState("");
264
265
  const shareTabDefaultValue = props.shareTabs?.defaultValue ?? "share";
265
266
  const [activeShareTab, setActiveShareTab] = useState(shareTabDefaultValue);
266
267
  const [pendingVisibility, setPendingVisibility] = useState<Visibility | null>(
@@ -295,6 +296,10 @@ export function ShareButton(props: ShareButtonProps) {
295
296
  }
296
297
  };
297
298
 
299
+ useEffect(() => {
300
+ setInviteEmail("");
301
+ }, [props.resourceType, props.resourceId]);
302
+
298
303
  const handleShareTabChange = (value: string) => {
299
304
  setActiveShareTab(value);
300
305
  props.shareTabs?.onValueChange?.(value);
@@ -399,6 +404,8 @@ export function ShareButton(props: ShareButtonProps) {
399
404
  >
400
405
  <SharePanel
401
406
  {...props}
407
+ inviteEmail={inviteEmail}
408
+ onInviteEmailChange={setInviteEmail}
402
409
  sharesQuery={sharesQuery}
403
410
  visibilityOverride={pendingVisibility}
404
411
  onVisibilityChange={handleVisibilityChange}
@@ -575,6 +582,8 @@ function mergeMembers(existing: OrgMember[], next: OrgMember[]): OrgMember[] {
575
582
 
576
583
  function SharePanel(
577
584
  props: ShareButtonProps & {
585
+ inviteEmail: string;
586
+ onInviteEmailChange: (email: string) => void;
578
587
  sharesQuery: ReturnType<typeof useActionQuery<SharesResponse>>;
579
588
  visibilityOverride: Visibility | null;
580
589
  onVisibilityChange: (visibility: Visibility) => Promise<void>;
@@ -591,17 +600,18 @@ function SharePanel(
591
600
  visibilityOverride,
592
601
  onVisibilityChange,
593
602
  onClose,
603
+ inviteEmail,
604
+ onInviteEmailChange,
594
605
  } = props;
595
606
 
596
607
  const share = useActionMutation("share-resource");
597
608
  const unshare = useActionMutation("unshare-resource");
598
609
 
599
- const [email, setEmail] = useState("");
600
610
  const [role, setRole] = useState<Role>("viewer");
601
611
  const [notifyPeople, setNotifyPeople] = useState(true);
602
612
  const [shareError, setShareError] = useState<string | null>(null);
603
613
  const [suggestionsOpen, setSuggestionsOpen] = useState(false);
604
- const hasInviteEmail = email.trim().length > 0;
614
+ const hasInviteEmail = inviteEmail.trim().length > 0;
605
615
 
606
616
  // Optimistic overlays so clicks feel instant.
607
617
  const [pendingAdds, setPendingAdds] = useState<Share[]>([]);
@@ -685,7 +695,10 @@ function SharePanel(
685
695
  .map((s) => ({ ...s, role: roleOverrides[keyOf(s)] ?? s.role })),
686
696
  ...pendingAdds,
687
697
  ];
688
- const memberSearch = useOrgMemberSearch(email, canManage && suggestionsOpen);
698
+ const memberSearch = useOrgMemberSearch(
699
+ inviteEmail,
700
+ canManage && suggestionsOpen,
701
+ );
689
702
  const excludedMemberEmails = new Set<string>();
690
703
  if (data?.ownerEmail) excludedMemberEmails.add(data.ownerEmail.toLowerCase());
691
704
  for (const s of shares) {
@@ -726,7 +739,7 @@ function SharePanel(
726
739
  };
727
740
 
728
741
  const handleAdd = () => {
729
- const trimmed = email.trim();
742
+ const trimmed = inviteEmail.trim();
730
743
  if (!trimmed) return;
731
744
  const optimistic: Share = {
732
745
  id: `pending-${trimmed}`,
@@ -739,7 +752,7 @@ function SharePanel(
739
752
  if (inFlight.has(k)) return;
740
753
  setShareError(null);
741
754
  setPendingAdds((p) => [...p, optimistic]);
742
- setEmail("");
755
+ onInviteEmailChange("");
743
756
  setSuggestionsOpen(false);
744
757
  addInFlight(k);
745
758
  share.mutate(
@@ -762,7 +775,7 @@ function SharePanel(
762
775
  onError: (err: any) => {
763
776
  setPendingAdds((p) => p.filter((s) => s.id !== optimistic.id));
764
777
  clearInFlight(k);
765
- setEmail(trimmed);
778
+ onInviteEmailChange(trimmed);
766
779
  setShareError(extractShareErrorMessage(err));
767
780
  },
768
781
  },
@@ -898,15 +911,15 @@ function SharePanel(
898
911
  <div className="mb-4 space-y-2">
899
912
  <div className="flex items-stretch gap-2">
900
913
  <MemberAutocomplete
901
- value={email}
914
+ value={inviteEmail}
902
915
  open={suggestionsOpen}
903
916
  onOpenChange={setSuggestionsOpen}
904
917
  onValueChange={(next) => {
905
- setEmail(next);
918
+ onInviteEmailChange(next);
906
919
  if (shareError) setShareError(null);
907
920
  }}
908
921
  onSelectMember={(member) => {
909
- setEmail(member.email);
922
+ onInviteEmailChange(member.email);
910
923
  setSuggestionsOpen(false);
911
924
  if (shareError) setShareError(null);
912
925
  }}
@@ -920,6 +933,14 @@ function SharePanel(
920
933
  search={memberSearch}
921
934
  />
922
935
  <RoleSelect value={role} onChange={setRole} />
936
+ <button
937
+ type="button"
938
+ onClick={handleAdd}
939
+ disabled={!hasInviteEmail}
940
+ className={BUTTON_PRIMARY_SM}
941
+ >
942
+ Add
943
+ </button>
923
944
  </div>
924
945
  {shareError ? (
925
946
  <div
@@ -35,7 +35,7 @@ export interface UseAgentChatHomeHandoffLinksOptions {
35
35
  enabled?: boolean;
36
36
  /** How long the handoff marker remains valid. Defaults to 6 hours. */
37
37
  ttlMs?: number;
38
- /** Only intercept links if a recent handoff marker already exists. */
38
+ /** Only intercept links if a recent handoff marker already exists. Defaults to true. */
39
39
  requireActiveHandoff?: boolean;
40
40
  }
41
41
 
@@ -148,7 +148,7 @@ export function useAgentChatHomeHandoffLinks({
148
148
  isChatPath,
149
149
  enabled = true,
150
150
  ttlMs,
151
- requireActiveHandoff = false,
151
+ requireActiveHandoff = true,
152
152
  }: UseAgentChatHomeHandoffLinksOptions): void {
153
153
  const location = useLocation();
154
154
  const navigate = useNavigate();
@@ -697,7 +697,10 @@ const messages = {
697
697
  configure: "Configure",
698
698
  connect: "Connect",
699
699
  connectWithOAuth: "Connect with OAuth",
700
+ useApiToken: "Use API token",
700
701
  connectSuggestion: "Connect {{name}} to use this link in chat",
702
+ connectSuggestionWithApiToken:
703
+ "Connect {{name}} with an API token to use this link in chat",
701
704
  dismissSuggestion: "Dismiss integration suggestion",
702
705
  backToIntegrations: "Back to integrations",
703
706
  customTitle: "Add custom MCP server",
@@ -792,7 +795,7 @@ const messages = {
792
795
  "Bring Figma design context and canvas actions into an agent.",
793
796
  useCase: "Design files, components, variables, design systems, canvas",
794
797
  setupNote:
795
- "Figma only allows clients listed in its MCP Catalog. Agent Native needs vendor approval before this remote endpoint can complete a connection; use the existing Figma OAuth connector for app-owned workflows in the meantime.",
798
+ "Figma MCP only allows clients listed in Figma's MCP Catalog, so this remote endpoint cannot connect from Agent Native yet. Use the Figma REST API fallback with a personal access token for reading file and node context; MCP canvas actions remain unavailable until Figma approves Agent Native.",
796
799
  },
797
800
  canva: {
798
801
  description: "Search, create, and update Canva designs and assets.",
@@ -197,4 +197,32 @@ export function registerFrameworkSecrets(): void {
197
197
  required: false,
198
198
  });
199
199
  }
200
+
201
+ if (!getRequiredSecret("FIGMA_ACCESS_TOKEN")) {
202
+ registerRequiredSecret({
203
+ key: "FIGMA_ACCESS_TOKEN",
204
+ label: "Figma access token",
205
+ description:
206
+ "Optional fallback for reading Figma file and node context when the hosted Figma MCP server is unavailable. Generate a personal access token with current_user:read and file_content:read.",
207
+ docsUrl:
208
+ "https://developers.figma.com/docs/rest-api/personal-access-tokens/",
209
+ scope: "user",
210
+ kind: "api-key",
211
+ required: false,
212
+ validator: async (value) => {
213
+ const response = await fetch("https://api.figma.com/v1/me", {
214
+ headers: {
215
+ "X-Figma-Token": value,
216
+ "User-Agent": "AgentNative/1.0",
217
+ },
218
+ });
219
+ return response.ok
220
+ ? { ok: true }
221
+ : {
222
+ ok: false,
223
+ error: `Figma rejected the token (HTTP ${response.status}).`,
224
+ };
225
+ },
226
+ });
227
+ }
200
228
  }
@@ -69,7 +69,7 @@ import {
69
69
  callerHasRunAccess,
70
70
  callerHasThreadAccess,
71
71
  } from "../agent/run-ownership.js";
72
- import { readBackgroundRunClaim } from "../agent/run-store.js";
72
+ import { markTurnAborted, readBackgroundRunClaim } from "../agent/run-store.js";
73
73
  import {
74
74
  buildCurrentTimeUserContext,
75
75
  buildRuntimeContextPrompt,
@@ -4137,6 +4137,31 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4137
4137
  callerHasRunAccess(owner, runId, "viewer", { orgId });
4138
4138
  const canEditRun = (runId: string) =>
4139
4139
  callerHasRunAccess(owner, runId, "editor", { orgId });
4140
+ const canEditThread = (threadId: string) =>
4141
+ callerHasThreadAccess(owner, threadId, "editor", { orgId });
4142
+
4143
+ // Route: POST /runs/turn/:turnId/abort
4144
+ // Covers the short window where the client has sent a turn but the
4145
+ // server has not yet created its background run id.
4146
+ const turnAbortMatch =
4147
+ url.match(/\/runs\/turn\/([^/?]+)\/abort/) ||
4148
+ url.match(/^\/turn\/([^/?]+)\/abort/);
4149
+ if (turnAbortMatch && method === "POST") {
4150
+ const turnId = decodeURIComponent(turnAbortMatch[1]);
4151
+ const body = await readBody(event).catch(() => null);
4152
+ const threadId =
4153
+ typeof body?.threadId === "string" ? body.threadId : "";
4154
+ if (
4155
+ !/^[a-zA-Z0-9_-]{1,160}$/.test(turnId) ||
4156
+ !threadId ||
4157
+ !(await canEditThread(threadId))
4158
+ ) {
4159
+ setResponseStatus(event, 404);
4160
+ return { error: "Run not found" };
4161
+ }
4162
+ await markTurnAborted(threadId, turnId);
4163
+ return { ok: true };
4164
+ }
4140
4165
 
4141
4166
  // Route: GET /runs/list?goalId=agent-team|agent-harness
4142
4167
  // Returns background agents in the Code hub-compatible run shape.
@@ -5180,6 +5205,9 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
5180
5205
  }
5181
5206
  workerBody = {
5182
5207
  ...parsedPayload,
5208
+ ...(prepared.body.internalContinuation === true
5209
+ ? { internalContinuation: true }
5210
+ : {}),
5183
5211
  [AGENT_CHAT_BACKGROUND_RUN_FIELD]: preparedMarker,
5184
5212
  };
5185
5213
  }
@@ -2579,6 +2579,27 @@ function createAgentNativePlugins(
2579
2579
  ].filter(Boolean);
2580
2580
  }
2581
2581
 
2582
+ function resolveAgentNativeTemplate(cwd: string): string {
2583
+ const configured = [
2584
+ process.env.AGENT_NATIVE_TEMPLATE,
2585
+ process.env.VITE_AGENT_NATIVE_TEMPLATE,
2586
+ process.env.VITE_APP_TEMPLATE,
2587
+ ].find((value) => value?.trim());
2588
+ if (configured) return configured.trim().toLowerCase();
2589
+
2590
+ const normalizedCwd = cwd.replaceAll("\\", "/");
2591
+ const marker = "/templates/";
2592
+ const markerIndex = normalizedCwd.lastIndexOf(marker);
2593
+ if (markerIndex === -1) return "";
2594
+ return (
2595
+ normalizedCwd
2596
+ .slice(markerIndex + marker.length)
2597
+ .split("/")[0]
2598
+ ?.trim()
2599
+ .toLowerCase() ?? ""
2600
+ );
2601
+ }
2602
+
2582
2603
  function createAgentNativeConfig(
2583
2604
  options: ClientConfigOptions | AgentNativeVitePluginOptions = {},
2584
2605
  command?: AgentNativeViteCommand,
@@ -2683,6 +2704,9 @@ function createAgentNativeConfig(
2683
2704
  __AGENT_NATIVE_MCP_INTEGRATIONS_CONFIG__: JSON.stringify(
2684
2705
  normalizeMcpIntegrationsConfig(options.mcpIntegrations),
2685
2706
  ),
2707
+ __AGENT_NATIVE_TEMPLATE__: JSON.stringify(
2708
+ resolveAgentNativeTemplate(cwd),
2709
+ ),
2686
2710
  },
2687
2711
  server: {
2688
2712
  ...(userConfig.server ?? {}),
@@ -83,7 +83,7 @@ When the user asks for a dashboard:
83
83
  2. If a metric definition, date range, or grain is ambiguous and the choice would change the panel's numbers, use the `ask-question` clarifying tool once before building. Skip it when the dictionary or the user already settled it.
84
84
  3. If a metric is not documented, do not guess column names. Ask for the table/columns or introspect the provider schema, then propose a dictionary entry with `save-data-dictionary-entry`.
85
85
  4. Build a complete `SqlDashboardConfig` with `name` and `panels`. Optionally set top-level `columns` (1–6, default 2) to control how many grid columns the panels before any section use.
86
- 5. Every panel needs `id`, `title`, `source`, `chartType`, `width`, and `sql`. `width` is the number of grid columns the panel spans (1..6, clamped to the active section's column count). Section panels skip `source` and `sql` and may set their own `columns` (1–6) to override the dashboard default for the panels following the section. Extension panels (`chartType: "extension"`) also skip `source` and `sql`; instead they require `config.extensionId` (see "Embedding An Extension As A Panel").
86
+ 5. Every panel needs `id`, `title`, `source`, `chartType`, `width`, and `sql`. `width` is the number of grid columns the panel spans (1..6, clamped to the active section's column count). Section panels skip `source` and `sql` and may set their own `columns` (1–6) to override the dashboard default for the panels following the section. Extension panels (`chartType: "extension"`) also skip `source` and `sql`; use `config.extensionSlotId` for new panels, with legacy `config.extensionId` supported for existing direct embeds (see "Embedding An Extension As A Panel").
87
87
  6. Persist with `update-dashboard`, not raw SQL or settings writes.
88
88
  7. Navigate to it with `pnpm action navigate --view=adhoc --dashboardId=<id>`.
89
89
 
@@ -114,14 +114,18 @@ rather than forcing it into a native dashboard config.
114
114
 
115
115
  ## Embedding An Extension As A Panel
116
116
 
117
- Use `chartType: "extension"` to embed an existing extension as a dashboard
118
- panel. This is different from the section above: there you replace the whole
119
- dashboard with an extension; here you drop a single extension widget into one
120
- panel slot alongside normal SQL charts. The panel renders the extension's
121
- sandboxed iframe instead of running a query, so it skips `source` and `sql` and
122
- instead requires `config.extensionId` (the id of an extension that already
123
- exists — create it first with `create-extension`). Validation rejects an
124
- extension panel without a non-empty `config.extensionId`.
117
+ Use `chartType: "extension"` to add an extension box alongside normal SQL
118
+ charts. The panel skips `source` and `sql`. New boxes use a stable slot id in
119
+ `config.extensionSlotId`:
120
+
121
+ ```text
122
+ analytics.dashboard.<dashboard-id>.panel.<panel-id>
123
+ ```
124
+
125
+ Create or choose the extension, call `add-extension-slot-target` with the
126
+ extension id and slot id, then call `install-extension` with the same values.
127
+ The dashboard panel is shared, while the installed extension is per-user.
128
+ Empty slots show the normal install affordance instead of a broken iframe.
125
129
 
126
130
  ```jsonc
127
131
  {
@@ -129,21 +133,22 @@ extension panel without a non-empty `config.extensionId`.
129
133
  "title": "Pipeline Widget",
130
134
  "chartType": "extension",
131
135
  "width": 3,
132
- "config": { "extensionId": "<existing-extension-id>" },
136
+ "config": {
137
+ "extensionSlotId": "analytics.dashboard.weekly-metrics.panel.pipeline-widget",
138
+ },
133
139
  }
134
140
  ```
135
141
 
136
142
  Notes:
137
143
 
138
- - The panel renders full-bleed (no card chrome/title) and does not receive the
139
- dashboard's filters/variables/date range it's a standalone widget for now.
140
- - Access is scoped per viewer: embedding does NOT grant access to the extension
141
- (same model as ExtensionSlots). If you share a dashboard more broadly than the
142
- embedded extension, viewers without access to that extension see an
143
- "extension unavailable" message instead of the content. Share the extension to
144
- the same audience as the dashboard so all viewers can see it.
144
+ - Slot-backed extensions receive the dashboard id, name, description, current
145
+ filters, and panel id/title/slot id as live context.
146
+ - Installs and extension access are per viewer. Sharing the dashboard does not
147
+ automatically install or grant access to its extension for other viewers.
148
+ - `config.extensionId` remains supported for legacy direct embeds. Use it only
149
+ when preserving an existing dashboard; new boxes should use a slot.
145
150
 
146
- ## Cloning An Extension-Backed Dashboard (e.g. per-customer copies)
151
+ ## Cloning A Legacy Direct-Extension Dashboard (e.g. per-customer copies)
147
152
 
148
153
  When the user asks for a copy of an existing extension-backed dashboard for a
149
154
  different customer/org (for example "make an Intuit version of the Roku usage
@@ -154,8 +159,11 @@ from that written file — never by pulling the body into chat context first or
154
159
  re-typing it as a `content` argument.
155
160
 
156
161
  1. `get-sql-dashboard` with `includeConfig: true` on the source dashboard and
157
- confirm the target panel is `chartType: "extension"`; grab its
158
- `config.extensionId`.
162
+ confirm the target panel is a legacy `chartType: "extension"` panel with
163
+ `config.extensionId`; grab that extension id. For a slot-backed panel, clone
164
+ the dashboard panel with a new stable `extensionSlotId`, then target and
165
+ install the desired extension into that slot instead of using this body-copy
166
+ playbook.
159
167
  2. `get-extension` for that id with `forceContent: true` **exactly once**. Reuse
160
168
  that body for the rest of the turn — a second same-run read intentionally
161
169
  omits `content` and returns `contentOmitted` instead. That is not the content
@@ -397,7 +405,7 @@ type PanelInput = PanelPatch & {
397
405
  chartType: NonNullable<PanelPatch["chartType"]>;
398
406
  source?: PanelPatch["source"]; // required for non-section / non-extension panels
399
407
  sql?: string; // required for non-section / non-extension panels
400
- // For chartType "extension": config.extensionId is required (the extension to embed).
408
+ // For chartType "extension": use config.extensionSlotId; extensionId is legacy direct embed compatibility.
401
409
  };
402
410
 
403
411
  type PanelFilter = {
@@ -163,6 +163,14 @@ membership id when its native update status reports `update-available`.
163
163
  serialization traps. The script is constrained: only documented dashboard
164
164
  method calls with JSON-compatible arguments are parsed; variables, imports,
165
165
  loops, functions, network, filesystem, and DB access are not available.
166
+ - Dashboard extension boxes use `chartType: "extension"` with
167
+ `config.extensionSlotId`. The stable per-box slot is
168
+ `analytics.dashboard.<dashboard-id>.panel.<panel-id>`. To add an extension,
169
+ create/choose it, call `add-extension-slot-target` with that slot id, then
170
+ `install-extension` with the same slot id. Installs are per-user; the
171
+ dashboard panel remains shared. Slot-backed extensions receive dashboard id,
172
+ name, description, current filters, and the panel id/title/slot id as context.
173
+ `config.extensionId` is legacy direct embedding for existing dashboards.
166
174
  - Dashboard saves keep bounded history in SQL. Use
167
175
  `list-dashboard-revisions` to inspect undo points and
168
176
  `restore-dashboard-revision` to restore one instead of hand-editing history
@@ -132,7 +132,7 @@ export const DASHBOARD_MUTATION_EXAMPLES = [
132
132
  `dashboard.insertPanel({"id":"new-kpi","title":"New KPI","source":"first-party","chartType":"metric","width":1,"config":{"timeScope":"dashboard"},"sql":"SELECT COUNT(*) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}'"}).atTop();`,
133
133
  `dashboard.insertPanel({"id":"new-chart","title":"New Chart","source":"first-party","chartType":"line","width":1,"config":{"timeScope":"dashboard"},"sql":"SELECT event_date AS date, COUNT(*) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}' GROUP BY event_date ORDER BY event_date"}).nextTo("retention-over-time");`,
134
134
  `dashboard.insertPanel({"id":"row-chart","title":"Row Chart","source":"first-party","chartType":"bar","width":1,"config":{"timeScope":"dashboard"},"sql":"SELECT event_name AS name, COUNT(*) AS value FROM analytics_events WHERE event_date >= '{{dateStart}}' AND event_date < '{{dateEnd}}' GROUP BY event_name"}).atRow(2);`,
135
- 'dashboard.insertPanel({"id":"pipeline-widget","title":"Pipeline Widget","chartType":"extension","width":3,"config":{"extensionId":"<existing-extension-id>"}}).atBottom();',
135
+ 'dashboard.insertPanel({"id":"pipeline-widget","title":"Pipeline Widget","chartType":"extension","width":3,"config":{"extensionSlotId":"analytics.dashboard.<dashboard-id>.panel.pipeline-widget"}}).atBottom();',
136
136
  ] as const;
137
137
 
138
138
  export type DashboardMutationOperation =
@@ -369,8 +369,12 @@ export function validateDashboardConfig(
369
369
  cfg && typeof cfg.extensionId === "string"
370
370
  ? cfg.extensionId.trim()
371
371
  : "";
372
- if (!extensionId) {
373
- return `panel[${i}].config.extensionId is required for extension panels (the id of the extension to render inline)`;
372
+ const extensionSlotId =
373
+ cfg && typeof cfg.extensionSlotId === "string"
374
+ ? cfg.extensionSlotId.trim()
375
+ : "";
376
+ if (!extensionId && !extensionSlotId) {
377
+ return `panel[${i}].config.extensionId or config.extensionSlotId is required for extension panels`;
374
378
  }
375
379
  }
376
380
  if (
@@ -1,5 +1,8 @@
1
1
  import { useDemoModeStatus, useT } from "@agent-native/core/client";
2
- import { EmbeddedExtension } from "@agent-native/core/client/extensions";
2
+ import {
3
+ EmbeddedExtension,
4
+ ExtensionSlot,
5
+ } from "@agent-native/core/client/extensions";
3
6
  import {
4
7
  IconArrowsSort,
5
8
  IconSortAscending,
@@ -1185,6 +1188,8 @@ interface SqlChartProps {
1185
1188
  className?: string;
1186
1189
  loadData?: boolean;
1187
1190
  onExportCsvChange?: (handler: (() => void) | null) => void;
1191
+ /** Dashboard/panel state sent to slot-backed extension boxes. */
1192
+ extensionContext?: Record<string, unknown> | null;
1188
1193
  }
1189
1194
 
1190
1195
  export function SqlChart({
@@ -1192,6 +1197,7 @@ export function SqlChart({
1192
1197
  resolvedSql,
1193
1198
  loadData = true,
1194
1199
  onExportCsvChange,
1200
+ extensionContext,
1195
1201
  }: SqlChartProps) {
1196
1202
  const t = useT();
1197
1203
  const { enabled: demoModeEnabled } = useDemoModeStatus();
@@ -1266,11 +1272,12 @@ export function SqlChart({
1266
1272
  );
1267
1273
  }
1268
1274
 
1269
- // Extension panels render a sandboxed extension iframe instead of querying a
1270
- // data source. The extension id lives in config.extensionId.
1275
+ // Extension panels render either a named extension-point slot or a legacy
1276
+ // direct extension iframe instead of querying a data source.
1271
1277
  if (isExtension) {
1272
1278
  const extensionId = panel.config?.extensionId;
1273
- if (!extensionId) {
1279
+ const slotId = panel.config?.extensionSlotId;
1280
+ if (!extensionId && !slotId) {
1274
1281
  return (
1275
1282
  <div className="flex flex-1 items-center justify-center px-4 py-8 min-h-[120px]">
1276
1283
  <p className="text-sm text-muted-foreground text-center">
@@ -1280,7 +1287,12 @@ export function SqlChart({
1280
1287
  );
1281
1288
  }
1282
1289
  return (
1283
- <DashboardExtensionPanel extensionId={extensionId} panelId={panel.id} />
1290
+ <DashboardExtensionPanel
1291
+ extensionId={extensionId}
1292
+ panelId={panel.id}
1293
+ slotId={slotId}
1294
+ context={extensionContext}
1295
+ />
1284
1296
  );
1285
1297
  }
1286
1298
  const colors = panel.config?.colors || DEFAULT_COLORS;
@@ -1409,9 +1421,13 @@ export function SqlChart({
1409
1421
  function DashboardExtensionPanel({
1410
1422
  extensionId,
1411
1423
  panelId,
1424
+ slotId,
1425
+ context,
1412
1426
  }: {
1413
- extensionId: string;
1427
+ extensionId?: string;
1414
1428
  panelId: string;
1429
+ slotId?: string;
1430
+ context?: Record<string, unknown> | null;
1415
1431
  }) {
1416
1432
  const t = useT();
1417
1433
  // Hold the report-readiness marker until the extension iframe paints so
@@ -1419,6 +1435,18 @@ function DashboardExtensionPanel({
1419
1435
  const [ready, setReady] = useState(false);
1420
1436
  const [unavailable, setUnavailable] = useState(false);
1421
1437
 
1438
+ if (slotId) {
1439
+ return (
1440
+ <ExtensionSlot
1441
+ id={slotId}
1442
+ context={context}
1443
+ showEmptyAffordance
1444
+ className="min-h-[120px] w-full"
1445
+ toolClassName="w-full"
1446
+ />
1447
+ );
1448
+ }
1449
+
1422
1450
  // Embedding never grants access to the extension itself (same model as
1423
1451
  // ExtensionSlots). A viewer with dashboard-only access who can't see the
1424
1452
  // referenced extension gets a clear message instead of a blank panel.
@@ -1438,7 +1466,7 @@ function DashboardExtensionPanel({
1438
1466
  data-dashboard-report-loading={ready ? undefined : "true"}
1439
1467
  >
1440
1468
  <EmbeddedExtension
1441
- extensionId={extensionId}
1469
+ extensionId={extensionId!}
1442
1470
  slotId={`dashboard-panel-${panelId}`}
1443
1471
  className="w-full"
1444
1472
  // Intentional for v1: extension panels are standalone widgets and do not
@@ -163,8 +163,6 @@ export function Layout({ children }: LayoutProps) {
163
163
  activePath: location.pathname,
164
164
  enabled: !isAskRoute && !reportScreenshot,
165
165
  });
166
- const sidebarScope = chatHomeHandoffActive ? null : analyticsScope;
167
-
168
166
  useEffect(() => {
169
167
  function handleChatRunning(event: Event) {
170
168
  const detail = (event as CustomEvent).detail;
@@ -294,7 +292,7 @@ export function Layout({ children }: LayoutProps) {
294
292
  t("chat.suggestionAnomalies"),
295
293
  t("chat.suggestionMrr"),
296
294
  ]}
297
- scope={sidebarScope}
295
+ scope={analyticsScope}
298
296
  composerSlot={<CreativeContextComposerChip />}
299
297
  >
300
298
  {contentFrame}