@agent-native/core 0.84.1 → 0.84.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +15 -0
  3. package/corpus/core/docs/design/durable-agent-runs.md +34 -35
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
  6. package/corpus/core/src/agent/production-agent.ts +9 -2
  7. package/corpus/core/src/agent/run-loop-with-resume.ts +38 -8
  8. package/corpus/core/src/agent/run-manager.ts +8 -7
  9. package/corpus/core/src/agent/thread-data-builder.ts +6 -0
  10. package/corpus/core/src/cli/design-connect.ts +175 -0
  11. package/corpus/core/src/cli/skills.ts +5 -7
  12. package/corpus/core/src/client/AgentPanel.tsx +8 -0
  13. package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
  14. package/corpus/core/src/client/agent-chat-adapter.ts +253 -24
  15. package/corpus/core/src/client/sharing/ShareButton.tsx +70 -21
  16. package/corpus/core/src/client/sse-event-processor.ts +46 -4
  17. package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  18. package/corpus/templates/analytics/AGENTS.md +1 -1
  19. package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
  20. package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
  21. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
  22. package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
  23. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
  24. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
  25. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
  26. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
  27. package/corpus/templates/design/app/components/design/types.ts +13 -0
  28. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  29. package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
  30. package/corpus/templates/design/app/i18n-data.ts +245 -0
  31. package/corpus/templates/design/app/pages/DesignEditor.tsx +890 -252
  32. package/corpus/templates/design/changelog/2026-06-30-agent-chat-now-reports-saved-design-generations-clearly-when.md +6 -0
  33. package/corpus/templates/design/changelog/2026-06-30-apply-styles-now-appears-only-for-localhost-visual-edit-scre.md +6 -0
  34. package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
  35. package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
  36. package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
  37. package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
  38. package/corpus/templates/design/changelog/2026-06-30-share-options-now-make-export-and-agent-handoff-easier-to-no.md +6 -0
  39. package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
  40. package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
  41. package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
  42. package/corpus/templates/design/changelog/2026-07-01-share-general-access-menu-stays-open-when-choosing-organization.md +6 -0
  43. package/corpus/templates/design/server/plugins/db.ts +10 -0
  44. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  45. package/dist/agent/engine/builder-engine.js +61 -10
  46. package/dist/agent/engine/builder-engine.js.map +1 -1
  47. package/dist/agent/production-agent.d.ts.map +1 -1
  48. package/dist/agent/production-agent.js +9 -2
  49. package/dist/agent/production-agent.js.map +1 -1
  50. package/dist/agent/run-loop-with-resume.d.ts +2 -2
  51. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  52. package/dist/agent/run-loop-with-resume.js +31 -8
  53. package/dist/agent/run-loop-with-resume.js.map +1 -1
  54. package/dist/agent/run-manager.d.ts +8 -7
  55. package/dist/agent/run-manager.d.ts.map +1 -1
  56. package/dist/agent/run-manager.js +8 -7
  57. package/dist/agent/run-manager.js.map +1 -1
  58. package/dist/agent/thread-data-builder.d.ts +2 -0
  59. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  60. package/dist/agent/thread-data-builder.js +5 -0
  61. package/dist/agent/thread-data-builder.js.map +1 -1
  62. package/dist/cli/design-connect.d.ts +2 -0
  63. package/dist/cli/design-connect.d.ts.map +1 -1
  64. package/dist/cli/design-connect.js +140 -0
  65. package/dist/cli/design-connect.js.map +1 -1
  66. package/dist/cli/skills.d.ts.map +1 -1
  67. package/dist/cli/skills.js +5 -7
  68. package/dist/cli/skills.js.map +1 -1
  69. package/dist/client/AgentPanel.d.ts +5 -1
  70. package/dist/client/AgentPanel.d.ts.map +1 -1
  71. package/dist/client/AgentPanel.js +4 -4
  72. package/dist/client/AgentPanel.js.map +1 -1
  73. package/dist/client/MultiTabAssistantChat.d.ts +3 -1
  74. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  75. package/dist/client/MultiTabAssistantChat.js +2 -2
  76. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  77. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  78. package/dist/client/agent-chat-adapter.js +216 -22
  79. package/dist/client/agent-chat-adapter.js.map +1 -1
  80. package/dist/client/sharing/ShareButton.d.ts +6 -0
  81. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  82. package/dist/client/sharing/ShareButton.js +25 -11
  83. package/dist/client/sharing/ShareButton.js.map +1 -1
  84. package/dist/client/sse-event-processor.d.ts +18 -1
  85. package/dist/client/sse-event-processor.d.ts.map +1 -1
  86. package/dist/client/sse-event-processor.js +34 -5
  87. package/dist/client/sse-event-processor.js.map +1 -1
  88. package/dist/collab/awareness.d.ts +2 -2
  89. package/dist/collab/awareness.d.ts.map +1 -1
  90. package/dist/collab/routes.d.ts +2 -2
  91. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  92. package/dist/notifications/routes.d.ts +3 -3
  93. package/dist/observability/routes.d.ts +7 -7
  94. package/dist/resources/handlers.d.ts +2 -2
  95. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  96. package/dist/server/transcribe-voice.d.ts +1 -1
  97. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
  98. package/docs/design/durable-agent-runs.md +34 -35
  99. package/package.json +1 -1
  100. package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
