@jini-ai/chat 0.3.11 → 0.5.1

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 (205) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +152 -6
  3. package/dist/core/ag-ui/entry.d.ts +53 -0
  4. package/dist/core/ag-ui/entry.js +135 -0
  5. package/dist/core/agentic/chat-capabilities.d.ts +1 -1
  6. package/dist/core/agentic/index.d.ts +1 -1
  7. package/dist/core/agentic/index.js +1 -1
  8. package/dist/core/assistant-content.d.ts +6 -2
  9. package/dist/core/assistant-content.js +2 -2
  10. package/dist/core/compact-events.d.ts +3 -1
  11. package/dist/core/compact-events.js +1 -1
  12. package/dist/core/events.d.ts +1 -1
  13. package/dist/core/events.js +1 -1
  14. package/dist/core/index.d.ts +3 -3
  15. package/dist/core/index.js +3 -3
  16. package/dist/core/messages.d.ts +4 -2
  17. package/dist/core/messages.js +2 -2
  18. package/dist/core/partial-json.d.ts +6 -2
  19. package/dist/core/partial-json.js +3 -3
  20. package/dist/core/persistence/index.d.ts +1 -1
  21. package/dist/core/persistence/index.js +1 -1
  22. package/dist/core/persistence/ports.d.ts +2 -123
  23. package/dist/core/persistence/title.d.ts +3 -1
  24. package/dist/core/persistence/title.js +1 -1
  25. package/dist/core/question-form/format.d.ts +16 -7
  26. package/dist/core/question-form/format.js +3 -3
  27. package/dist/core/question-form/normalize.d.ts +7 -2
  28. package/dist/core/question-form/normalize.js +5 -5
  29. package/dist/core/question-form/scan.d.ts +18 -6
  30. package/dist/core/question-form/scan.js +14 -14
  31. package/dist/core/run-activity.d.ts +16 -4
  32. package/dist/core/run-activity.js +11 -11
  33. package/dist/core/run-events/entry.d.ts +121 -0
  34. package/dist/core/run-events/entry.js +290 -0
  35. package/dist/core/todos.d.ts +20 -8
  36. package/dist/core/todos.js +11 -11
  37. package/dist/core/tool-events.d.ts +3 -1
  38. package/dist/core/tool-events.js +1 -1
  39. package/dist/core/tool-output.d.ts +3 -1
  40. package/dist/core/tool-output.js +1 -1
  41. package/dist/core/tools.d.ts +14 -3
  42. package/dist/core/tools.js +3 -3
  43. package/dist/core/transcript.d.ts +13 -5
  44. package/dist/core/transcript.js +5 -5
  45. package/dist/core/util/manifest.d.ts +9 -3
  46. package/dist/core/util/manifest.js +3 -3
  47. package/dist/core/util/markdown-context.d.ts +11 -3
  48. package/dist/core/util/markdown-context.js +3 -3
  49. package/dist/core/util/markup-attributes.d.ts +3 -1
  50. package/dist/core/util/markup-attributes.js +1 -1
  51. package/dist/core/util/parser.d.ts +9 -5
  52. package/dist/core/util/parser.js +13 -13
  53. package/dist/core/util/recover.d.ts +6 -2
  54. package/dist/core/util/recover.js +6 -6
  55. package/dist/core/util/strip.d.ts +19 -5
  56. package/dist/core/util/strip.js +17 -17
  57. package/dist/core/util/validate.d.ts +3 -1
  58. package/dist/core/util/validate.js +1 -1
  59. package/dist/embed-widget/browser/page-actions.d.ts +121 -0
  60. package/dist/embed-widget/browser/page-actions.js +113 -0
  61. package/dist/embed-widget/browser/session-store.d.ts +44 -0
  62. package/dist/embed-widget/browser/session-store.js +103 -0
  63. package/dist/embed-widget/react/embed.d.ts +45 -0
  64. package/dist/embed-widget/react/embed.js +87 -0
  65. package/dist/embed-widget/react/header.d.ts +21 -0
  66. package/dist/embed-widget/react/header.js +18 -0
  67. package/dist/embed-widget/transports/fetch-sse.d.ts +75 -0
  68. package/dist/embed-widget/transports/fetch-sse.js +175 -0
  69. package/dist/react/agent-bridge/frontend-session-bridge.d.ts +15 -3
  70. package/dist/react/agent-bridge/frontend-session-bridge.js +13 -6
  71. package/dist/react/artifact-types.d.ts +3 -3
  72. package/dist/react/artifact-types.js +3 -3
  73. package/dist/react/components/A2uiSurfaceCard.js +34 -13
  74. package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -2
  75. package/dist/react/components/AttachmentPreviewModal.hooks.js +5 -5
  76. package/dist/react/components/AttachmentPreviewModal.js +2 -2
  77. package/dist/react/components/AttachmentTray.d.ts +3 -1
  78. package/dist/react/components/AttachmentTray.js +2 -2
  79. package/dist/react/components/ChatFab.js +1 -1
  80. package/dist/react/components/Composer.js +9 -9
  81. package/dist/react/components/ConversationList.js +1 -1
  82. package/dist/react/components/McpUiSurfaceCard.d.ts +4 -2
  83. package/dist/react/components/McpUiSurfaceCard.js +5 -5
  84. package/dist/react/components/MessageList.js +1 -1
  85. package/dist/react/components/MessageRow.js +9 -9
  86. package/dist/react/components/QuestionForm.js +5 -5
  87. package/dist/react/components/ToolCard.js +9 -9
  88. package/dist/react/components/composer-discovery.d.ts +29 -8
  89. package/dist/react/components/composer-discovery.js +10 -10
  90. package/dist/react/ext-event-renderer-registry.d.ts +15 -5
  91. package/dist/react/ext-event-renderer-registry.js +4 -4
  92. package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +3 -1
  93. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +3 -3
  94. package/dist/react/features/chat-pane/components/ChatPane.js +43 -43
  95. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.d.ts +4 -1
  96. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +5 -35
  97. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.d.ts +5 -2
  98. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +1 -1
  99. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +38 -10
  100. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +15 -15
  101. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +31 -31
  102. package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.d.ts +3 -1
  103. package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +3 -3
  104. package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.d.ts +4 -1
  105. package/dist/react/features/chat-pane/hooks/useChatPaneComposerPlaceholder.hooks.js +1 -1
  106. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +30 -30
  107. package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +57 -57
  108. package/dist/react/features/chat-pane/rules.d.ts +13 -4
  109. package/dist/react/features/chat-pane/rules.js +5 -5
  110. package/dist/react/features/chat-pane/types.d.ts +1 -1
  111. package/dist/react/hooks/attachment-preview-cache.d.ts +7 -2
  112. package/dist/react/hooks/attachment-preview-cache.js +2 -2
  113. package/dist/react/hooks/composer-draft-cache.d.ts +17 -5
  114. package/dist/react/hooks/composer-draft-cache.js +6 -6
  115. package/dist/react/hooks/useArtifactStream.d.ts +5 -1
  116. package/dist/react/hooks/useArtifactStream.js +3 -3
  117. package/dist/react/hooks/useChatFabDrag.d.ts +9 -4
  118. package/dist/react/hooks/useChatFabDrag.js +4 -4
  119. package/dist/react/hooks/useComposer.js +8 -8
  120. package/dist/react/hooks/useConversation.js +56 -4
  121. package/dist/react/hooks/useExtEventGroups.d.ts +5 -1
  122. package/dist/react/hooks/useExtEventGroups.js +1 -1
  123. package/dist/react/hooks/useLatestOperation.d.ts +8 -3
  124. package/dist/react/hooks/useLatestOperation.js +4 -4
  125. package/dist/react/hooks/usePinnedTodos.d.ts +3 -1
  126. package/dist/react/hooks/usePinnedTodos.js +3 -3
  127. package/dist/react/hooks/useQuestionForms.d.ts +7 -2
  128. package/dist/react/hooks/useQuestionForms.js +7 -7
  129. package/dist/react/hooks/useRunActivity.d.ts +6 -1
  130. package/dist/react/hooks/useRunActivity.js +3 -3
  131. package/dist/react/hooks/useRunStream.d.ts +3 -1
  132. package/dist/react/hooks/useRunStream.js +1 -1
  133. package/dist/react/hooks/useToolTimeline.d.ts +3 -1
  134. package/dist/react/hooks/useToolTimeline.js +3 -3
  135. package/dist/react/index.d.ts +4 -4
  136. package/dist/react/index.js +6 -6
  137. package/dist/react/message-blocks.d.ts +10 -4
  138. package/dist/react/message-blocks.js +2 -2
  139. package/dist/react/styles/reference.css +3 -3
  140. package/dist/react/tool-renderer-registry.d.ts +8 -3
  141. package/dist/react/tool-renderer-registry.js +3 -3
  142. package/dist/react/util/defined-props.d.ts +3 -1
  143. package/dist/react/util/defined-props.js +1 -1
  144. package/dist/server/run-finalizer/entry.d.ts +47 -0
  145. package/dist/server/run-finalizer/entry.js +156 -0
  146. package/dist/server/run-finalizer/ports.d.ts +52 -0
  147. package/dist/store/contracts/index.d.ts +5 -0
  148. package/dist/store/contracts/index.js +5 -0
  149. package/dist/store/errors.d.ts +12 -0
  150. package/dist/store/errors.js +18 -0
  151. package/dist/store/index.d.ts +4 -0
  152. package/dist/store/index.js +2 -0
  153. package/dist/store/legacy/index.d.ts +3 -0
  154. package/dist/store/legacy/index.js +2 -0
  155. package/dist/store/legacy/sqlite/conversations.d.ts +129 -0
  156. package/dist/store/legacy/sqlite/conversations.js +274 -0
  157. package/dist/store/legacy/sqlite/index.d.ts +6 -0
  158. package/dist/store/legacy/sqlite/index.js +5 -0
  159. package/dist/store/legacy/sqlite/messages.d.ts +156 -0
  160. package/dist/store/legacy/sqlite/messages.js +246 -0
  161. package/dist/store/legacy/sqlite/schema.d.ts +3 -0
  162. package/dist/store/legacy/sqlite/schema.js +40 -0
  163. package/dist/store/pglite/index.d.ts +21 -0
  164. package/dist/store/pglite/index.js +17 -0
  165. package/dist/store/ports.d.ts +121 -0
  166. package/dist/store/ports.js +2 -0
  167. package/dist/store/postgres/index.d.ts +21 -0
  168. package/dist/store/postgres/index.js +17 -0
  169. package/dist/store/sql/boundary.d.ts +28 -0
  170. package/dist/store/sql/boundary.js +32 -0
  171. package/dist/store/sql/cursor.d.ts +24 -0
  172. package/dist/store/sql/cursor.js +49 -0
  173. package/dist/store/sql/maintenance.d.ts +9 -0
  174. package/dist/store/sql/maintenance.js +16 -0
  175. package/dist/store/sql/store.d.ts +22 -0
  176. package/dist/store/sql/store.js +340 -0
  177. package/dist/store/sql/tables.d.ts +34 -0
  178. package/dist/store/sql/tables.js +2 -0
  179. package/dist/store/sqlite/index.d.ts +36 -0
  180. package/dist/store/sqlite/index.js +29 -0
  181. package/dist/store/sqlite/schema.d.ts +49 -0
  182. package/dist/store/sqlite/schema.js +80 -0
  183. package/package.json +211 -14
  184. package/dist/react/features/model-picker/components/CredentialStatusBadge.d.ts +0 -7
  185. package/dist/react/features/model-picker/components/CredentialStatusBadge.js +0 -20
  186. package/dist/react/features/model-picker/components/ModelPicker.d.ts +0 -9
  187. package/dist/react/features/model-picker/components/ModelPicker.js +0 -34
  188. package/dist/react/features/model-picker/constants.d.ts +0 -6
  189. package/dist/react/features/model-picker/constants.js +0 -9
  190. package/dist/react/features/model-picker/dependencies.d.ts +0 -11
  191. package/dist/react/features/model-picker/dependencies.js +0 -10
  192. package/dist/react/features/model-picker/hooks/useModelPicker.hooks.d.ts +0 -27
  193. package/dist/react/features/model-picker/hooks/useModelPicker.hooks.js +0 -62
  194. package/dist/react/features/model-picker/index.d.ts +0 -32
  195. package/dist/react/features/model-picker/index.js +0 -29
  196. package/dist/react/features/model-picker/ports.d.ts +0 -11
  197. package/dist/react/features/model-picker/rules.d.ts +0 -26
  198. package/dist/react/features/model-picker/rules.js +0 -84
  199. package/dist/react/features/model-picker/types.d.ts +0 -38
  200. package/dist/react/features/model-picker/types.js +0 -2
  201. package/dist/react/poc-mcp-ui-official/PocMcpUiCard.d.ts +0 -9
  202. package/dist/react/poc-mcp-ui-official/PocMcpUiCard.js +0 -29
  203. package/dist/react/poc-mcp-ui-official/create-poc-resource.d.ts +0 -30
  204. package/dist/react/poc-mcp-ui-official/create-poc-resource.js +0 -44
  205. /package/dist/{react/features/model-picker → server/run-finalizer}/ports.js +0 -0
