@agent-native/core 0.90.4 → 0.90.7

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 (189) hide show
  1. package/README.md +34 -7
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +21 -0
  4. package/corpus/core/docs/content/agent-native-toolkit.mdx +1 -1
  5. package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
  6. package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
  7. package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
  8. package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
  9. package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
  10. package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
  11. package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
  12. package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
  13. package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
  14. package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
  15. package/corpus/core/docs/content/toolkit-agent-ux.mdx +1 -1
  16. package/corpus/core/docs/content/toolkit-app-adapters.mdx +1 -1
  17. package/corpus/core/docs/content/toolkit-collaboration-ui.mdx +1 -1
  18. package/corpus/core/docs/content/toolkit-collaboration.mdx +1 -1
  19. package/corpus/core/docs/content/toolkit-command-navigation.mdx +2 -2
  20. package/corpus/core/docs/content/toolkit-comments-review.mdx +1 -1
  21. package/corpus/core/docs/content/toolkit-history.mdx +1 -1
  22. package/corpus/core/docs/content/toolkit-observability.mdx +1 -1
  23. package/corpus/core/docs/content/toolkit-org-team.mdx +1 -1
  24. package/corpus/core/docs/content/toolkit-resources.mdx +1 -1
  25. package/corpus/core/docs/content/toolkit-settings.mdx +1 -1
  26. package/corpus/core/docs/content/toolkit-setup-connections.mdx +1 -1
  27. package/corpus/core/docs/content/toolkit-sharing-ui.mdx +1 -1
  28. package/corpus/core/docs/content/toolkit-sharing.mdx +1 -1
  29. package/corpus/core/docs/content/toolkit-shell-hooks.mdx +1 -1
  30. package/corpus/core/docs/content/toolkit-ui.mdx +1 -1
  31. package/corpus/core/package.json +1 -1
  32. package/corpus/core/src/agent/durable-background.ts +18 -18
  33. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
  34. package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
  35. package/corpus/core/src/agent/production-agent.ts +22 -11
  36. package/corpus/core/src/agent/run-manager.ts +17 -6
  37. package/corpus/core/src/agent/run-store.ts +16 -11
  38. package/corpus/core/src/cli/design-connect.ts +16 -2
  39. package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
  40. package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
  41. package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
  42. package/corpus/core/src/client/use-db-sync.ts +99 -1
  43. package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
  44. package/corpus/core/src/collab/client.ts +49 -4
  45. package/corpus/core/src/deploy/build.ts +103 -0
  46. package/corpus/core/src/server/core-routes-plugin.ts +137 -8
  47. package/corpus/templates/clips/changelog/2026-07-07-faster-local-whisper-dictation-start.md +6 -0
  48. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +311 -31
  49. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +2 -0
  50. package/corpus/templates/content/AGENTS.md +1 -0
  51. package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
  52. package/corpus/templates/content/actions/_database-utils.ts +2 -1
  53. package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
  54. package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
  55. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
  56. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
  57. package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
  58. package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
  59. package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
  60. package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
  61. package/corpus/templates/content/app/i18n-data.ts +31 -0
  62. package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
  63. package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
  64. package/corpus/templates/content/parity/matrix.md +24 -24
  65. package/corpus/templates/content/parity/matrix.ts +2 -0
  66. package/corpus/templates/content/shared/api.ts +59 -4
  67. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +9 -0
  68. package/corpus/templates/design/actions/connect-localhost.ts +26 -3
  69. package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
  70. package/corpus/templates/design/actions/read-source-file.ts +28 -0
  71. package/corpus/templates/design/actions/update-file.ts +76 -7
  72. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +207 -11
  73. package/corpus/templates/design/app/components/design/EditPanel.tsx +4 -0
  74. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +387 -25
  75. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1110 -88
  76. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +368 -16
  77. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +24 -0
  78. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +27 -6
  79. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +18 -0
  80. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +5 -4
  81. package/corpus/templates/design/app/components/design/multi-screen/handle-hit-zones.ts +124 -0
  82. package/corpus/templates/design/app/components/design/multi-screen/types.ts +23 -0
  83. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  84. package/corpus/templates/design/app/i18n-data.ts +19 -0
  85. package/corpus/templates/design/changelog/2026-07-07-dragging-a-free-floating-element-into-a-layout-column-or-row.md +6 -0
  86. package/corpus/templates/design/changelog/2026-07-07-dragging-a-repeated-list-item-like-a-to-do-card-generated-fr.md +6 -0
  87. package/corpus/templates/design/changelog/2026-07-07-dragging-an-element-onto-a-button-or-chip-s-middle-no-longer.md +6 -0
  88. package/corpus/templates/design/changelog/2026-07-07-elements-dropped-into-another-board-shape-now-stay-exactly-w.md +6 -0
  89. package/corpus/templates/design/changelog/2026-07-07-fixed-drag-reordering-elements-within-a-screen-no-longer-sil.md +6 -0
  90. package/corpus/templates/design/changelog/2026-07-07-fixed-dragging-an-element-from-a-screen-onto-the-canvas-now-.md +6 -0
  91. package/corpus/templates/design/changelog/2026-07-07-fixed-dragging-elements-between-screens-into-ai-generated-co.md +6 -0
  92. package/corpus/templates/design/changelog/2026-07-07-fixed-moving-elements-between-screens-no-longer-bakes-intern.md +6 -0
  93. package/corpus/templates/design/changelog/2026-07-07-fixed-the-cross-screen-drop-indicator-now-points-at-the-corr.md +6 -0
  94. package/corpus/templates/design/changelog/2026-07-07-fixed-the-zoom-menu-no-longer-leaves-the-canvas-unresponsive.md +6 -0
  95. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
  96. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
  97. package/corpus/templates/design/changelog/2026-07-07-text-drawn-on-the-canvas-is-now-readable-white-instead-of-in.md +6 -0
  98. package/corpus/templates/design/server/source-workspace.ts +27 -3
  99. package/corpus/templates/design/shared/board-file.ts +359 -0
  100. package/corpus/templates/design/shared/breakpoint-media.ts +21 -3
  101. package/corpus/templates/design/shared/code-layer.ts +145 -10
  102. package/dist/agent/durable-background.d.ts +12 -12
  103. package/dist/agent/durable-background.js +18 -18
  104. package/dist/agent/durable-background.js.map +1 -1
  105. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  106. package/dist/agent/engine/ai-sdk-engine.js +8 -2
  107. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  108. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  109. package/dist/agent/engine/anthropic-engine.js +11 -1
  110. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  111. package/dist/agent/production-agent.d.ts +2 -2
  112. package/dist/agent/production-agent.d.ts.map +1 -1
  113. package/dist/agent/production-agent.js +18 -11
  114. package/dist/agent/production-agent.js.map +1 -1
  115. package/dist/agent/run-manager.d.ts +7 -1
  116. package/dist/agent/run-manager.d.ts.map +1 -1
  117. package/dist/agent/run-manager.js +6 -1
  118. package/dist/agent/run-manager.js.map +1 -1
  119. package/dist/agent/run-store.d.ts +1 -1
  120. package/dist/agent/run-store.d.ts.map +1 -1
  121. package/dist/agent/run-store.js +15 -10
  122. package/dist/agent/run-store.js.map +1 -1
  123. package/dist/cli/design-connect.d.ts.map +1 -1
  124. package/dist/cli/design-connect.js +12 -2
  125. package/dist/cli/design-connect.js.map +1 -1
  126. package/dist/client/KeepTabOpenNotice.d.ts +9 -10
  127. package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
  128. package/dist/client/KeepTabOpenNotice.js +8 -4
  129. package/dist/client/KeepTabOpenNotice.js.map +1 -1
  130. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  131. package/dist/client/RunStuckBanner.js +14 -13
  132. package/dist/client/RunStuckBanner.js.map +1 -1
  133. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  134. package/dist/client/agent-chat-adapter.js +25 -17
  135. package/dist/client/agent-chat-adapter.js.map +1 -1
  136. package/dist/client/use-db-sync.d.ts +18 -0
  137. package/dist/client/use-db-sync.d.ts.map +1 -1
  138. package/dist/client/use-db-sync.js +88 -1
  139. package/dist/client/use-db-sync.js.map +1 -1
  140. package/dist/client/use-run-stuck-detection.d.ts +1 -1
  141. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  142. package/dist/client/use-run-stuck-detection.js +6 -4
  143. package/dist/client/use-run-stuck-detection.js.map +1 -1
  144. package/dist/collab/client.d.ts.map +1 -1
  145. package/dist/collab/client.js +43 -4
  146. package/dist/collab/client.js.map +1 -1
  147. package/dist/collab/routes.d.ts +1 -1
  148. package/dist/collab/struct-routes.d.ts +1 -1
  149. package/dist/deploy/build.d.ts +1 -0
  150. package/dist/deploy/build.d.ts.map +1 -1
  151. package/dist/deploy/build.js +57 -0
  152. package/dist/deploy/build.js.map +1 -1
  153. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  154. package/dist/notifications/routes.d.ts +2 -2
  155. package/dist/resources/handlers.d.ts +3 -3
  156. package/dist/secrets/routes.d.ts +9 -9
  157. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  158. package/dist/server/core-routes-plugin.d.ts +10 -0
  159. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  160. package/dist/server/core-routes-plugin.js +103 -9
  161. package/dist/server/core-routes-plugin.js.map +1 -1
  162. package/docs/content/agent-native-toolkit.mdx +1 -1
  163. package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
  164. package/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
  165. package/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
  166. package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
  167. package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
  168. package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
  169. package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
  170. package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
  171. package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
  172. package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
  173. package/docs/content/toolkit-agent-ux.mdx +1 -1
  174. package/docs/content/toolkit-app-adapters.mdx +1 -1
  175. package/docs/content/toolkit-collaboration-ui.mdx +1 -1
  176. package/docs/content/toolkit-collaboration.mdx +1 -1
  177. package/docs/content/toolkit-command-navigation.mdx +2 -2
  178. package/docs/content/toolkit-comments-review.mdx +1 -1
  179. package/docs/content/toolkit-history.mdx +1 -1
  180. package/docs/content/toolkit-observability.mdx +1 -1
  181. package/docs/content/toolkit-org-team.mdx +1 -1
  182. package/docs/content/toolkit-resources.mdx +1 -1
  183. package/docs/content/toolkit-settings.mdx +1 -1
  184. package/docs/content/toolkit-setup-connections.mdx +1 -1
  185. package/docs/content/toolkit-sharing-ui.mdx +1 -1
  186. package/docs/content/toolkit-sharing.mdx +1 -1
  187. package/docs/content/toolkit-shell-hooks.mdx +1 -1
  188. package/docs/content/toolkit-ui.mdx +1 -1
  189. package/package.json +1 -1