@@ -17,6 +17,7 @@ import { formatChatErrorText, normalizeChatError } from "./error-format.js";
17
17
  import {
18
18
  AgentAutoContinueSignal,
19
19
  type AgentActivityTrailEntry,
20
+ type AgentAutoContinueErrorInfo,
20
21
  type ContentPart,
21
22
  readSSEStream,
22
23
  settleInterruptedToolCalls,
@@ -696,6 +697,41 @@ function hasContinuationProgress(content: ContentPart[]): boolean {
696
697
  );
697
698
  }
698
699
 
700
+ function lastCompletedSideEffectTool(
701
+ content: ContentPart[],
702
+ ): Extract<ContentPart, { type: "tool-call" }> | undefined {
703
+ for (let i = content.length - 1; i >= 0; i--) {
704
+ const part = content[i];
705
+ if (
706
+ part.type === "tool-call" &&
707
+ part.activity !== true &&
708
+ part.result !== undefined &&
709
+ part.isError !== true &&
710
+ part.completedSideEffect === true
711
+ ) {
712
+ return part;
713
+ }
714
+ }
715
+ return undefined;
716
+ }
717
+
718
+ function humanizeActionName(toolName: string): string {
719
+ return toolName
720
+ .replace(/^agent:/, "")
721
+ .replace(/[-_]+/g, " ")
722
+ .trim();
723
+ }
724
+
725
+ function completedToolTimeoutMessage(toolName: string): string {
726
+ if (toolName === "generate-design" || toolName === "update-design") {
727
+ return "The design was saved, but the assistant timed out before sending its final note. You can open the generated design from the completed tool card above.";
728
+ }
729
+ if (toolName === "present-design-variants") {
730
+ return "The design variants were saved, but the assistant timed out before sending its final note. You can review the completed variants from the tool card above.";
731
+ }
732
+ return `The ${humanizeActionName(toolName)} action completed, but the assistant timed out before sending its final response. The saved result is in the completed tool card above.`;
733
+ }
734
+
699
735
  /**
700
736
  * Signature of the *unique* sentence-like segments in a continuation's newly
701
737
  * streamed text, used to detect a degenerate repetition loop. A stuck model
@@ -784,6 +820,8 @@ function toolContinuationKey(
784
820
  stableJson(part.args),
785
821
  part.result === undefined ? "pending" : "done",
786
822
  part.result ?? "",
823
+ part.isError === true ? "error" : "",
824
+ part.completedSideEffect === true ? "side-effect" : "",
787
825
  part.activity === true ? "activity" : "tool",
788
826
  part.mcpApp ? "mcp-app" : "",
789
827
  ].join("\u0000");
@@ -850,6 +888,8 @@ function incrementalActionGuidance(tool: string): string | undefined {
850
888
  case "generate-design":
851
889
  case "update-design":
852
890
  return "persist a minimal first version (fewer files) with `generate-design`, then refine individual files with `edit-design` search/replace instead of resending everything";
891
+ case "present-design-variants":
892
+ return "save compact but complete variant screens with `present-design-variants` first, keeping each HTML direction focused enough to finish, then refine the chosen direction with `generate-design` or `edit-design`";
853
893
  case "create-visual-plan":
854
894
  case "create-ui-plan":
855
895
  case "create-plan-design":
@@ -1348,11 +1388,13 @@ export function createAgentChatAdapter(
1348
1388
  // its own budget rather than inheriting the prior payload's.
1349
1389
  let lastInFlightToolSignature: string | undefined;
1350
1390
  let repeatedInFlightToolCount = 0;
1391
+ let recoveryGaveUpOnInFlightTool = false;
1351
1392
  const MAX_REPEATED_INFLIGHT_TOOL_STALLS = 3;
1352
1393
  const continuationHistoryFragments: string[] = [];
1353
1394
  const structuredContinuationFragments: AgentChatStructuredMessage[] = [];
1354
1395
  let visibleContinuationPrefix: ContentPart[] = [];
1355
1396
  let lastAutoContinueReason: string | null = null;
1397
+ let lastRecoverableRunError: AgentAutoContinueErrorInfo | null = null;
1356
1398
  const attemptedRunIds: string[] = [];
1357
1399
  let authRecoveryAttempted = false;
1358
1400
  let continuationToolCallCounter = 0;
@@ -1364,6 +1406,12 @@ export function createAgentChatAdapter(
1364
1406
  lastAutoContinueReason
1365
1407
  ? `last_auto_continue_reason: ${lastAutoContinueReason}`
1366
1408
  : "",
1409
+ lastRecoverableRunError?.errorCode
1410
+ ? `last_recoverable_error_code: ${lastRecoverableRunError.errorCode}`
1411
+ : "",
1412
+ lastRecoverableRunError?.message
1413
+ ? `last_recoverable_error: ${lastRecoverableRunError.message}`
1414
+ : "",
1367
1415
  `stale_run_continuations: ${staleRunContinuationAttempts}`,
1368
1416
  `stalled_transient_continuations: ${stalledTransientContinuationAttempts}`,
1369
1417
  `empty_transient_continuations: ${emptyTransientContinuationAttempts}`,
@@ -1382,6 +1430,9 @@ export function createAgentChatAdapter(
1382
1430
  };
1383
1431
 
1384
1432
  const exhaustedRecoveryMessage = (reason?: string): string => {
1433
+ if (recoveryGaveUpOnInFlightTool) {
1434
+ return "The agent got stuck waiting for the same tool to finish, so I stopped the automatic retries. The tool did not report a completed result.";
1435
+ }
1385
1436
  if (recoveryGaveUpOnRepetition) {
1386
1437
  return "The agent got stuck repeating the same response without finishing, so I stopped the automatic retries. This often happens when it tries to re-type a large pasted file into one action — starting a new chat, or asking for a smaller first step, usually gets it unstuck.";
1387
1438
  }
@@ -1666,6 +1717,83 @@ export function createAgentChatAdapter(
1666
1717
  return false;
1667
1718
  };
1668
1719
 
1720
+ const reconnectBackgroundContinuationForRunTimeout =
1721
+ async function* (): AsyncGenerator<
1722
+ ChatModelRunResult,
1723
+ boolean,
1724
+ unknown
1725
+ > {
1726
+ if (!threadId || !runId) return false;
1727
+ const interruptedRunId = runId;
1728
+ const interruptedLastSeq = lastSeq;
1729
+ let lastActiveRunError: unknown = null;
1730
+ for (let attempt = 0; attempt < 3; attempt++) {
1731
+ if (attempt > 0) {
1732
+ await delay(500, abortSignal);
1733
+ }
1734
+ if (abortSignal.aborted) return true;
1735
+ try {
1736
+ const activeRes = await fetch(
1737
+ `${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`,
1738
+ { signal: abortSignal },
1739
+ );
1740
+ if (!activeRes.ok) {
1741
+ if (activeRes.status === 404) return false;
1742
+ lastActiveRunError = new Error(
1743
+ `Active run lookup failed: ${activeRes.status}`,
1744
+ );
1745
+ continue;
1746
+ }
1747
+ const active = await activeRes.json();
1748
+ if (!active?.active || !active.runId) return false;
1749
+ const activeRunId = String(active.runId);
1750
+ const dispatchMode =
1751
+ typeof active.dispatchMode === "string"
1752
+ ? active.dispatchMode
1753
+ : "";
1754
+ if (activeRunId === interruptedRunId) {
1755
+ if (dispatchMode.startsWith("background")) continue;
1756
+ return false;
1757
+ }
1758
+ const activeTurnId =
1759
+ typeof active.turnId === "string" ? active.turnId : "";
1760
+ const activeStatus =
1761
+ typeof active.status === "string" ? active.status : "";
1762
+ if (!dispatchMode.startsWith("background")) return false;
1763
+ if (activeTurnId && activeTurnId !== turnId) return false;
1764
+ if (activeStatus !== "running" && activeStatus !== "starting") {
1765
+ return false;
1766
+ }
1767
+ runId = activeRunId;
1768
+ if (!attemptedRunIds.includes(activeRunId)) {
1769
+ attemptedRunIds.push(activeRunId);
1770
+ }
1771
+ lastSeq = -1;
1772
+ setActiveRun({ threadId, runId: activeRunId, lastSeq: -1 });
1773
+ const reconnected = yield* reconnectCurrentRun();
1774
+ if (reconnected) return true;
1775
+ } catch (activeErr: unknown) {
1776
+ if (
1777
+ activeErr instanceof Error &&
1778
+ activeErr.name === "AbortError"
1779
+ ) {
1780
+ clearActiveRun();
1781
+ return true;
1782
+ }
1783
+ lastActiveRunError = activeErr;
1784
+ }
1785
+ }
1786
+ if (lastActiveRunError) {
1787
+ captureChatClientError(
1788
+ lastActiveRunError,
1789
+ "reconnect-background-continuation-failed",
1790
+ );
1791
+ }
1792
+ runId = interruptedRunId;
1793
+ lastSeq = interruptedLastSeq;
1794
+ return false;
1795
+ };
1796
+
1669
1797
  const visibleContentForContinuation = (): ContentPart[] => {
1670
1798
  return contentAfterContinuationPrefix(
1671
1799
  content,
@@ -1675,8 +1803,15 @@ export function createAgentChatAdapter(
1675
1803
 
1676
1804
  const prepareAutoContinuation = (
1677
1805
  signal: AgentAutoContinueSignal,
1678
- ): { ok: boolean; resetVisibleContent: boolean } => {
1806
+ ): {
1807
+ ok: boolean;
1808
+ resetVisibleContent: boolean;
1809
+ completedToolName?: string;
1810
+ } => {
1679
1811
  lastAutoContinueReason = signal.reason;
1812
+ if (signal.errorInfo) {
1813
+ lastRecoverableRunError = signal.errorInfo;
1814
+ }
1680
1815
  const isTransient = signal.reason !== "loop_limit";
1681
1816
  const visibleContent = visibleContentForContinuation();
1682
1817
  const currentPartialHistory =
@@ -1686,14 +1821,13 @@ export function createAgentChatAdapter(
1686
1821
  // whitespace-only output cannot keep the run alive indefinitely.
1687
1822
  const madeContentProgress = hasContinuationProgress(visibleContent);
1688
1823
  // An action was streamed but has not returned yet (a tool_start with
1689
- // no tool_done), or the activity trail shows the server was working
1690
- // on a tool. A run_timeout that fires in this window means the agent
1691
- // was actively making progress the server's foldAssistantTurn
1692
- // persisted the in-flight call — so it must NOT count against the
1693
- // stalled/empty continuation budgets.
1694
- const hasInFlightTool =
1695
- hasInFlightToolCall(visibleContent) ||
1696
- Boolean(lastActivityTool(signal.activityTrail));
1824
+ // no tool_done). This is durable enough to survive continuation: the
1825
+ // server already emitted a real tool call. A tool-scoped activity
1826
+ // card ("Preparing generate-design") is useful UI, but it happens
1827
+ // before tool_start; treating it as progress caused silent retry
1828
+ // loops when the LLM timed out while assembling a large tool input.
1829
+ const hasInFlightTool = hasInFlightToolCall(visibleContent);
1830
+ const completedSideEffectTool = lastCompletedSideEffectTool(content);
1697
1831
  // Either real output or an actively-running tool counts as progress
1698
1832
  // for the stalled/empty caps.
1699
1833
  const madeProgress = madeContentProgress || hasInFlightTool;
@@ -1708,11 +1842,11 @@ export function createAgentChatAdapter(
1708
1842
  // MAX_REPEATED_INFLIGHT_TOOL_STALLS consecutive stream_ended events,
1709
1843
  // bail with a clear message.
1710
1844
  //
1711
- // Only count stream_ended (connection drop / reconnect failed), NOT
1712
- // run_timeout (server legitimately still executing a slow tool). A
1713
- // run_timeout with an in-flight tool means the server is actively
1714
- // working and reconnection may still recover the result; a repeated
1715
- // stream_ended means the connection keeps breaking under that payload.
1845
+ // Count broken streams (connection drop / reconnect failed) and
1846
+ // no_progress stalls (the client aborts a stream that stayed open but
1847
+ // stopped producing events). Do NOT count run_timeout: with an
1848
+ // in-flight tool that means the server is still actively executing a
1849
+ // slow action and reconnection may recover the result.
1716
1850
  const currentInFlightToolPart = visibleContent.find(
1717
1851
  (p): p is Extract<ContentPart, { type: "tool-call" }> =>
1718
1852
  p.type === "tool-call" &&
@@ -1723,8 +1857,9 @@ export function createAgentChatAdapter(
1723
1857
  const currentInFlightToolSignature = currentInFlightToolPart
1724
1858
  ? inFlightToolInputSignature(currentInFlightToolPart)
1725
1859
  : undefined;
1726
- const isConnectionDrop = signal.reason === "stream_ended";
1727
- if (currentInFlightToolName && isConnectionDrop) {
1860
+ const isBrokenInFlightTool =
1861
+ signal.reason === "stream_ended" || signal.reason === "no_progress";
1862
+ if (currentInFlightToolName && isBrokenInFlightTool) {
1728
1863
  if (
1729
1864
  currentInFlightToolName === lastInFlightToolName &&
1730
1865
  currentInFlightToolSignature === lastInFlightToolSignature
@@ -1763,13 +1898,31 @@ export function createAgentChatAdapter(
1763
1898
  emptyTransientContinuationAttempts = 0;
1764
1899
  } else {
1765
1900
  totalTransientContinuationAttempts += 1;
1901
+ // If a mutating action already completed, do not turn a missing
1902
+ // closing sentence into a scary connection failure. Give the model
1903
+ // one continuation opportunity (the completed tool itself counts
1904
+ // as progress on the first timeout); if the follow-up produces no
1905
+ // new content, stop locally with a clear "saved, final note timed
1906
+ // out" message.
1907
+ if (
1908
+ completedSideEffectTool &&
1909
+ !hasInFlightToolCall(content) &&
1910
+ !madeContentProgress &&
1911
+ !hasInFlightTool
1912
+ ) {
1913
+ return {
1914
+ ok: false,
1915
+ resetVisibleContent: false,
1916
+ completedToolName: completedSideEffectTool.toolName,
1917
+ };
1918
+ }
1766
1919
  // Bail when the same write tool is stuck in-flight across too many
1767
1920
  // consecutive continuations. Checked before the text-repeat guard
1768
1921
  // because hasInFlightTool=true would mask the repeat as progress.
1769
1922
  if (
1770
1923
  repeatedInFlightToolCount >= MAX_REPEATED_INFLIGHT_TOOL_STALLS
1771
1924
  ) {
1772
- recoveryGaveUpOnRepetition = true;
1925
+ recoveryGaveUpOnInFlightTool = true;
1773
1926
  return { ok: false, resetVisibleContent: false };
1774
1927
  }
1775
1928
  // Bail fast on a non-advancing repetition loop, well before the
@@ -2166,6 +2319,11 @@ export function createAgentChatAdapter(
2166
2319
  if (err.reason === "no_progress") {
2167
2320
  await abortCurrentRun();
2168
2321
  }
2322
+ if (err.reason === "run_timeout" && !err.errorInfo) {
2323
+ const reconnected =
2324
+ yield* reconnectBackgroundContinuationForRunTimeout();
2325
+ if (reconnected) return;
2326
+ }
2169
2327
  if (err.reason === "stream_ended") {
2170
2328
  const reconnected = yield* reconnectCurrentRun();
2171
2329
  if (reconnected) return;
@@ -2174,15 +2332,53 @@ export function createAgentChatAdapter(
2174
2332
  }
2175
2333
  const continuation = prepareAutoContinuation(err);
2176
2334
  if (!continuation.ok) {
2177
- const message = exhaustedRecoveryMessage(err.reason);
2335
+ if (continuation.completedToolName) {
2336
+ const message = completedToolTimeoutMessage(
2337
+ continuation.completedToolName,
2338
+ );
2339
+ content.push({ type: "text", text: message });
2340
+ yield {
2341
+ content: [...content],
2342
+ status: {
2343
+ type: "complete" as const,
2344
+ reason: "stop" as const,
2345
+ },
2346
+ metadata: {
2347
+ custom: {
2348
+ ...(runId ? { runId } : {}),
2349
+ runWarning: {
2350
+ message,
2351
+ errorCode: "final_response_timeout_after_tool",
2352
+ recoverable: true,
2353
+ },
2354
+ },
2355
+ },
2356
+ };
2357
+ clearActiveRun();
2358
+ return;
2359
+ }
2360
+ const preservedError =
2361
+ err.errorInfo ?? lastRecoverableRunError ?? null;
2362
+ const message =
2363
+ preservedError?.message ??
2364
+ exhaustedRecoveryMessage(err.reason);
2365
+ const details = [
2366
+ preservedError?.details,
2367
+ connectionRecoveryDetails(),
2368
+ ]
2369
+ .filter(Boolean)
2370
+ .join("\n\n");
2371
+ const errorCode =
2372
+ preservedError?.errorCode ?? "connection_error";
2178
2373
  captureChatClientError(err, "auto-continuation-exhausted", {
2179
2374
  autoContinueReason: err.reason,
2375
+ ...(errorCode ? { errorCode } : {}),
2180
2376
  });
2181
2377
  const runError = {
2182
2378
  message,
2183
- details: connectionRecoveryDetails(),
2184
- errorCode: "connection_error",
2185
- recoverable: true,
2379
+ ...(details ? { details } : {}),
2380
+ errorCode,
2381
+ recoverable: preservedError?.recoverable ?? true,
2186
2382
  ...(runId ? { runId } : {}),
2187
2383
  };
2188
2384
  if (typeof window !== "undefined") {
@@ -2192,10 +2388,16 @@ export function createAgentChatAdapter(
2192
2388
  }),
2193
2389
  );
2194
2390
  }
2195
- settleInterruptedToolCalls(content);
2391
+ settleInterruptedToolCalls(content, undefined, {
2392
+ includeActivity: true,
2393
+ });
2196
2394
  content.push({
2197
2395
  type: "text",
2198
- text: `Something went wrong: ${message}`,
2396
+ text: formatChatErrorText(
2397
+ message,
2398
+ preservedError?.upgradeUrl,
2399
+ errorCode,
2400
+ ),
2199
2401
  });
2200
2402
  yield {
2201
2403
  content: [...content],
@@ -2327,6 +2529,31 @@ export function createAgentChatAdapter(
2327
2529
  new AgentAutoContinueSignal({ reason: "stream_ended" }),
2328
2530
  );
2329
2531
  if (!continuation.ok) {
2532
+ if (continuation.completedToolName) {
2533
+ const message = completedToolTimeoutMessage(
2534
+ continuation.completedToolName,
2535
+ );
2536
+ content.push({ type: "text", text: message });
2537
+ yield {
2538
+ content: [...content],
2539
+ status: {
2540
+ type: "complete" as const,
2541
+ reason: "stop" as const,
2542
+ },
2543
+ metadata: {
2544
+ custom: {
2545
+ ...(runId ? { runId } : {}),
2546
+ runWarning: {
2547
+ message,
2548
+ errorCode: "final_response_timeout_after_tool",
2549
+ recoverable: true,
2550
+ },
2551
+ },
2552
+ },
2553
+ };
2554
+ clearActiveRun();
2555
+ return;
2556
+ }
2330
2557
  const message = exhaustedRecoveryMessage("stream_ended");
2331
2558
  captureChatClientError(err, "recovery-exhausted");
2332
2559
  const runError = {
@@ -2343,7 +2570,9 @@ export function createAgentChatAdapter(
2343
2570
  }),
2344
2571
  );
2345
2572
  }
2346
- settleInterruptedToolCalls(content);
2573
+ settleInterruptedToolCalls(content, undefined, {
2574
+ includeActivity: true,
2575
+ });
2347
2576
  content.push({
2348
2577
  type: "text",
2349
2578
  text: `Something went wrong: ${message}`,
@@ -15,6 +15,7 @@ import {
15
15
  import { useQueryClient } from "@tanstack/react-query";
16
16
  import {
17
17
  useCallback,
18
+ type ComponentPropsWithoutRef,
18
19
  useEffect,
19
20
  useId,
20
21
  useMemo,
@@ -69,6 +70,10 @@ export interface ShareButtonProps {
69
70
  /** Where to render share links in the popover. Defaults to the bottom,
70
71
  * matching the historical Google-Docs-style share dialog. */