@@ -108,11 +108,11 @@ function resolveComposerAttachmentPicker(uploadAttachments, pane, attachmentAcce
108
108
  if (uploadAttachments === undefined)
109
109
  return {};
110
110
  return {
111
- attachmentPicker: definedProps({
112
- onFiles: pane.addAttachments,
113
- uploading: pane.isUploadingAttachments,
114
- accept: attachmentAccept,
115
- }),
111
+ attachmentPicker: definedProps({ source: {
112
+ onFiles: pane.addAttachments,
113
+ uploading: pane.isUploadingAttachments,
114
+ accept: attachmentAccept,
115
+ } }),
116
116
  };
117
117
  }
118
118
  /**
@@ -169,7 +169,7 @@ function ChatPaneComposerArea({ fileDrop, uploadAttachments, pane, disabled, una
169
169
  // rather than refusing it, so the operator can keep writing while the agent thinks.
170
170
  disabled: disabled || unavailable,
171
171
  // The one blocker `send()` handles itself. Every other refusal still greys out the button.
172
- sendDisabled: !pane.canSend && pane.sendBlocker !== 'streaming', running: pane.conversation.isStreaming, onCancel: pane.conversation.cancel, onInterrupt: pane.interruptSend, ...definedProps({ placeholder }), slots: slots, ...resolveComposerAttachmentPicker(uploadAttachments, pane, attachmentAccept), ...resolveComposerWorkingDirectory(workingDirectoryAccess, workingDirectoryControlPlacement, pane) }), _jsx(ChatPaneWorkingDirectoryBlock, { workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, pane: pane, t: t })] }));
172
+ sendDisabled: !pane.canSend && pane.sendBlocker !== 'streaming', running: pane.conversation.isStreaming, onCancel: pane.conversation.cancel, onInterrupt: pane.interruptSend, ...definedProps({ source: { placeholder } }), slots: slots, ...resolveComposerAttachmentPicker(uploadAttachments, pane, attachmentAccept), ...resolveComposerWorkingDirectory(workingDirectoryAccess, workingDirectoryControlPlacement, pane) }), _jsx(ChatPaneWorkingDirectoryBlock, { workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, pane: pane, t: t })] }));
173
173
  }
174
174
  /**
175
175
  * Renders the self-contained chat-pane composition, including runtime and
@@ -180,37 +180,37 @@ function ChatPaneComposerArea({ fileDrop, uploadAttachments, pane, disabled, una
180
180
  */