@@ -2,23 +2,22 @@
2
2
  * before the hosted ~40s soft-timeout chunk boundary. */
3
3
  export declare const DEFAULT_KEEP_TAB_OPEN_AFTER_MS = 30000;
4
4
  /**
5
- * Subtle, non-blocking notice that a long-running FOREGROUND agent turn
6
- * depends on this tab staying open.
5
+ * Subtle, non-blocking notice that a long-running client-continued foreground
6
+ * agent turn depends on this tab staying open.
7
7
  *
8
8
  * On hosted deployments a foreground turn runs in ~40s chunks and the CLIENT
9
- * re-POSTs `auto_continue` to start each next chunk — close the tab
10
- * mid-turn and the work silently stops at the next chunk boundary. Only
11
- * server-driven runs (`dispatchMode` starting with "background": the durable
12
- * background worker, or a server self-chained continuation) survive a closed
13
- * tab, and for those this notice never shows.
9
+ * may need to re-POST `auto_continue` to start each next chunk. Server-owned
10
+ * runs (`dispatchMode` starting with "background" or equal to
11
+ * "foreground-self-chain") survive a closed tab, and for those this notice
12
+ * never shows.
14
13
  *
15
14
  * Visibility rules (show only while the condition is true — no new
16
15
  * always-visible chrome):
17
- * - a foreground-dispatched run for this thread has been continuously
16
+ * - a client-continued foreground run for this thread has been continuously
18
17
  * running for `showAfterMs` (default 30s — approaching the hosted ~40s
19
18
  * chunk boundary where the client-driven continuation starts), and
20
- * - the run is NOT background-dispatched (hidden immediately when the
21
- * server owns recovery), and
19
+ * - the run is not server-continued (hidden immediately when the server owns
20
+ * recovery), and
22
21
  * - this is a production client bundle (`hosted` auto-detect): local dev
23
22
  * runs a turn unbounded in a single chunk that survives a closed tab,
24
23
  * so the notice would be wrong there.
@@ -1 +1 @@
1
- {"version":3,"file":"KeepTabOpenNotice.d.ts","sourceRoot":"","sources":["../../src/client/KeepTabOpenNotice.tsx"],"names":[],"mappings":"AAMA;0DAC0D;AAC1D,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqBD,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,OAAc,EACd,MAAM,EACN,WAA4C,EAC5C,MAAM,EACN,SAAS,GACV,EAAE,sBAAsB,sCAwFxB"}
1
+ {"version":3,"file":"KeepTabOpenNotice.d.ts","sourceRoot":"","sources":["../../src/client/KeepTabOpenNotice.tsx"],"names":[],"mappings":"AAMA;0DAC0D;AAC1D,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4BD,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,OAAc,EACd,MAAM,EACN,WAA4C,EAC5C,MAAM,EACN,SAAS,GACV,EAAE,sBAAsB,sCAuFxB"}
@@ -21,6 +21,10 @@ function isDevClientBundle() {
21
21
  return false;
22
22
  }
23
23
  }
24
+ function isServerContinuedDispatch(dispatchMode) {
25
+ return (dispatchMode === "foreground-self-chain" ||
26
+ dispatchMode?.startsWith("background") === true);
27
+ }
24
28
  export function KeepTabOpenNotice({ threadId, enabled = true, apiUrl, showAfterMs = DEFAULT_KEEP_TAB_OPEN_AFTER_MS, hosted, className, }) {
25
29
  const effectiveHosted = hosted ?? !isDevClientBundle();
26
30
  const state = useRunStuckDetection({
@@ -29,8 +33,8 @@ export function KeepTabOpenNotice({ threadId, enabled = true, apiUrl, showAfterM
29
33
  apiUrl,
30
34
  pollIntervalMs: NOTICE_POLL_INTERVAL_MS,
31
35
  });
32
- const isBackgroundDispatch = state.dispatchMode?.startsWith("background") === true;
33
- const foregroundRunning = state.status === "running" && Boolean(state.runId) && !isBackgroundDispatch;
36
+ const isServerContinued = isServerContinuedDispatch(state.dispatchMode);
37
+ const foregroundRunning = state.status === "running" && Boolean(state.runId) && !isServerContinued;
34
38
  const [visible, setVisible] = useState(false);
35
39
  const runningSinceRef = useRef(null);
36
40
  // Reset tracking whenever the monitored thread changes so a long run on
@@ -40,7 +44,7 @@ export function KeepTabOpenNotice({ threadId, enabled = true, apiUrl, showAfterM
40
44
  setVisible(false);
41
45
  }, [threadId]);
42
46
  useEffect(() => {
43
- if (!effectiveHosted || isBackgroundDispatch) {
47
+ if (!effectiveHosted || isServerContinued) {
44
48
  // Server-owned turn (or non-hosted client): the tab is not load-bearing.
45
49
  // Hide immediately and reset — no linger.
46
50
  runningSinceRef.current = null;
@@ -74,7 +78,7 @@ export function KeepTabOpenNotice({ threadId, enabled = true, apiUrl, showAfterM
74
78
  }, [
75
79
  effectiveHosted,
76
80
  foregroundRunning,
77
- isBackgroundDispatch,
81
+ isServerContinued,
78
82
  showAfterMs,
79
83
  visible,
80
84
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"KeepTabOpenNotice.js","sourceRoot":"","sources":["../../src/client/KeepTabOpenNotice.tsx"],"names":[],"mappings":"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","sourcesContent":["import { IconInfoCircle } from \"@tabler/icons-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { useRunStuckDetection } from \"./use-run-stuck-detection.js\";\nimport { cn } from \"./utils.js\";\n\n/** Continuous foreground running time before the notice appears — just\n * before the hosted ~40s soft-timeout chunk boundary. */\nexport const DEFAULT_KEEP_TAB_OPEN_AFTER_MS = 30_000;\n\n/**\n * Subtle, non-blocking notice that a long-running FOREGROUND agent turn\n * depends on this tab staying open.\n *\n * On hosted deployments a foreground turn runs in ~40s chunks and the CLIENT\n * re-POSTs `auto_continue` to start each next chunk — close the tab\n * mid-turn and the work silently stops at the next chunk boundary. Only\n * server-driven runs (`dispatchMode` starting with \"background\": the durable\n * background worker, or a server self-chained continuation) survive a closed\n * tab, and for those this notice never shows.\n *\n * Visibility rules (show only while the condition is true — no new\n * always-visible chrome):\n * - a foreground-dispatched run for this thread has been continuously\n * running for `showAfterMs` (default 30s — approaching the hosted ~40s\n * chunk boundary where the client-driven continuation starts), and\n * - the run is NOT background-dispatched (hidden immediately when the\n * server owns recovery), and\n * - this is a production client bundle (`hosted` auto-detect): local dev\n * runs a turn unbounded in a single chunk that survives a closed tab,\n * so the notice would be wrong there.\n * Brief non-running blips (the sub-second gap between continuation chunks)\n * are debounced so the notice does not flicker across chunk boundaries.\n */\nexport interface KeepTabOpenNoticeProps {\n /** The thread to monitor. Pass null/undefined to disable. */\n threadId: string | null | undefined;\n /**\n * Set false to skip polling entirely — used when this notice is mounted\n * for a background tab kept alive via display:none. Only the active tab\n * should poll `/runs/active`. Defaults to true.\n */\n enabled?: boolean;\n /** API base path. Default `/_agent-native/agent-chat`. */\n apiUrl?: string;\n /**\n * How long a foreground run must have been continuously running before\n * the notice appears. Default 30s — just before the hosted ~40s\n * soft-timeout chunk boundary, so short turns never see it.\n */\n showAfterMs?: number;\n /**\n * Whether this client is talking to a hosted (serverless) deployment\n * whose foreground turns run in client-driven chunks. Defaults to\n * auto-detection from the bundle mode (a dev bundle → not hosted).\n */\n hosted?: boolean;\n /** Extra class on the outer container. */\n className?: string;\n}\n\n/** How long the notice lingers through a non-running poll result before\n * hiding — covers the brief idle gap between continuation chunks. */\nconst IDLE_LINGER_MS = 12_000;\n\n/** Poll interval for the notice's `/runs/active` loop. Deliberately slower\n * than the stuck-detector's default — the notice is passive, so freshness\n * matters less than keeping the extra polling cheap. */\nconst NOTICE_POLL_INTERVAL_MS = 8_000;\n\nfunction isDevClientBundle(): boolean {\n try {\n return (\n (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV === true\n );\n } catch {\n return false;\n }\n}\n\nexport function KeepTabOpenNotice({\n threadId,\n enabled = true,\n apiUrl,\n showAfterMs = DEFAULT_KEEP_TAB_OPEN_AFTER_MS,\n hosted,\n className,\n}: KeepTabOpenNoticeProps) {\n const effectiveHosted = hosted ?? !isDevClientBundle();\n const state = useRunStuckDetection({\n threadId,\n enabled: enabled && effectiveHosted,\n apiUrl,\n pollIntervalMs: NOTICE_POLL_INTERVAL_MS,\n });\n const isBackgroundDispatch =\n state.dispatchMode?.startsWith(\"background\") === true;\n const foregroundRunning =\n state.status === \"running\" && Boolean(state.runId) && !isBackgroundDispatch;\n\n const [visible, setVisible] = useState(false);\n const runningSinceRef = useRef<number | null>(null);\n\n // Reset tracking whenever the monitored thread changes so a long run on\n // the previous thread cannot leak the notice onto the new one.\n useEffect(() => {\n runningSinceRef.current = null;\n setVisible(false);\n }, [threadId]);\n\n useEffect(() => {\n if (!effectiveHosted || isBackgroundDispatch) {\n // Server-owned turn (or non-hosted client): the tab is not load-bearing.\n // Hide immediately and reset — no linger.\n runningSinceRef.current = null;\n setVisible(false);\n return;\n }\n if (foregroundRunning) {\n if (runningSinceRef.current == null) {\n runningSinceRef.current = Date.now();\n }\n const elapsed = Date.now() - runningSinceRef.current;\n if (elapsed >= showAfterMs) {\n setVisible(true);\n return;\n }\n const timer = setTimeout(() => setVisible(true), showAfterMs - elapsed);\n return () => clearTimeout(timer);\n }\n if (!visible) {\n runningSinceRef.current = null;\n return;\n }\n // The run left \"running\" while the notice is up. Linger briefly — the\n // gap between continuation chunks is sub-second, and hiding/re-showing\n // across every boundary would flicker — then reset.\n const timer = setTimeout(() => {\n runningSinceRef.current = null;\n setVisible(false);\n }, IDLE_LINGER_MS);\n return () => clearTimeout(timer);\n }, [\n effectiveHosted,\n foregroundRunning,\n isBackgroundDispatch,\n showAfterMs,\n visible,\n ]);\n\n if (!visible) return null;\n\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n \"mx-3 mt-2 flex items-start gap-2 rounded-md border border-border/70 bg-muted/40 px-3 py-2 text-xs text-muted-foreground\",\n className,\n )}\n >\n <IconInfoCircle\n size={14}\n className=\"mt-0.5 shrink-0 opacity-70\"\n aria-hidden=\"true\"\n />\n <span className=\"min-w-0 leading-snug\">\n <span className=\"font-medium text-foreground/80\">\n Keep this tab open.\n </span>{\" \"}\n This task continues from your browser — closing or leaving this tab will\n pause it until you come back.\n </span>\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"KeepTabOpenNotice.js","sourceRoot":"","sources":["../../src/client/KeepTabOpenNotice.tsx"],"names":[],"mappings":"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","sourcesContent":["import { IconInfoCircle } from \"@tabler/icons-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { useRunStuckDetection } from \"./use-run-stuck-detection.js\";\nimport { cn } from \"./utils.js\";\n\n/** Continuous foreground running time before the notice appears — just\n * before the hosted ~40s soft-timeout chunk boundary. */\nexport const DEFAULT_KEEP_TAB_OPEN_AFTER_MS = 30_000;\n\n/**\n * Subtle, non-blocking notice that a long-running client-continued foreground\n * agent turn depends on this tab staying open.\n *\n * On hosted deployments a foreground turn runs in ~40s chunks and the CLIENT\n * may need to re-POST `auto_continue` to start each next chunk. Server-owned\n * runs (`dispatchMode` starting with \"background\" or equal to\n * \"foreground-self-chain\") survive a closed tab, and for those this notice\n * never shows.\n *\n * Visibility rules (show only while the condition is true — no new\n * always-visible chrome):\n * - a client-continued foreground run for this thread has been continuously\n * running for `showAfterMs` (default 30s — approaching the hosted ~40s\n * chunk boundary where the client-driven continuation starts), and\n * - the run is not server-continued (hidden immediately when the server owns\n * recovery), and\n * - this is a production client bundle (`hosted` auto-detect): local dev\n * runs a turn unbounded in a single chunk that survives a closed tab,\n * so the notice would be wrong there.\n * Brief non-running blips (the sub-second gap between continuation chunks)\n * are debounced so the notice does not flicker across chunk boundaries.\n */\nexport interface KeepTabOpenNoticeProps {\n /** The thread to monitor. Pass null/undefined to disable. */\n threadId: string | null | undefined;\n /**\n * Set false to skip polling entirely — used when this notice is mounted\n * for a background tab kept alive via display:none. Only the active tab\n * should poll `/runs/active`. Defaults to true.\n */\n enabled?: boolean;\n /** API base path. Default `/_agent-native/agent-chat`. */\n apiUrl?: string;\n /**\n * How long a foreground run must have been continuously running before\n * the notice appears. Default 30s — just before the hosted ~40s\n * soft-timeout chunk boundary, so short turns never see it.\n */\n showAfterMs?: number;\n /**\n * Whether this client is talking to a hosted (serverless) deployment\n * whose foreground turns run in client-driven chunks. Defaults to\n * auto-detection from the bundle mode (a dev bundle → not hosted).\n */\n hosted?: boolean;\n /** Extra class on the outer container. */\n className?: string;\n}\n\n/** How long the notice lingers through a non-running poll result before\n * hiding — covers the brief idle gap between continuation chunks. */\nconst IDLE_LINGER_MS = 12_000;\n\n/** Poll interval for the notice's `/runs/active` loop. Deliberately slower\n * than the stuck-detector's default — the notice is passive, so freshness\n * matters less than keeping the extra polling cheap. */\nconst NOTICE_POLL_INTERVAL_MS = 8_000;\n\nfunction isDevClientBundle(): boolean {\n try {\n return (\n (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV === true\n );\n } catch {\n return false;\n }\n}\n\nfunction isServerContinuedDispatch(dispatchMode: string | null): boolean {\n return (\n dispatchMode === \"foreground-self-chain\" ||\n dispatchMode?.startsWith(\"background\") === true\n );\n}\n\nexport function KeepTabOpenNotice({\n threadId,\n enabled = true,\n apiUrl,\n showAfterMs = DEFAULT_KEEP_TAB_OPEN_AFTER_MS,\n hosted,\n className,\n}: KeepTabOpenNoticeProps) {\n const effectiveHosted = hosted ?? !isDevClientBundle();\n const state = useRunStuckDetection({\n threadId,\n enabled: enabled && effectiveHosted,\n apiUrl,\n pollIntervalMs: NOTICE_POLL_INTERVAL_MS,\n });\n const isServerContinued = isServerContinuedDispatch(state.dispatchMode);\n const foregroundRunning =\n state.status === \"running\" && Boolean(state.runId) && !isServerContinued;\n\n const [visible, setVisible] = useState(false);\n const runningSinceRef = useRef<number | null>(null);\n\n // Reset tracking whenever the monitored thread changes so a long run on\n // the previous thread cannot leak the notice onto the new one.\n useEffect(() => {\n runningSinceRef.current = null;\n setVisible(false);\n }, [threadId]);\n\n useEffect(() => {\n if (!effectiveHosted || isServerContinued) {\n // Server-owned turn (or non-hosted client): the tab is not load-bearing.\n // Hide immediately and reset — no linger.\n runningSinceRef.current = null;\n setVisible(false);\n return;\n }\n if (foregroundRunning) {\n if (runningSinceRef.current == null) {\n runningSinceRef.current = Date.now();\n }\n const elapsed = Date.now() - runningSinceRef.current;\n if (elapsed >= showAfterMs) {\n setVisible(true);\n return;\n }\n const timer = setTimeout(() => setVisible(true), showAfterMs - elapsed);\n return () => clearTimeout(timer);\n }\n if (!visible) {\n runningSinceRef.current = null;\n return;\n }\n // The run left \"running\" while the notice is up. Linger briefly — the\n // gap between continuation chunks is sub-second, and hiding/re-showing\n // across every boundary would flicker — then reset.\n const timer = setTimeout(() => {\n runningSinceRef.current = null;\n setVisible(false);\n }, IDLE_LINGER_MS);\n return () => clearTimeout(timer);\n }, [\n effectiveHosted,\n foregroundRunning,\n isServerContinued,\n showAfterMs,\n visible,\n ]);\n\n if (!visible) return null;\n\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n \"mx-3 mt-2 flex items-start gap-2 rounded-md border border-border/70 bg-muted/40 px-3 py-2 text-xs text-muted-foreground\",\n className,\n )}\n >\n <IconInfoCircle\n size={14}\n className=\"mt-0.5 shrink-0 opacity-70\"\n aria-hidden=\"true\"\n />\n <span className=\"min-w-0 leading-snug\">\n <span className=\"font-medium text-foreground/80\">\n Keep this tab open.\n </span>{\" \"}\n This task continues from your browser — closing or leaving this tab will\n pause it until you come back.\n </span>\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"RunStuckBanner.d.ts","sourceRoot":"","sources":["../../src/client/RunStuckBanner.tsx"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAGtC;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACpD;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2FD,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,OAAc,EACd,MAAM,EACN,gBAAgB,EAChB,OAAO,EACP,kBAAkB,EAClB,SAAiB,EACjB,gBAAgB,EAChB,SAAS,GACV,EAAE,mBAAmB,sCAoNrB"}
1
+ {"version":3,"file":"RunStuckBanner.d.ts","sourceRoot":"","sources":["../../src/client/RunStuckBanner.tsx"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AAGtC;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACpD;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA2FD,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,OAAc,EACd,MAAM,EACN,gBAAgB,EAChB,OAAO,EACP,kBAAkB,EAClB,SAAiB,EACjB,gBAAgB,EAChB,SAAS,GACV,EAAE,mBAAmB,sCAqNrB"}
@@ -77,15 +77,16 @@ export function RunStuckBanner({ threadId, enabled = true, apiUrl, stuckThreshol
77
77
  }
78
78
  const ownerId = autoRetryOwnerId ?? generatedOwnerIdRef.current;
79
79
  const backgroundWorkerStillAlive = isFreshBackgroundWorker(state);
80
- // Background-dispatched runs are recovered by the SERVER (chained
81
- // continuation chunks + lost-handoff sweep); an automatic client abort
82
- // would kill a live server-chained run. Auto-retry is therefore disabled
83
- // unconditionally for ANY background dispatch modenot just a
84
- // fresh-heartbeat worker — and the localStorage/Web-Locks auto-retry claim
85
- // below is never taken for these runs (the adapter's follow loop is
86
- // read-only, so multiple tabs need no retry dedup). Only the manual banner
87
- // remains, on the wider background threshold from useRunStuckDetection.
88
- const isBackgroundDispatch = state.dispatchMode?.startsWith("background") === true;
80
+ // Server-continued runs are recovered by the SERVER (chained continuation
81
+ // chunks + lost-handoff sweep); an automatic client abort would kill a live
82
+ // server-chained run. Auto-retry is therefore disabled unconditionally for
83
+ // these modes not just a fresh-heartbeat worker and the
84
+ // localStorage/Web-Locks auto-retry claim below is never taken for them (the
85
+ // adapter's follow loop is read-only, so multiple tabs need no retry dedup).
86
+ // Only the manual banner remains, on the wider server-owned threshold from
87
+ // useRunStuckDetection.
88
+ const isServerContinuedDispatch = state.dispatchMode === "foreground-self-chain" ||
89
+ state.dispatchMode?.startsWith("background") === true;
89
90
  const lastReportedRef = useRef({ isStuck: false, runId: null });
90
91
  useEffect(() => {
91
92
  const last = lastReportedRef.current;
@@ -121,9 +122,9 @@ export function RunStuckBanner({ threadId, enabled = true, apiUrl, stuckThreshol
121
122
  }, [state.runId, state.status]);
122
123
  useEffect(() => {
123
124
  if (!autoRetry ||
124
- // Server owns recovery for background dispatch modes — never auto-abort
125
- // (see comment on isBackgroundDispatch above).
126
- isBackgroundDispatch ||
125
+ // Server owns recovery for these dispatch modes — never auto-abort (see
126
+ // comment on isServerContinuedDispatch above).
127
+ isServerContinuedDispatch ||
127
128
  backgroundWorkerStillAlive ||
128
129
  !state.isStuck ||
129
130
  !state.runId ||
@@ -157,7 +158,7 @@ export function RunStuckBanner({ threadId, enabled = true, apiUrl, stuckThreshol
157
158
  autoRetry,
158
159
  backgroundWorkerStillAlive,
159
160
  busy,
160
- isBackgroundDispatch,
161
+ isServerContinuedDispatch,
161
162
  onRetry,
162
163
  ownerId,
163
164
  state.isStuck,
@@ -1 +1 @@
1
- {"version":3,"file":"RunStuckBanner.js","sourceRoot":"","sources":["../../src/client/RunStuckBanner.tsx"],"names":[],"mappings":"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","sourcesContent":["import { IconAlertTriangle, IconLoader2 } from \"@tabler/icons-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { trackEvent } from \"./analytics.js\";\nimport {\n useRunStuckDetection,\n useAbortRun,\n type RunStuckState,\n} from \"./use-run-stuck-detection.js\";\nimport { cn } from \"./utils.js\";\n\n/**\n * Surface a user-visible affordance when a chat run hasn't emitted any\n * events for an unusually long time. The adapter's silent reconnect logic\n * keeps trying in the background; this banner is the fallback when those\n * attempts haven't restored progress and the user is staring at a frozen\n * spinner.\n */\nexport interface RunStuckBannerProps {\n /** The thread to monitor. Pass null/undefined to disable. */\n threadId: string | null | undefined;\n /**\n * Set false to skip polling entirely — used when this banner is mounted\n * for a background tab kept alive via display:none. Only the active tab\n * should poll `/runs/active`. Defaults to true.\n */\n enabled?: boolean;\n /** API base path. Default `/_agent-native/agent-chat`. */\n apiUrl?: string;\n /**\n * Threshold above which an in-flight run is considered stuck.\n * Defaults to 90s (after the adapter's 75s no-progress reconnect\n * has had a chance to recover).\n */\n stuckThresholdMs?: number;\n /**\n * Called when the user clicks Retry. Implementations should re-prompt\n * the agent (typically via `chatHandle.sendMessage(...)`) — the banner\n * itself only handles aborting the prior run.\n */\n onRetry?: (runId: string) => void;\n /**\n * Called whenever the stuck state transitions. Useful for surfacing\n * observability events (Sentry, PostHog) at the call site.\n */\n onStuckStateChange?: (state: RunStuckState) => void;\n /**\n * Automatically abort and retry once when the server reports a run is\n * alive but has stopped making real progress. Manual controls remain visible\n * as the fallback if the automatic recovery cannot clear the run.\n */\n autoRetry?: boolean;\n /**\n * Stable browser-tab/window id used to coordinate automatic retries across\n * multiple mounted chat views for the same thread. A local id is generated\n * when omitted.\n */\n autoRetryOwnerId?: string;\n /** Extra class on the outer container. */\n className?: string;\n}\n\nconst AUTO_RETRY_CLAIM_TTL_MS = 5 * 60 * 1000;\nconst BACKGROUND_WORKER_FRESH_HEARTBEAT_MS = 30_000;\n\ntype BusyState = { type: \"none\" } | { type: \"cancel\" | \"retry\"; runId: string };\n\ntype MaybeLockManager = {\n request<T>(\n name: string,\n options: { mode?: \"exclusive\" | \"shared\"; ifAvailable?: boolean },\n callback: (lock: unknown) => T | Promise<T>,\n ): Promise<T>;\n};\n\nfunction createAutoRetryOwnerId() {\n const cryptoApi =\n typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto\n : null;\n return cryptoApi?.randomUUID() ?? `owner-${Math.random().toString(36)}`;\n}\n\nfunction autoRetryClaimKey(threadId: string, runId: string) {\n return `agent-native:stuck-auto-retry:${threadId}:${runId}`;\n}\n\nfunction isFreshBackgroundWorker(state: RunStuckState): boolean {\n return Boolean(\n state.status === \"running\" &&\n state.dispatchMode === \"background-processing\" &&\n state.heartbeatSinceMs != null &&\n state.heartbeatSinceMs >= 0 &&\n state.heartbeatSinceMs < BACKGROUND_WORKER_FRESH_HEARTBEAT_MS,\n );\n}\n\nfunction markAutoRetryClaim(key: string, ownerId: string) {\n if (typeof window === \"undefined\") return true;\n const now = Date.now();\n try {\n const raw = window.localStorage.getItem(key);\n if (raw) {\n const existing = JSON.parse(raw) as {\n ownerId?: unknown;\n expiresAt?: unknown;\n };\n const expiresAt =\n typeof existing.expiresAt === \"number\" ? existing.expiresAt : 0;\n if (expiresAt > now && existing.ownerId !== ownerId) return false;\n }\n window.localStorage.setItem(\n key,\n JSON.stringify({ ownerId, expiresAt: now + AUTO_RETRY_CLAIM_TTL_MS }),\n );\n const confirmed = JSON.parse(window.localStorage.getItem(key) ?? \"{}\") as {\n ownerId?: unknown;\n };\n return confirmed.ownerId === ownerId;\n } catch {\n // Private browsing or disabled storage: fall back to in-tab retry. The\n // server abort is idempotent, and manual controls remain available.\n return true;\n }\n}\n\nasync function claimAutoRetryAttempt(\n threadId: string | null | undefined,\n runId: string,\n ownerId: string,\n) {\n if (!threadId) return true;\n const key = autoRetryClaimKey(threadId, runId);\n const locks =\n typeof navigator !== \"undefined\"\n ? (navigator as Navigator & { locks?: MaybeLockManager }).locks\n : undefined;\n if (locks?.request) {\n try {\n return await locks.request(\n key,\n { mode: \"exclusive\", ifAvailable: true },\n (lock) => (lock ? markAutoRetryClaim(key, ownerId) : false),\n );\n } catch {\n return markAutoRetryClaim(key, ownerId);\n }\n }\n return markAutoRetryClaim(key, ownerId);\n}\n\nexport function RunStuckBanner({\n threadId,\n enabled = true,\n apiUrl,\n stuckThresholdMs,\n onRetry,\n onStuckStateChange,\n autoRetry = false,\n autoRetryOwnerId,\n className,\n}: RunStuckBannerProps) {\n const state = useRunStuckDetection({\n threadId,\n enabled,\n stuckThresholdMs,\n apiUrl,\n });\n const abortRun = useAbortRun(apiUrl);\n const [busy, setBusy] = useState<BusyState>({ type: \"none\" });\n const autoRetriedRunIdsRef = useRef<Set<string>>(new Set());\n const generatedOwnerIdRef = useRef<string | null>(null);\n if (!generatedOwnerIdRef.current) {\n generatedOwnerIdRef.current = createAutoRetryOwnerId();\n }\n const ownerId = autoRetryOwnerId ?? generatedOwnerIdRef.current;\n const backgroundWorkerStillAlive = isFreshBackgroundWorker(state);\n // Background-dispatched runs are recovered by the SERVER (chained\n // continuation chunks + lost-handoff sweep); an automatic client abort\n // would kill a live server-chained run. Auto-retry is therefore disabled\n // unconditionally for ANY background dispatch mode — not just a\n // fresh-heartbeat worker — and the localStorage/Web-Locks auto-retry claim\n // below is never taken for these runs (the adapter's follow loop is\n // read-only, so multiple tabs need no retry dedup). Only the manual banner\n // remains, on the wider background threshold from useRunStuckDetection.\n const isBackgroundDispatch =\n state.dispatchMode?.startsWith(\"background\") === true;\n\n const lastReportedRef = useRef<{\n isStuck: boolean;\n runId: string | null;\n }>({ isStuck: false, runId: null });\n useEffect(() => {\n const last = lastReportedRef.current;\n if (last.isStuck === state.isStuck && last.runId === state.runId) return;\n lastReportedRef.current = { isStuck: state.isStuck, runId: state.runId };\n onStuckStateChange?.(state);\n if (state.isStuck && state.runId) {\n trackEvent(\"agent_chat_stuck_detected\", {\n runId: state.runId,\n threadId: threadId ?? null,\n stuckSinceMs: state.stuckSinceMs ?? null,\n stuckSinceSec:\n state.stuckSinceMs != null\n ? Math.floor(state.stuckSinceMs / 1000)\n : null,\n runStatus: state.status,\n });\n }\n }, [state, onStuckStateChange, threadId]);\n\n // Reset the busy spinner once the underlying run is no longer the one we\n // acted on. A recovery continuation can start quickly enough that polling\n // never observes an idle state between run ids.\n useEffect(() => {\n setBusy((current) => {\n if (current.type === \"none\") return current;\n if (state.status !== \"running\") return { type: \"none\" };\n if (state.runId && state.runId !== current.runId) return { type: \"none\" };\n return current;\n });\n }, [state.runId, state.status]);\n\n useEffect(() => {\n if (\n !autoRetry ||\n // Server owns recovery for background dispatch modes — never auto-abort\n // (see comment on isBackgroundDispatch above).\n isBackgroundDispatch ||\n backgroundWorkerStillAlive ||\n !state.isStuck ||\n !state.runId ||\n busy.type !== \"none\" ||\n autoRetriedRunIdsRef.current.has(state.runId)\n ) {\n return;\n }\n\n const runId = state.runId;\n void claimAutoRetryAttempt(threadId, runId, ownerId).then((claimed) => {\n autoRetriedRunIdsRef.current.add(runId);\n if (!claimed) return;\n setBusy({ type: \"retry\", runId });\n trackEvent(\"agent_chat_stuck_auto_retry\", {\n runId,\n threadId: threadId ?? null,\n stuckSinceMs: state.stuckSinceMs ?? null,\n });\n void abortRun(runId, \"auto_stuck_retry\").then((aborted) => {\n if (aborted) {\n onRetry?.(aborted);\n return;\n }\n setBusy((current) =>\n current.type !== \"none\" && current.runId === runId\n ? { type: \"none\" }\n : current,\n );\n });\n });\n }, [\n abortRun,\n autoRetry,\n backgroundWorkerStillAlive,\n busy,\n isBackgroundDispatch,\n onRetry,\n ownerId,\n state.isStuck,\n state.runId,\n state.stuckSinceMs,\n threadId,\n ]);\n\n if (!state.isStuck || !state.runId) return null;\n\n const handleCancel = async () => {\n if (!state.runId || busy.type !== \"none\") return;\n const runId = state.runId;\n setBusy({ type: \"cancel\", runId });\n trackEvent(\"agent_chat_stuck_cancel\", {\n runId,\n threadId: threadId ?? null,\n stuckSinceMs: state.stuckSinceMs ?? null,\n });\n await abortRun(runId, \"user_stuck_cancel\");\n };\n\n const handleRetry = async () => {\n if (!state.runId || busy.type !== \"none\") return;\n const runId = state.runId;\n setBusy({ type: \"retry\", runId });\n trackEvent(\"agent_chat_stuck_retry\", {\n runId,\n threadId: threadId ?? null,\n stuckSinceMs: state.stuckSinceMs ?? null,\n });\n const aborted = await abortRun(runId, \"user_stuck_retry\");\n if (aborted) onRetry?.(aborted);\n };\n\n const busyType = busy.type;\n\n const stuckSeconds =\n state.stuckSinceMs != null ? Math.floor(state.stuckSinceMs / 1000) : null;\n\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n \"mx-3 mt-2 flex items-start gap-2.5 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2.5 text-xs text-foreground\",\n className,\n )}\n >\n <IconAlertTriangle\n size={16}\n className=\"mt-0.5 shrink-0 text-amber-500\"\n aria-hidden=\"true\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n <div className=\"leading-snug\">\n <span className=\"font-medium\">\n {backgroundWorkerStillAlive\n ? \"The agent is still working.\"\n : \"This chat looks stuck.\"}\n </span>{\" \"}\n <span className=\"text-muted-foreground\">\n No progress\n {stuckSeconds != null ? ` for ${stuckSeconds}s` : \"\"}.{\" \"}\n {backgroundWorkerStillAlive\n ? \"The background worker is still alive; large updates can take a few minutes.\"\n : \"The agent may have hit a server timeout or lost its connection.\"}\n {autoRetry && busyType === \"retry\"\n ? \" Retrying automatically now.\"\n : \"\"}\n </span>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleRetry}\n disabled={busyType !== \"none\"}\n className=\"inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md bg-foreground px-2.5 text-[11px] font-medium text-background transition-colors hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {busyType === \"retry\" ? (\n <IconLoader2\n size={12}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : null}\n Retry\n </button>\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={busyType !== \"none\"}\n className=\"inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-[11px] font-medium text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {busyType === \"cancel\" ? (\n <IconLoader2\n size={12}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : null}\n Cancel\n </button>\n </div>\n </div>\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"RunStuckBanner.js","sourceRoot":"","sources":["../../src/client/RunStuckBanner.tsx"],"names":[],"mappings":"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","sourcesContent":["import { IconAlertTriangle, IconLoader2 } from \"@tabler/icons-react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nimport { trackEvent } from \"./analytics.js\";\nimport {\n useRunStuckDetection,\n useAbortRun,\n type RunStuckState,\n} from \"./use-run-stuck-detection.js\";\nimport { cn } from \"./utils.js\";\n\n/**\n * Surface a user-visible affordance when a chat run hasn't emitted any\n * events for an unusually long time. The adapter's silent reconnect logic\n * keeps trying in the background; this banner is the fallback when those\n * attempts haven't restored progress and the user is staring at a frozen\n * spinner.\n */\nexport interface RunStuckBannerProps {\n /** The thread to monitor. Pass null/undefined to disable. */\n threadId: string | null | undefined;\n /**\n * Set false to skip polling entirely — used when this banner is mounted\n * for a background tab kept alive via display:none. Only the active tab\n * should poll `/runs/active`. Defaults to true.\n */\n enabled?: boolean;\n /** API base path. Default `/_agent-native/agent-chat`. */\n apiUrl?: string;\n /**\n * Threshold above which an in-flight run is considered stuck.\n * Defaults to 90s (after the adapter's 75s no-progress reconnect\n * has had a chance to recover).\n */\n stuckThresholdMs?: number;\n /**\n * Called when the user clicks Retry. Implementations should re-prompt\n * the agent (typically via `chatHandle.sendMessage(...)`) — the banner\n * itself only handles aborting the prior run.\n */\n onRetry?: (runId: string) => void;\n /**\n * Called whenever the stuck state transitions. Useful for surfacing\n * observability events (Sentry, PostHog) at the call site.\n */\n onStuckStateChange?: (state: RunStuckState) => void;\n /**\n * Automatically abort and retry once when the server reports a run is\n * alive but has stopped making real progress. Manual controls remain visible\n * as the fallback if the automatic recovery cannot clear the run.\n */\n autoRetry?: boolean;\n /**\n * Stable browser-tab/window id used to coordinate automatic retries across\n * multiple mounted chat views for the same thread. A local id is generated\n * when omitted.\n */\n autoRetryOwnerId?: string;\n /** Extra class on the outer container. */\n className?: string;\n}\n\nconst AUTO_RETRY_CLAIM_TTL_MS = 5 * 60 * 1000;\nconst BACKGROUND_WORKER_FRESH_HEARTBEAT_MS = 30_000;\n\ntype BusyState = { type: \"none\" } | { type: \"cancel\" | \"retry\"; runId: string };\n\ntype MaybeLockManager = {\n request<T>(\n name: string,\n options: { mode?: \"exclusive\" | \"shared\"; ifAvailable?: boolean },\n callback: (lock: unknown) => T | Promise<T>,\n ): Promise<T>;\n};\n\nfunction createAutoRetryOwnerId() {\n const cryptoApi =\n typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\"\n ? crypto\n : null;\n return cryptoApi?.randomUUID() ?? `owner-${Math.random().toString(36)}`;\n}\n\nfunction autoRetryClaimKey(threadId: string, runId: string) {\n return `agent-native:stuck-auto-retry:${threadId}:${runId}`;\n}\n\nfunction isFreshBackgroundWorker(state: RunStuckState): boolean {\n return Boolean(\n state.status === \"running\" &&\n state.dispatchMode === \"background-processing\" &&\n state.heartbeatSinceMs != null &&\n state.heartbeatSinceMs >= 0 &&\n state.heartbeatSinceMs < BACKGROUND_WORKER_FRESH_HEARTBEAT_MS,\n );\n}\n\nfunction markAutoRetryClaim(key: string, ownerId: string) {\n if (typeof window === \"undefined\") return true;\n const now = Date.now();\n try {\n const raw = window.localStorage.getItem(key);\n if (raw) {\n const existing = JSON.parse(raw) as {\n ownerId?: unknown;\n expiresAt?: unknown;\n };\n const expiresAt =\n typeof existing.expiresAt === \"number\" ? existing.expiresAt : 0;\n if (expiresAt > now && existing.ownerId !== ownerId) return false;\n }\n window.localStorage.setItem(\n key,\n JSON.stringify({ ownerId, expiresAt: now + AUTO_RETRY_CLAIM_TTL_MS }),\n );\n const confirmed = JSON.parse(window.localStorage.getItem(key) ?? \"{}\") as {\n ownerId?: unknown;\n };\n return confirmed.ownerId === ownerId;\n } catch {\n // Private browsing or disabled storage: fall back to in-tab retry. The\n // server abort is idempotent, and manual controls remain available.\n return true;\n }\n}\n\nasync function claimAutoRetryAttempt(\n threadId: string | null | undefined,\n runId: string,\n ownerId: string,\n) {\n if (!threadId) return true;\n const key = autoRetryClaimKey(threadId, runId);\n const locks =\n typeof navigator !== \"undefined\"\n ? (navigator as Navigator & { locks?: MaybeLockManager }).locks\n : undefined;\n if (locks?.request) {\n try {\n return await locks.request(\n key,\n { mode: \"exclusive\", ifAvailable: true },\n (lock) => (lock ? markAutoRetryClaim(key, ownerId) : false),\n );\n } catch {\n return markAutoRetryClaim(key, ownerId);\n }\n }\n return markAutoRetryClaim(key, ownerId);\n}\n\nexport function RunStuckBanner({\n threadId,\n enabled = true,\n apiUrl,\n stuckThresholdMs,\n onRetry,\n onStuckStateChange,\n autoRetry = false,\n autoRetryOwnerId,\n className,\n}: RunStuckBannerProps) {\n const state = useRunStuckDetection({\n threadId,\n enabled,\n stuckThresholdMs,\n apiUrl,\n });\n const abortRun = useAbortRun(apiUrl);\n const [busy, setBusy] = useState<BusyState>({ type: \"none\" });\n const autoRetriedRunIdsRef = useRef<Set<string>>(new Set());\n const generatedOwnerIdRef = useRef<string | null>(null);\n if (!generatedOwnerIdRef.current) {\n generatedOwnerIdRef.current = createAutoRetryOwnerId();\n }\n const ownerId = autoRetryOwnerId ?? generatedOwnerIdRef.current;\n const backgroundWorkerStillAlive = isFreshBackgroundWorker(state);\n // Server-continued runs are recovered by the SERVER (chained continuation\n // chunks + lost-handoff sweep); an automatic client abort would kill a live\n // server-chained run. Auto-retry is therefore disabled unconditionally for\n // these modes — not just a fresh-heartbeat worker — and the\n // localStorage/Web-Locks auto-retry claim below is never taken for them (the\n // adapter's follow loop is read-only, so multiple tabs need no retry dedup).\n // Only the manual banner remains, on the wider server-owned threshold from\n // useRunStuckDetection.\n const isServerContinuedDispatch =\n state.dispatchMode === \"foreground-self-chain\" ||\n state.dispatchMode?.startsWith(\"background\") === true;\n\n const lastReportedRef = useRef<{\n isStuck: boolean;\n runId: string | null;\n }>({ isStuck: false, runId: null });\n useEffect(() => {\n const last = lastReportedRef.current;\n if (last.isStuck === state.isStuck && last.runId === state.runId) return;\n lastReportedRef.current = { isStuck: state.isStuck, runId: state.runId };\n onStuckStateChange?.(state);\n if (state.isStuck && state.runId) {\n trackEvent(\"agent_chat_stuck_detected\", {\n runId: state.runId,\n threadId: threadId ?? null,\n stuckSinceMs: state.stuckSinceMs ?? null,\n stuckSinceSec:\n state.stuckSinceMs != null\n ? Math.floor(state.stuckSinceMs / 1000)\n : null,\n runStatus: state.status,\n });\n }\n }, [state, onStuckStateChange, threadId]);\n\n // Reset the busy spinner once the underlying run is no longer the one we\n // acted on. A recovery continuation can start quickly enough that polling\n // never observes an idle state between run ids.\n useEffect(() => {\n setBusy((current) => {\n if (current.type === \"none\") return current;\n if (state.status !== \"running\") return { type: \"none\" };\n if (state.runId && state.runId !== current.runId) return { type: \"none\" };\n return current;\n });\n }, [state.runId, state.status]);\n\n useEffect(() => {\n if (\n !autoRetry ||\n // Server owns recovery for these dispatch modes — never auto-abort (see\n // comment on isServerContinuedDispatch above).\n isServerContinuedDispatch ||\n backgroundWorkerStillAlive ||\n !state.isStuck ||\n !state.runId ||\n busy.type !== \"none\" ||\n autoRetriedRunIdsRef.current.has(state.runId)\n ) {\n return;\n }\n\n const runId = state.runId;\n void claimAutoRetryAttempt(threadId, runId, ownerId).then((claimed) => {\n autoRetriedRunIdsRef.current.add(runId);\n if (!claimed) return;\n setBusy({ type: \"retry\", runId });\n trackEvent(\"agent_chat_stuck_auto_retry\", {\n runId,\n threadId: threadId ?? null,\n stuckSinceMs: state.stuckSinceMs ?? null,\n });\n void abortRun(runId, \"auto_stuck_retry\").then((aborted) => {\n if (aborted) {\n onRetry?.(aborted);\n return;\n }\n setBusy((current) =>\n current.type !== \"none\" && current.runId === runId\n ? { type: \"none\" }\n : current,\n );\n });\n });\n }, [\n abortRun,\n autoRetry,\n backgroundWorkerStillAlive,\n busy,\n isServerContinuedDispatch,\n onRetry,\n ownerId,\n state.isStuck,\n state.runId,\n state.stuckSinceMs,\n threadId,\n ]);\n\n if (!state.isStuck || !state.runId) return null;\n\n const handleCancel = async () => {\n if (!state.runId || busy.type !== \"none\") return;\n const runId = state.runId;\n setBusy({ type: \"cancel\", runId });\n trackEvent(\"agent_chat_stuck_cancel\", {\n runId,\n threadId: threadId ?? null,\n stuckSinceMs: state.stuckSinceMs ?? null,\n });\n await abortRun(runId, \"user_stuck_cancel\");\n };\n\n const handleRetry = async () => {\n if (!state.runId || busy.type !== \"none\") return;\n const runId = state.runId;\n setBusy({ type: \"retry\", runId });\n trackEvent(\"agent_chat_stuck_retry\", {\n runId,\n threadId: threadId ?? null,\n stuckSinceMs: state.stuckSinceMs ?? null,\n });\n const aborted = await abortRun(runId, \"user_stuck_retry\");\n if (aborted) onRetry?.(aborted);\n };\n\n const busyType = busy.type;\n\n const stuckSeconds =\n state.stuckSinceMs != null ? Math.floor(state.stuckSinceMs / 1000) : null;\n\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n \"mx-3 mt-2 flex items-start gap-2.5 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2.5 text-xs text-foreground\",\n className,\n )}\n >\n <IconAlertTriangle\n size={16}\n className=\"mt-0.5 shrink-0 text-amber-500\"\n aria-hidden=\"true\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n <div className=\"leading-snug\">\n <span className=\"font-medium\">\n {backgroundWorkerStillAlive\n ? \"The agent is still working.\"\n : \"This chat looks stuck.\"}\n </span>{\" \"}\n <span className=\"text-muted-foreground\">\n No progress\n {stuckSeconds != null ? ` for ${stuckSeconds}s` : \"\"}.{\" \"}\n {backgroundWorkerStillAlive\n ? \"The background worker is still alive; large updates can take a few minutes.\"\n : \"The agent may have hit a server timeout or lost its connection.\"}\n {autoRetry && busyType === \"retry\"\n ? \" Retrying automatically now.\"\n : \"\"}\n </span>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleRetry}\n disabled={busyType !== \"none\"}\n className=\"inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md bg-foreground px-2.5 text-[11px] font-medium text-background transition-colors hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {busyType === \"retry\" ? (\n <IconLoader2\n size={12}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : null}\n Retry\n </button>\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={busyType !== \"none\"}\n className=\"inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-[11px] font-medium text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {busyType === \"cancel\" ? (\n <IconLoader2\n size={12}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : null}\n Cancel\n </button>\n </div>\n </div>\n </div>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-chat-adapter.d.ts","sourceRoot":"","sources":["../../src/client/agent-chat-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAsB,MAAM,qBAAqB,CAAC;AAOhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAmBrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,oBAAoB;AAC9B;;;;GAIG;AACD,KAAK;AACP,0EAA0E;GACxE,WAAW,CAAC;AAssChB;;;;GAIG;AACH;;;GAGG;AACH,MAAM,WAAW,6BAA6B;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3C,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,SAAS,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3D,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAyED,wBAAgB,sBAAsB,CACpC,OAAO,CAAC,EAAE,6BAA6B,GACtC,gBAAgB,CAi8DlB"}
1
+ {"version":3,"file":"agent-chat-adapter.d.ts","sourceRoot":"","sources":["../../src/client/agent-chat-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAsB,MAAM,qBAAqB,CAAC;AAOhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAmBrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,oBAAoB;AAC9B;;;;GAIG;AACD,KAAK;AACP,0EAA0E;GACxE,WAAW,CAAC;AAqsChB;;;;GAIG;AACH;;;GAGG;AACH,MAAM,WAAW,6BAA6B;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3C,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,SAAS,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3D,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAyED,wBAAgB,sBAAsB,CACpC,OAAO,CAAC,EAAE,6BAA6B,GACtC,gBAAgB,CA68DlB"}
@@ -79,16 +79,15 @@ const LARGE_INPUT_TOOL_NAMES = new Set([
79
79
  const MAX_HISTORY_LARGE_TOOL_ARGS_CHARS = 200_000;
80
80
  const STARTUP_RESPONSE_TIMEOUT_MS = 45_000;
81
81
  // ── Background follow mode ──────────────────────────────────────────────────
82
- // For background-dispatched runs (dispatchMode starts with "background") the
83
- // SERVER is the sole recovery brain: it chains continuation chunks itself
84
- // (fresh runId, same turnId), pre-inserts the successor run row BEFORE the old
85
- // chunk completes (so /runs/active shows an active run continuously across
86
- // chunk boundaries), and a server sweep reaps lost handoffs into loud terminal
87
- // errors. The client therefore never POSTs synthetic continuations for these
88
- // runs it demotes itself to a READER of server state: poll /runs/active,
89
- // (re)attach to whichever run of this turn is live, and fold its events into
90
- // the same assistant message. Read-only, so multiple tabs following the same
91
- // run are safe (no localStorage/Web-Locks claim needed).
82
+ // For server-continued runs (dispatchMode starts with "background" or equals
83
+ // "foreground-self-chain") the SERVER normally chains continuation chunks
84
+ // itself (fresh runId, same turnId), pre-inserts the successor run row BEFORE
85
+ // the old chunk completes (so /runs/active shows an active run continuously
86
+ // across chunk boundaries), and a server sweep reaps lost handoffs into loud
87
+ // terminal errors. The client demotes itself to a READER of server state for
88
+ // healthy handoffs. Foreground self-chain keeps one escape hatch: if the server
89
+ // reports that the self-dispatch itself failed before a successor claimed the
90
+ // run, the existing client auto-continue POST remains the fallback.
92
91
  const BACKGROUND_FOLLOW_POLL_INTERVAL_MS = 1_000;
93
92
  // How long the follow loop tolerates seeing NO active run for this turn before
94
93
  // treating the turn as ended. The server pre-inserts the successor row before
@@ -1333,11 +1332,20 @@ export function createAgentChatAdapter(options) {
1333
1332
  if (mode)
1334
1333
  currentRunDispatchMode = mode;
1335
1334
  };
1336
- // Mode switch for recovery ownership: background-dispatched runs are
1337
- // recovered by the SERVER (chained continuations, sweeps); the client
1338
- // only follows. Foreground (null/"foreground"/unknown) keeps the full
1339
- // client-side continuation machinery unchanged.
1340
- const isBackgroundDispatch = () => currentRunDispatchMode?.startsWith("background") === true;
1335
+ // Mode switch for recovery ownership: durable/background-dispatched runs
1336
+ // are always recovered by the server. Foreground self-chain follows the
1337
+ // server on healthy handoffs, but a loud self-dispatch failure falls back
1338
+ // to the client POST path because no successor run owns recovery yet.
1339
+ const isDurableBackgroundDispatch = () => currentRunDispatchMode?.startsWith("background") === true;
1340
+ const isForegroundSelfChainDispatch = () => currentRunDispatchMode === "foreground-self-chain";
1341
+ const shouldFollowServerContinuation = (signal) => {
1342
+ if (isDurableBackgroundDispatch())
1343
+ return true;
1344
+ if (!isForegroundSelfChainDispatch())
1345
+ return false;
1346
+ return (signal?.errorInfo?.errorCode !==
1347
+ "background_continuation_dispatch_failed");
1348
+ };
1341
1349
  const rememberRunSeq = (seq) => {
1342
1350
  lastSeq = seq;
1343
1351
  if (runId) {
@@ -2425,7 +2433,7 @@ export function createAgentChatAdapter(options) {
2425
2433
  // following of server state instead. This is the fix for the
2426
2434
  // client/server recovery race: client watchdog signals here are
2427
2435
  // just "reattach", not "recover".
2428
- if (isBackgroundDispatch() && threadId) {
2436
+ if (shouldFollowServerContinuation(err) && threadId) {
2429
2437
  yield* followBackgroundTurn(err);
2430
2438
  return;
2431
2439
  }
@@ -2588,7 +2596,7 @@ export function createAgentChatAdapter(options) {
2588
2596
  // synthetic-continuation POST below. (An initial POST that failed
2589
2597
  // before any response headers arrived has no dispatch mode yet
2590
2598
  // and keeps the foreground startup-retry path.)
2591
- if (isBackgroundDispatch() && threadId) {
2599
+ if (shouldFollowServerContinuation() && threadId) {
2592
2600
  yield* followBackgroundTurn(new AgentAutoContinueSignal({ reason: "stream_ended" }));
2593
2601
  return;
2594
2602
  }