@jini-ai/chat 0.5.6 → 0.5.8

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 (127) hide show
  1. package/dist/core/ag-ui/entry.js +2 -0
  2. package/dist/core/assistant-client/durable-run-subscription.d.ts +4 -1
  3. package/dist/core/assistant-client/durable-run-subscription.js +13 -4
  4. package/dist/core/durable-projection.d.ts +3 -1
  5. package/dist/core/durable-projection.js +40 -7
  6. package/dist/core/events.d.ts +3 -0
  7. package/dist/core/history-tool-metadata.d.ts +23 -0
  8. package/dist/core/history-tool-metadata.js +13 -0
  9. package/dist/core/index.d.ts +4 -0
  10. package/dist/core/index.js +4 -0
  11. package/dist/core/persistence/title.d.ts +24 -0
  12. package/dist/core/persistence/title.js +60 -9
  13. package/dist/core/run-activity.d.ts +15 -2
  14. package/dist/core/run-activity.js +6 -4
  15. package/dist/core/run-events/entry.d.ts +12 -1
  16. package/dist/core/run-events/entry.js +19 -6
  17. package/dist/core/tool-call-detail.d.ts +51 -0
  18. package/dist/core/tool-call-detail.js +67 -0
  19. package/dist/core/turn-usage.d.ts +50 -0
  20. package/dist/core/turn-usage.js +114 -0
  21. package/dist/core/usage-format.d.ts +10 -0
  22. package/dist/core/usage-format.js +15 -0
  23. package/dist/embed-widget/browser/lazy-loader.d.ts +34 -0
  24. package/dist/embed-widget/browser/lazy-loader.js +116 -0
  25. package/dist/embed-widget/react/dialog.hooks.d.ts +28 -0
  26. package/dist/embed-widget/react/dialog.hooks.js +54 -0
  27. package/dist/embed-widget/react/embed.d.ts +1 -0
  28. package/dist/embed-widget/react/embed.js +5 -3
  29. package/dist/embed-widget/react/header.d.ts +3 -1
  30. package/dist/embed-widget/react/header.hooks.d.ts +13 -0
  31. package/dist/embed-widget/react/header.hooks.js +32 -0
  32. package/dist/embed-widget/react/header.js +4 -14
  33. package/dist/embed-widget/vanilla/index.d.ts +63 -0
  34. package/dist/embed-widget/vanilla/index.js +319 -0
  35. package/dist/embed-widget/vanilla/state.d.ts +48 -0
  36. package/dist/embed-widget/vanilla/state.js +120 -0
  37. package/dist/embed-widget/vanilla/style.css +29 -0
  38. package/dist/react/chat-pane.d.ts +2 -2
  39. package/dist/react/chat-pane.js +1 -1
  40. package/dist/react/components/A2uiSurfaceCard.d.ts +5 -1
  41. package/dist/react/components/A2uiSurfaceCard.js +9 -9
  42. package/dist/react/components/AttachmentPreviewModal.hooks.d.ts +7 -0
  43. package/dist/react/components/AttachmentPreviewModal.hooks.js +1 -1
  44. package/dist/react/components/ChatFab.d.ts +4 -1
  45. package/dist/react/components/ChatFab.js +2 -2
  46. package/dist/react/components/Composer.d.ts +2 -0
  47. package/dist/react/components/Composer.js +5 -2
  48. package/dist/react/components/ConversationList.d.ts +8 -1
  49. package/dist/react/components/ConversationList.hooks.d.ts +18 -0
  50. package/dist/react/components/ConversationList.hooks.js +25 -0
  51. package/dist/react/components/ConversationList.js +12 -5
  52. package/dist/react/components/ConversationList.rules.d.ts +25 -0
  53. package/dist/react/components/ConversationList.rules.js +19 -0
  54. package/dist/react/components/Markdown.js +6 -6
  55. package/dist/react/components/McpUiSurfaceCard.d.ts +1 -1
  56. package/dist/react/components/McpUiSurfaceCard.js +8 -4
  57. package/dist/react/components/MessageList.d.ts +8 -13
  58. package/dist/react/components/MessageList.hooks.d.ts +4 -0
  59. package/dist/react/components/MessageList.hooks.js +6 -1
  60. package/dist/react/components/MessageList.js +23 -4
  61. package/dist/react/components/MessageRow.d.ts +23 -1
  62. package/dist/react/components/MessageRow.js +61 -36
  63. package/dist/react/components/MessageRow.usage.d.ts +48 -0
  64. package/dist/react/components/MessageRow.usage.js +47 -0
  65. package/dist/react/components/MidRunUserMessage.d.ts +17 -0
  66. package/dist/react/components/MidRunUserMessage.js +41 -0
  67. package/dist/react/components/SentAttachment.d.ts +19 -0
  68. package/dist/react/components/SentAttachment.hooks.d.ts +44 -0
  69. package/dist/react/components/SentAttachment.hooks.js +56 -0
  70. package/dist/react/components/SentAttachment.js +14 -0
  71. package/dist/react/components/ToolCallGroup.d.ts +12 -0
  72. package/dist/react/components/ToolCallGroup.hooks.d.ts +20 -0
  73. package/dist/react/components/ToolCallGroup.hooks.js +23 -0
  74. package/dist/react/components/ToolCallGroup.js +13 -0
  75. package/dist/react/components/ToolCard.js +41 -22
  76. package/dist/react/components/mcp-ui-surface-state.d.ts +21 -0
  77. package/dist/react/components/mcp-ui-surface-state.js +26 -0
  78. package/dist/react/ext-event-renderer-registry.d.ts +7 -0
  79. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  80. package/dist/react/features/chat-pane/components/ChatPane.js +20 -7
  81. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +44 -0
  82. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +69 -0
  83. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +4 -1
  84. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +65 -10
  85. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.d.ts +25 -0
  86. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +70 -0
  87. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +4 -0
  88. package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.d.ts +9 -0
  89. package/dist/react/features/chat-pane/hooks/useChatPaneSuggestion.hooks.js +11 -0
  90. package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.d.ts +17 -0
  91. package/dist/react/features/chat-pane/hooks/useChatPaneSurface.hooks.js +15 -0
  92. package/dist/react/features/chat-pane/index.d.ts +2 -1
  93. package/dist/react/features/chat-pane/index.js +1 -0
  94. package/dist/react/features/chat-pane/rules.d.ts +27 -0
  95. package/dist/react/features/chat-pane/rules.js +32 -0
  96. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  97. package/dist/react/features/chat-pane/styles.js +92 -1
  98. package/dist/react/features/chat-pane/types.d.ts +39 -1
  99. package/dist/react/hooks/composer-draft-cache.d.ts +15 -0
  100. package/dist/react/hooks/composer-draft-cache.js +20 -0
  101. package/dist/react/hooks/useAnsweredCards.d.ts +20 -0
  102. package/dist/react/hooks/useAnsweredCards.js +23 -0
  103. package/dist/react/hooks/useComposer.d.ts +14 -0
  104. package/dist/react/hooks/useComposer.js +78 -6
  105. package/dist/react/hooks/useConversation.d.ts +8 -0
  106. package/dist/react/hooks/useConversation.js +34 -0
  107. package/dist/react/hooks/useRunActivity.d.ts +5 -3
  108. package/dist/react/hooks/useRunActivity.js +4 -3
  109. package/dist/react/index.d.ts +8 -2
  110. package/dist/react/index.js +6 -1
  111. package/dist/react/message-blocks.d.ts +22 -3
  112. package/dist/react/message-blocks.js +25 -3
  113. package/dist/react/tool-call-display.d.ts +90 -0
  114. package/dist/react/tool-call-display.js +97 -0
  115. package/dist/server/durable-runs/recover.js +19 -11
  116. package/dist/server/run-finalizer/entry.js +2 -1
  117. package/dist/store/ports.d.ts +25 -2
  118. package/dist/store/runtime/durable-run-store.js +5 -2
  119. package/dist/store/runtime/entry.d.ts +1 -0
  120. package/dist/store/runtime/entry.js +1 -0
  121. package/dist/store/runtime/history-tools.d.ts +27 -0
  122. package/dist/store/runtime/history-tools.js +169 -0
  123. package/dist/store/runtime/run-ledger.js +5 -2
  124. package/dist/store/sql/store.js +52 -0
  125. package/dist/testing/index.d.ts +7 -0
  126. package/dist/testing/index.js +22 -0
  127. package/package.json +48 -18
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * @module ToolCard
4
4
  *
