@agent-native/core 0.149.6 → 0.151.0

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 (177) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  3. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +83 -45
  4. package/corpus/templates/analytics/changelog/2026-08-11-analytics-dashboards-keep-the-selected-tab-when-reopened.md +6 -0
  5. package/corpus/templates/assets/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  6. package/corpus/templates/brain/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  7. package/corpus/templates/calendar/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  8. package/corpus/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  9. package/corpus/templates/clips/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  10. package/corpus/templates/clips/changelog/2026-08-11-clips-recovers-from-transient-updates-and-avoids-macos-priva.md +6 -0
  11. package/corpus/templates/clips/desktop/src/app.tsx +32 -176
  12. package/corpus/templates/clips/desktop/src/lib/updater.ts +52 -37
  13. package/corpus/templates/clips/desktop/src/styles.css +1 -166
  14. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -0
  15. package/corpus/templates/clips/desktop/src-tauri/Info.plist +4 -0
  16. package/corpus/templates/clips/desktop/src-tauri/src/eventkit.rs +28 -1
  17. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  18. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +48 -1
  19. package/corpus/templates/clips/desktop/src-tauri/src/permission_status.rs +3 -0
  20. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +5 -0
  21. package/corpus/templates/clips/server/plugins/auth.ts +3 -0
  22. package/corpus/templates/content/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  23. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +2 -1
  24. package/corpus/templates/design/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  25. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +5 -1
  26. package/corpus/templates/design/changelog/2026-08-11-design-agent-prompts-copy-successfully-in-the-desktop-app.md +6 -0
  27. package/corpus/templates/dispatch/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  28. package/corpus/templates/dispatch/app/global.css +2 -2
  29. package/corpus/templates/dispatch/changelog/2026-08-10-desktop-workspace-sign-in.md +6 -0
  30. package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-shows-the-generic-chat-starter-among-defa.md +6 -0
  31. package/corpus/templates/dispatch/changelog/2026-08-11-integrations-and-scheduled-work-open-as-first-class-electron-pages.md +6 -0
  32. package/corpus/templates/dispatch/changelog/2026-08-11-sidebar-text-is-slightly-easier-to-read-in-light-and-dark-mo.md +6 -0
  33. package/corpus/templates/dispatch/server/lib/identity-sso.ts +57 -31
  34. package/corpus/templates/dispatch/server/plugins/feature-flags.ts +5 -0
  35. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +82 -33
  36. package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +7 -15
  37. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +9 -1
  38. package/corpus/templates/dispatch/shared/feature-flags.ts +15 -0
  39. package/corpus/templates/factory/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  40. package/corpus/templates/forms/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  41. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +8 -7
  42. package/corpus/templates/forms/app/hooks/use-forms.ts +8 -1
  43. package/corpus/templates/forms/changelog/2026-08-11-fixed-public-forms-so-conditional-questions-stay-hidden-unti.md +6 -0
  44. package/corpus/templates/forms/server/handlers/submissions.ts +2 -1
  45. package/corpus/templates/forms/server/lib/public-form-ssr.ts +37 -2
  46. package/corpus/templates/forms/shared/page-url.ts +54 -0
  47. package/corpus/templates/forms/shared/types.ts +4 -4
  48. package/corpus/templates/mail/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  49. package/corpus/templates/plan/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  50. package/corpus/templates/slides/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  51. package/corpus/templates/slides/actions/add-slide.ts +0 -27
  52. package/corpus/templates/slides/actions/import-file.ts +28 -9
  53. package/corpus/templates/slides/actions/view-screen.ts +4 -4
  54. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +8 -1
  55. package/corpus/templates/slides/app/components/editor/AiEditingMarker.tsx +25 -0
  56. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +43 -48
  57. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +9 -6
  58. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -9
  59. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +2 -0
  60. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +0 -26
  61. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +904 -217
  62. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
  63. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +11 -0
  64. package/corpus/templates/slides/app/components/editor/inline-edit-session.ts +16 -0
  65. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +22 -6
  66. package/corpus/templates/slides/app/components/editor/style-clipboard.ts +61 -0
  67. package/corpus/templates/slides/app/components/layout/Layout.tsx +26 -2
  68. package/corpus/templates/slides/app/components/ui/context-menu.tsx +1 -0
  69. package/corpus/templates/slides/app/context/DeckContext.tsx +75 -25
  70. package/corpus/templates/slides/app/global.css +1 -1
  71. package/corpus/templates/slides/app/i18n/en-US.ts +6 -1
  72. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
  73. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +12 -12
  74. package/corpus/templates/slides/app/lib/slide-agent-context.ts +52 -0
  75. package/corpus/templates/slides/app/lib/slide-image-replacement.ts +61 -0
  76. package/corpus/templates/slides/app/pages/DeckEditor.tsx +55 -116
  77. package/corpus/templates/slides/app/pages/Index.tsx +28 -3
  78. package/corpus/templates/slides/changelog/2026-08-11-agent-chat-now-follows-the-current-slide-and-selection.md +6 -0
  79. package/corpus/templates/slides/changelog/2026-08-11-deck-titles-now-use-slide-content-instead-of-placeholder-filenames.md +6 -0
  80. package/corpus/templates/slides/changelog/2026-08-11-history-now-opens-from-the-deck-overflow-menu.md +6 -0
  81. package/corpus/templates/slides/changelog/2026-08-11-images-can-be-dropped-directly-onto-slides-and-resized-or-re.md +6 -0
  82. package/corpus/templates/slides/changelog/2026-08-11-new-decks-now-transition-directly-to-a-full-page-editor-load.md +6 -0
  83. package/corpus/templates/slides/changelog/2026-08-11-selected-slide-layers-can-be-nudged-precisely-with-arrow-key.md +6 -0
  84. package/corpus/templates/slides/changelog/2026-08-11-slides-can-copy-and-paste-element-styles-with-cmd-option-c-v.md +6 -0
  85. package/corpus/templates/slides/changelog/2026-08-11-slides-shows-one-compact-ai-marker-and-keeps-your-current-sl.md +6 -0
  86. package/corpus/templates/slides/changelog/2026-08-11-text-edits-preserve-slide-layout-when-no-changes-are-made.md +6 -0
  87. package/corpus/templates/slides/changelog/2026-08-11-the-style-toolbar-now-uses-the-full-available-width.md +6 -0
  88. package/corpus/templates/slides/changelog/2026-08-11-undo-and-redo-now-move-one-visible-slide-state-at-a-time.md +6 -0
  89. package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -2
  90. package/corpus/templates/slides/shared/deck-title.ts +56 -0
  91. package/corpus/templates/tasks/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  92. package/dist/agent/run-store.js +6 -1
  93. package/dist/agent/thread-data-builder.js +19 -44
  94. package/dist/agent/types.d.ts +13 -0
  95. package/dist/catalog.json +1 -0
  96. package/dist/cli/create.js +1 -1
  97. package/dist/client/AssistantChat.js +58 -10
  98. package/dist/client/FileStorageSetupCard.d.ts +6 -0
  99. package/dist/client/FileStorageSetupCard.js +18 -0
  100. package/dist/client/NewWorkspaceAppFlow.js +10 -0
  101. package/dist/client/agent-chat-adapter.js +87 -11
  102. package/dist/client/agent-chat.d.ts +1 -1
  103. package/dist/client/agent-chat.js +8 -3
  104. package/dist/client/chat/attachment-adapters.js +10 -0
  105. package/dist/client/chat/message-components.d.ts +4 -1
  106. package/dist/client/chat/message-components.js +37 -13
  107. package/dist/client/chat/tool-call-display.js +14 -0
  108. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  109. package/dist/client/chat-first/apps-rail.js +6 -6
  110. package/dist/client/chat-first/copy.js +1 -0
  111. package/dist/client/chat-first/types.d.ts +1 -0
  112. package/dist/client/extensions/EmbeddedExtension.js +1 -1
  113. package/dist/client/extensions/ExtensionViewer.js +1 -1
  114. package/dist/client/extensions/InlineExtensionFrame.js +1 -1
  115. package/dist/client/onboarding/FirstRunOnboarding.js +17 -2
  116. package/dist/client/settings/FileStorageSettingsForm.d.ts +2 -0
  117. package/dist/client/settings/FileStorageSettingsForm.js +113 -0
  118. package/dist/client/settings/SettingsPanel.js +2 -1
  119. package/dist/client/sse-event-processor.js +21 -0
  120. package/dist/collab/awareness.d.ts +2 -2
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/extensions/actions.js +2 -2
  123. package/dist/extensions/html-shell.js +3 -1
  124. package/dist/file-upload/pre-upload-attachments.d.ts +15 -10
  125. package/dist/file-upload/pre-upload-attachments.js +68 -24
  126. package/dist/file-upload/registry.d.ts +4 -5
  127. package/dist/file-upload/registry.js +7 -14
  128. package/dist/file-upload/s3.d.ts +9 -0
  129. package/dist/file-upload/s3.js +168 -0
  130. package/dist/file-upload/types.d.ts +3 -3
  131. package/dist/file-upload/types.js +2 -2
  132. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  133. package/dist/notifications/routes.d.ts +6 -6
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/onboarding/app-profile.js +23 -1
  136. package/dist/onboarding/default-steps.js +69 -0
  137. package/dist/onboarding/types.d.ts +2 -0
  138. package/dist/progress/routes.d.ts +1 -1
  139. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  140. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  141. package/dist/secrets/routes.d.ts +3 -3
  142. package/dist/server/agent-chat/browser-team-tools.js +20 -0
  143. package/dist/server/agent-chat-plugin.js +12 -3
  144. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  145. package/dist/server/auth.js +45 -21
  146. package/dist/server/better-auth-instance.d.ts +2 -0
  147. package/dist/server/better-auth-instance.js +2 -2
  148. package/dist/server/core-routes-plugin.d.ts +1 -0
  149. package/dist/server/core-routes-plugin.js +26 -16
  150. package/dist/server/identity-sso-store.d.ts +3 -0
  151. package/dist/server/identity-sso-store.js +45 -0
  152. package/dist/server/identity-sso.d.ts +20 -10
  153. package/dist/server/identity-sso.js +79 -31
  154. package/dist/server/index.d.ts +1 -1
  155. package/dist/server/index.js +1 -1
  156. package/dist/server/realtime-token.d.ts +1 -1
  157. package/dist/server/transcribe-voice.d.ts +1 -1
  158. package/dist/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  159. package/dist/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  160. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
  161. package/dist/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  162. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
  163. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
  164. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
  165. package/docs/content/authentication.mdx +27 -0
  166. package/docs/content/cross-app-sso.mdx +38 -8
  167. package/docs/content/extensions.mdx +15 -15
  168. package/docs/content/file-uploads.mdx +10 -12
  169. package/docs/content/generative-ui.mdx +6 -1
  170. package/package.json +2 -2
  171. package/src/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  172. package/src/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  173. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
  174. package/src/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
  175. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
  176. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
  177. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
