@agent-native/core 0.163.4 → 0.164.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 (228) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +78 -19
  3. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  4. package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +4 -4
  5. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -0
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +6 -0
  7. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  8. package/corpus/templates/analytics/app/routes/_index.tsx +3 -2
  9. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +7 -2
  10. package/corpus/templates/analytics/app/routes/dashboard.tsx +3 -2
  11. package/corpus/templates/analytics/app/routes/overview.tsx +3 -2
  12. package/corpus/templates/analytics/app/routes/traffic.tsx +3 -2
  13. package/corpus/templates/analytics/changelog/2026-08-18-verify-cross-app-feature-flag-changes.md +6 -0
  14. package/corpus/templates/analytics/netlify.toml +2 -0
  15. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +196 -48
  16. package/corpus/templates/analytics/server/plugins/feature-flags.ts +5 -0
  17. package/corpus/templates/analytics/shared/feature-flags.ts +10 -0
  18. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +5 -1
  19. package/corpus/templates/assets/app/routes/brand-kits._index.tsx +4 -1
  20. package/corpus/templates/assets/app/routes/libraries.tsx +4 -1
  21. package/corpus/templates/assets/app/routes/picker.tsx +4 -1
  22. package/corpus/templates/assets/netlify.toml +2 -0
  23. package/corpus/templates/brain/netlify.toml +2 -0
  24. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  25. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  26. package/corpus/templates/calendar/netlify.toml +2 -0
  27. package/corpus/templates/chat/netlify.toml +2 -0
  28. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  29. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  30. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  31. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  32. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  33. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  34. package/corpus/templates/clips/app/routes/_app.wispr.tsx +2 -1
  35. package/corpus/templates/clips/app/routes/_index.tsx +7 -3
  36. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  37. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  38. package/corpus/templates/clips/netlify.toml +2 -0
  39. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  40. package/corpus/templates/content/netlify.toml +2 -0
  41. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  42. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  43. package/corpus/templates/design/AGENTS.md +3 -1
  44. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  45. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  46. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  47. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  48. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  49. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  50. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  51. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  52. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  53. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  54. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  55. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  56. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  126. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  127. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  128. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  129. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  130. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  131. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  132. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  133. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  134. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  135. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  136. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  137. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  138. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  139. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  140. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  141. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  142. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  143. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  144. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  145. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  146. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  147. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  148. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  149. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  150. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  151. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  152. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  153. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  154. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  155. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  156. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  157. package/corpus/templates/design/app/routes/examples.tsx +2 -1
  158. package/corpus/templates/design/netlify.toml +2 -0
  159. package/corpus/templates/design/shared/board-file.ts +2 -1
  160. package/corpus/templates/dispatch/netlify.toml +2 -0
  161. package/corpus/templates/forms/app/routes/_index.tsx +3 -2
  162. package/corpus/templates/forms/netlify.toml +2 -0
  163. package/corpus/templates/mail/app/routes/_index.tsx +3 -2
  164. package/corpus/templates/mail/netlify.toml +2 -0
  165. package/corpus/templates/plan/netlify.toml +2 -0
  166. package/corpus/templates/slides/netlify.toml +2 -0
  167. package/corpus/templates/tasks/app/routes/_index.tsx +2 -1
  168. package/dist/agent/engine/builder-engine.d.ts +6 -4
  169. package/dist/agent/engine/builder-engine.js +91 -26
  170. package/dist/agent/engine/registry.js +16 -0
  171. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  172. package/dist/cli/code-agent-agent-tools.js +188 -0
  173. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  174. package/dist/cli/code-agent-collaboration.js +104 -0
  175. package/dist/cli/code-agent-executor.js +6 -0
  176. package/dist/cli/code-agent-schedules.d.ts +54 -0
  177. package/dist/cli/code-agent-schedules.js +198 -0
  178. package/dist/client/AgentPanel.js +1 -1
  179. package/dist/client/ConnectBuilderCard.js +5 -2
  180. package/dist/client/EnvironmentBadge.js +6 -5
  181. package/dist/client/chat/run-recovery.js +3 -2
  182. package/dist/client/chat/tool-call-display.js +2 -3
  183. package/dist/client/components/CodeRequiredDialog.js +4 -2
  184. package/dist/client/i18n.js +5 -4
  185. package/dist/client/settings/SettingsPanel.js +1 -1
  186. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  187. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  188. package/dist/client/settings/useBuilderStatus.js +22 -33
  189. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  190. package/dist/code-agents/index.d.ts +2 -0
  191. package/dist/code-agents/index.js +2 -0
  192. package/dist/collab/awareness.d.ts +2 -2
  193. package/dist/collab/routes.d.ts +1 -1
  194. package/dist/deploy/build.js +10 -4
  195. package/dist/localization/shared.d.ts +2 -0
  196. package/dist/localization/shared.js +5 -0
  197. package/dist/mcp-client/oauth-client.d.ts +1 -0
  198. package/dist/mcp-client/oauth-client.js +50 -1
  199. package/dist/notifications/routes.d.ts +3 -3
  200. package/dist/observability/routes.d.ts +1 -1
  201. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  202. package/dist/server/auth-marketing-locales.d.ts +7 -0
  203. package/dist/server/auth-marketing-locales.js +1322 -0
  204. package/dist/server/auth-marketing.d.ts +1 -0
  205. package/dist/server/auth-marketing.js +35 -6
  206. package/dist/server/auth.js +4 -17
  207. package/dist/server/builder-browser.d.ts +22 -0
  208. package/dist/server/builder-browser.js +77 -1
  209. package/dist/server/builder-oauth.d.ts +50 -0
  210. package/dist/server/builder-oauth.js +263 -0
  211. package/dist/server/core-routes-plugin.d.ts +11 -0
  212. package/dist/server/core-routes-plugin.js +291 -415
  213. package/dist/server/credential-provider.d.ts +1 -1
  214. package/dist/server/credential-provider.js +1 -1
  215. package/dist/server/csrf.js +2 -2
  216. package/dist/server/onboarding-html.js +52 -120
  217. package/dist/server/ssr-handler.js +13 -4
  218. package/dist/settings/store.d.ts +9 -0
  219. package/dist/settings/store.js +19 -0
  220. package/dist/shared/cache-control.d.ts +22 -0
  221. package/dist/shared/cache-control.js +25 -0
  222. package/dist/shared/index.d.ts +1 -0
  223. package/dist/shared/index.js +1 -0
  224. package/dist/templates/chat/netlify.toml +2 -0
  225. package/docs/content/deployment.mdx +2 -0
  226. package/docs/content/multi-app-workspace.mdx +1 -1
  227. package/package.json +2 -2
  228. package/src/templates/chat/netlify.toml +2 -0