@@ -20,8 +20,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
20
  * `AskUserQuestion`) falls through to `GenericCard`, which is a correct,
21
21
  * generic rendering for it.
22
22
  */
23
- import { useState } from 'react';
24
- import { formatToolOutputForDisplay, isDelegatedWrapperToolName, isTodoWriteToolName, parseTodoWriteInput, toRenderProps } from '../../core/index.js';
23
+ import { useId, useState } from 'react';
24
+ import { describeCollapsedToolCall, describeToolCallDetail, formatToolOutputForDisplay, isDelegatedWrapperToolName, isTodoWriteToolName, parseTodoWriteInput, toRenderProps } from '../../core/index.js';
25
25
  import { useT } from '../hooks/context.js';
26
26
  import { getToolRenderer } from '../tool-renderer-registry.js';
27
27
  import { Icon } from './Icon.js';
@@ -132,7 +132,6 @@ function primaryTarget(input) {
132
132
  */
133
133
  function DelegatedToolCard({ name, input, result, runStreaming, runSucceeded }) {
134
134
  const t = useT();
135
- const [open, setOpen] = useState(false);
136
135
  // Wrapper form (only reached on a failed wrapper call, per ToolCard's suppression comment):
137
136
  // the real id and args are nested under {toolId, input}, not `input` itself.
138
137
  const wrapper = (input ?? {});
@@ -140,21 +139,19 @@ function DelegatedToolCard({ name, input, result, runStreaming, runSucceeded })
140
139
  const args = isDelegatedWrapperToolName({ name: name }) ? wrapper.input : input;
141
140
  const target = primaryTarget(args);
142
141
  const isRunning = runStreaming && !result;
143
- return (_jsxs("div", { className: "op-card op-jini-tool", children: [_jsxs("button", { type: "button", className: "op-card-head", onClick: () => setOpen((o) => !o), children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsxs("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: [t('Tool call'), " \u00B7 ", humanizeToolId(toolId)] }), target ? _jsx("span", { className: "op-meta", children: target }) : null, _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { className: `accordion-collapsible${open ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsxs("div", { className: "op-card-detail", children: [_jsx("pre", { className: "op-command", children: JSON.stringify(args ?? {}) }), result?.content ? _jsx("pre", { className: "op-output", children: truncate(formatToolOutputForDisplay({ text: result.content }), 2000) }) : null, _jsx(ToolResultMedia, { result: result })] }) }) }), _jsx(FileErrorDetail, { result: result })] }));
142
+ return (_jsx(ExpandableCard, { className: "op-jini-tool", call: { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsxs("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: [t('Tool call'), " \u00B7 ", humanizeToolId(toolId)] }), target ? _jsx("span", { className: "op-meta", children: target }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded, children: _jsx(ToolResultMedia, { result: result }) }) }));
144
143
  }
145
144
  function SearchToolsCard({ input, result, runStreaming, runSucceeded }) {
146
145
  const t = useT();
147
- const [open, setOpen] = useState(false);
148
146
  const obj = (input ?? {});
149
147
  const isRunning = runStreaming && !result;
150
- return (_jsxs("div", { className: "op-card op-jini-tool", children: [_jsxs("button", { type: "button", className: "op-card-head", onClick: () => setOpen((o) => !o), children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Search tools') }), obj.query ? _jsx("span", { className: "op-meta", children: obj.query }) : null, _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { className: `accordion-collapsible${open ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsx("div", { className: "op-card-detail", children: result?.content ? _jsx("pre", { className: "op-output", children: truncate(formatToolOutputForDisplay({ text: result.content }), 2000) }) : null }) }) }), _jsx(FileErrorDetail, { result: result })] }));
148
+ return (_jsx(ExpandableCard, { className: "op-jini-tool", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Search tools') }), obj.query ? _jsx("span", { className: "op-meta", children: obj.query }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
151
149
  }
152
150
  function DescribeToolCard({ input, result, runStreaming, runSucceeded }) {
153
151
  const t = useT();
154
- const [open, setOpen] = useState(false);
155
152
  const obj = (input ?? {});
156
153
  const isRunning = runStreaming && !result;
157
- return (_jsxs("div", { className: "op-card op-jini-tool", children: [_jsxs("button", { type: "button", className: "op-card-head", onClick: () => setOpen((o) => !o), children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Describe tool') }), obj.id ? _jsx("span", { className: "op-meta", children: obj.id }) : null, _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { className: `accordion-collapsible${open ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsx("div", { className: "op-card-detail", children: result?.content ? _jsx("pre", { className: "op-output", children: truncate(formatToolOutputForDisplay({ text: result.content }), 2000) }) : null }) }) }), _jsx(FileErrorDetail, { result: result })] }));
154
+ return (_jsx(ExpandableCard, { className: "op-jini-tool", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Describe tool') }), obj.id ? _jsx("span", { className: "op-meta", children: obj.id }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
158
155
  }
159
156
  function OpenInTabButton({ filePath, ctx }) {
160
157
  const t = useT();
@@ -174,68 +171,90 @@ function OpenInTabButton({ filePath, ctx }) {
174
171
  }
175
172
  function FileWriteCard({ input, result, runStreaming, runSucceeded, ctx }) {
176
173
  const t = useT();
177
- const [open, setOpen] = useState(false);
178
174
  const obj = (input ?? {});
179
175
  const file = obj.file_path ?? obj.filePath ?? obj.path ?? '(unnamed)';
180
176
  // See OpenInTabButton's baseName comment: `.split('/').pop()` is never undefined.
181
177
  const baseName = file.split('/').pop();
182
178
  const lines = typeof obj.content === 'string' ? obj.content.split('\n').length : null;
183
179
  const isRunning = runStreaming && !result;
184
- return (_jsxs("div", { className: "op-card op-file", children: [_jsxs("button", { type: "button", className: "op-card-head", onClick: () => setOpen((o) => !o), children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Write') }), _jsxs("span", { className: "op-meta", children: [baseName, lines !== null ? ` · ${t('{n} lines', { n: lines })}` : ''] }), _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { className: `accordion-collapsible${open ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }) }) }), _jsx(FileErrorDetail, { result: result })] }));
180
+ return (_jsxs(ExpandableCard, { className: "op-file", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Write') }), _jsxs("span", { className: "op-meta", children: [baseName, lines !== null ? ` · ${t('{n} lines', { n: lines })}` : ''] })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
185
181
  }
186
182
  function FileEditCard({ input, result, runStreaming, runSucceeded, ctx }) {
187
183
  const t = useT();
188
- const [open, setOpen] = useState(false);
189
184
  const obj = (input ?? {});
190
185
  const file = obj.file_path ?? obj.filePath ?? obj.path ?? '(unnamed)';
191
186
  // See OpenInTabButton's baseName comment: `.split('/').pop()` is never undefined.
192
187
  const baseName = file.split('/').pop();
193
188
  const editCount = Array.isArray(obj.edits) ? obj.edits.length : 1;
194
189
  const isRunning = runStreaming && !result;
195
- return (_jsxs("div", { className: "op-card op-file", children: [_jsxs("button", { type: "button", className: "op-card-head", onClick: () => setOpen((o) => !o), children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Edit') }), _jsxs("span", { className: "op-meta", children: [baseName, " \u00B7 ", editCount, " ", editCount === 1 ? t('change') : t('changes')] }), _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { className: `accordion-collapsible${open ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }) }) }), _jsx(FileErrorDetail, { result: result })] }));
190
+ return (_jsxs(ExpandableCard, { className: "op-file", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Edit') }), _jsxs("span", { className: "op-meta", children: [baseName, " \u00B7 ", editCount, " ", editCount === 1 ? t('change') : t('changes')] })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
196
191
  }
197
192
  function FileReadCard({ input, result, runStreaming, runSucceeded, ctx }) {
198
193
  const t = useT();
199
- const [open, setOpen] = useState(false);
200
194
  const obj = (input ?? {});
201
195
  const file = obj.file_path ?? obj.filePath ?? obj.path ?? '(unnamed)';
202
196
  // See OpenInTabButton's baseName comment: `.split('/').pop()` is never undefined.
203
197
  const baseName = file.split('/').pop();
204
198
  const isRunning = runStreaming && !result;
205
- return (_jsxs("div", { className: "op-card op-file", children: [_jsxs("button", { type: "button", className: "op-card-head", onClick: () => setOpen((o) => !o), children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Read') }), _jsx("span", { className: "op-meta", children: baseName }), _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { className: `accordion-collapsible${open ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }) }) }), _jsx(FileErrorDetail, { result: result })] }));
199
+ return (_jsxs(ExpandableCard, { className: "op-file", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Read') }), _jsx("span", { className: "op-meta", children: baseName })] }), after: _jsx(FileErrorDetail, { result: result }), children: [_jsxs("div", { className: "op-card-detail op-card-file-detail", children: [_jsx("code", { className: "op-path", children: file }), _jsx(OpenInTabButton, { filePath: file, ctx: ctx })] }), _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded })] }));
206
200
  }
207
201
  function BashCard({ input, result, runStreaming, runSucceeded }) {
208
202
  const t = useT();
209
203
  const obj = (input ?? {});
210
204
  const command = obj.command ?? '';
211
205
  const desc = obj.description;
212
- const [open, setOpen] = useState(false);
213
206
  const isRunning = runStreaming && !result;
214
- return (_jsxs("div", { className: "op-card op-bash", children: [_jsxs("button", { type: "button", className: "op-card-head", onClick: () => setOpen((o) => !o), children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Bash') }), desc ? _jsx("span", { className: "op-meta op-desc", children: desc }) : null, _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { className: `accordion-collapsible${open ? ' open' : ''}`, children: _jsx("div", { className: "accordion-collapsible-inner", children: _jsxs("div", { className: "op-card-detail", children: [_jsx("pre", { className: "op-command", children: truncate(command, 400) }), result?.content ? _jsx("pre", { className: "op-output", children: truncate(formatToolOutputForDisplay({ text: result.content }), 4000) }) : null] }) }) })] }));
207
+ return (_jsx(ExpandableCard, { className: "op-bash", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: `op-title${isRunning ? ' shimmer-text' : ''}`, children: t('Bash') }), desc ? _jsx("span", { className: "op-meta op-desc", children: desc }) : null] }), children: _jsxs("div", { className: "op-card-detail", children: [_jsx("pre", { className: "op-command", children: truncate(command, 400) }), result?.content ? _jsx("pre", { className: "op-output", children: truncate(formatToolOutputForDisplay({ text: result.content }), 4000) }) : null] }) }));
215
208
  }
216
209
  function GlobCard({ input, result, runStreaming, runSucceeded }) {
217
210
  const t = useT();
218
211
  const obj = (input ?? {});
219
- return (_jsx("div", { className: "op-card op-search", children: _jsxs("div", { className: "op-card-head", children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search files') }), _jsxs("span", { className: "op-meta", children: [obj.pattern ?? '*', obj.path ? ` in ${obj.path}` : ''] })] }) }));
212
+ return (_jsx(ExpandableCard, { className: "op-search", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search files') }), _jsxs("span", { className: "op-meta", children: [obj.pattern ?? '*', obj.path ? ` in ${obj.path}` : ''] })] }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
220
213
  }
221
214
  function GrepCard({ input, result, runStreaming, runSucceeded }) {
222
215
  const t = useT();
223
216
  const obj = (input ?? {});
224
- return (_jsx("div", { className: "op-card op-search", children: _jsxs("div", { className: "op-card-head", children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search content') }), _jsxs("span", { className: "op-meta", children: [obj.pattern ?? '', obj.path ? ` in ${obj.path}` : ''] })] }) }));
217
+ return (_jsx(ExpandableCard, { className: "op-search", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search content') }), _jsxs("span", { className: "op-meta", children: [obj.pattern ?? '', obj.path ? ` in ${obj.path}` : ''] })] }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
225
218
  }
226
219
  function WebFetchCard({ input, result, runStreaming, runSucceeded }) {
227
220
  const t = useT();
228
221
  const obj = (input ?? {});
229
- return (_jsx("div", { className: "op-card op-web", children: _jsxs("div", { className: "op-card-head", children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Fetch') }), _jsx("span", { className: "op-meta", children: obj.url ?? '' })] }) }));
222
+ return (_jsx(ExpandableCard, { className: "op-web", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Fetch') }), _jsx("span", { className: "op-meta", children: obj.url ?? '' })] }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
230
223
  }
231
224
  function WebSearchCard({ input, result, runStreaming, runSucceeded }) {
232
225
  const t = useT();
233
226
  const obj = (input ?? {});
234
- return (_jsx("div", { className: "op-card op-web", children: _jsxs("div", { className: "op-card-head", children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search') }), _jsx("span", { className: "op-meta", children: obj.query ?? '' })] }) }));
227
+ return (_jsx(ExpandableCard, { className: "op-web", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: t('Search') }), _jsx("span", { className: "op-meta", children: obj.query ?? '' })] }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
235
228
  }
229
+ /** Any tool this package has no family card for — an MCP tool, or a card tool such as `assistant_ask_choice` whose form renders elsewhere in the turn. The row still opens to the full call. */
236
230
  function GenericCard({ name, input, result, runStreaming, runSucceeded }) {
237
231
  const summary = describeInput(input);
238
- return (_jsxs("div", { className: "op-card op-generic", children: [_jsxs("div", { className: "op-card-head", children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: name }), summary ? _jsx("span", { className: "op-meta", children: truncate(summary, 200) }) : null] }), _jsx(ToolResultMedia, { result: result })] }));
232
+ return (_jsx(ExpandableCard, { className: "op-generic", call: { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }, head: _jsxs(_Fragment, { children: [_jsx(ResultBadge, { result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }), _jsx("span", { className: "op-title", children: name }), summary ? _jsx("span", { className: "op-meta", children: truncate(summary, 200) }) : null] }), after: _jsx(ToolResultMedia, { result: result }), children: _jsx(ToolCallDetail, { input: input, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
233
+ }
234
+ /**
235
+ * The one row shell every card shares: a real `<button>` head (Enter/Space toggle it) that reports
236
+ * `aria-expanded` and points `aria-controls` at its panel. The panel's `children` mount only while
237
+ * open: a long chat mounts hundreds of collapsed rows, and keeping every full detail mounted made a
238
+ * 38-message chat carry ~13k DOM nodes and 3.45 MB of hidden text. Collapsed, the panel holds one
239
+ * hidden node with the call's capped gist instead, so a `page.*` DOM reader still sees what was
240
+ * called and what came back through `textContent` without anyone clicking.
241
+ * `after` renders outside the panel, for content that must stay visible collapsed (an error, media).
242
+ */
243
+ function ExpandableCard({ className, head, after, call, children }) {
244
+ const [open, setOpen] = useState(false);
245
+ const panelId = useId();
246
+ return (_jsxs("div", { className: `op-card ${className}`, children: [_jsxs("button", { type: "button", className: "op-card-head", "aria-expanded": open, "aria-controls": panelId, onClick: () => setOpen((o) => !o), children: [head, _jsx("span", { className: "op-expand-chev", "aria-hidden": true, children: _jsx(Icon, { name: open ? 'chevron-down' : 'chevron-right', size: 11 }) })] }), _jsx("div", { id: panelId, className: `accordion-collapsible${open ? ' open' : ''}`, children: open ? _jsx("div", { className: "accordion-collapsible-inner", children: children }) : _jsx(CollapsedReaderText, { call: call }) }), after] }));
247
+ }
248
+ /** The collapsed row's stand-in for its panel — see {@link ExpandableCard} and `describeCollapsedToolCall`. */
249
+ function CollapsedReaderText({ call }) {
250
+ const text = describeCollapsedToolCall({ input: call.input, result: call.result }, { runStreaming: call.runStreaming, runSucceeded: call.runSucceeded });
251
+ return _jsx("span", { className: "op-card-reader-text", hidden: true, children: text });
252
+ }
253
+ /** The call's input, its result (or why there is none), and any extra `children` — see `describeToolCallDetail` for the redaction and formatting rules. */
254
+ function ToolCallDetail({ input, result, runStreaming, runSucceeded, children }) {
255
+ const t = useT();
256
+ const detail = describeToolCallDetail({ input: input, result: result }, { runStreaming: runStreaming, runSucceeded: runSucceeded });
257
+ return (_jsxs("div", { className: "op-card-detail", children: [detail.inputText !== null ? _jsx("pre", { className: "op-command op-input", children: detail.inputText }) : null, detail.outputText !== null ? _jsx("pre", { className: "op-output", children: detail.outputText }) : null, detail.noteKey !== null ? _jsx("p", { className: "op-detail-note", children: t(detail.noteKey) }) : null, children] }));
239
258
  }
240
259
  function ResultBadge({ result, runStreaming, runSucceeded }) {
241
260
  const t = useT();
@@ -5,6 +5,7 @@
5
5
  * `McpUiSurfaceCard.tsx` so the component only renders; moved here from that file 2026-10-04 when
6
6
  * the card's own deadline (`useSurfaceExpiry`) became a second reason to close it.
7
7
  */
8
+ import type { McpUiToolCallHandler } from '@jini-ai/ui/mcp-ui';
8
9
  import type { ExtEventCall } from '../ext-event-renderer-registry.js';
9
10
  import type { useT } from '../hooks/context.js';
10
11
  type Translate = ReturnType<typeof useT>;
@@ -23,5 +24,25 @@ export declare function closedSurfaceLabel({ t, call, runStreaming, documentsFor
23
24
  documentsForUri: number;
24
25
  expired: boolean;
25
26
  }): string | undefined;
27
+ /**
28
+ * Whether a View's `tools/call` result is a held-open question's delivery receipt: the host route
29
+ * answers `{delivered: true}` (HTTP 202) the moment the click reaches the parked tool call. That
30
+ * call then keeps running the work the answer started, so this receipt — not its later result — is
31
+ * when the card stops waiting on the person.
32
+ *
33
+ * @complexity O(1).
34
+ */
35
+ export declare function isDeliveredAnswer(result: unknown): boolean;
36
+ /**
37
+ * Wraps a card's tool executor so a delivered answer is reported once it resolves. The result is
38
+ * passed through unchanged, and a rejected call reports nothing (the dialog shows its error and can
39
+ * be answered again).
40
+ *
41
+ * @complexity O(1) per call, plus `onToolCall`'s own cost.
42
+ */
43
+ export declare function reportDeliveredAnswer({ onToolCall, onAnswered }: {
44
+ onToolCall: McpUiToolCallHandler;
45
+ onAnswered: () => void;
46
+ }): McpUiToolCallHandler;
26
47
  export {};
27
48
  //# sourceMappingURL=mcp-ui-surface-state.d.ts.map
@@ -30,4 +30,30 @@ function settledLabel(t, call, runStreaming, documentsForUri) {
30
30
  export function closedSurfaceLabel({ t, call, runStreaming, documentsForUri, expired }) {
31
31
  return settledLabel(t, call, runStreaming, documentsForUri) ?? (expired ? t('This question expired') : undefined);
32
32
  }
33
+ /**
34
+ * Whether a View's `tools/call` result is a held-open question's delivery receipt: the host route
35
+ * answers `{delivered: true}` (HTTP 202) the moment the click reaches the parked tool call. That
36
+ * call then keeps running the work the answer started, so this receipt — not its later result — is
37
+ * when the card stops waiting on the person.
38
+ *
39
+ * @complexity O(1).
40
+ */
41
+ export function isDeliveredAnswer(result) {
42
+ return typeof result === 'object' && result !== null && result.delivered === true;
43
+ }
44
+ /**
45
+ * Wraps a card's tool executor so a delivered answer is reported once it resolves. The result is
46
+ * passed through unchanged, and a rejected call reports nothing (the dialog shows its error and can
47
+ * be answered again).
48
+ *
49
+ * @complexity O(1) per call, plus `onToolCall`'s own cost.
50
+ */
51
+ export function reportDeliveredAnswer({ onToolCall, onAnswered }) {
52
+ return async (call) => {
53
+ const result = await onToolCall(call);
54
+ if (isDeliveredAnswer(result))
55
+ onAnswered();
56
+ return result;
57
+ };
58
+ }
33
59
  //# sourceMappingURL=mcp-ui-surface-state.js.map
@@ -51,6 +51,13 @@ export interface ExtEventRenderProps {
51
51
  runId: string | undefined;
52
52
  /** See {@link ExtEventCall}. Absent when the group's first event arrived outside any tool call. */
53
53
  call?: ExtEventCall;
54
+ /** Whether this card already reported its answer delivered (see {@link onAnswered}). */
55
+ answered?: boolean;
56
+ /**
57
+ * A card calls this once the person's answer reached the server. Its tool call may stay open for
58
+ * the work the answer started, and the run must stop reading as "waiting for your answer".
59
+ */
60
+ onAnswered?: () => void;
54
61
  }
55
62
  /**
56
63
  * Ext-event render callback. Runs inline during `MessageRow`'s render — hook-free, like
@@ -6,5 +6,5 @@ import type { ChatPaneProps } from '../types.js';
6
6
  * @complexity Time/space: O(n) in rendered messages, agents, and suggestions.
7
7
  * @overallScore 100/100
8
8
  */
9
- export declare function ChatPane({ transport, agents: injectedAgents, runtimeAccess, runtimeStatusPollMs, title, variant, initialMessages, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement, projectFileNames, uploadAttachments, attachmentAccept, disabled, runtimePickerPlacement, composerSlots, header, leadingAccessory, footer, className, style, }: ChatPaneProps): import("react").JSX.Element;
9
+ export declare function ChatPane({ transport, agents: injectedAgents, runtimeAccess, runtimeStatusPollMs, title, variant, initialMessages, refreshSignal, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents, daemonOnline, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, deliverMidRunMessage, composerHandle, placeholder, placeholders, suggestions, emptyState, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement, projectFileNames, toolCallDisplay, showUsageLine, showThinking, uploadAttachments, attachmentAccept, disabled, runtimePickerPlacement, composerSlots, header, leadingAccessory, footer, className, style, }: ChatPaneProps): import("react").JSX.Element;
10
10
  //# sourceMappingURL=ChatPane.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { SECRET_REDACTED_NOTICE } from '../../../../core/user-text-redaction.js';
3
- import { useEffect, useRef } from 'react';
3
+ import { useEffect, useMemo, useRef } from 'react';
4
4
  import { WorkingDirPicker } from '@jini-ai/ui';
5
5
  import { Composer } from '../../../components/Composer.js';
6
6
  import { appendComposerDiscovery } from '../../../components/composer-discovery.js';
@@ -12,6 +12,9 @@ import { useChatPane } from '../hooks/useChatPane.hooks.js';
12
12
  import { useChatPaneAgentControl } from '../hooks/useChatPaneAgentControl.hooks.js';
13
13
  import { useChatPaneComposerPlaceholder } from '../hooks/useChatPaneComposerPlaceholder.hooks.js';
14
14
  import { useChatPaneControlsHeight } from '../hooks/useChatPaneControlsHeight.hooks.js';
15
+ import { useChatPaneNewTabLinks } from '../hooks/useChatPaneNewTabLinks.hooks.js';
16
+ import { useChatPaneSurface } from '../hooks/useChatPaneSurface.hooks.js';
17
+ import { useChatPaneSuggestion } from '../hooks/useChatPaneSuggestion.hooks.js';
15
18
  import { useChatPaneFileDrop, } from '../hooks/useChatPaneFileDrop.hooks.js';
16
19
  import { useChatPaneRuntimeInventory, } from '../hooks/useChatPaneRuntimeInventory.hooks.js';
17
20
  import { CHAT_PANE_STYLES } from '../styles.js';
@@ -179,8 +182,9 @@ function ChatPaneComposerArea({ fileDrop, uploadAttachments, pane, disabled, una
179
182
  * @complexity Time/space: O(n) in rendered messages, agents, and suggestions.
180
183
  * @overallScore 100/100
181
184
  */
182
- 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, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, composerHandle, placeholder, placeholders, suggestions = [], workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
185
+ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, runtimeAccess, runtimeStatusPollMs = 5_000, title, variant = 'workspace', initialMessages, refreshSignal, conversationId, initialSelection, selection: controlledSelection, onSelectionChange, runContext, agentControl, onActivityChange, onMessagesChange, redactUserText, onSecretRedacted, onRescanAgents, scanningAgents = false, daemonOnline = true, executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange, initialDraft, composerHistoryScope, composerHistoryStorage, validateAttachments, deliverTypedAnswer, deliverMidRunMessage, composerHandle, placeholder, placeholders, suggestions = [], emptyState, workingDirectory, initialWorkingDirectory, onChangeWorkingDirectory, workingDirectoryAccess, workingDirectoryControlPlacement = 'below', projectFileNames, toolCallDisplay = 'show', showUsageLine = true, showThinking = false, uploadAttachments, attachmentAccept, disabled = false, runtimePickerPlacement = 'up', composerSlots, header, leadingAccessory, footer, className, style, }) {
183
186
  const t = useT();
187
+ const surface = useChatPaneSurface({ variant, composerSlots, workingDirectoryControlPlacement, uploadAttachments });
184
188
  const composerPlaceholder = useChatPaneComposerPlaceholder({ placeholder: placeholder, placeholders: placeholders });
185
189
  const inventory = useChatPaneRuntimeInventory(definedProps({ source: {
186
190
  access: runtimeAccess,
@@ -196,6 +200,7 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
196
200
  transport,
197
201
  agents: runtimeView.agents,
198
202
  initialMessages,
203
+ refreshSignal,
199
204
  conversationId,
200
205
  initialSelection,
201
206
  selection: controlledSelection,
@@ -216,6 +221,7 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
216
221
  workingDirectoryAccess,
217
222
  apiModeConfigured,
218
223
  deliverTypedAnswer,
224
+ deliverMidRunMessage,
219
225
  } }));
220
226
  // Mirrors `Composer.tsx`'s own `draftRef`: `composerHandle.insertText` (below) is called from
221
227
  // OUTSIDE any render, so it cannot close over `pane.composer.draft` directly — that would freeze
@@ -252,7 +258,7 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
252
258
  // the provider directly over HTTP and never touches the CLI inventory.
253
259
  const unavailable = pane.selectedAgent === undefined && !apiModeConfigured;
254
260
  const fileDrop = useChatPaneFileDrop({
255
- enabled: uploadAttachments !== undefined
261
+ enabled: surface.uploadAttachments !== undefined
256
262
  && !disabled
257
263
  && !unavailable
258
264
  && !pane.conversation.isStreaming
@@ -263,11 +269,18 @@ export function ChatPane({ transport, agents: injectedAgents = EMPTY_AGENTS, run
263
269
  // `ChatPaneProps.composerSlots`'s doc), so the two explicit keys below are never overwriting a
264
270
  // caller-supplied value — they are the ONLY source for both.
265
271
  const slots = definedProps({ source: {
266
- ...composerSlots,
267
- leadingAccessories: leadingAccessory,
268
- 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 } }) })),
272
+ ...surface.composerSlots,
273
+ leadingAccessories: surface.embed ? undefined : leadingAccessory,
274
+ footerAccessories: surface.embed ? undefined : (_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 } }) })),
269
275
  } });
270
276
  const { rootRef, controlsRef } = useChatPaneControlsHeight();
271
- 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 }), pane.secretRedacted ? _jsx("div", { className: "jini-chat-pane__notice", role: "status", children: t(SECRET_REDACTED_NOTICE) }) : null, _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, typedAnswerNotice: pane.typedAnswerNotice, onSendAsNewMessage: () => void pane.sendAsNewMessage(), 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] }));
277
+ const selectSuggestion = useChatPaneSuggestion({ rootRef, setDraft: pane.composer.setDraft });
278
+ const openLinkInNewTab = useChatPaneNewTabLinks();
279
+ // `MessageList` is memoized so a composer keystroke does not re-render the transcript; these two
280
+ // props would otherwise be fresh objects on every render and defeat it.
281
+ const { queuedPrompt, cancelQueued } = pane;
282
+ const pendingPrompt = useMemo(() => (queuedPrompt === null ? null : { text: queuedPrompt, onCancel: cancelQueued }), [queuedPrompt, cancelQueued]);
283
+ const projectFileNameSet = useMemo(() => (projectFileNames === undefined ? undefined : new Set(projectFileNames)), [projectFileNames]);
284
+ return (_jsxs("section", { className: chatPaneClassName(variant, className), style: style, "data-activity": pane.activity, ref: rootRef, onClickCapture: openLinkInNewTab, 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: [pane.conversation.messages.length === 0 && emptyState ? (_jsxs("div", { className: "jini-message-list jini-message-list--empty", children: [emptyState, surface.embed && _jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: selectSuggestion, t: t })] })) : (_jsx(MessageList, { messages: pane.conversation.messages, isStreaming: pane.conversation.isStreaming, scrollIntent: pane.conversation.scrollIntent, onScrolled: pane.conversation.acknowledgeScroll, pendingPrompt: pendingPrompt, toolCallDisplay: toolCallDisplay, showUsageLine: showUsageLine, showThinking: showThinking, ...(projectFileNameSet === undefined ? {} : { projectFileNames: projectFileNameSet }) })), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [(!surface.embed || (pane.conversation.messages.length === 0 && !emptyState)) && (_jsx(ChatPaneSuggestionsRow, { suggestions: suggestions, onSelect: selectSuggestion, t: t })), pane.secretRedacted ? _jsx("div", { className: "jini-chat-pane__notice", role: "status", children: t(SECRET_REDACTED_NOTICE) }) : null, _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, typedAnswerNotice: pane.typedAnswerNotice, onSendAsNewMessage: () => void pane.sendAsNewMessage(), t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: surface.uploadAttachments, pane: pane, disabled: disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: attachmentAccept, workingDirectoryAccess: workingDirectoryAccess, workingDirectoryControlPlacement: surface.workingDirectoryControlPlacement, t: t })] })] }), footer] }));
272
285
  }
273
286
  //# sourceMappingURL=ChatPane.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @module create-mid-run-message-poster
3
+ *
4
+ * The client half of a mid-run message: a ready-made {@link DeliverMidRunMessage} that posts text the
5
+ * human sent while a run streams to that run's live agent. Pass it as `ChatPane`'s
6
+ * `deliverMidRunMessage`.
7
+ *
8
+ * ```tsx
9
+ * <ChatPane deliverMidRunMessage={createMidRunMessagePoster({ baseUrl: '', fetch })} />
10
+ * ```
11
+ *
12
+ * Sibling of `create-typed-answer-poster.ts`. The route is `@jini-ai/daemon`'s
13
+ * `POST /api/runs/:runId/messages`: `202` when the agent took the text into its running turn, `409`
14
+ * with `details.delivery` when it did not.
15
+ *
16
+ * ## Why it resolves an outcome instead of throwing
17
+ *
18
+ * The pane acts differently on each outcome and must never drop the text: `'unsupported'` stops the
19
+ * run and sends the text as the next turn; `'not-running'` and `'failed'` queue it behind the run,
20
+ * which is already ending (or may be — never cut short a run the host could not reach). A 404 means
21
+ * the daemon never saw this run (a host-side run such as a BYOK API turn), so it is `'unsupported'`
22
+ * too: no live process to steer, but an interrupt still works.
23
+ */
24
+ import { type UserTextRedactionOptions } from '../../../core/user-text-redaction.js';
25
+ import type { DeliverMidRunMessage } from './types.js';
26
+ export interface CreateMidRunMessagePosterOptions extends UserTextRedactionOptions {
27
+ /** Abandon a post after this many ms (reported as `'failed'`). Defaults to 30s. */
28
+ readonly timeoutMs?: number;
29
+ /** Extra headers per request (e.g. a CSRF token). `content-type` is always set and cannot be overridden. */
30
+ readonly headers?: Readonly<Record<string, string>>;
31
+ }
32
+ /**
33
+ * Builds a {@link DeliverMidRunMessage} that POSTs `{text}` to `${baseUrl}/api/runs/:runId/messages`.
34
+ *
35
+ * @param deps.baseUrl - Origin to call; `''` for same-origin.
36
+ * @param deps.fetch - The fetch to use (the browser's, in production).
37
+ * @returns Resolves to the outcome; never rejects.
38
+ * @complexity O(1) — one request per call.
39
+ */
40
+ export declare function createMidRunMessagePoster({ baseUrl, fetch }: {
41
+ baseUrl: string;
42
+ fetch: typeof globalThis.fetch;
43
+ }, options?: CreateMidRunMessagePosterOptions): DeliverMidRunMessage;
44
+ //# sourceMappingURL=create-mid-run-message-poster.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @module create-mid-run-message-poster
3
+ *
4
+ * The client half of a mid-run message: a ready-made {@link DeliverMidRunMessage} that posts text the
5
+ * human sent while a run streams to that run's live agent. Pass it as `ChatPane`'s
6
+ * `deliverMidRunMessage`.
7
+ *
8
+ * ```tsx
9
+ * <ChatPane deliverMidRunMessage={createMidRunMessagePoster({ baseUrl: '', fetch })} />
10
+ * ```
11
+ *
12
+ * Sibling of `create-typed-answer-poster.ts`. The route is `@jini-ai/daemon`'s
13
+ * `POST /api/runs/:runId/messages`: `202` when the agent took the text into its running turn, `409`
14
+ * with `details.delivery` when it did not.
15
+ *
16
+ * ## Why it resolves an outcome instead of throwing
17
+ *
18
+ * The pane acts differently on each outcome and must never drop the text: `'unsupported'` stops the
19
+ * run and sends the text as the next turn; `'not-running'` and `'failed'` queue it behind the run,
20
+ * which is already ending (or may be — never cut short a run the host could not reach). A 404 means
21
+ * the daemon never saw this run (a host-side run such as a BYOK API turn), so it is `'unsupported'`
22
+ * too: no live process to steer, but an interrupt still works.
23
+ */
24
+ import { redactUserText } from '../../../core/user-text-redaction.js';
25
+ const DEFAULT_TIMEOUT_MS = 30_000;
26
+ /** Reads `error.details.delivery` off a 409 body; anything unreadable counts as `'unsupported'`. */
27
+ async function conflictDelivery(response) {
28
+ const body = await response.json().catch(() => null);
29
+ const delivery = body?.error?.details?.delivery;
30
+ return delivery === 'not-running' ? 'not-running' : 'unsupported';
31
+ }
32
+ /** Maps the route's response onto an outcome. Only 202 is a delivery. */
33
+ async function deliveryFor(response) {
34
+ if (response.status === 202)
35
+ return 'delivered';
36
+ if (response.status === 409)
37
+ return conflictDelivery(response);
38
+ return response.status === 404 ? 'unsupported' : 'failed';
39
+ }
40
+ /**
41
+ * Builds a {@link DeliverMidRunMessage} that POSTs `{text}` to `${baseUrl}/api/runs/:runId/messages`.
42
+ *
43
+ * @param deps.baseUrl - Origin to call; `''` for same-origin.
44
+ * @param deps.fetch - The fetch to use (the browser's, in production).
45
+ * @returns Resolves to the outcome; never rejects.
46
+ * @complexity O(1) — one request per call.
47
+ */
48
+ export function createMidRunMessagePoster({ baseUrl, fetch }, options = {}) {
49
+ const origin = baseUrl.replace(/\/$/u, '');
50
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
51
+ return async ({ runId, text }) => {
52
+ const result = (options.redactUserText ?? redactUserText)({ text });
53
+ if (result.secretRedacted)
54
+ options.onSecretRedacted?.({ secretRedacted: true, count: result.count });
55
+ const controller = new AbortController();
56
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
57
+ const outcome = await fetch(`${origin}/api/runs/${encodeURIComponent(runId)}/messages`, {
58
+ method: 'POST',
59
+ // Same-origin cookie session: the human's own credentials authorize steering their run.
60
+ credentials: 'same-origin',
61
+ headers: { ...options.headers, 'content-type': 'application/json' },
62
+ body: JSON.stringify({ text: result.text }),
63
+ signal: controller.signal,
64
+ }).then(deliveryFor, () => 'failed');
65
+ clearTimeout(timer);
66
+ return outcome;
67
+ };
68
+ }
69
+ //# sourceMappingURL=create-mid-run-message-poster.js.map
@@ -1,6 +1,6 @@
1
1
  import type { UserTextRedactionOptions } from '../../../../core/user-text-redaction.js';
2
2
  import { type ChatPaneSendBlocker } from '../rules.js';
3
- import type { ChatPaneActivity, ChatPaneAgent, ChatPaneAttachmentUploadOptions, ChatPaneAgentSelection, ChatPaneRunContext, ChatPaneWorkingDirectoryAccess, DeliverTypedAnswer, TypedAnswerNotice } from '../types.js';
3
+ import type { ChatPaneActivity, ChatPaneAgent, ChatPaneAttachmentUploadOptions, ChatPaneAgentSelection, ChatPaneRunContext, ChatPaneWorkingDirectoryAccess, DeliverMidRunMessage, DeliverTypedAnswer, TypedAnswerNotice } from '../types.js';
4
4
  import type { ChatAttachment, ChatMessage } from '@jini-ai/chat';
5
5
  import type { ComposerHistoryStoragePort } from '../../../../core/composer-history.js';
6
6
  import type { ChatTransport } from '@jini-ai/chat';
@@ -11,6 +11,7 @@ export interface UseChatPaneOptions extends UserTextRedactionOptions {
11
11
  transport: ChatTransport;
12
12
  agents: readonly ChatPaneAgent[];
13
13
  initialMessages?: ChatMessage[];
14
+ refreshSignal?: import('../../../hooks/useConversation.js').UseConversationOptions['refreshSignal'];
14
15
  conversationId?: string | null;
15
16
  initialSelection?: ChatPaneAgentSelection;
16
17
  selection?: ChatPaneAgentSelection;
@@ -36,6 +37,8 @@ export interface UseChatPaneOptions extends UserTextRedactionOptions {
36
37
  apiModeConfigured?: boolean;
37
38
  /** See `ChatPaneProps.deliverTypedAnswer`. Omitted keeps today's queue-while-streaming behavior. */
38
39
  deliverTypedAnswer?: DeliverTypedAnswer;
40
+ /** See `ChatPaneProps.deliverMidRunMessage`. Omitted keeps today's queue-while-streaming behavior. */
41
+ deliverMidRunMessage?: DeliverMidRunMessage;
39
42
  }
40
43
  export interface UseChatPaneResult extends UseChatPaneWorkingDirectoryResult {
41
44
  /** Show a localized non-secret notice when a paste was removed. */