@@ -49,6 +49,34 @@ sendToAgentChat({
49
49
  });
50
50
  ```
51
51
 
52
+ ### Keep user text separate from injected context
53
+
54
+ Treat the visible `message` as the user's request or the shortest clear
55
+ description of an app-initiated operation. Put context the user did not type —
56
+ IDs, URLs, filenames, current selection or screen state, serialized records,
57
+ upload instructions, and bounded source excerpts — in the `context` field.
58
+ `context` is model input carried through the agent turn; it is stripped from
59
+ the rendered user message, so do not concatenate it into `message` with labels,
60
+ blank-line sections, or `<context>` tags yourself.
61
+
62
+ Use the related surface for each kind of supporting input:
63
+
64
+ | Surface | Use for |
65
+ | --- | --- |
66
+ | `message` | User-authored intent or a concise app action description |
67
+ | `context` | Derived metadata and bounded text needed to carry out that intent |
68
+ | `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
69
+ | `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
70
+
71
+ If an app builds a prompt from a form, selection, upload, or editor state, keep
72
+ the visible message short and pass the assembled details as `context`. Preserve
73
+ the user's actual freeform text in `message` when it is the request; do not
74
+ restate it as metadata in `context` unless the agent needs a structured copy.
75
+
76
+ This boundary applies to auto-submitted turns and prefills. A hidden context
77
+ field is not a license to send unbounded records or secrets: cap excerpts,
78
+ prefer stable IDs and URLs, and use an action or resource lookup for full data.
79
+
52
80
  **From the UI, in the background:**
53
81
 
54
82
  ```ts