@@ -0,0 +1,304 @@
1
+ import { assertDesignHtmlEditIntegrity } from "@shared/html-integrity";
2
+ import { sourceContentHash } from "@shared/source-workspace";
3
+ import type { QueryClient } from "@tanstack/react-query";
4
+ import type { Dispatch, RefObject, SetStateAction } from "react";
5
+ import { toast } from "sonner";
6
+ import * as Y from "yjs";
7
+
8
+ import { trace } from "@/components/design/design-trace";
9
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
10
+ import {
11
+ LOCAL_EDIT_ORIGIN,
12
+ TAB_ID,
13
+ } from "@/pages/design-editor/editor-session";
14
+ import type { PreviewContentReplaceResult } from "@/pages/design-editor/editor-state";
15
+ import { previewContentReplaceNeedsRenderFallback } from "@/pages/design-editor/editor-state";
16
+ import type {
17
+ ContentHistoryChange,
18
+ ContentHistoryEntry,
19
+ } from "@/pages/design-editor/history";
20
+ import { designSaveErrorMessage } from "@/pages/design-editor/save-failure";
21
+ import type { DesignFile } from "@/pages/design-editor/types";
22
+
23
+ export interface ApplyLocalContentUpdateArgs {
24
+ acknowledgeAuthoritativeClipboardMutation: (args: {
25
+ fileId: string;
26
+ nextContent: string;
27
+ publication?: ClipboardContentMutationPublication;
28
+ }) => void;
29
+ activeFile: DesignFile;
30
+ canEditDesignRef: RefObject<boolean>;
31
+ cancelQueuedFileContentSave: (fileId: string) => void;
32
+ clearPendingLocalFileContent: (
33
+ fileId: string,
34
+ expectedContent?: string,
35
+ ) => void;
36
+ collabContentFileIdRef: RefObject<string | null>;
37
+ collabContentRef: RefObject<string | null>;
38
+ id: string | undefined;
39
+ isSynced: boolean;
40
+ lastAckedFileContentHashRef: RefObject<Record<string, string>>;
41
+ lastLocalContentRef: RefObject<string | null>;
42
+ latestActiveContentRef: RefObject<string | null>;
43
+ markPendingLocalFileContent: (
44
+ fileId: string,
45
+ content: string,
46
+ baseUpdatedAt?: string | null,
47
+ ) => void;
48
+ queryClient: QueryClient;
49
+ queueFileContentSave: (
50
+ fileId: string,
51
+ content: string,
52
+ options?: { syncCollab?: boolean; immediate?: boolean },
53
+ ) => void;
54
+ recordContentHistoryEntry: (entry: ContentHistoryEntry) => void;
55
+ recordLocalContentHistoryChangeFallback: (
56
+ change: ContentHistoryChange,
57
+ ) => void;
58
+ recordLocalContentHistoryEntry: (change: ContentHistoryChange) => void;
59
+ replacePreviewContent: (
60
+ nextContent: string,
61
+ selector?: string | null,
62
+ options?: { forceFullDocument?: boolean },
63
+ ) => PreviewContentReplaceResult;
64
+ setCollabContent: Dispatch<SetStateAction<string | null>>;
65
+ setCollabContentFileId: Dispatch<SetStateAction<string | null>>;
66
+ setContentRenderRevision: Dispatch<SetStateAction<number>>;
67
+ suppressContentHistoryRef: RefObject<boolean>;
68
+ t: (key: string, options?: Record<string, unknown>) => string;
69
+ undoManagerRef: RefObject<Y.UndoManager | null>;
70
+ viewModeRef: RefObject<"single" | "overview">;
71
+ ydoc: Y.Doc | null;
72
+ }
73
+
74
+ export function runApplyLocalContentUpdate(
75
+ {
76
+ acknowledgeAuthoritativeClipboardMutation,
77
+ activeFile,
78
+ canEditDesignRef,
79
+ cancelQueuedFileContentSave,
80
+ clearPendingLocalFileContent,
81
+ collabContentFileIdRef,
82
+ collabContentRef,
83
+ id,
84
+ isSynced,
85
+ lastAckedFileContentHashRef,
86
+ lastLocalContentRef,
87
+ latestActiveContentRef,
88
+ markPendingLocalFileContent,
89
+ queryClient,
90
+ queueFileContentSave,
91
+ recordContentHistoryEntry,
92
+ recordLocalContentHistoryChangeFallback,
93
+ recordLocalContentHistoryEntry,
94
+ replacePreviewContent,
95
+ setCollabContent,
96
+ setCollabContentFileId,
97
+ setContentRenderRevision,
98
+ suppressContentHistoryRef,
99
+ t,
100
+ undoManagerRef,
101
+ viewModeRef,
102
+ ydoc,
103
+ }: ApplyLocalContentUpdateArgs,
104
+ nextContent: string,
105
+ options: {
106
+ refreshPreview?: boolean;
107
+ skipPreview?: boolean;
108
+ forcePreviewFullDocument?: boolean;
109
+ immediateSave?: boolean;
110
+ persist?: boolean;
111
+ recordHistory?: boolean;
112
+ historyBeforeContent?: string;
113
+ updatedAt?: string;
114
+ clipboardMutation?: ClipboardContentMutationPublication;
115
+ } = {},
116
+ ) {
117
+ trace("persist", "write-file", {
118
+ file: activeFile?.filename ?? null,
119
+ bytes: nextContent.length,
120
+ blocked: !activeFile
121
+ ? "no active file"
122
+ : !canEditDesignRef.current
123
+ ? "read-only design"
124
+ : null,
125
+ });
126
+ if (!activeFile || !canEditDesignRef.current) return;
127
+ const shouldRecordHistory =
128
+ options.recordHistory !== false && !options.updatedAt;
129
+ const previousContent =
130
+ typeof options.historyBeforeContent === "string"
131
+ ? options.historyBeforeContent
132
+ : collabContentFileIdRef.current === activeFile.id &&
133
+ typeof collabContentRef.current === "string"
134
+ ? collabContentRef.current
135
+ : (activeFile.content ?? "");
136
+ try {
137
+ assertDesignHtmlEditIntegrity({
138
+ previousContent,
139
+ nextContent,
140
+ fileType: activeFile.fileType,
141
+ });
142
+ } catch (error) {
143
+ toast.error(designSaveErrorMessage(error) ?? t("common.genericError"), {
144
+ id: `design-source-integrity:${activeFile.id}`,
145
+ });
146
+ return;
147
+ }
148
+ // No implicit authority from `recordHistory`: save/query/Yjs echoes can
149
+ // traverse this same function with history enabled. Only a publication
150
+ // allocated synchronously at a user-action boundary may advance lineage.
151
+ acknowledgeAuthoritativeClipboardMutation({
152
+ fileId: activeFile.id,
153
+ nextContent,
154
+ publication: options.clipboardMutation,
155
+ });
156
+ const yjsHistoryAvailable = Boolean(
157
+ shouldRecordHistory &&
158
+ viewModeRef.current !== "overview" &&
159
+ ydoc &&
160
+ isSynced &&
161
+ undoManagerRef.current,
162
+ );
163
+ if (
164
+ !suppressContentHistoryRef.current &&
165
+ shouldRecordHistory &&
166
+ !yjsHistoryAvailable &&
167
+ previousContent !== nextContent
168
+ ) {
169
+ const change = {
170
+ fileId: activeFile.id,
171
+ before: previousContent,
172
+ after: nextContent,
173
+ };
174
+ if (viewModeRef.current === "overview") {
175
+ recordContentHistoryEntry(change);
176
+ } else {
177
+ recordLocalContentHistoryEntry(change);
178
+ }
179
+ } else if (
180
+ !suppressContentHistoryRef.current &&
181
+ shouldRecordHistory &&
182
+ yjsHistoryAvailable &&
183
+ previousContent !== nextContent
184
+ ) {
185
+ // The Yjs UndoManager is the primary undo path here, but it (and its
186
+ // whole undo stack) is destroyed on every view-mode switch and zoom
187
+ // (docId goes null -> ydoc changes). Mirror the same before/after into
188
+ // the local fallback stack so handleUndo can still recover the edit
189
+ // once the Yjs stack is gone; it is only consulted when Yjs itself has
190
+ // nothing left to undo, so this never causes a double-undo.
191
+ recordLocalContentHistoryChangeFallback({
192
+ fileId: activeFile.id,
193
+ before: previousContent,
194
+ after: nextContent,
195
+ });
196
+ }
197
+ if (options.updatedAt) {
198
+ clearPendingLocalFileContent(activeFile.id);
199
+ // options.updatedAt means this content is already server-persisted
200
+ // (apply-source-edit's onApplied host-sync and friends) — record it
201
+ // as the server-acknowledged base so the next guarded update-file
202
+ // save carries the POST-shader hash, not a stale pre-shader one.
203
+ lastAckedFileContentHashRef.current[activeFile.id] =
204
+ sourceContentHash(nextContent);
205
+ } else {
206
+ markPendingLocalFileContent(
207
+ activeFile.id,
208
+ nextContent,
209
+ activeFile.updatedAt,
210
+ );
211
+ }
212
+ setCollabContent(nextContent);
213
+ setCollabContentFileId(activeFile.id);
214
+ collabContentRef.current = nextContent;
215
+ collabContentFileIdRef.current = activeFile.id;
216
+ lastLocalContentRef.current = nextContent;
217
+ latestActiveContentRef.current = nextContent;
218
+ if (id) {
219
+ queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
220
+ if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
221
+ return old;
222
+ }
223
+ return {
224
+ ...old,
225
+ files: old.files.map((file: DesignFile) =>
226
+ file.id === activeFile.id
227
+ ? // Update content optimistically but keep the file's prior
228
+ // (server-clock) updatedAt. Seeding the reconcile watermark
229
+ // from a client-clock timestamp can, under clock skew, make a
230
+ // later server-authored agent edit look "older" and get
231
+ // dropped by the watermark gate (agent edit silently lost).
232
+ {
233
+ ...file,
234
+ content: nextContent,
235
+ ...(options.updatedAt ? { updatedAt: options.updatedAt } : {}),
236
+ }
237
+ : file,
238
+ ),
239
+ };
240
+ });
241
+ }
242
+ const forceRefresh = options.refreshPreview === true;
243
+ // Holistic flash pipeline: `forcePreviewFullDocument` tells the bridge
244
+ // to use its whole-document innerHTML replace (needed when the change
245
+ // isn't scoped to the currently selected element's subtree — e.g. an
246
+ // undo/redo that can touch anywhere in the document), NOT to force a
247
+ // full iframe srcdoc rebuild here. `replaceRuntimeDocument`'s full-body
248
+ // branch already swaps content inside the SAME live iframe document (no
249
+ // navigation, no onload refire, persistent overlay nodes preserved —
250
+ // see that function's module doc) — it is exactly as flash-free as the
251
+ // scoped single-element patch, just broader. Previously this always
252
+ // bumped contentRenderRevision whenever forcePreviewFullDocument was
253
+ // set, even after replacePreviewContent already applied the update in
254
+ // place, forcing a completely redundant full srcdoc rebuild (real
255
+ // iframe reload, white flash, lost scroll/CSS-transition/Alpine state)
256
+ // on top of a change that had already rendered correctly. Only fall
257
+ // back to the expensive srcdoc rebuild when the live patch genuinely
258
+ // couldn't run (bridge not registered for this surface yet, or an
259
+ // explicit forceRefresh request).
260
+ const replacedPreview = options.skipPreview
261
+ ? "applied"
262
+ : forceRefresh
263
+ ? "unavailable"
264
+ : replacePreviewContent(
265
+ nextContent,
266
+ null,
267
+ options.forcePreviewFullDocument
268
+ ? { forceFullDocument: true }
269
+ : undefined,
270
+ );
271
+ if (
272
+ forceRefresh ||
273
+ previewContentReplaceNeedsRenderFallback(replacedPreview)
274
+ ) {
275
+ setContentRenderRevision((revision) => revision + 1);
276
+ }
277
+ if (ydoc && isSynced) {
278
+ const ytext = ydoc.getText("content");
279
+ if (ytext.toString() !== nextContent) {
280
+ if (!yjsHistoryAvailable) {
281
+ // Untracked full rewrite (recordHistory:false callers such as the
282
+ // code-layer id-stamping effect, or history-suppressed replays) —
283
+ // see U1 note above: clear the undo stack so a stale tracked
284
+ // delta can't be replayed against content it no longer matches.
285
+ undoManagerRef.current?.clear(true, false);
286
+ }
287
+ ydoc.transact(
288
+ () => {
289
+ ytext.delete(0, ytext.length);
290
+ ytext.insert(0, nextContent);
291
+ },
292
+ yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
293
+ );
294
+ }
295
+ }
296
+ if (options.persist === false) {
297
+ cancelQueuedFileContentSave(activeFile.id);
298
+ } else {
299
+ queueFileContentSave(activeFile.id, nextContent, {
300
+ syncCollab: !(ydoc && isSynced),
301
+ immediate: options.immediateSave,
302
+ });
303
+ }
304
+ }
@@ -0,0 +1,376 @@
1
+ import { callAction } from "@agent-native/core/client/hooks";
2
+ import type { Dispatch, RefObject, SetStateAction } from "react";
3
+ import { toast } from "sonner";
4
+
5
+ import { sendDesignSourceHandoffAndConfirm } from "@/lib/agent-chat";
6
+ import { actionErrorDetail } from "@/pages/design-editor/action-error";
7
+ import type {
8
+ PendingStructureVerificationSession,
9
+ PendingStructureVerificationStatus,
10
+ RuntimeLayerSnapshot,
11
+ } from "@/pages/design-editor/command-types";
12
+ import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
13
+ import {
14
+ HOST_TURN_START_TIMEOUT_MS,
15
+ PENDING_STRUCTURE_RUNTIME_POLL_MS,
16
+ PENDING_STRUCTURE_RUNTIME_TIMEOUT_MS,
17
+ PENDING_STRUCTURE_SOURCE_POLL_MS,
18
+ PENDING_STRUCTURE_VERIFICATION_TIMEOUT_MS,
19
+ } from "@/pages/design-editor/editor-constants";
20
+ import type {
21
+ PendingLiveNonStyleEdit,
22
+ PendingLiveStructureEdit,
23
+ PendingVisualStyleEdit,
24
+ } from "@/pages/design-editor/pending-edits";
25
+ import {
26
+ buildPendingVisualStyleRevertPatches,
27
+ pendingStructureEditSourcePaths,
28
+ } from "@/pages/design-editor/pending-edits";
29
+ import { verifyPendingStructuresRuntime } from "@/pages/design-editor/pending-structure-verification";
30
+ import type { DesignLeftPanel } from "@/pages/design-editor/types";
31
+
32
+ export interface ApplyPendingVisualStylesWithAgentArgs {
33
+ cancelPendingStructureVerification: (
34
+ nextStatus?: PendingStructureVerificationStatus,
35
+ ) => void;
36
+ clearPendingLiveEditState: () => void;
37
+ id: string | undefined;
38
+ overviewScreens: OverviewScreen[];
39
+ pendingAgentHandoffBusyRef: RefObject<boolean>;
40
+ pendingLiveNonStyleEdits: PendingLiveNonStyleEdit[];
41
+ stagedHandoffStartTimerRef: RefObject<number | undefined>;
42
+ stagedSourceHandoffRef: RefObject<"idle" | "awaiting-start" | "running">;
43
+ pendingStructureVerificationRevisionRef: RefObject<number>;
44
+ pendingStructureVerificationSessionRef: RefObject<
45
+ PendingStructureVerificationSession | undefined
46
+ >;
47
+ pendingStructureVerificationSnapshotsRef: RefObject<
48
+ Map<number, Record<string, RuntimeLayerSnapshot>>
49
+ >;
50
+ pendingStructureVerificationStatus: PendingStructureVerificationStatus;
51
+ pendingVisualStyleEdits: PendingVisualStyleEdit[];
52
+ pendingVisualStylePrompt: string;
53
+ setActiveLeftPanel: Dispatch<SetStateAction<DesignLeftPanel>>;
54
+ setApplyingViaHost: Dispatch<SetStateAction<boolean>>;
55
+ setPendingAgentHandoffBusy: Dispatch<SetStateAction<boolean>>;
56
+ setPendingStructureAckRequest: Dispatch<
57
+ SetStateAction<{
58
+ requestId: number;
59
+ acks: Array<{ screenId: string; requestId: string; applied: boolean }>;
60
+ } | null>
61
+ >;
62
+ setPendingStructureVerificationStatus: Dispatch<
63
+ SetStateAction<PendingStructureVerificationStatus>
64
+ >;
65
+ setPendingVisualStyleBaselineResetRequest: Dispatch<
66
+ SetStateAction<number | null>
67
+ >;
68
+ setPendingVisualStyleRevertRequest: Dispatch<
69
+ SetStateAction<{
70
+ requestId: number;
71
+ patches: ReturnType<typeof buildPendingVisualStyleRevertPatches>;
72
+ } | null>
73
+ >;
74
+ setRuntimeStructureVerificationRequest: Dispatch<
75
+ SetStateAction<{ requestId: number; screenIds: string[] } | null>
76
+ >;
77
+ t: (key: string, options?: Record<string, unknown>) => string;
78
+ }
79
+
80
+ export async function runApplyPendingVisualStylesWithAgent({
81
+ cancelPendingStructureVerification,
82
+ clearPendingLiveEditState,
83
+ id,
84
+ overviewScreens,
85
+ pendingAgentHandoffBusyRef,
86
+ pendingLiveNonStyleEdits,
87
+ stagedHandoffStartTimerRef,
88
+ stagedSourceHandoffRef,
89
+ pendingStructureVerificationRevisionRef,
90
+ pendingStructureVerificationSessionRef,
91
+ pendingStructureVerificationSnapshotsRef,
92
+ pendingStructureVerificationStatus,
93
+ pendingVisualStyleEdits,
94
+ pendingVisualStylePrompt,
95
+ setActiveLeftPanel,
96
+ setApplyingViaHost,
97
+ setPendingAgentHandoffBusy,
98
+ setPendingStructureAckRequest,
99
+ setPendingStructureVerificationStatus,
100
+ setPendingVisualStyleBaselineResetRequest,
101
+ setPendingVisualStyleRevertRequest,
102
+ setRuntimeStructureVerificationRequest,
103
+ t,
104
+ }: ApplyPendingVisualStylesWithAgentArgs) {
105
+ if (
106
+ pendingVisualStyleEdits.length === 0 &&
107
+ pendingLiveNonStyleEdits.length === 0
108
+ ) {
109
+ return;
110
+ }
111
+ if (
112
+ pendingAgentHandoffBusyRef.current ||
113
+ pendingStructureVerificationStatus === "checking-source" ||
114
+ pendingStructureVerificationStatus === "awaiting-source" ||
115
+ pendingStructureVerificationStatus === "awaiting-runtime"
116
+ ) {
117
+ return;
118
+ }
119
+ pendingAgentHandoffBusyRef.current = true;
120
+ setPendingAgentHandoffBusy(true);
121
+ try {
122
+ const preservePreviewPatches = pendingVisualStyleEdits
123
+ .map((edit) => ({
124
+ screenId: edit.screenId,
125
+ selector: edit.selector,
126
+ sourceId: edit.sourceId,
127
+ styles: edit.styles,
128
+ ...(edit.interactionState
129
+ ? { interactionState: edit.interactionState }
130
+ : {}),
131
+ }))
132
+ .filter((patch) => Object.keys(patch.styles).length > 0);
133
+ const structureEdits = pendingLiveNonStyleEdits.filter(
134
+ (edit): edit is PendingLiveStructureEdit => edit.kind === "structure",
135
+ );
136
+ const structureAcks = structureEdits
137
+ .filter((edit) => Boolean(edit.requestId))
138
+ .map((edit) => ({
139
+ screenId: edit.screenId,
140
+ requestId: edit.requestId!,
141
+ applied: true,
142
+ }));
143
+
144
+ const finalizeWithoutStructureVerification = () => {
145
+ clearPendingLiveEditState();
146
+ const previewRequestId = Date.now() + Math.random();
147
+ window.setTimeout(() => {
148
+ if (preservePreviewPatches.length > 0) {
149
+ setPendingVisualStyleRevertRequest({
150
+ requestId: previewRequestId,
151
+ patches: preservePreviewPatches,
152
+ });
153
+ }
154
+ setPendingVisualStyleBaselineResetRequest(previewRequestId);
155
+ }, 50);
156
+ };
157
+
158
+ if (structureEdits.length === 0) {
159
+ const delivery = await sendDesignSourceHandoffAndConfirm(
160
+ {
161
+ message: t("designEditor.pendingVisualStyles.agentMessage"),
162
+ context: pendingVisualStylePrompt,
163
+ submit: true,
164
+ openSidebar: true,
165
+ },
166
+ { timeoutMs: 10_000 },
167
+ );
168
+ if (!delivery.delivered) {
169
+ toast.error(
170
+ t("designEditor.pendingVisualStyles.agentHandoffFailedToast"),
171
+ );
172
+ return;
173
+ }
174
+ if (delivery.awaitingHostTurn) {
175
+ stagedSourceHandoffRef.current = "awaiting-start";
176
+ setApplyingViaHost(true);
177
+ stagedHandoffStartTimerRef.current = window.setTimeout(() => {
178
+ stagedHandoffStartTimerRef.current = undefined;
179
+ if (stagedSourceHandoffRef.current !== "awaiting-start") return;
180
+ stagedSourceHandoffRef.current = "idle";
181
+ setApplyingViaHost(false);
182
+ toast.error(
183
+ t("designEditor.pendingVisualStyles.agentHandoffFailedToast"),
184
+ );
185
+ }, HOST_TURN_START_TIMEOUT_MS);
186
+ } else finalizeWithoutStructureVerification();
187
+ if (delivery.target === "local") setActiveLeftPanel("agent");
188
+ toast.success(t("designEditor.pendingVisualStyles.sentToast"));
189
+ return;
190
+ }
191
+
192
+ if (!id) return;
193
+ pendingStructureVerificationRevisionRef.current += 1;
194
+ const requestId = pendingStructureVerificationRevisionRef.current;
195
+ const session: PendingStructureVerificationSession = {
196
+ requestId,
197
+ cancelled: false,
198
+ edits: structureEdits,
199
+ sources: [],
200
+ };
201
+ pendingStructureVerificationSessionRef.current = session;
202
+ pendingStructureVerificationSnapshotsRef.current.set(requestId, {});
203
+ setPendingStructureVerificationStatus("checking-source");
204
+
205
+ const sourceTargets = new Map<
206
+ string,
207
+ { connectionId: string; path: string }
208
+ >();
209
+ for (const edit of structureEdits) {
210
+ const connectionId = overviewScreens.find(
211
+ (screen) => screen.id === edit.screenId,
212
+ )?.connectionId;
213
+ const paths = pendingStructureEditSourcePaths(edit);
214
+ if (!connectionId || !paths) {
215
+ cancelPendingStructureVerification("conflict");
216
+ toast.error(t("designEditor.toasts.reactSourceAnchorsLoading"));
217
+ return;
218
+ }
219
+ for (const path of paths) {
220
+ sourceTargets.set(`${connectionId}:${path}`, {
221
+ connectionId,
222
+ path,
223
+ });
224
+ }
225
+ }
226
+
227
+ try {
228
+ session.sources = await Promise.all(
229
+ Array.from(sourceTargets.values()).map(async (source) => {
230
+ // read-local-file declares `http: { method: "GET" }`, so a
231
+ // default POST is refused with 405 and every Apply preflight
232
+ // fails before it reads a single baseline hash.
233
+ const result = (await callAction(
234
+ "read-local-file",
235
+ {
236
+ designId: id,
237
+ connectionId: source.connectionId,
238
+ path: source.path,
239
+ },
240
+ { method: "GET" },
241
+ )) as { versionHash?: string } | undefined;
242
+ if (!result?.versionHash) {
243
+ throw new Error(`Missing version hash for ${source.path}`);
244
+ }
245
+ return {
246
+ ...source,
247
+ baselineVersionHash: result.versionHash,
248
+ };
249
+ }),
250
+ );
251
+ if (session.cancelled) return;
252
+
253
+ const delivery = await sendDesignSourceHandoffAndConfirm(
254
+ {
255
+ message: t("designEditor.pendingVisualStyles.agentMessage"),
256
+ context: pendingVisualStylePrompt,
257
+ submit: true,
258
+ openSidebar: true,
259
+ },
260
+ { timeoutMs: 10_000 },
261
+ );
262
+ if (session.cancelled) return;
263
+ if (!delivery.delivered) {
264
+ cancelPendingStructureVerification();
265
+ toast.error(
266
+ t("designEditor.pendingVisualStyles.agentHandoffFailedToast"),
267
+ );
268
+ return;
269
+ }
270
+
271
+ const screenIds = Array.from(
272
+ new Set(structureEdits.map((edit) => edit.screenId)),
273
+ );
274
+ setPendingStructureVerificationStatus("awaiting-source");
275
+ if (delivery.target === "local") setActiveLeftPanel("agent");
276
+ toast.success(t("designEditor.pendingVisualStyles.sentToast"));
277
+
278
+ let deadline = Date.now() + PENDING_STRUCTURE_VERIFICATION_TIMEOUT_MS;
279
+ let nextSourcePollAt = 0;
280
+ let sourceChanged = false;
281
+ let verificationRuntimeMounted = false;
282
+ while (!session.cancelled && Date.now() < deadline) {
283
+ const runtimeSnapshots =
284
+ pendingStructureVerificationSnapshotsRef.current.get(requestId) ?? {};
285
+ if (
286
+ verificationRuntimeMounted &&
287
+ screenIds.every((screenId) => runtimeSnapshots[screenId])
288
+ ) {
289
+ const runtimeResult = verifyPendingStructuresRuntime(
290
+ runtimeSnapshots,
291
+ structureEdits,
292
+ );
293
+ if (runtimeResult.ok) {
294
+ if (structureAcks.length > 0) {
295
+ setPendingStructureAckRequest({
296
+ requestId: Date.now() + Math.random(),
297
+ acks: structureAcks,
298
+ });
299
+ }
300
+ clearPendingLiveEditState();
301
+ toast.success(t("designEditor.pendingVisualStyles.verifiedToast"));
302
+ return;
303
+ }
304
+ }
305
+
306
+ if (Date.now() >= nextSourcePollAt) {
307
+ nextSourcePollAt = Date.now() + PENDING_STRUCTURE_SOURCE_POLL_MS;
308
+ try {
309
+ const currentVersions = await Promise.all(
310
+ session.sources.map(async (source) => {
311
+ const result = (await callAction(
312
+ "read-local-file",
313
+ {
314
+ designId: id,
315
+ connectionId: source.connectionId,
316
+ path: source.path,
317
+ },
318
+ { method: "GET" },
319
+ )) as { versionHash?: string } | undefined;
320
+ return result?.versionHash;
321
+ }),
322
+ );
323
+ if (session.cancelled) return;
324
+ sourceChanged = currentVersions.some(
325
+ (versionHash, index) =>
326
+ Boolean(versionHash) &&
327
+ versionHash !== session.sources[index]?.baselineVersionHash,
328
+ );
329
+ if (sourceChanged) {
330
+ if (!verificationRuntimeMounted) {
331
+ verificationRuntimeMounted = true;
332
+ deadline = Math.min(
333
+ deadline,
334
+ Date.now() + PENDING_STRUCTURE_RUNTIME_TIMEOUT_MS,
335
+ );
336
+ pendingStructureVerificationSnapshotsRef.current.set(
337
+ requestId,
338
+ {},
339
+ );
340
+ setRuntimeStructureVerificationRequest({
341
+ requestId,
342
+ screenIds,
343
+ });
344
+ }
345
+ setPendingStructureVerificationStatus("awaiting-runtime");
346
+ }
347
+ // coercion-ok: moved verbatim; a failed optional probe here is indistinguishable from "not applicable" by design.
348
+ } catch {
349
+ // A transient bridge read must not discard the still-undoable
350
+ // preview. Keep polling until the bounded deadline.
351
+ }
352
+ }
353
+ await new Promise((resolve) =>
354
+ window.setTimeout(resolve, PENDING_STRUCTURE_RUNTIME_POLL_MS),
355
+ );
356
+ }
357
+ if (session.cancelled) return;
358
+ cancelPendingStructureVerification("conflict");
359
+ toast.error(t("designEditor.pendingVisualStyles.conflictToast"));
360
+ } catch (error) {
361
+ if (session.cancelled) return;
362
+ console.error(
363
+ "[DesignEditor] pending structure verification failed:",
364
+ error,
365
+ );
366
+ cancelPendingStructureVerification("conflict");
367
+ toast.error(
368
+ t("designEditor.pendingVisualStyles.sourceCheckFailedToast"),
369
+ { description: actionErrorDetail(error) },
370
+ );
371
+ }
372
+ } finally {
373
+ pendingAgentHandoffBusyRef.current = false;
374
+ setPendingAgentHandoffBusy(false);
375
+ }
376
+ }