181
181
  export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, runtimeAccess, runtimeStatusPollMs = 5_000, title, variant = 'workspace', initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, validateAttachments, composerHandle, placeholder, placeholders, suggestions = [], workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
182
182
  const t = useT();
183
- const composerPlaceholder = useChatPaneComposerPlaceholder(placeholder, placeholders);
184
- const inventory = useChatPaneRuntimeInventory(definedProps({
185
- access: runtimeAccess,
186
- initialAgents: injectedAgents,
187
- pollIntervalMs: runtimeStatusPollMs,
188
- }));
183
+ const composerPlaceholder = useChatPaneComposerPlaceholder({ placeholder: placeholder, placeholders: placeholders });
184
+ const inventory = useChatPaneRuntimeInventory(definedProps({ source: {
185
+ access: runtimeAccess,
186
+ initialAgents: injectedAgents,
187
+ pollIntervalMs: runtimeStatusPollMs,
188
+ } }));
189
189
  const runtimeView = resolveRuntimeAccessView(runtimeAccess, injectedAgents, scanningAgents, daemonOnline, onRescanAgents, inventory);
190
190
  // Computed once and threaded into both `useChatPane` (so `sendBlocker` agrees) and this
191
191
  // component's own `unavailable` below (so the status banner and composer agree) — see
192
192
  // `isChatPaneApiModeConfigured` for why API mode alone isn't enough.
193
193
  const apiModeConfigured = isChatPaneApiModeConfigured({ executionMode, apiModeAvailable, byokRuntime });
194
- const pane = useChatPane(definedProps({
195
- transport,
196
- agents: runtimeView.agents,
197
- initialMessages,
198
- conversationId,
199
- initialSelection,
200
- selection: controlledSelection,
201
- onSelectionChange,
202
- runContext,
203
- initialDraft,
204
- validateAttachments,
205
- uploadAttachments,
206
- onActivityChange,
207
- onMessagesChange,
208
- workingDirectory,
209
- initialWorkingDirectory,
210
- onChangeWorkingDirectory,
211
- workingDirectoryAccess,
212
- apiModeConfigured,
213
- }));
194
+ const pane = useChatPane(definedProps({ source: {
195
+ transport,
196
+ agents: runtimeView.agents,
197
+ initialMessages,
198
+ conversationId,
199
+ initialSelection,
200
+ selection: controlledSelection,
201
+ onSelectionChange,
202
+ runContext,
203
+ initialDraft,
204
+ validateAttachments,
205
+ uploadAttachments,
206
+ onActivityChange,
207
+ onMessagesChange,
208
+ workingDirectory,
209
+ initialWorkingDirectory,
210
+ onChangeWorkingDirectory,
211
+ workingDirectoryAccess,
212
+ apiModeConfigured,
213
+ } }));
214
214
  // Mirrors `Composer.tsx`'s own `draftRef`: `composerHandle.insertText` (below) is called from
215
215
  // OUTSIDE any render, so it cannot close over `pane.composer.draft` directly — that would freeze
216
216
  // it at whatever the draft was on the render that captured it. Reassigned unconditionally every
@@ -222,7 +222,7 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
222
222
  return;
223
223
  composerHandle.current = {
224
224
  insertText: (text) => {
225
- pane.composer.setDraft(appendComposerDiscovery(composerDraftRef.current, text));
225
+ pane.composer.setDraft(appendComposerDiscovery({ draft: composerDraftRef.current, insertText: text }));
226
226
  },
227
227
  };