71
72
  shareUrlPlacement?: "top" | "bottom";
73
+ /** Whether to render copyable share URL fields. Defaults to true. */
74
+ showShareLinks?: boolean;
75
+ /** Whether to render the bottom Done button. Defaults to true. */
76
+ showDoneButton?: boolean;
72
77
  /** Optional placeholder shown in the share-URL slot when `shareUrl` is
73
78
  * undefined. Use this to explain *why* there's no link yet (e.g. "Publish
74
79
  * this form to get a public response link") instead of leaving the slot
@@ -99,6 +104,8 @@ export interface ShareButtonProps {
99
104
  generalAccessLabel?: ReactNode;
100
105
  /** Optional note rendered between general access and the copyable link. */
101
106
  accessNote?: ReactNode;
107
+ /** Optional host-rendered footer for compact app-specific share actions. */
108
+ shareFooterContent?: ReactNode;
102
109
  /** Optional Notion-style organization access control. When present, the
103
110
  * share panel exposes a "Hide in search" switch under Advanced for org
104
111
  * visibility. */
@@ -171,6 +178,8 @@ const BUTTON_GHOST_ICON = cn(
171
178
  );
172
179
  const SHARE_POPOVER_SURFACE =
173
180
  "border border-border bg-popover text-popover-foreground";
