@agent-native/core 0.107.0 → 0.107.2

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 (156) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/integrations.mdx +27 -27
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/a2a/artifact-response.ts +32 -10
  6. package/corpus/core/src/agent/production-agent.ts +19 -0
  7. package/corpus/core/src/agent/run-store.ts +98 -2
  8. package/corpus/core/src/client/AssistantChat.tsx +13 -0
  9. package/corpus/core/src/client/CommandMenu.tsx +5 -0
  10. package/corpus/core/src/client/active-run-state.ts +34 -0
  11. package/corpus/core/src/client/agent-chat-adapter.ts +4 -0
  12. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +41 -11
  13. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +27 -9
  14. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +45 -0
  15. package/corpus/core/src/client/sharing/ShareButton.tsx +30 -9
  16. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +2 -2
  17. package/corpus/core/src/integrations/a2a-continuation-processor.ts +107 -3
  18. package/corpus/core/src/integrations/pending-tasks-store.ts +98 -1
  19. package/corpus/core/src/integrations/plugin.ts +129 -4
  20. package/corpus/core/src/integrations/webhook-handler.ts +339 -36
  21. package/corpus/core/src/localization/default-messages.ts +4 -1
  22. package/corpus/core/src/secrets/register-framework-secrets.ts +28 -0
  23. package/corpus/core/src/server/agent-chat-plugin.ts +122 -9
  24. package/corpus/core/src/vite/client.ts +24 -0
  25. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +29 -21
  26. package/corpus/templates/analytics/AGENTS.md +8 -0
  27. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
  28. package/corpus/templates/analytics/actions/update-dashboard.ts +6 -2
  29. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +35 -7
  30. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -3
  31. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -1
  32. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/extension-slot.ts +11 -0
  33. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +27 -0
  34. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +6 -0
  35. package/corpus/templates/analytics/changelog/2026-07-17-dashboard-extension-boxes.md +6 -0
  36. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +8 -0
  37. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -0
  38. package/corpus/templates/assets/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  39. package/corpus/templates/brain/app/hooks/use-navigation-state.ts +6 -1
  40. package/corpus/templates/brain/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  41. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -1
  42. package/corpus/templates/calendar/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  43. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  44. package/corpus/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  45. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  46. package/corpus/templates/clips/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  47. package/corpus/templates/content/actions/_content-space-access.ts +45 -2
  48. package/corpus/templates/content/actions/create-content-database.ts +41 -7
  49. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  50. package/corpus/templates/content/app/root.tsx +1 -10
  51. package/corpus/templates/content/changelog/2026-07-17-creating-a-database-now-completes-reliably-in-hosted-workspa.md +6 -0
  52. package/corpus/templates/content/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  53. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +4 -1
  54. package/corpus/templates/dispatch/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  55. package/corpus/templates/forms/app/components/layout/Layout.tsx +2 -4
  56. package/corpus/templates/forms/app/components/ui/popover.tsx +25 -1
  57. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +6 -1
  58. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +13 -10
  59. package/corpus/templates/forms/app/root.tsx +6 -1
  60. package/corpus/templates/forms/changelog/2026-07-17-form-builder-popovers-stay-within-the-viewport.md +6 -0
  61. package/corpus/templates/forms/changelog/2026-07-17-form-editor-loading-matches-current-sidebar-free-layout.md +6 -0
  62. package/corpus/templates/forms/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  63. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
  64. package/corpus/templates/mail/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  65. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +14 -3
  66. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +8 -1
  67. package/corpus/templates/plan/app/root.tsx +0 -2
  68. package/corpus/templates/plan/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  69. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  70. package/corpus/templates/slides/app/context/DeckContext.tsx +23 -23
  71. package/corpus/templates/slides/changelog/2026-07-17-duplicated-slides-now-stay-in-the-deck.md +6 -0
  72. package/corpus/templates/slides/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  73. package/corpus/templates/tasks/app/components/layout/Layout.tsx +2 -13
  74. package/corpus/templates/tasks/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it.md +6 -0
  75. package/dist/a2a/artifact-response.d.ts +5 -1
  76. package/dist/a2a/artifact-response.d.ts.map +1 -1
  77. package/dist/a2a/artifact-response.js +18 -12
  78. package/dist/a2a/artifact-response.js.map +1 -1
  79. package/dist/agent/production-agent.d.ts.map +1 -1
  80. package/dist/agent/production-agent.js +13 -1
  81. package/dist/agent/production-agent.js.map +1 -1
  82. package/dist/agent/run-store.d.ts +17 -0
  83. package/dist/agent/run-store.d.ts.map +1 -1
  84. package/dist/agent/run-store.js +84 -2
  85. package/dist/agent/run-store.js.map +1 -1
  86. package/dist/client/AssistantChat.d.ts.map +1 -1
  87. package/dist/client/AssistantChat.js +10 -1
  88. package/dist/client/AssistantChat.js.map +1 -1
  89. package/dist/client/CommandMenu.d.ts.map +1 -1
  90. package/dist/client/CommandMenu.js +4 -0
  91. package/dist/client/CommandMenu.js.map +1 -1
  92. package/dist/client/active-run-state.d.ts +7 -0
  93. package/dist/client/active-run-state.d.ts.map +1 -1
  94. package/dist/client/active-run-state.js +28 -0
  95. package/dist/client/active-run-state.js.map +1 -1
  96. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  97. package/dist/client/agent-chat-adapter.js +4 -1
  98. package/dist/client/agent-chat-adapter.js.map +1 -1
  99. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  100. package/dist/client/resources/McpConnectionSuggestion.js +27 -8
  101. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  102. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  103. package/dist/client/resources/McpIntegrationDialog.js +4 -2
  104. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  105. package/dist/client/resources/mcp-integration-catalog.d.ts +6 -0
  106. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  107. package/dist/client/resources/mcp-integration-catalog.js +33 -0
  108. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  109. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  110. package/dist/client/sharing/ShareButton.js +15 -12
  111. package/dist/client/sharing/ShareButton.js.map +1 -1
  112. package/dist/client/use-agent-chat-home-handoff.d.ts +1 -1
  113. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  114. package/dist/client/use-agent-chat-home-handoff.js +1 -1
  115. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  116. package/dist/collab/awareness.d.ts +2 -2
  117. package/dist/collab/awareness.d.ts.map +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/integrations/a2a-continuation-processor.js +62 -5
  120. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  121. package/dist/integrations/pending-tasks-store.d.ts +3 -0
  122. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  123. package/dist/integrations/pending-tasks-store.js +75 -1
  124. package/dist/integrations/pending-tasks-store.js.map +1 -1
  125. package/dist/integrations/plugin.d.ts.map +1 -1
  126. package/dist/integrations/plugin.js +81 -5
  127. package/dist/integrations/plugin.js.map +1 -1
  128. package/dist/integrations/webhook-handler.d.ts +23 -2
  129. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  130. package/dist/integrations/webhook-handler.js +257 -35
  131. package/dist/integrations/webhook-handler.js.map +1 -1
  132. package/dist/localization/default-messages.d.ts +2 -0
  133. package/dist/localization/default-messages.d.ts.map +1 -1
  134. package/dist/localization/default-messages.js +3 -1
  135. package/dist/localization/default-messages.js.map +1 -1
  136. package/dist/notifications/routes.d.ts +2 -2
  137. package/dist/observability/routes.d.ts +5 -5
  138. package/dist/resources/handlers.d.ts +1 -1
  139. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  140. package/dist/secrets/register-framework-secrets.js +25 -0
  141. package/dist/secrets/register-framework-secrets.js.map +1 -1
  142. package/dist/secrets/routes.d.ts +9 -9
  143. package/dist/server/agent-chat-plugin.d.ts +6 -0
  144. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  145. package/dist/server/agent-chat-plugin.js +94 -7
  146. package/dist/server/agent-chat-plugin.js.map +1 -1
  147. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  148. package/dist/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  149. package/dist/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  150. package/dist/vite/client.d.ts.map +1 -1
  151. package/dist/vite/client.js +20 -0
  152. package/dist/vite/client.js.map +1 -1
  153. package/docs/content/integrations.mdx +27 -27
  154. package/package.json +1 -1
  155. package/src/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  156. package/src/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