228
228
  return () => {
@@ -236,11 +236,11 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
236
236
  // No `runContext` here on purpose: agent-driven sends go through `pane.sendPrompt`, which builds
237
237
  // the context from the SAME `runContext` already handed to `useChatPane` above. A second copy
238
238
  // would be a second source of truth that could silently drift from the composer's.
239
- useChatPaneAgentControl(pane, definedProps({
240
- enabled: agentControl?.enabled ?? false,
241
- bridgeAccess: agentControl?.bridgeAccess,
242
- webmcp: agentControl?.webmcp,
243
- }));
239
+ useChatPaneAgentControl({ pane: pane }, definedProps({ source: {
240
+ enabled: agentControl?.enabled ?? false,
241
+ bridgeAccess: agentControl?.bridgeAccess,
242
+ webmcp: agentControl?.webmcp,
243
+ } }));
244
244
  // Selection resolution only returns available agents, so absence normally means nothing usable
245
245
  // is selected — except a configured BYOK turn (`apiModeConfigured`, computed above), which calls
246
246
  // the provider directly over HTTP and never touches the CLI inventory.
@@ -256,11 +256,11 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
256
256
  // `composerSlots`'s type excludes both `leadingAccessories` and `footerAccessories` (see
257
257
  // `ChatPaneProps.composerSlots`'s doc), so the two explicit keys below are never overwriting a
258
258
  // caller-supplied value — they are the ONLY source for both.
259
- const slots = definedProps({
260
- ...composerSlots,
261
- leadingAccessories: leadingAccessory,
262
- footerAccessories: (_jsx(AgentRuntimePicker, { agents: runtimeView.agents, value: pane.selection, onChange: pane.setSelection, ...definedProps({ onRescan: runtimeView.rescanAgents }), scanning: runtimeView.scanningAgents, daemonOnline: runtimeView.daemonOnline, placement: runtimePickerPlacement, executionMode: executionMode, apiModeAvailable: apiModeAvailable, ...definedProps({ onExecutionModeChange, byokRuntime, onByokModelChange }) })),
263
- });
259
+ const slots = definedProps({ source: {
260
+ ...composerSlots,
261
+ leadingAccessories: leadingAccessory,
262
+ footerAccessories: (_jsx(AgentRuntimePicker, { agents: runtimeView.agents, value: pane.selection, onChange: pane.setSelection, ...definedProps({ source: { onRescan: runtimeView.rescanAgents } }), scanning: runtimeView.scanningAgents, daemonOnline: runtimeView.daemonOnline, placement: runtimePickerPlacement, executionMode: executionMode, apiModeAvailable: apiModeAvailable, ...definedProps({ source: { onExecutionModeChange, byokRuntime, onByokModelChange } }) })),
263
+ } });
264
264
  const { rootRef, controlsRef } = useChatPaneControlsHeight();
265
265
  return (_jsxs("section", { className: chatPaneClassName(variant, className), style: style, "data-activity": pane.activity, ref: rootRef, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), resolveChatPaneHeader(header, title, pane.reset, t), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompt: pane.queuedPrompt === null ? null : { text: pane.queuedPrompt, onCancel: pane.cancelQueued }, ...(projectFileNames === undefined ? {} : { projectFileNames: new Set(projectFileNames) }) }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [_jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: pane.composer.setDraft, t: t }), _jsx(ChatPaneStatusMessages, { unavailable: unavailable, scanningAgents: runtimeView.scanningAgents, connectingAgents: runtimeView.connectingAgents, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, conversationError: pane.conversation.error, attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: uploadAttachments, pane: pane, disabled: disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: attachmentAccept, workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: workingDirectoryControlPlacement, t: t })] })] }), footer] }));
266
266
  }