181
+ const SHARE_NESTED_OVERLAY_ATTR = "data-agent-native-share-overlay";
182
+ const SHARE_NESTED_OVERLAY_Z = "z-[100020]";
174
183
  const MEMBER_SUGGESTION_LIMIT = 25;
175
184
  const MEMBER_SEARCH_DEBOUNCE_MS = 140;
176
185
 
@@ -219,6 +228,28 @@ const ROLE_OPTIONS: Array<{ value: Role; label: string; description: string }> =
219
228
  },
220
229
  ];
221
230
 
231
+ type SharePopoverInteractOutsideEvent = Parameters<
232
+ NonNullable<
233
+ ComponentPropsWithoutRef<typeof PopoverContent>["onInteractOutside"]
234
+ >
235
+ >[0];
236
+
237
+ function isShareNestedOverlayTarget(target: EventTarget | null): boolean {
238
+ return (
239
+ target instanceof Element &&
240
+ target.closest(`[${SHARE_NESTED_OVERLAY_ATTR}]`) !== null
241
+ );
242
+ }
243
+
244
+ function handleSharePopoverInteractOutside(
245
+ event: SharePopoverInteractOutsideEvent,
246
+ ) {
247
+ const originalTarget = event.detail.originalEvent.target;
248
+ if (isShareNestedOverlayTarget(originalTarget)) {
249
+ event.preventDefault();
250
+ }
251
+ }
252
+
222
253
  /**
223
254
  * Framework share control. Renders a shadcn-outline-styled trigger that
224
255
  * opens a Google-Docs-style popover anchored beneath it. Uses Tailwind
@@ -344,12 +375,14 @@ export function ShareButton(props: ShareButtonProps) {
344
375
  <PopoverContent
345
376
  align="end"
346
377
  sideOffset={6}
378
+ data-agent-native-share-overlay=""
347
379
  className={cn(
348
380
  "z-[2000] w-[min(460px,92vw)] rounded-lg p-4 shadow-lg",
349
381
  SHARE_POPOVER_SURFACE,
350
382
  props.popoverClassName,
351
383
  )}
352
384
  onOpenAutoFocus={(e) => e.preventDefault()}
385
+ onInteractOutside={handleSharePopoverInteractOutside}
353
386
  >
354
387
  <SharePanel
355
388
  {...props}
@@ -622,9 +655,11 @@ function SharePanel(
622
655
  </>
623
656
  );
624
657
  const showShareLinks =
625
- Boolean(props.shareUrl) ||
626
- Boolean(props.shareUrlPlaceholder) ||
627
- Boolean(props.secondaryShareUrl);
658
+ (props.showShareLinks ?? true) &&
659
+ (Boolean(props.shareUrl) ||
660
+ Boolean(props.shareUrlPlaceholder) ||
661
+ Boolean(props.secondaryShareUrl));
662
+ const showDoneButton = props.showDoneButton ?? true;
628
663
  const shareUrlPlacement = props.shareUrlPlacement ?? "bottom";
629
664
  const extraTabs = props.shareTabs?.tabs ?? [];
630
665
  const hasTabs = extraTabs.length > 0;
@@ -821,11 +856,13 @@ function SharePanel(
821
856
  <div className="mb-4 h-7 rounded-md bg-muted animate-pulse" />
822
857
  <div className="mb-2 text-sm font-semibold">{generalAccessLabel}</div>
823
858
  <div className="mb-4 h-9 rounded-md bg-muted animate-pulse" />
824
- <div className="mt-2 flex justify-end">
825
- <button type="button" onClick={onClose} className={BUTTON_PRIMARY_SM}>
826
- Done
827
- </button>
828
- </div>
859
+ {showDoneButton ? (
860
+ <div className="mt-2 flex justify-end">
861
+ <button type="button" onClick={onClose} className={BUTTON_PRIMARY_SM}>
862
+ Done
863
+ </button>
864
+ </div>
865
+ ) : null}
829
866
  </div>
830
867
  ) : (
831
868
  <div>
@@ -990,15 +1027,19 @@ function SharePanel(
990
1027
 
991
1028
  {showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null}
992
1029
 
993
- <div className="mt-2 flex justify-end">
994
- <button
995
- type="button"
996
- onClick={handleDone}
997
- className={BUTTON_PRIMARY_SM}
998
- >
999
- Done
1000
- </button>
1001
- </div>
1030
+ {props.shareFooterContent}
1031
+
1032
+ {showDoneButton ? (
1033
+ <div className="mt-2 flex justify-end">
1034
+ <button
1035
+ type="button"
1036
+ onClick={handleDone}
1037
+ className={BUTTON_PRIMARY_SM}
1038
+ >
1039
+ Done
1040
+ </button>
1041
+ </div>
1042
+ ) : null}
1002
1043
  </div>
1003
1044
  );
1004
1045
 
@@ -1076,8 +1117,13 @@ function AdvancedAccessPopover({
1076
1117
  <PopoverContent
1077
1118
  align="start"
1078
1119
  sideOffset={6}
1120
+ data-agent-native-share-overlay=""
1079
1121
  onOpenAutoFocus={(event) => event.preventDefault()}
1080
- className={cn("z-[2300] w-72 p-3 shadow-lg", SHARE_POPOVER_SURFACE)}
1122
+ className={cn(
1123
+ SHARE_NESTED_OVERLAY_Z,
1124
+ "w-72 p-3 shadow-lg",
1125
+ SHARE_POPOVER_SURFACE,
1126
+ )}
1081
1127
  >
1082
1128
  <div className="space-y-3">
1083
1129
  <div>
@@ -1291,9 +1337,11 @@ function MemberAutocomplete({
1291
1337
  <PopoverContent
1292
1338
  align="start"
1293
1339
  sideOffset={4}
1340
+ data-agent-native-share-overlay=""
1294
1341
  onOpenAutoFocus={(event) => event.preventDefault()}
1295
1342
  className={cn(
1296
- "z-[2200] w-[var(--radix-popper-anchor-width)] min-w-[18rem] rounded-md p-1 shadow-lg",
1343
+ SHARE_NESTED_OVERLAY_Z,
1344
+ "w-[var(--radix-popper-anchor-width)] min-w-[18rem] rounded-md p-1 shadow-lg",
1297
1345
  SHARE_POPOVER_SURFACE,
1298
1346
  )}
1299
1347
  >
@@ -1441,8 +1489,7 @@ function CopyLinkField({
1441
1489
  // Radix Select wrappers styled like shadcn Select (no native <select> anywhere)
1442
1490
  // ---------------------------------------------------------------------------
1443
1491
 
1444
- const selectContentClass =
1445
- "z-[2100] min-w-[12rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0";
1492
+ const selectContentClass = `${SHARE_NESTED_OVERLAY_Z} min-w-[12rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0`;
1446
1493
  const selectItemClass =
1447
1494
  "relative flex w-full cursor-pointer select-none items-start gap-2 rounded-sm py-2 ps-8 pe-3 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
1448
1495
 
@@ -1517,6 +1564,7 @@ function RoleSelect(props: {
1517
1564
  </Select.Trigger>
1518
1565
  <Select.Portal>
1519
1566
  <Select.Content
1567
+ data-agent-native-share-overlay=""
1520
1568
  className={selectContentClass}
1521
1569
  position="popper"
1522
1570
  sideOffset={4}
@@ -1563,6 +1611,7 @@ function VisibilitySelect(props: {
1563
1611
  </Select.Trigger>
1564
1612
  <Select.Portal>
1565
1613
  <Select.Content
1614
+ data-agent-native-share-overlay=""
1566
1615
  className={selectContentClass}
1567
1616
  position="popper"
1568
1617
  sideOffset={4}