@@ -9,6 +9,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
9
9
 
10
10
  import { AgentAskPopover } from "../AgentAskPopover.js";
11
11
  import { agentNativePath } from "../api-path.js";
12
+ import { openAgentSettings } from "../CommandMenu.js";
12
13
  import {
13
14
  Dialog,
14
15
  DialogContent,
@@ -27,6 +28,7 @@ import {
27
28
  buildMcpOAuthStartUrl,
28
29
  createMcpIntegrationFormDefaults,
29
30
  filterMcpIntegrations,
31
+ getMcpIntegrationApiFallback,
30
32
  getDefaultMcpIntegrations,
31
33
  isCustomMcpIntegrationEnabled,
32
34
  resolveMcpIntegrationScope,
@@ -445,6 +447,7 @@ export function McpIntegrationDialog({
445
447
  )}
446
448
  <div className="grid gap-3 sm:grid-cols-2">
447
449
  {filteredIntegrations.map((integration) => {
450
+ const apiFallback = getMcpIntegrationApiFallback(integration);
448
451
  const connected = connectedUrls.has(
449
452
  compareUrl(integration.url),
450
453
  );
@@ -515,15 +518,30 @@ export function McpIntegrationDialog({
515
518
  {t("mcpIntegrations.connected")}
516
519
  </button>
517
520
  ) : setupOnly ? (
518
- <a
519
- href={integration.docsUrl}
520
- target="_blank"
521
- rel="noopener noreferrer"
522
- className="inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-[12px] font-medium text-foreground hover:bg-accent"
523
- >
524
- {t("mcpIntegrations.viewSetup")}
525
- <IconExternalLink className="h-3.5 w-3.5" />
526
- </a>
521
+ <>
522
+ {apiFallback && (
523
+ <button
524
+ type="button"
525
+ onClick={() =>
526
+ openAgentSettings(
527
+ `secrets:${apiFallback.secretKey}`,
528
+ )
529
+ }
530
+ className="inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-md bg-primary px-2.5 text-[12px] font-medium text-primary-foreground hover:bg-primary/90"
531
+ >
532
+ {t("mcpIntegrations.useApiToken")}
533
+ </button>
534
+ )}
535
+ <a
536
+ href={integration.docsUrl}
537
+ target="_blank"
538
+ rel="noopener noreferrer"
539
+ className="inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-[12px] font-medium text-foreground hover:bg-accent"
540
+ >
541
+ {t("mcpIntegrations.viewSetup")}
542
+ <IconExternalLink className="h-3.5 w-3.5" />
543
+ </a>
544
+ </>
527
545
  ) : (
528
546
  <button
529
547
  type="button"
@@ -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();
@@ -171,11 +171,22 @@ async function processClaimedContinuation(
171
171
  ...(auth.apiKeyFallbacks ? { fallbackApiKeys: auth.apiKeyFallbacks } : {}),
172
172
  });
173
173
  const deadline = Date.now() + PROCESSOR_WAIT_MS;
174
+ const recoverableArtifactSecrets =
175
+ await resolveContinuationArtifactSecrets(continuation);
174
176
  let task: Task | null = null;
177
+ let latestRecoverableArtifactText: string | null = null;
175
178
 
176
179
  try {
177
180
  while (Date.now() < deadline) {
178
181
  task = await client.getTask(continuation.a2aTaskId);
182
+ const recoverableArtifactText = extractVerifiedRecoverableArtifactText(
183
+ task,
184
+ continuation.agentUrl,
185
+ recoverableArtifactSecrets,
186
+ );
187
+ if (recoverableArtifactText) {
188
+ latestRecoverableArtifactText = recoverableArtifactText;
189
+ }
179
190
  if (TERMINAL_STATES.has(task.status.state)) break;
180
191
  await reportA2AContinuationProgress(continuation, progress, task);
181
192
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
@@ -183,6 +194,17 @@ async function processClaimedContinuation(
183
194
  } catch (err) {
184
195
  if (isTransientA2APollError(err)) {
185
196
  if (shouldStopPollingRemoteTask(continuation)) {
197
+ if (latestRecoverableArtifactText) {
198
+ await deliverAndCompleteA2AContinuation(
199
+ continuation,
200
+ adapter,
201
+ formatRecoverableArtifactFallbackText(
202
+ latestRecoverableArtifactText,
203
+ ),
204
+ progress,
205
+ );
206
+ return;
207
+ }
186
208
  await notifyAndFailA2AContinuation(
187
209
  continuation,
188
210
  adapter,
@@ -209,6 +231,15 @@ async function processClaimedContinuation(
209
231
 
210
232
  if (!task || !TERMINAL_STATES.has(task.status.state)) {
211
233
  if (shouldStopPollingRemoteTask(continuation)) {
234
+ if (latestRecoverableArtifactText) {
235
+ await deliverAndCompleteA2AContinuation(
236
+ continuation,
237
+ adapter,
238
+ formatRecoverableArtifactFallbackText(latestRecoverableArtifactText),
239
+ progress,
240
+ );
241
+ return;
242
+ }
212
243
  await notifyAndFailA2AContinuation(
213
244
  continuation,
214
245
  adapter,
@@ -222,6 +253,15 @@ async function processClaimedContinuation(
222
253
  }
223
254
 
224
255
  if (task.status.state !== "completed") {
256
+ if (latestRecoverableArtifactText) {
257
+ await deliverAndCompleteA2AContinuation(
258
+ continuation,
259
+ adapter,
260
+ formatRecoverableArtifactFallbackText(latestRecoverableArtifactText),
261
+ progress,
262
+ );
263
+ return;
264
+ }
225
265
  const reason =
226
266
  extractTaskText(task) ||
227
267
  `Remote A2A task ${continuation.a2aTaskId} ended with state ${task.status.state}`;
@@ -234,6 +274,15 @@ async function processClaimedContinuation(
234
274
  continuation.agentUrl,
235
275
  );
236
276
  if (!text.trim()) {
277
+ if (latestRecoverableArtifactText) {
278
+ await deliverAndCompleteA2AContinuation(
279
+ continuation,
280
+ adapter,
281
+ formatRecoverableArtifactFallbackText(latestRecoverableArtifactText),
282
+ progress,
283
+ );
284
+ return;
285
+ }
237
286
  await notifyAndFailA2AContinuation(
238
287
  continuation,
239
288
  adapter,
@@ -382,6 +431,8 @@ async function deliverAndCompleteA2AContinuation(
382
431
  `${deliveryContinuation.platform} response delivery timed out`,
383
432
  );
384
433
  let persistenceError: unknown;
434
+ const artifactSecrets =
435
+ await resolveContinuationArtifactSecrets(deliveryContinuation);
385
436
  for (let attempt = 0; attempt < 3; attempt += 1) {
386
437
  try {
387
438
  await persistA2AContinuationDelivery(
@@ -389,6 +440,7 @@ async function deliverAndCompleteA2AContinuation(
389
440
  outgoing,
390
441
  deliveryReceipt,
391
442
  text,
443
+ artifactSecrets,
392
444
  );
393
445
  persistenceError = undefined;
394
446
  break;
@@ -463,6 +515,7 @@ async function persistA2AContinuationDelivery(
463
515
  outgoing: OutgoingMessage,
464
516
  receipt: PlatformDeliveryReceipt,
465
517
  artifactText: string,
518
+ artifactSecrets: readonly string[],
466
519
  ): Promise<void> {
467
520
  const mapping = await getThreadMapping(
468
521
  continuation.platform,
@@ -480,9 +533,12 @@ async function persistA2AContinuationDelivery(
480
533
  }
481
534
  if (!Array.isArray(repo.messages)) repo.messages = [];
482
535
 
483
- const artifacts = extractA2AArtifactIdentities([
484
- { tool: "call-agent", result: artifactText },
485
- ]);
536
+ const artifacts = extractA2AArtifactIdentities(
537
+ [{ tool: "call-agent", result: artifactText }],
538
+ {
539
+ persistedArtifactSecrets: artifactSecrets,
540
+ },
541
+ );
486
542
  const metadata: Record<string, unknown> = {
487
543
  integrationDeliveryAttempted: true,
488
544
  integrationDelivery: {
@@ -726,6 +782,24 @@ async function signFreshContinuationTokens(
726
782
  return tokens;
727
783
  }
728
784
 
785
+ async function resolveContinuationArtifactSecrets(
786
+ continuation: A2AContinuation,
787
+ ): Promise<string[]> {
788
+ const secrets: string[] = [];
789
+ const add = (secret: string | null | undefined) => {
790
+ const value = secret?.trim();
791
+ if (value && !secrets.includes(value)) secrets.push(value);
792
+ };
793
+ add(process.env.A2A_SECRET);
794
+ if (continuation.orgId) {
795
+ try {
796
+ const { getOrgA2ASecret } = await import("../org/context.js");
797
+ add(await getOrgA2ASecret(continuation.orgId));
798
+ } catch {}
799
+ }
800
+ return secrets;
801
+ }
802
+
729
803
  function isLikelyJwt(token: string): boolean {
730
804
  return token.split(".").length === 3;
731
805
  }
@@ -740,6 +814,36 @@ function extractTaskText(task: Task): string {
740
814
  .join("\n");
741
815
  }
742
816
 
817
+ function extractVerifiedRecoverableArtifactText(
818
+ task: Task,
819
+ agentUrl: string,
820
+ artifactSecrets: readonly string[],
821
+ ): string | null {
822
+ if (task.status.message?.metadata?.agentNativeRecoverableArtifacts !== true) {
823
+ return null;
824
+ }
825
+
826
+ const text = formatContinuationArtifactText(extractTaskText(task), agentUrl);
827
+ if (!text.trim()) return null;
828
+
829
+ // Require the signed identity ledger so arbitrary peer progress prose cannot
830
+ // prematurely complete the continuation.
831
+ const artifacts = extractA2AArtifactIdentities(
832
+ [{ tool: "call-agent", result: text }],
833
+ {
834
+ persistedArtifactSecrets: artifactSecrets,
835
+ },
836
+ );
837
+ return artifacts.length > 0 ? text : null;
838
+ }
839
+
840
+ function formatRecoverableArtifactFallbackText(text: string): string {
841
+ return text.replace(
842
+ "The agent is still working on the full response, but these verified artifacts already exist:",
843
+ "The downstream agent did not finish its full response, but these verified artifacts already exist:",
844
+ );
845
+ }
846
+
743
847
  function formatContinuationArtifactText(
744
848
  text: string,
745
849
  agentUrl: string,
@@ -272,6 +272,19 @@ export async function claimPendingTask(
272
272
  AND active.status = 'processing'
273
273
  AND active.id <> integration_pending_tasks.id
274
274
  )
275
+ AND NOT EXISTS (
276
+ SELECT 1 FROM integration_pending_tasks earlier
277
+ WHERE earlier.platform = integration_pending_tasks.platform
278
+ AND earlier.external_thread_id = integration_pending_tasks.external_thread_id
279
+ AND earlier.status = 'pending'
280
+ AND (
281
+ earlier.created_at < integration_pending_tasks.created_at
282
+ OR (
283
+ earlier.created_at = integration_pending_tasks.created_at
284
+ AND earlier.id < integration_pending_tasks.id
285
+ )
286
+ )
287
+ )
275
288
  RETURNING id, platform, external_thread_id, payload, owner_email, org_id, status, attempts, error_message, created_at, updated_at, completed_at`
276
289
  : `UPDATE integration_pending_tasks
277
290
  SET status = ?, attempts = attempts + 1, updated_at = ?
@@ -282,6 +295,19 @@ export async function claimPendingTask(
282
295
  AND active.external_thread_id = integration_pending_tasks.external_thread_id
283
296
  AND active.status = 'processing'
284
297
  AND active.id <> integration_pending_tasks.id
298
+ )
299
+ AND NOT EXISTS (
300
+ SELECT 1 FROM integration_pending_tasks earlier
301
+ WHERE earlier.platform = integration_pending_tasks.platform
302
+ AND earlier.external_thread_id = integration_pending_tasks.external_thread_id
303
+ AND earlier.status = 'pending'
304
+ AND (
305
+ earlier.created_at < integration_pending_tasks.created_at
306
+ OR (
307
+ earlier.created_at = integration_pending_tasks.created_at
308
+ AND earlier.id < integration_pending_tasks.id
309
+ )
310
+ )
285
311
  )`,
286
312
  args: ["processing", now, id],
287
313
  });
@@ -312,7 +338,7 @@ export async function getNextPendingTaskIdForThread(
312
338
  const { rows } = await getDbExec().execute({
313
339
  sql: `SELECT id FROM integration_pending_tasks
314
340
  WHERE platform = ? AND external_thread_id = ? AND status = 'pending'
315
- ORDER BY created_at ASC LIMIT 1`,
341
+ ORDER BY created_at ASC, id ASC LIMIT 1`,
316
342
  args: [platform, externalThreadId],
317
343
  });
318
344
  return rows[0]?.id ? String(rows[0].id) : null;
@@ -354,6 +380,77 @@ export async function markTaskRetryable(
354
380
  });
355
381
  }
356
382
 
383
+ export async function stageTaskDeliveryPayload(
384
+ id: string,
385
+ payload: string,
386
+ ): Promise<void> {
387
+ await ensureTable();
388
+ const client = getDbExec();
389
+ const now = Date.now();
390
+ const result = await client.execute({
391
+ sql: `UPDATE integration_pending_tasks
392
+ SET payload = ?, updated_at = ?
393
+ WHERE id = ? AND status = 'processing'`,
394
+ args: [payload, now, id],
395
+ });
396
+ const affected = Number(
397
+ (result as { rowsAffected?: number }).rowsAffected ??
398
+ (result as { rowCount?: number }).rowCount ??
399
+ 0,
400
+ );
401
+ if (affected === 0) {
402
+ throw new Error("Integration task is no longer claimable for delivery");
403
+ }
404
+ }
405
+
406
+ export async function markTaskDeliveryRetryable(
407
+ id: string,
408
+ payload: string,
409
+ errorMessage: string,
410
+ ): Promise<void> {
411
+ await ensureTable();
412
+ const client = getDbExec();
413
+ const result = await client.execute({
414
+ sql: `UPDATE integration_pending_tasks
415
+ SET status = ?, payload = ?, updated_at = ?, error_message = ?
416
+ WHERE id = ? AND status = 'processing'`,
417
+ args: ["pending", payload, Date.now(), errorMessage.slice(0, 2000), id],
418
+ });
419
+ const affected = Number(
420
+ (result as { rowsAffected?: number }).rowsAffected ??
421
+ (result as { rowCount?: number }).rowCount ??
422
+ 0,
423
+ );
424
+ if (affected === 0) {
425
+ throw new Error(
426
+ "Integration task is no longer claimable for delivery retry",
427
+ );
428
+ }
429
+ }
430
+
431
+ export async function failTaskDeliveryTransition(
432
+ id: string,
433
+ errorMessage: string,
434
+ ): Promise<void> {
435
+ await ensureTable();
436
+ const result = await getDbExec().execute({
437
+ sql: `UPDATE integration_pending_tasks
438
+ SET status = ?, updated_at = ?, error_message = ?, payload = ?
439
+ WHERE id = ? AND status = 'processing'`,
440
+ args: ["failed", Date.now(), errorMessage.slice(0, 2000), "{}", id],
441
+ });
442
+ const affected = Number(
443
+ (result as { rowsAffected?: number }).rowsAffected ??
444
+ (result as { rowCount?: number }).rowCount ??
445
+ 0,
446
+ );
447
+ if (affected === 0) {
448
+ throw new Error(
449
+ "Integration task delivery failure transition lost its race",
450
+ );
451
+ }
452
+ }
453
+
357
454
  /** Mark a task as failed and stash an error message. */
358
455
  export async function markTaskFailed(
359
456
  id: string,