@@ -1792,6 +1792,7 @@ export async function getRunStatus(runId) {
1792
1792
  * a second, competing truth.
1793
1793
  */
1794
1794
  const TURN_ENDING_ABORT_REASONS = new Set(["user", "displaced"]);
1795
+ const USER_INITIATED_ABORT_REASONS = new Set(["user", "abort"]);
1795
1796
  // Only infrastructure interruptions that the client can safely resume are
1796
1797
  // recoverable. An arbitrary caller-supplied abort reason must never become an
1797
1798
  // auto-continue signal, because that turns an explicit stop into new work.
@@ -1816,8 +1817,12 @@ export function terminalEventForAbortReason(reason) {
1816
1817
  };
1817
1818
  }
1818
1819
  if (TURN_ENDING_ABORT_REASONS.has(normalized) ||
1820
+ USER_INITIATED_ABORT_REASONS.has(normalized) ||
1819
1821
  normalized.startsWith("user_")) {
1820
- return { type: "done" };
1822
+ return {
1823
+ type: "done",
1824
+ ...(normalized !== "displaced" ? { reason: "user" } : {}),
1825
+ };
1821
1826
  }
1822
1827
  return {
1823
1828
  type: "error",
@@ -3,13 +3,6 @@ import { isCredentialGapCodeAgentEvent, normalizeCodeAgentTranscript, } from "..
3
3
  const INTERRUPTED_TOOL_RESULT = "Interrupted before this tool returned a result.";
4
4
  export const ASSISTANT_RUN_DURATION_METADATA_KEY = "agentNativeRunDurationMs";
5
5
  const MAX_STORED_ATTACHMENT_CHARS = 60_000;
6
- /**
7
- * When no file-upload provider is configured we fall back to storing base64
8
- * directly in the SQL thread_data column. Cap the raw base64 per attachment to
9
- * avoid unbounded row growth. Attachments larger than this get a '[truncated]'
10
- * marker so the transcript still renders but the column stays sane.
11
- */
12
- const MAX_STORED_BASE64_BYTES = 2 * 1024 * 1024; // 2 MB per attachment
13
6
  function isInternalContinuationError(event) {
14
7
  const code = String(event.errorCode ?? "").toLowerCase();
15
8
  const msg = event.error.toLowerCase();
@@ -949,23 +942,6 @@ function textAttachmentEnvelope(att, text) {
949
942
  ].filter(Boolean);
950
943
  return `<attachment ${attrs.join(" ")}>\n${truncateStoredAttachment(text)}\n</attachment>`;
951
944
  }
952
- /**
953
- * Cap a base64 data-URL string for storage. When the encoded string is over
954
- * the limit we replace the base64 payload with a truncation marker so the
955
- * transcript still renders the attachment chip but doesn't bloat SQL.
956
- */
957
- function capBase64DataUrl(dataUrl) {
958
- const commaIdx = dataUrl.indexOf(",");
959
- if (commaIdx === -1)
960
- return dataUrl;
961
- const header = dataUrl.slice(0, commaIdx + 1);
962
- const b64 = dataUrl.slice(commaIdx + 1);
963
- // Each base64 char encodes 6 bits; 4 chars = 3 bytes.
964
- const approxBytes = Math.floor((b64.length * 3) / 4);
965
- if (approxBytes <= MAX_STORED_BASE64_BYTES)
966
- return dataUrl;
967
- return `${header}[base64 truncated — ${approxBytes.toLocaleString()} bytes exceeds storage limit]`;
968
- }
969
945
  function buildStoredAttachments(attachments, runId) {
970
946
  return (attachments ?? [])
971
947
  .map((att, index) => {
@@ -1008,17 +984,7 @@ function buildStoredAttachments(attachments, runId) {
1008
984
  },
1009
985
  };
1010
986
  }
1011
- if (att.type === "image" && att.data) {
1012
- return {
1013
- id,
1014
- type: "image",
1015
- name: att.name,
1016
- contentType: att.contentType,
1017
- status: { type: "complete" },
1018
- content: [{ type: "image", image: capBase64DataUrl(att.data) }],
1019
- };
1020
- }
1021
- if (att.data) {
987
+ if (typeof att.text === "string" && att.text.length > 0) {
1022
988
  return {
1023
989
  id,
1024
990
  type: "file",
@@ -1026,25 +992,34 @@ function buildStoredAttachments(attachments, runId) {
1026
992
  contentType: att.contentType,
1027
993
  status: { type: "complete" },
1028
994
  content: [
1029
- {
1030
- type: "file",
1031
- data: capBase64DataUrl(att.data),
1032
- mimeType: att.contentType,
1033
- filename: att.name,
1034
- },
995
+ { type: "text", text: textAttachmentEnvelope(att, att.text) },
1035
996
  ],
1036
997
  };
1037
998
  }
1038
- if (typeof att.text === "string" && att.text.length > 0) {
999
+ // Binary attachment data is request-scoped input, not thread state. If
1000
+ // the provider was unavailable or failed, retain only a visible marker
1001
+ // so the transcript can explain why the attachment needs storage setup
1002
+ // without putting base64 bytes in SQL.
1003
+ if (att.storageRequired === true || typeof att.data === "string") {
1004
+ const uploadFailed = att.storageUploadFailed === true;
1039
1005
  return {
1040
1006
  id,
1041
- type: "file",
1007
+ type: att.type === "image" ? "image" : "file",
1042
1008
  name: att.name,
1043
1009
  contentType: att.contentType,
1044
1010
  status: { type: "complete" },
1045
1011
  content: [
1046
- { type: "text", text: textAttachmentEnvelope(att, att.text) },
1012
+ {
1013
+ type: "text",
1014
+ text: uploadFailed
1015
+ ? "Attachment not retained: the configured object-storage upload failed. Retry the upload to keep files available throughout this thread."
1016
+ : "Attachment not retained: connect object storage to keep files available throughout this thread.",
1017
+ },
1047
1018
  ],
1019
+ metadata: {
1020
+ storageRequired: true,
1021
+ ...(uploadFailed ? { storageUploadFailed: true } : {}),
1022
+ },
1048
1023
  };
1049
1024
  }
1050
1025
  return null;
@@ -110,6 +110,17 @@ export interface AgentChatAttachment {
110
110
  type: string;
111
111
  name: string;
112
112
  data?: string;
113
+ /** Stable object-storage URL for this attachment, when uploaded. */
114
+ url?: string;
115
+ /** Provider that owns `url` (for example Builder or S3). */
116
+ uploadProvider?: string;
117
+ /** SVG and other unsafe files must stay references, never inline content. */
118
+ referenceOnly?: boolean;
119
+ securityNote?: string;
120
+ /** Set when the current turn could not create a durable object-storage URL. */
121
+ storageRequired?: boolean;
122
+ /** Set when a configured object-storage provider rejected or failed the upload. */
123
+ storageUploadFailed?: boolean;
113
124
  contentType?: string;
114
125
  text?: string;
115
126
  }
@@ -332,6 +343,8 @@ export type AgentChatEvent = {
332
343
  summary: string;
333
344
  } | {
334
345
  type: "done";
346
+ /** Set when a human explicitly stopped the current turn. */
347
+ reason?: "user";
335
348
  } | {
336
349
  type: "error";
337
350
  error: string;
package/dist/catalog.json CHANGED
@@ -17,6 +17,7 @@
17
17
  "@tiptap/extension-horizontal-rule": "3.28.0",
18
18
  "@tiptap/extension-image": "3.28.0",
19
19
  "@tiptap/extension-link": "3.28.0",
20
+ "@tiptap/extension-list": "3.28.0",
20
21
  "@tiptap/extension-placeholder": "3.28.0",
21
22
  "@tiptap/extension-table": "3.28.0",
22
23
  "@tiptap/extension-table-cell": "3.28.0",
@@ -554,7 +554,7 @@ async function scaffoldWorkspaceRoot(targetDir, name) {
554
554
  const existing = fs.existsSync(wsPath)
555
555
  ? fs.readFileSync(wsPath, "utf-8")
556
556
  : "";
557
- if (!existing.includes("catalog:")) {
557
+ if (!/^catalog:\s*$/m.test(existing)) {
558
558
  const catalogYaml = Object.entries(catalog)
559
559
  .map(([k, v]) => ` "${k}": "${v}"`)
560
560
  .join("\n");
@@ -19,7 +19,7 @@ import { AGENT_CHAT_VIEW_TRANSITION_PREPARE_EVENT } from "./chat-view-transition
19
19
  // ─── chat/ module imports ─────────────────────────────────────────────────────
20
20
  import { DownscalingImageAttachmentAdapter, BinaryDocumentAttachmentAdapter, MAX_ESTIMATED_BODY_BYTES, AGGRESSIVE_MAX_IMAGE_DIMENSION, AGGRESSIVE_JPEG_QUALITY, transcodeImageToDataURL, createAgentImageAttachments, serializeQueuedAttachments, estimateAttachmentBodyBytes, } from "./chat/attachment-adapters.js";
21
21
  import { TextStreamingContext } from "./chat/markdown-renderer.js";
22
- import { CheckpointContext, MessageActionsContext, assistantMessageRunId, UserMessage, AssistantMessage, SelectionAttachedPill, RunningActivityStatus, displayableUserMessageText, isHiddenUserMessage, ServerRunActiveContext, } from "./chat/message-components.js";
22
+ import { CheckpointContext, MessageActionsContext, assistantMessageRunId, UserMessage, AssistantMessage, SelectionAttachedPill, RunningActivityStatus, displayableUserMessageText, isHiddenUserMessage, ServerRunActiveContext, UserStoppedRunContext, } from "./chat/message-components.js";
23
23
  import { repoHasAssistantMessage, getRepoMessages, getRepoMessage, shouldImportServerThreadData, dedupeRepoMessagesById, dropEmptyAssistantMessages, withLastAssistantRunDuration, } from "./chat/repo-helpers.js";
24
24
  import { BuilderSetupCard, BuilderSetupContent, LoopLimitContinueCard, RunErrorRecoveryCard, PlanModeCallout, getLoopLimitMetadata, getRunErrorMetadata, getRequestModeMetadata, runErrorKey, } from "./chat/run-recovery.js";
25
25
  import { createAgentChatRuntimeAdapter, } from "./chat/runtime.js";
@@ -3400,6 +3400,50 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3400
3400
  });
3401
3401
  }
3402
3402
  }, [tabId, threadId, threadRuntime]);
3403
+ const markVisibleRunStopped = useCallback(() => {
3404
+ try {
3405
+ const repo = normalizeThreadRepository(threadRuntime.export());
3406
+ const messages = getRepoMessages(repo);
3407
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
3408
+ const entry = messages[index];
3409
+ const message = getRepoMessage(entry);
3410
+ if (message?.role !== "assistant")
3411
+ continue;
3412
+ const metadata = message.metadata ?? {};
3413
+ const custom = metadata.custom && typeof metadata.custom === "object"
3414
+ ? metadata.custom
3415
+ : {};
3416
+ if (custom.userStopped === true)
3417
+ return;
3418
+ const nextMessage = {
3419
+ ...message,
3420
+ metadata: {
3421
+ ...metadata,
3422
+ custom: { ...custom, userStopped: true },
3423
+ },
3424
+ };
3425
+ const nextMessages = messages.slice();
3426
+ nextMessages[index] =
3427
+ entry.message === undefined
3428
+ ? { ...entry, ...nextMessage }
3429
+ : { ...entry, message: nextMessage };
3430
+ threadRuntime.import(ensureMessageMetadata({ ...repo, messages: nextMessages }));
3431
+ return;
3432
+ }
3433
+ }
3434
+ catch (err) {
3435
+ captureError(err, {
3436
+ tags: {
3437
+ source: "agent-chat-client",
3438
+ phase: "mark-user-stopped-run",
3439
+ },
3440
+ extra: {
3441
+ threadId: threadId ?? null,
3442
+ tabId: tabId ?? null,
3443
+ },
3444
+ });
3445
+ }
3446
+ }, [tabId, threadId, threadRuntime]);
3403
3447
  // Abort the active server run (identical to what the Stop button does) so
3404
3448
  // an immediate-while-running send can proceed cleanly without a 409 race.
3405
3449
  // Captured in a stable ref so addToQueue can call it without listing
@@ -3467,6 +3511,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3467
3511
  reconnectTailOnlyRef.current = false;
3468
3512
  }
3469
3513
  settleVisibleInterruptedTools();
3514
+ markVisibleRunStopped();
3470
3515
  threadRuntime.cancelRun();
3471
3516
  if (typeof window !== "undefined") {
3472
3517
  window.dispatchEvent(new CustomEvent("agentNative.chatRunning", {
@@ -3482,6 +3527,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3482
3527
  applyLocalQueuedMessages,
3483
3528
  clearAutoResume,
3484
3529
  isReconnecting,
3530
+ markVisibleRunStopped,
3485
3531
  resetRunningActivity,
3486
3532
  reconnectContent,
3487
3533
  settleVisibleInterruptedTools,
@@ -4142,14 +4188,14 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4142
4188
  return;
4143
4189
  }
4144
4190
  void addToQueue(suggestion);
4145
- }, className: "w-full px-2 py-1 text-center text-[13px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: suggestion }, suggestion))) })) : null, showInlineEmptyThreadFooterSlot ? (_jsx("div", { className: "agent-thread-footer-slot agent-thread-footer-slot--empty", children: resolvedThreadFooterSlot })) : null] })) : (_jsxs(MessageScrollerContent, { className: "agent-thread-content gap-4 px-4 py-4", children: [_jsx(AssistantMessageListErrorBoundary, { resetKey: messageListResetKey, children: _jsx(ThreadPrimitive.Messages
4146
- // assistant-ui indexes message parts through tap resources.
4147
- // Keep this key tied to that shape so clear/add transitions remount stale
4148
- // lookups without remounting on every streamed text update.
4149
- , { components: {
4150
- UserMessage: AssistantChatUserMessageItem,
4151
- AssistantMessage: AssistantChatAssistantMessageItem,
4152
- } }, messageListResetKey) }), visibleLoopLimit && !showRunningInUI && (_jsx(MessageScrollerItem, { children: _jsx(LoopLimitContinueCard, { info: visibleLoopLimit, onContinue: () => {
4191
+ }, className: "w-full px-2 py-1 text-center text-[13px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: suggestion }, suggestion))) })) : null, showInlineEmptyThreadFooterSlot ? (_jsx("div", { className: "agent-thread-footer-slot agent-thread-footer-slot--empty", children: resolvedThreadFooterSlot })) : null] })) : (_jsxs(MessageScrollerContent, { className: "agent-thread-content gap-4 px-4 py-4", children: [_jsx(AssistantMessageListErrorBoundary, { resetKey: messageListResetKey, children: _jsx(UserStoppedRunContext.Provider, { value: wasRecentlyStoppedRun, children: _jsx(ThreadPrimitive.Messages
4192
+ // assistant-ui indexes message parts through tap resources.
4193
+ // Keep this key tied to that shape so clear/add transitions remount stale
4194
+ // lookups without remounting on every streamed text update.
4195
+ , { components: {
4196
+ UserMessage: AssistantChatUserMessageItem,
4197
+ AssistantMessage: AssistantChatAssistantMessageItem,
4198
+ } }, messageListResetKey) }) }), visibleLoopLimit && !showRunningInUI && (_jsx(MessageScrollerItem, { children: _jsx(LoopLimitContinueCard, { info: visibleLoopLimit, onContinue: () => {
4153
4199
  setShowContinue(false);
4154
4200
  setLoopLimitInfo(null);
4155
4201
  addToQueue("Continue from where you left off.", undefined, undefined, undefined, undefined, "queued", "continue");
@@ -4216,7 +4262,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4216
4262
  isRunning,
4217
4263
  requestedIntent: options?.intent,
4218
4264
  }), undefined, true)
4219
- : undefined, willQueue: engineSetupRequired || isRunning, onSlashCommand: onSlashCommand, execMode: execMode, onExecModeChange: onExecModeChange, planModeDisabled: planModeDisabled, planModeDisabledReason: planModeDisabledReason, selectedModel: selectedModel ?? defaultModel, selectedEffort: selectedEffort, availableModels: availableModels, modelListLoading: modelListLoading, onModelChange: onModelChange, onEffortChange: onEffortChange, imageModelMenu: imageModelMenu, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, toolbarSlot: composerToolbarSlot, contextItems: composerContextItems, onRemoveContextItem: removeComposerContextItem, plusMenuMode: plusMenuMode, layoutVariant: composerLayoutVariant, providerConnectStatusEnabled: providerStatusChecksEnabled, voiceEnabled: true, draftScope: threadId || tabId, interceptBuildRequestsForBuilder: true, onAttachmentError: setComposerError, extraActionButton: composerExtraActionButton, stopButton: showRunningInUI ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: () => stopActiveRun(), "aria-label": "Stop response", "data-agent-composer-slot": "stop-button", className: "shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: _jsx(IconPlayerStopFilled, { className: "h-3 w-3" }) }) }), _jsx(TooltipContent, { children: "Stop response" })] })) : undefined })] })) }), showMissingKeySetup &&
4265
+ : undefined, willQueue: engineSetupRequired || isRunning, onSlashCommand: onSlashCommand, execMode: execMode, onExecModeChange: onExecModeChange, planModeDisabled: planModeDisabled, planModeDisabledReason: planModeDisabledReason, selectedModel: selectedModel ?? defaultModel, selectedEffort: selectedEffort, availableModels: availableModels, modelListLoading: modelListLoading, onModelChange: onModelChange, onEffortChange: onEffortChange, imageModelMenu: imageModelMenu, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, toolbarSlot: composerToolbarSlot, contextItems: composerContextItems, onRemoveContextItem: removeComposerContextItem, plusMenuMode: plusMenuMode, layoutVariant: composerLayoutVariant, providerConnectStatusEnabled: providerStatusChecksEnabled, voiceEnabled: true, draftScope: threadId || tabId, interceptBuildRequestsForBuilder: true, onAttachmentError: setComposerError, extraActionButton: composerExtraActionButton, stopButton: showRunningInUI ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: () => stopActiveRun({
4266
+ preserveQueuedMessages: true,
4267
+ }), "aria-label": "Stop response", "data-agent-composer-slot": "stop-button", className: "shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: _jsx(IconPlayerStopFilled, { className: "h-3 w-3" }) }) }), _jsx(TooltipContent, { children: "Stop response" })] })) : undefined })] })) }), showMissingKeySetup &&
4220
4268
  !showInlineMissingKeySetup ? (_jsx(PopoverContent, { side: missingApiKeySetupLayout === "sidebar"
4221
4269
  ? "top"
4222
4270
  : "bottom", align: "center", sideOffset: 8, collisionPadding: 12, "data-agent-native-composer-popover": "true", className: "z-[260] box-border w-[min(calc(100vw-2rem),var(--radix-popover-content-available-width,26rem),26rem)] rounded-lg border-border p-3 shadow-lg", children: _jsx(BuilderSetupContent, { onConnected: handleBuilderConnected, layout: missingApiKeySetupLayout }) })) : null] })] })] }) }) }) }) }) }) }) }) }) }));
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Inline storage setup shown when an attachment cannot be made durable.
3
+ * Builder connect and the custom-key path intentionally share the same
4
+ * surface so the user does not have to understand provider internals first.
5
+ */
6
+ export declare function FileStorageSetupCard(): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ActionButton } from "@agent-native/toolkit/design-system";
3
+ import { BuilderConnectCard, DefaultBuilderConnectCardView, } from "./setup-connections/BuilderConnectCard.js";
4
+ /**
5
+ * Inline storage setup shown when an attachment cannot be made durable.
6
+ * Builder connect and the custom-key path intentionally share the same
7
+ * surface so the user does not have to understand provider internals first.
8
+ */
9
+ export function FileStorageSetupCard() {
10
+ const openCustomStorage = () => {
11
+ if (typeof window === "undefined")
12
+ return;
13
+ window.dispatchEvent(new CustomEvent("agent-panel:open-settings", {
14
+ detail: { section: "uploads" },
15
+ }));
16
+ };
17
+ return (_jsx("div", { className: "space-y-2", "data-testid": "file-storage-setup-card", children: _jsx(BuilderConnectCard, { title: "Connect Builder for file storage", description: "Builder keeps uploaded images and files on durable object storage so the agent can reuse them throughout this thread.", trackingSource: "file_upload_chat_card", render: ({ viewModel, className }) => (_jsxs("div", { className: "space-y-2", children: [_jsx(DefaultBuilderConnectCardView, { viewModel: viewModel, className: className }), _jsxs(ActionButton, { type: "button", intent: "neutral", emphasis: "outline", size: "compact", onPress: openCustomStorage, className: "h-auto w-full justify-start rounded-md px-3 py-2 text-left text-xs font-medium text-foreground", children: ["Use custom storage keys", _jsx("span", { className: "mt-0.5 block text-[11px] font-normal text-muted-foreground", children: "Configure an S3-compatible bucket with a stable public URL." })] })] })) }) }));
18
+ }
@@ -241,6 +241,16 @@ export function NewWorkspaceAppFlow({ sourceApp = "chat", className = "", dispat
241
241
  setBranchUrl(result?.url || null);
242
242
  setStatusMessage("Builder branch created.");
243
243
  }
244
+ else if (result?.mode === "local-agent") {
245
+ sendToAgentChat({
246
+ message: result.prompt ?? message,
247
+ submit: true,
248
+ type: "code",
249
+ newTab: true,
250
+ reuseEmptyTab: true,
251
+ });
252
+ setStatusMessage("Sent to the local agent.");
253
+ }
244
254
  else {
245
255
  setStatusMessage(result?.message ||
246
256
  "This requires a code change. Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.");
@@ -217,6 +217,11 @@ const BACKGROUND_CONTINUATION_TERMINAL_REASONS = new Set([
217
217
  "network_interrupted",
218
218
  "no_progress",
219
219
  ]);
220
+ function isUserInitiatedTerminalReason(reason) {
221
+ return (reason === "aborted:user" ||
222
+ reason === "aborted:abort" ||
223
+ reason.startsWith("aborted:user_"));
224
+ }
220
225
  /**
221
226
  * True when the given `/runs/active` snapshot, if surfaced as a background
222
227
  * terminal outcome right now, would render as an error rather than a
@@ -230,6 +235,8 @@ function isBackgroundTerminalErrorOutcome(lastKnown) {
230
235
  const rawTerminalReason = typeof lastKnown?.terminalReason === "string"
231
236
  ? lastKnown.terminalReason
232
237
  : "";
238
+ if (isUserInitiatedTerminalReason(rawTerminalReason))
239
+ return false;
233
240
  const hasErrorTerminalReason = rawTerminalReason.startsWith("error:");
234
241
  const terminalReason = rawTerminalReason.replace(/^error:/, "");
235
242
  const mappedMessage = terminalReason
@@ -360,37 +367,73 @@ function extractAttachmentsFromMessage(message) {
360
367
  for (const att of message.attachments ?? []) {
361
368
  for (const part of att.content) {
362
369
  if (part.type === "image" && typeof part.image === "string") {
370
+ const persistedMetadata = att && typeof att.metadata === "object"
371
+ ? att.metadata
372
+ : undefined;
373
+ const imageIsDataUrl = part.image.startsWith("data:");
363
374
  attachments.push({
364
375
  type: "image",
365
376
  name: att.name,
366
377
  contentType: att.contentType,
367
- data: part.image,
378
+ ...(imageIsDataUrl ? { data: part.image } : { url: part.image }),
379
+ ...(typeof persistedMetadata?.uploadProvider === "string"
380
+ ? { uploadProvider: persistedMetadata.uploadProvider }
381
+ : {}),
382
+ ...(persistedMetadata?.referenceOnly === true
383
+ ? {
384
+ referenceOnly: true,
385
+ ...(typeof persistedMetadata.securityNote === "string"
386
+ ? { securityNote: persistedMetadata.securityNote }
387
+ : {}),
388
+ }
389
+ : {}),
368
390
  });
369
391
  }
370
- else if (part.type === "file" && typeof part.data === "string") {
392
+ else if (part.type === "file" &&
393
+ (typeof part.data === "string" || typeof part.url === "string")) {
371
394
  const contentType = att.contentType ??
372
395
  (typeof part.mimeType === "string" ? part.mimeType : undefined);
373
- const preserveDataUrl = part.data.startsWith("data:") &&
396
+ const data = typeof part.data === "string" ? part.data : undefined;
397
+ const url = typeof part.url === "string" ? part.url : undefined;
398
+ const persistedMetadata = att && typeof att.metadata === "object"
399
+ ? att.metadata
400
+ : undefined;
401
+ const preserveDataUrl = typeof data === "string" &&
402
+ data.startsWith("data:") &&
374
403
  shouldPreserveFileDataUrl({ name: att.name, contentType });
375
- const decodedText = part.data.startsWith("data:")
376
- ? decodeTextDataUrl(part.data)
404
+ const decodedText = data?.startsWith("data:")
405
+ ? decodeTextDataUrl(data)
377
406
  : null;
378
407
  attachments.push({
379
408
  type: "file",
380
409
  name: att.name,
381
410
  contentType,
411
+ ...(url ? { url } : {}),
382
412
  ...(preserveDataUrl
383
413
  ? {
384
- data: part.data,
414
+ data,
385
415
  ...(decodedText !== null
386
416
  ? { text: truncateOutboundAttachment(decodedText) }
387
417
  : {}),
388
418
  }
389
419
  : decodedText !== null
390
420
  ? { text: truncateOutboundAttachment(decodedText) }
391
- : part.data.startsWith("data:")
392
- ? { data: part.data }
393
- : { text: truncateOutboundAttachment(part.data) }),
421
+ : data?.startsWith("data:")
422
+ ? { data }
423
+ : url
424
+ ? {}
425
+ : { text: truncateOutboundAttachment(data ?? "") }),
426
+ ...(typeof persistedMetadata?.uploadProvider === "string"
427
+ ? { uploadProvider: persistedMetadata.uploadProvider }
428
+ : {}),
429
+ ...(persistedMetadata?.referenceOnly === true
430
+ ? {
431
+ referenceOnly: true,
432
+ ...(typeof persistedMetadata.securityNote === "string"
433
+ ? { securityNote: persistedMetadata.securityNote }
434
+ : {}),
435
+ }
436
+ : {}),
394
437
  });
395
438
  }
396
439
  else if (part.type === "text" && typeof part.text === "string") {
@@ -405,11 +448,12 @@ function extractAttachmentsFromMessage(message) {
405
448
  }
406
449
  for (const part of message.content ?? []) {
407
450
  if (part.type === "image" && typeof part.image === "string") {
451
+ const imageIsDataUrl = part.image.startsWith("data:");
408
452
  attachments.push({
409
453
  type: "image",
410
454
  name: "image",
411
455
  contentType: /^data:([^;,]+)/.exec(part.image)?.[1],
412
- data: part.image,
456
+ ...(imageIsDataUrl ? { data: part.image } : { url: part.image }),
413
457
  });
414
458
  }
415
459
  }
@@ -447,11 +491,15 @@ function attachmentHistoryText(attachment) {
447
491
  attachment.type
448
492
  ? `type="${escapeAttachmentAttribute(attachment.type)}"`
449
493
  : null,
494
+ attachment.url
495
+ ? `url="${escapeAttachmentAttribute(attachment.url)}"`
496
+ : null,
450
497
  ].filter(Boolean);
451
498
  return `<attachment ${attrs.join(" ")}>\n${truncateHistoryAttachment(attachment.text)}\n</attachment>`;
452
499
  }
453
500
  if (attachment.name) {
454
- return `[Attached ${attachment.type || "file"}: ${attachment.name}${attachment.contentType ? ` (${attachment.contentType})` : ""}]`;
501
+ const hostedUrl = attachment.url ? `; hosted URL: ${attachment.url}` : "";
502
+ return `[Attached ${attachment.type || "file"}: ${attachment.name}${attachment.contentType ? ` (${attachment.contentType})` : ""}${hostedUrl}]`;
455
503
  }
456
504
  return null;
457
505
  }
@@ -2071,6 +2119,27 @@ export function createAgentChatAdapter(options) {
2071
2119
  const rawTerminalReason = typeof lastKnown?.terminalReason === "string"
2072
2120
  ? lastKnown.terminalReason
2073
2121
  : "";
2122
+ if (isUserInitiatedTerminalReason(rawTerminalReason)) {
2123
+ settleInterruptedToolCalls(content, undefined, {
2124
+ includeActivity: true,
2125
+ });
2126
+ settleTerminalChatRun();
2127
+ yield {
2128
+ content: [...content],
2129
+ status: {
2130
+ type: "complete",
2131
+ reason: "stop",
2132
+ },
2133
+ metadata: {
2134
+ custom: {
2135
+ ...(runId ? { runId } : {}),
2136
+ userStopped: true,
2137
+ },
2138
+ },
2139
+ };
2140
+ clearActiveRun();
2141
+ return;
2142
+ }
2074
2143
  // terminal_reason is either a bare reason ("dispatch_payload_missing")
2075
2144
  // or "error:<errorCode>" when derived from a terminal error event.
2076
2145
  const hasErrorTerminalReason = rawTerminalReason.startsWith("error:");
@@ -2406,6 +2475,13 @@ export function createAgentChatAdapter(options) {
2406
2475
  if (activeRunId && isOurRun) {
2407
2476
  lastSeenActive = active;
2408
2477
  updateCurrentRunDispatchMode(active?.dispatchMode);
2478
+ const activeTerminalReason = typeof active?.terminalReason === "string"
2479
+ ? active.terminalReason
2480
+ : "";
2481
+ if (isUserInitiatedTerminalReason(activeTerminalReason)) {
2482
+ yield* emitBackgroundTerminalOutcome(active);
2483
+ return "completed";
2484
+ }
2409
2485
  const isTerminal = activeStatus === "completed" || activeStatus === "errored";
2410
2486
  if (isTerminal && replayedTerminalRunIds.has(activeRunId)) {
2411
2487
  if (lastAutoContinueReason === "stale_run" &&
@@ -23,7 +23,7 @@ export interface AgentChatMessage {
23
23
  referenceImagePaths?: string[];
24
24
  /** Optional uploaded reference images */
25
25
  uploadedReferenceImages?: string[];
26
- /** Optional image data URLs to include in the submitted chat message */
26
+ /** Optional image data URLs or durable image URLs to include in the submitted chat message */
27
27
  images?: string[];
28
28
  /** Stable tab identifier — auto-generated if omitted */
29
29
  tabId?: string;
@@ -561,9 +561,14 @@ export function parseSubmitChatMessage(event) {
561
561
  const message = typeof raw.message === "string" ? raw.message : "";
562
562
  if (!message)
563
563
  return null;
564
- const images = Array.isArray(raw.images)
565
- ? raw.images.filter((image) => typeof image === "string" && image.length > 0)
566
- : undefined;
564
+ const imageSources = [
565
+ ...(Array.isArray(raw.images) ? raw.images : []),
566
+ ...(Array.isArray(raw.referenceImagePaths) ? raw.referenceImagePaths : []),
567
+ ...(Array.isArray(raw.uploadedReferenceImages)
568
+ ? raw.uploadedReferenceImages
569
+ : []),
570
+ ].filter((image) => typeof image === "string" && image.trim().length > 0);
571
+ const images = imageSources.length > 0 ? [...new Set(imageSources)] : undefined;
567
572
  return {
568
573
  message,
569
574
  context: typeof raw.context === "string" ? raw.context : undefined,
@@ -267,6 +267,16 @@ export function serializeAttachmentContentPart(part) {
267
267
  ...(typeof part.filename === "string" ? { filename: part.filename } : {}),
268
268
  };
269
269
  }
270
+ if (part.type === "file" && typeof part.url === "string") {
271
+ return {
272
+ type: "file",
273
+ url: part.url,
274
+ mimeType: typeof part.mimeType === "string"
275
+ ? part.mimeType
276
+ : "application/octet-stream",
277
+ ...(typeof part.filename === "string" ? { filename: part.filename } : {}),
278
+ };
279
+ }
270
280
  return null;
271
281
  }
272
282
  export async function serializeQueuedAttachments(attachments) {
@@ -51,6 +51,7 @@ export declare function shouldOfferRestore(args: {
51
51
  export declare function assistantMessageRunId(message: unknown): string | undefined;
52
52
  /** Stable logical-turn identity shared by chained continuation run IDs. */
53
53
  export declare function assistantMessageTurnId(message: unknown): string | undefined;
54
+ export declare function assistantMessageWasUserStopped(message: unknown): boolean;
54
55
  export declare function resolveAssistantRequestId(message: unknown, activeRun: {
55
56
  threadId: string;
56
57
  runId: string;
@@ -91,13 +92,15 @@ export declare function shouldShowAssistantMessageFooter({ isLast, chatRunning,
91
92
  * that the agent stopped.
92
93
  */
93
94
  export declare const ServerRunActiveContext: React.Context<boolean>;
94
- export declare function shouldShowMissingFinalResponse({ isCurrentTurnRunning, serverRunActive, statusIsTerminal, hasAssistantText, hasUnresolvedTool, hasCompletedCustomUi, }: {
95
+ export declare const UserStoppedRunContext: React.Context<(runId?: string) => boolean>;
96
+ export declare function shouldShowMissingFinalResponse({ isCurrentTurnRunning, serverRunActive, statusIsTerminal, hasAssistantText, hasUnresolvedTool, hasCompletedCustomUi, userStoppedRun, }: {
95
97
  isCurrentTurnRunning: boolean;
96
98
  serverRunActive?: boolean;
97
99
  statusIsTerminal: boolean;
98
100
  hasAssistantText: boolean;
99
101
  hasUnresolvedTool: boolean;
100
102
  hasCompletedCustomUi?: boolean;
103
+ userStoppedRun?: boolean;
101
104
  }): boolean;
102
105
  export declare function useSettledFlag(active: boolean, delayMs: number): boolean;
103
106
  export declare function shouldShowAssistantWorkSummary({ isLast, isComplete, hasCollapsibleWork, hasUnresolvedTool, chatRunning, }: {