@@ -17,5 +17,8 @@ export interface CreateDaemonAttachmentUploaderOptions {
17
17
  * @param baseUrl Origin (or same-origin path prefix) the daemon's API is reachable at. Pass `''`
18
18
  * when the page is served through a proxy that already forwards `/api`.
19
19
  */
20
- export declare function createDaemonAttachmentUploader(baseUrl: string, options?: CreateDaemonAttachmentUploaderOptions): NonNullable<ChatPaneProps['uploadAttachments']>;
20
+ export declare function createDaemonAttachmentUploader({ baseUrl, fetch }: {
21
+ baseUrl: string;
22
+ fetch?: typeof globalThis.fetch;
23
+ }, options?: CreateDaemonAttachmentUploaderOptions): NonNullable<ChatPaneProps['uploadAttachments']>;
21
24
  //# sourceMappingURL=create-daemon-attachment-uploader.d.ts.map
@@ -1,34 +1,3 @@
1
- /**
2
- * @module create-daemon-attachment-uploader
3
- *
4
- * The client half of `@jini-ai/http-kit`'s attachment capability: a ready-made
5
- * `ChatPaneProps['uploadAttachments']` that talks to `POST`/`DELETE /api/attachments`.
6
- *
7
- * ```tsx
8
- * <ChatPane transport={transport} uploadAttachments={createDaemonAttachmentUploader(daemonUrl)} />
9
- * ```
10
- *
11
- * That one line is the whole wire-up for composer drag-and-drop and the file picker — `ChatPane`
12
- * gates both on `uploadAttachments` being present (see `ChatPane.tsx`'s `resolveDropTargetProps`).
13
- *
14
- * **Why the client enforces quotas at all**, when the daemon enforces them anyway: a user who drags
15
- * in a 400 MB video should be told immediately, not after the browser has finished streaming it to
16
- * localhost. These checks are a courtesy, never the security boundary — the daemon re-derives every
17
- * one of them, and only its answer decides anything.
18
- *
19
- * **Bounded concurrency, preserved order.** Two workers pull from a shared index, so at most two
20
- * requests are in flight while results still land at their input positions. The first failure aborts
21
- * the rest and deletes whatever already landed, so a partly-uploaded turn never leaves files behind
22
- * on the daemon waiting for their TTL.
23
- *
24
- * **`content-type` is always `application/octet-stream`**, deliberately not `file.type`. The daemon
25
- * sniffs the real kind from the leading bytes and ignores this header entirely, so forwarding the
26
- * browser's guess buys nothing — and actively breaks things: a dropped `.json` file gets
27
- * `content-type: application/json` from the browser, which an app-wide `express.json()` on the
28
- * daemon then claims, draining the request stream before the upload route can read a byte. A fixed
29
- * octet-stream type is immune to every body parser.
30
- */
31
- import { FETCH_TIMEOUT_MS, fetchWithTimeout } from '@jini-ai/platform/fetch-with-timeout';
32
1
  const DEFAULTS = {
33
2
  maxAttachmentBytes: 20 * 1024 * 1024,
34
3
  maxAttachmentCount: 10,
@@ -80,7 +49,7 @@ async function messageForFailedUpload(response, fallback) {
80
49
  * @param baseUrl Origin (or same-origin path prefix) the daemon's API is reachable at. Pass `''`
81
50
  * when the page is served through a proxy that already forwards `/api`.
82
51
  */
83
- export function createDaemonAttachmentUploader(baseUrl, options = {}) {
52
+ export function createDaemonAttachmentUploader({ baseUrl, fetch }, options = {}) {
84
53
  const maxAttachmentBytes = options.maxAttachmentBytes ?? DEFAULTS.maxAttachmentBytes;
85
54
  const maxAttachmentCount = options.maxAttachmentCount ?? DEFAULTS.maxAttachmentCount;
86
55
  const maxBatchBytes = options.maxBatchBytes ?? DEFAULTS.maxBatchBytes;
@@ -91,7 +60,7 @@ export function createDaemonAttachmentUploader(baseUrl, options = {}) {
91
60
  const batchUsage = new Map();
92
61
  async function uploadOne(file, batchId, signal) {
93
62
  const query = new URLSearchParams({ batch: batchId, name: file.name });
94
- const response = await fetch(`${endpoint}?${query}`, {
63
+ const response = await (fetch ?? globalThis.fetch)(`${endpoint}?${query}`, {
95
64
  method: 'POST',
96
65
  // Fixed, not `file.type` — see this module's doc.
97
66
  headers: { 'content-type': 'application/octet-stream' },
@@ -115,11 +84,12 @@ export function createDaemonAttachmentUploader(baseUrl, options = {}) {
115
84
  // caller-supplied signal to compose with and no large body — it is a small best-effort JSON
116
85
  // DELETE, and an unprotected `fetch` here would hang `uploadAttachments`'s rejection (the
117
86
  // `await` below) on a stalled daemon instead of surfacing `firstError` promptly.
118
- await fetchWithTimeout(endpoint, {
87
+ await (fetch ?? globalThis.fetch)(endpoint, {
119
88
  method: 'DELETE',
89
+ signal: AbortSignal.timeout(15_000),
120
90
  headers: { 'content-type': 'application/json' },
121
91
  body: JSON.stringify({ batchId, paths: attachments.map((attachment) => attachment.path) }),
122
- }, { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
92
+ });
123
93
  }
124
94
  catch {
125
95
  // Best effort by design: the daemon expires unclaimed uploads on its own, so a failed cleanup
@@ -5,7 +5,7 @@
5
5
  * {@link McpUiToolCallHandler} that relays a View's `tools/call` to an HTTP endpoint.
6
6
  *
7
7
  * ```tsx
8
- * registerMcpUiSurfaceRenderer({ onToolCall: createMcpUiToolCaller('') });
8
+ * registerMcpUiSurfaceRenderer({ onToolCall: createMcpUiToolCaller({ baseUrl: '', fetch }) });
9
9
  * ```
10
10
  *
11
11
  * That one line completes the loop. `@jini-ai/daemon`'s `delegated-tool-bridge.ts` splits a UI
@@ -66,5 +66,8 @@ export interface McpUiToolCallRequest {
66
66
  * proxied to its own API.
67
67
  * @complexity O(1) — one request per call.
68
68
  */
69
- export declare function createMcpUiToolCaller(baseUrl: string, options?: CreateMcpUiToolCallerOptions): McpUiToolCallHandler;
69
+ export declare function createMcpUiToolCaller({ baseUrl, fetch }: {
70
+ baseUrl: string;
71
+ fetch: typeof globalThis.fetch;
72
+ }, options?: CreateMcpUiToolCallerOptions): McpUiToolCallHandler;
70
73
  //# sourceMappingURL=create-mcp-ui-tool-caller.d.ts.map
@@ -38,7 +38,7 @@ function errorFrom(body, status) {
38
38
  * proxied to its own API.
39
39
  * @complexity O(1) — one request per call.
40
40
  */
41
- export function createMcpUiToolCaller(baseUrl, options = {}) {
41
+ export function createMcpUiToolCaller({ baseUrl, fetch }, options = {}) {
42
42
  const path = options.path ?? DEFAULT_PATH;
43
43
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
44
44
  const endpoint = `${baseUrl.replace(/\/$/u, '')}${path}`;
@@ -29,34 +29,59 @@ import type { ByokRuntimeSummary, ChatPaneAgent, ChatPaneAgentSelection, Runtime
29
29
  * @complexity Time: O(n) in the supplied option count; space: O(1).
30
30
  * @overallScore 100/100
31
31
  */
32
- export declare function runtimeOptionLabel(options: readonly {
33
- id: string;
34
- label: string;
35
- }[] | undefined, value: string | undefined, fallback: string): string;
32
+ export declare function runtimeOptionLabel({ options, value, fallback }: {
33
+ options: readonly {
34
+ id: string;
35
+ label: string;
36
+ }[] | undefined;
37
+ value: string | undefined;
38
+ fallback: string;
39
+ }): string;
36
40
  /**
37
41
  * Computes a viewport-bounded fixed position for the body-portaled picker.
38
42
  *
39
43
  * @complexity Time/space: O(1).
40
44
  * @overallScore 100/100
41
45
  */
42
- export declare function runtimePopoverPosition(trigger: DOMRect, placement: RuntimePickerPlacement): CSSProperties;
46
+ export declare function runtimePopoverPosition({ trigger, placement }: {
47
+ trigger: DOMRect;
48
+ placement: RuntimePickerPlacement;
49
+ }): CSSProperties;
43
50
  /** `Tab` wraps focus around the popover's control list instead of leaving it (the popover is a
44
51
  * modal-ish dialog). Returns the index to wrap TO, or `null` when `Tab` should behave natively
45
52
  * (not at an edge). `currentIndex` may be `-1` (active element not in `controls`); `atStart`/
46
53
  * `atEnd` below only matter when it legitimately is 0 or `count - 1`. */
47
- export declare function resolveTabWrapIndex(shiftKey: boolean, currentIndex: number, count: number): number | null;
54
+ export declare function resolveTabWrapIndex({ shiftKey, currentIndex, count }: {
55
+ shiftKey: boolean;
56
+ currentIndex: number;
57
+ count: number;
58
+ }): number | null;
48
59
  /** Arrow/Home/End roving-tabindex target, or `null` for any other key. `currentIndex === -1`
49
60
  * (nothing in `controls` focused yet) is a legitimate input — the modulo arithmetic below relies
50
61
  * on it wrapping the same way a real index would. */
51
- export declare function resolveRovingIndex(key: string, currentIndex: number, count: number): number | null;
62
+ export declare function resolveRovingIndex({ key, currentIndex, count }: {
63
+ key: string;
64
+ currentIndex: number;
65
+ count: number;
66
+ }): number | null;
52
67
  /**
53
68
  * The control index the popover's key handler should move focus to, or `null` to leave focus alone
54
69
  * (and, in the caller, to skip `preventDefault()`). `Tab` is resolved and returned immediately —
55
70
  * it must never fall through to the roving-tabindex logic below it.
56
71
  */
57
- export declare function resolvePopoverKeyAction(key: string, shiftKey: boolean, isButtonTarget: boolean, currentIndex: number, count: number): number | null;
72
+ export declare function resolvePopoverKeyAction({ key, shiftKey, isButtonTarget, currentIndex, count }: {
73
+ key: string;
74
+ shiftKey: boolean;
75
+ isButtonTarget: boolean;
76
+ currentIndex: number;
77
+ count: number;
78
+ }): number | null;
58
79
  /** Builds the one-line "which runtime am I talking to" summary shown in the popover head. */
59
- export declare function runtimeSummaryText(selectedAgent: ChatPaneAgent | undefined, modelLabel: string, t: (key: string) => string): string;
80
+ export declare function runtimeSummaryText({ selectedAgent, modelLabel, t }: {
81
+ selectedAgent: ChatPaneAgent | undefined;
82
+ modelLabel: string;
83
+ t: (key: string) => string;
84
+ }): string;
60
85
  /**
61
86
  * The head/trigger labels while `executionMode` is `'api'`.
62
87
  *
@@ -68,7 +93,10 @@ export declare function runtimeSummaryText(selectedAgent: ChatPaneAgent | undefi
68
93
  *
69
94
  * @complexity Time/space: O(1).
70
95
  */
71
- export declare function byokSummaryText(byokRuntime: ByokRuntimeSummary | undefined, t: (key: string) => string): {
96
+ export declare function byokSummaryText({ byokRuntime, t }: {
97
+ byokRuntime: ByokRuntimeSummary | undefined;
98
+ t: (key: string) => string;
99
+ }): {
72
100
  title: string;
73
101
  modelLabel: string;
74
102
  summary: string;
@@ -31,7 +31,7 @@ const FOCUSABLE_CONTROLS = 'button:not(:disabled), select:not(:disabled), input:
31
31
  * @complexity Time: O(n) in the supplied option count; space: O(1).
32
32
  * @overallScore 100/100
33
33
  */
34
- export function runtimeOptionLabel(options, value, fallback) {
34
+ export function runtimeOptionLabel({ options, value, fallback }) {
35
35
  if (!value || value === 'default')
36
36
  return fallback;
37
37
  return options?.find((option) => option.id === value)?.label ?? value;
@@ -42,7 +42,7 @@ export function runtimeOptionLabel(options, value, fallback) {
42
42
  * @complexity Time/space: O(1).
43
43
  * @overallScore 100/100
44
44
  */
45
- export function runtimePopoverPosition(trigger, placement) {
45
+ export function runtimePopoverPosition({ trigger, placement }) {
46
46
  const margin = 12;
47
47
  const gap = 8;
48
48
  const width = Math.min(320, window.innerWidth - margin * 2);
@@ -75,7 +75,7 @@ export function runtimePopoverPosition(trigger, placement) {
75
75
  * modal-ish dialog). Returns the index to wrap TO, or `null` when `Tab` should behave natively
76
76
  * (not at an edge). `currentIndex` may be `-1` (active element not in `controls`); `atStart`/
77
77
  * `atEnd` below only matter when it legitimately is 0 or `count - 1`. */
78
- export function resolveTabWrapIndex(shiftKey, currentIndex, count) {
78
+ export function resolveTabWrapIndex({ shiftKey, currentIndex, count }) {
79
79
  if (shiftKey && currentIndex === 0)
80
80
  return count - 1;
81
81
  if (!shiftKey && currentIndex === count - 1)
@@ -85,7 +85,7 @@ export function resolveTabWrapIndex(shiftKey, currentIndex, count) {
85
85
  /** Arrow/Home/End roving-tabindex target, or `null` for any other key. `currentIndex === -1`
86
86
  * (nothing in `controls` focused yet) is a legitimate input — the modulo arithmetic below relies
87
87
  * on it wrapping the same way a real index would. */
88
- export function resolveRovingIndex(key, currentIndex, count) {
88
+ export function resolveRovingIndex({ key, currentIndex, count }) {
89
89
  if (key === 'Home')
90
90
  return 0;
91
91
  if (key === 'End')
@@ -101,17 +101,17 @@ export function resolveRovingIndex(key, currentIndex, count) {
101
101
  * (and, in the caller, to skip `preventDefault()`). `Tab` is resolved and returned immediately —
102
102
  * it must never fall through to the roving-tabindex logic below it.
103
103
  */
104
- export function resolvePopoverKeyAction(key, shiftKey, isButtonTarget, currentIndex, count) {
104
+ export function resolvePopoverKeyAction({ key, shiftKey, isButtonTarget, currentIndex, count }) {
105
105
  if (key === 'Tab')
106
- return resolveTabWrapIndex(shiftKey, currentIndex, count);
106
+ return resolveTabWrapIndex({ shiftKey: shiftKey, currentIndex: currentIndex, count: count });
107
107
  // Native selects own their arrow/Home/End behavior. Roving applies only
108
108
  // when a button (including the agent radios) has focus.
109
109
  if (!isButtonTarget)
110
110
  return null;
111
- return resolveRovingIndex(key, currentIndex, count);
111
+ return resolveRovingIndex({ key: key, currentIndex: currentIndex, count: count });
112
112
  }
113
113
  /** Builds the one-line "which runtime am I talking to" summary shown in the popover head. */
114
- export function runtimeSummaryText(selectedAgent, modelLabel, t) {
114
+ export function runtimeSummaryText({ selectedAgent, modelLabel, t }) {
115
115
  if (selectedAgent === undefined)
116
116
  return t('No agent selected');
117
117
  const version = selectedAgent.version ? ` · ${selectedAgent.version}` : '';
@@ -128,7 +128,7 @@ export function runtimeSummaryText(selectedAgent, modelLabel, t) {
128
128
  *
129
129
  * @complexity Time/space: O(1).
130
130
  */
131
- export function byokSummaryText(byokRuntime, t) {
131
+ export function byokSummaryText({ byokRuntime, t }) {
132
132
  const title = byokRuntime?.providerLabel?.trim() || t('API · BYOK');
133
133
  const model = byokRuntime?.model?.trim();
134
134
  const modelLabel = model || t('No model configured');
@@ -146,16 +146,16 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
146
146
  const [position, setPosition] = useState();
147
147
  const triggerRef = useRef(null);
148
148
  const popoverRef = useRef(null);
149
- const orderedAgents = useMemo(() => orderChatPaneAgents(agents.filter((agent) => agent.available !== false)), [agents]);
149
+ const orderedAgents = useMemo(() => orderChatPaneAgents({ agents: agents.filter((agent) => agent.available !== false) }), [agents]);
150
150
  const selectedAgent = orderedAgents.find((agent) => agent.id === value.agentId);
151
151
  // `selectedAgent` stays resolved in BOTH modes on purpose — the CLI selection is still the stored
152
152
  // one, and switching back to Local CLI must not have silently lost it. What changes in `'api'` is
153
153
  // only what gets DESCRIBED, because none of the CLI's labels are true of an API turn.
154
- const localModelLabel = runtimeOptionLabel(selectedAgent?.models, value.model, t('Default model'));
155
- const byok = byokSummaryText(byokRuntime, t);
154
+ const localModelLabel = runtimeOptionLabel({ options: selectedAgent?.models, value: value.model, fallback: t('Default model') });
155
+ const byok = byokSummaryText({ byokRuntime: byokRuntime, t: t });
156
156
  const isApi = executionMode === 'api';
157
157
  const modelLabel = isApi ? byok.modelLabel : localModelLabel;
158
- const runtimeSummary = isApi ? byok.summary : runtimeSummaryText(selectedAgent, localModelLabel, t);
158
+ const runtimeSummary = isApi ? byok.summary : runtimeSummaryText({ selectedAgent: selectedAgent, modelLabel: localModelLabel, t: t });
159
159
  const triggerTitle = isApi ? byok.title : (selectedAgent?.name ?? t('Choose agent'));
160
160
  // Read during render rather than in an effect: the popover only mounts once `open` is true, which
161
161
  // is itself a re-render, and `triggerRef` is populated at the first commit — so by the time this
@@ -205,7 +205,7 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
205
205
  const update = () => {
206
206
  const rect = triggerRef.current?.getBoundingClientRect();
207
207
  if (rect)
208
- setPosition(runtimePopoverPosition(rect, placement));
208
+ setPosition(runtimePopoverPosition({ trigger: rect, placement: placement }));
209
209
  };
210
210
  update();
211
211
  window.addEventListener('resize', update);
@@ -233,7 +233,7 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
233
233
  if (controls.length === 0)
234
234
  return;
235
235
  const currentIndex = controls.indexOf(document.activeElement);
236
- const nextIndex = resolvePopoverKeyAction(event.key, event.shiftKey, event.target instanceof HTMLButtonElement, currentIndex, controls.length);
236
+ const nextIndex = resolvePopoverKeyAction({ key: event.key, shiftKey: event.shiftKey, isButtonTarget: event.target instanceof HTMLButtonElement, currentIndex: currentIndex, count: controls.length });
237
237
  if (nextIndex === null)
238
238
  return;
239
239
  event.preventDefault();
@@ -70,29 +70,29 @@ export function useChatPane(options) {
70
70
  const attachmentBatchIdRef = useRef(createAttachmentBatchId());
71
71
  const activeUploadsRef = useRef(new Set());
72
72
  const [internalSelection, setInternalSelection] = useState(options.initialSelection ?? { agentId: '' });
73
- const workingDirectoryState = useChatPaneWorkingDirectory(definedProps({
74
- workingDirectory: options.workingDirectory,
75
- initialWorkingDirectory: options.initialWorkingDirectory,
76
- onChangeWorkingDirectory: options.onChangeWorkingDirectory,
77
- workingDirectoryAccess: options.workingDirectoryAccess,
78
- }));
73
+ const workingDirectoryState = useChatPaneWorkingDirectory(definedProps({ source: {
74
+ workingDirectory: options.workingDirectory,
75
+ initialWorkingDirectory: options.initialWorkingDirectory,
76
+ onChangeWorkingDirectory: options.onChangeWorkingDirectory,
77
+ workingDirectoryAccess: options.workingDirectoryAccess,
78
+ } }));
79
79
  const requestedSelection = options.selection ?? internalSelection;
80
- const selection = useMemo(() => resolveChatPaneSelection(options.agents, requestedSelection), [options.agents, requestedSelection]);
80
+ const selection = useMemo(() => resolveChatPaneSelection({ agents: options.agents, requested: requestedSelection }), [options.agents, requestedSelection]);
81
81
  const selectedAgent = options.agents.find((agent) => agent.id === selection.agentId);
82
- const composer = useComposer(definedProps({
83
- initialDraft: options.initialDraft,
84
- initialAgent: selection,
85
- conversationId: options.conversationId,
86
- validateAttachments: options.validateAttachments,
87
- }));
88
- const conversation = useConversation(definedProps({
89
- transport: options.transport,
90
- initialMessages: options.initialMessages,
91
- conversationId: options.conversationId,
92
- // Keys off an empty string, not `undefined` — `selection.agentId` is always a string (never
93
- // absent), so only the falsy "no agent selected" case should omit the key.
94
- agentId: selection.agentId || undefined,
95
- }));
82
+ const composer = useComposer(definedProps({ source: {
83
+ initialDraft: options.initialDraft,
84
+ initialAgent: selection,
85
+ conversationId: options.conversationId,
86
+ validateAttachments: options.validateAttachments,
87
+ } }));
88
+ const conversation = useConversation(definedProps({ source: {
89
+ transport: options.transport,
90
+ initialMessages: options.initialMessages,
91
+ conversationId: options.conversationId,
92
+ // Keys off an empty string, not `undefined` — `selection.agentId` is always a string (never
93
+ // absent), so only the falsy "no agent selected" case should omit the key.
94
+ agentId: selection.agentId || undefined,
95
+ } }));
96
96
  const activity = resolveChatPaneActivity(selectedAgent, conversation);
97
97
  useEffect(() => {
98
98
  options.onActivityChange?.(activity);
@@ -131,7 +131,7 @@ export function useChatPane(options) {
131
131
  selection,
132
132
  ]);
133
133
  const setSelection = useCallback((next) => {
134
- const validated = resolveChatPaneSelection(options.agents, next);
134
+ const validated = resolveChatPaneSelection({ agents: options.agents, requested: next });
135
135
  setInternalSelection(validated);
136
136
  composer.setAgent(validated);
137
137
  options.onSelectionChange?.(validated);
@@ -174,7 +174,7 @@ export function useChatPane(options) {
174
174
  onAttachment: (attachment, file) => {
175
175
  composer.addAttachment(attachment);
176
176
  if (file)
177
- cacheAttachmentPreviewSource(attachment.path, file);
177
+ cacheAttachmentPreviewSource({ path: attachment.path, file });
178
178
  },
179
179
  onError: setAttachmentError,
180
180
  });
@@ -204,13 +204,13 @@ export function useChatPane(options) {
204
204
  attachmentGenerationRef.current += 1;
205
205
  attachmentBatchIdRef.current = createAttachmentBatchId();
206
206
  options.onActivityChange?.('queued');
207
- await conversation.sendMessage(trimmed, definedProps({
208
- agentId: selection.agentId,
209
- // Omitted when the array is EMPTY, not merely absent — an empty `attachments: []` would be a
210
- // different (valid, present) value to the transport than "no attachments key at all".
211
- attachments: attachments.length === 0 ? undefined : attachments,
212
- context,
213
- }));
207
+ await conversation.sendMessage(trimmed, definedProps({ source: {
208
+ agentId: selection.agentId,
209
+ // Omitted when the array is EMPTY, not merely absent — an empty `attachments: []` would be a
210
+ // different (valid, present) value to the transport than "no attachments key at all".
211
+ attachments: attachments.length === 0 ? undefined : attachments,
212
+ context,
213
+ } }));
214
214
  }, [
215
215
  composer,
216
216
  conversation,
@@ -226,7 +226,7 @@ export function useChatPane(options) {
226
226
  return;
227
227
  // Queue instead of no-op'ing. `setDraft('')` and NOT `composer.reset()`: reset also discards
228
228
  // staged attachments, which this turn still needs when it finally goes out.
229
- if (isChatPaneQueueableBlocker(sendBlocker)) {
229
+ if (isChatPaneQueueableBlocker({ blocker: sendBlocker })) {
230
230
  queuedConversationIdRef.current = options.conversationId;
231
231
  setQueuedPrompt(prompt);
232
232
  composer.setDraft('');
@@ -34,5 +34,7 @@ export interface UseChatPaneAgentControlOptions {
34
34
  * The relayed channel arrives having already passed `ToolExecutor`; a WebMCP call has no run and
35
35
  * no principal, so it cannot. See {@link UseChatPaneAgentControlOptions.webmcp}.
36
36
  */
37
- export declare function useChatPaneAgentControl(pane: UseChatPaneResult, options?: UseChatPaneAgentControlOptions): void;
37
+ export declare function useChatPaneAgentControl({ pane }: {
38
+ pane: UseChatPaneResult;
39
+ }, options?: UseChatPaneAgentControlOptions): void;
38
40
  //# sourceMappingURL=useChatPaneAgentControl.hooks.d.ts.map
@@ -82,7 +82,7 @@ async function selectAgentAction(pane, input) {
82
82
  }
83
83
  const model = optionalStringField(input, 'model');
84
84
  const reasoning = optionalStringField(input, 'reasoning');
85
- pane.setSelection(definedProps({ agentId, model, reasoning }));
85
+ pane.setSelection(definedProps({ source: { agentId, model, reasoning } }));
86
86
  return { ok: true };
87
87
  }
88
88
  async function cancelRunAction(pane) {
@@ -122,7 +122,7 @@ async function getStateAction(pane) {
122
122
  canSend: pane.canSend,
123
123
  sendBlocker: pane.sendBlocker,
124
124
  messageCount: pane.conversation.messages.length,
125
- ...definedProps({ lastMessage: lastMessageSummary }),
125
+ ...definedProps({ source: { lastMessage: lastMessageSummary } }),
126
126
  /**
127
127
  * Conversation content is user- and agent-authored text, not instructions from the
128
128
  * host. A caller reading it back must treat it as data.
@@ -190,7 +190,7 @@ async function deliverBridgeAction(pane, bridge, action) {
190
190
  * The relayed channel arrives having already passed `ToolExecutor`; a WebMCP call has no run and
191
191
  * no principal, so it cannot. See {@link UseChatPaneAgentControlOptions.webmcp}.
192
192
  */
193
- export function useChatPaneAgentControl(pane, options = {}) {
193
+ export function useChatPaneAgentControl({ pane }, options = {}) {
194
194
  const enabled = options.enabled ?? false;
195
195
  const paneRef = useRef(pane);
196
196
  paneRef.current = pane;
@@ -6,5 +6,8 @@
6
6
  * @complexity Time/space: O(1) per render; the rotation timer is a single `setInterval`.
7
7
  * @overallScore 100/100
8
8
  */
9
- export declare function useChatPaneComposerPlaceholder(placeholder: string | undefined, placeholders: readonly string[] | undefined): string | undefined;
9
+ export declare function useChatPaneComposerPlaceholder({ placeholder, placeholders }: {
10
+ placeholder: string | undefined;
11
+ placeholders: readonly string[] | undefined;
12
+ }): string | undefined;
10
13
  //# sourceMappingURL=useChatPaneComposerPlaceholder.hooks.d.ts.map
@@ -38,7 +38,7 @@ function usePrefersReducedMotion() {
38
38
  * @complexity Time/space: O(1) per render; the rotation timer is a single `setInterval`.
39
39
  * @overallScore 100/100
40
40
  */
41
- export function useChatPaneComposerPlaceholder(placeholder, placeholders) {
41
+ export function useChatPaneComposerPlaceholder({ placeholder, placeholders }) {
42
42
  const reducedMotion = usePrefersReducedMotion();
43
43
  const [index, setIndex] = useState(0);
44
44
  // A joined key, not the array reference: a host writing `placeholders={['a', 'b']}` inline