@jini-ai/chat 0.6.15-rc.1 → 0.6.15

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 (93) hide show
  1. package/dist/core/accepted-input.js +29 -14
  2. package/dist/core/ag-ui/entry.js +3 -1
  3. package/dist/core/agent-plugin-chat-state.js +3 -1
  4. package/dist/core/assistant-client/ag-ui-transport.js +1 -1
  5. package/dist/core/assistant-client/attachment-liveness.js +1 -1
  6. package/dist/core/assistant-client/durable-run-subscription.js +2 -1
  7. package/dist/core/assistant-client/transport.js +13 -7
  8. package/dist/core/assistant-content.js +2 -1
  9. package/dist/core/composer-history.js +2 -1
  10. package/dist/core/durable-projection.js +2 -1
  11. package/dist/core/durable-runs/continuation.js +2 -1
  12. package/dist/core/messages.d.ts +5 -1
  13. package/dist/core/partial-json.js +18 -9
  14. package/dist/core/persistence/title.d.ts +1 -1
  15. package/dist/core/persistence/title.js +25 -7
  16. package/dist/core/question-answer.js +9 -4
  17. package/dist/core/question-form/normalize.js +1 -1
  18. package/dist/core/question-form/scan.js +25 -15
  19. package/dist/core/question-form/secret-answers.js +2 -1
  20. package/dist/core/run-activity.js +8 -4
  21. package/dist/core/run-events/entry.js +6 -2
  22. package/dist/core/transcript.js +13 -6
  23. package/dist/core/turn-usage.js +12 -4
  24. package/dist/core/user-text-redaction.d.ts +2 -5
  25. package/dist/core/user-text-redaction.js +3 -3
  26. package/dist/core/util/manifest.js +13 -11
  27. package/dist/core/util/markdown-context.d.ts +14 -0
  28. package/dist/core/util/markdown-context.js +42 -4
  29. package/dist/core/util/markup-attributes.js +1 -1
  30. package/dist/core/util/parser.js +7 -7
  31. package/dist/core/util/pointer.js +22 -6
  32. package/dist/core/util/recover.js +5 -8
  33. package/dist/core/util/strip.js +12 -15
  34. package/dist/core/util/validate.js +30 -18
  35. package/dist/embed-widget/browser/lazy-loader.js +16 -12
  36. package/dist/embed-widget/react/dialog.hooks.js +2 -2
  37. package/dist/embed-widget/react/embed.js +1 -1
  38. package/dist/embed-widget/react/header.hooks.js +1 -1
  39. package/dist/embed-widget/transports/fetch-sse.js +2 -1
  40. package/dist/embed-widget/vanilla/index.js +19 -12
  41. package/dist/embed-widget/vanilla/state.js +4 -2
  42. package/dist/react/components/AttachmentPreviewModal.hooks.js +2 -1
  43. package/dist/react/components/ChatSettingsDialog.js +18 -9
  44. package/dist/react/components/Composer.js +5 -4
  45. package/dist/react/components/ConversationList.js +5 -4
  46. package/dist/react/components/Markdown.js +29 -26
  47. package/dist/react/components/McpUiSurfaceCard.js +2 -1
  48. package/dist/react/components/MessageList.hooks.js +3 -2
  49. package/dist/react/components/MessageRow.js +23 -7
  50. package/dist/react/components/MessageRow.usage.js +7 -2
  51. package/dist/react/components/QuestionForm.js +4 -5
  52. package/dist/react/components/SentAttachment.js +4 -1
  53. package/dist/react/components/TodoCard.js +9 -1
  54. package/dist/react/components/ToolCard.js +8 -7
  55. package/dist/react/components/composer-discovery.js +8 -8
  56. package/dist/react/features/chat-pane/components/AgentPluginUsage.js +1 -2
  57. package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +1 -1
  58. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +15 -5
  59. package/dist/react/features/chat-pane/components/ChatPane.js +5 -8
  60. package/dist/react/features/chat-pane/components/RuntimeDropUp.js +1 -1
  61. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +1 -1
  62. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +2 -2
  63. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +12 -7
  64. package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +18 -22
  65. package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +1 -1
  66. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +2 -2
  67. package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +2 -2
  68. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +2 -2
  69. package/dist/react/features/chat-pane/model-options.js +4 -2
  70. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  71. package/dist/react/features/chat-pane/styles.js +26 -3
  72. package/dist/react/features/chat-pane/types.d.ts +8 -0
  73. package/dist/react/hooks/assistant-chats.js +22 -8
  74. package/dist/react/hooks/useArtifactStream.js +2 -1
  75. package/dist/react/hooks/useComposer.js +2 -2
  76. package/dist/react/hooks/useComposerHistoryKeys.hooks.js +2 -2
  77. package/dist/react/hooks/useConversation.js +9 -4
  78. package/dist/react/hooks/useExtEventGroups.js +4 -2
  79. package/dist/react/hooks/useLatestOperation.js +2 -1
  80. package/dist/react/hooks/useQuestionForms.js +32 -22
  81. package/dist/react/hooks/useRunStream.js +4 -2
  82. package/dist/react/message-blocks.js +4 -2
  83. package/dist/react/styles/reference.css +9 -0
  84. package/dist/react/tool-call-display.js +1 -2
  85. package/dist/server/durable-runs/recover.js +13 -5
  86. package/dist/server/run-finalizer/entry.js +1 -0
  87. package/dist/store/runtime/chat-history-store.js +9 -3
  88. package/dist/store/runtime/durable-run-store.js +34 -24
  89. package/dist/store/runtime/run-ledger.js +7 -4
  90. package/dist/store/sql/boundary.js +3 -1
  91. package/dist/store/sql/cursor.js +8 -3
  92. package/dist/store/sql/store.js +36 -28
  93. package/package.json +15 -15
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ const RUNNING_TITLE_CLASS = ' shimmer-text';
2
3
  /**
3
4
  * @module ToolCard
4
5
  *
@@ -146,19 +147,19 @@ function DelegatedToolCard({ name, input, result, runStreaming, runSucceeded })
146
147
  const args = isDelegatedWrapperToolName({ name: name }) ? wrapper.input : input;
147
148
  const target = primaryTarget(args);
148
149
  const isRunning = runStreaming && !result;
149
- 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: headSummary({ text: target }) }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded, children: _jsx(ToolResultMedia, { result: result }) }) }));
150
+ 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 ? RUNNING_TITLE_CLASS : ''}`, children: [t('Tool call'), " \u00B7 ", humanizeToolId(toolId)] }), target ? _jsx("span", { className: "op-meta", children: headSummary({ text: target }) }) : null] }), after: _jsx(FileErrorDetail, { result: result }), children: _jsx(ToolCallDetail, { input: args ?? {}, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded, children: _jsx(ToolResultMedia, { result: result }) }) }));
150
151
  }
151
152
  function SearchToolsCard({ input, result, runStreaming, runSucceeded }) {
152
153
  const t = useT();
153
154
  const obj = (input ?? {});
154
155
  const isRunning = runStreaming && !result;
155
- 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 }) }));
156
+ 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 ? RUNNING_TITLE_CLASS : ''}`, 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 }) }));
156
157
  }
157
158
  function DescribeToolCard({ input, result, runStreaming, runSucceeded }) {
158
159
  const t = useT();
159
160
  const obj = (input ?? {});
160
161
  const isRunning = runStreaming && !result;
161
- 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 }) }));
162
+ 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 ? RUNNING_TITLE_CLASS : ''}`, 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 }) }));
162
163
  }
163
164
  function OpenInTabButton({ filePath, ctx }) {
164
165
  const t = useT();
@@ -184,7 +185,7 @@ function FileWriteCard({ input, result, runStreaming, runSucceeded, ctx }) {
184
185
  const baseName = file.split('/').pop();
185
186
  const lines = typeof obj.content === 'string' ? obj.content.split('\n').length : null;
186
187
  const isRunning = runStreaming && !result;
187
- 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 })] }));
188
+ 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 ? RUNNING_TITLE_CLASS : ''}`, 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 })] }));
188
189
  }
189
190
  function FileEditCard({ input, result, runStreaming, runSucceeded, ctx }) {
190
191
  const t = useT();
@@ -194,7 +195,7 @@ function FileEditCard({ input, result, runStreaming, runSucceeded, ctx }) {
194
195
  const baseName = file.split('/').pop();
195
196
  const editCount = Array.isArray(obj.edits) ? obj.edits.length : 1;
196
197
  const isRunning = runStreaming && !result;
197
- 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 })] }));
198
+ 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 ? RUNNING_TITLE_CLASS : ''}`, 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 })] }));
198
199
  }
199
200
  function FileReadCard({ input, result, runStreaming, runSucceeded, ctx }) {
200
201
  const t = useT();
@@ -203,7 +204,7 @@ function FileReadCard({ input, result, runStreaming, runSucceeded, ctx }) {
203
204
  // See OpenInTabButton's baseName comment: `.split('/').pop()` is never undefined.
204
205
  const baseName = file.split('/').pop();
205
206
  const isRunning = runStreaming && !result;
206
- 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 })] }));
207
+ 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 ? RUNNING_TITLE_CLASS : ''}`, 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 })] }));
207
208
  }
208
209
  function BashCard({ input, result, runStreaming, runSucceeded }) {
209
210
  const t = useT();
@@ -211,7 +212,7 @@ function BashCard({ input, result, runStreaming, runSucceeded }) {
211
212
  const command = obj.command ?? '';
212
213
  const desc = obj.description;
213
214
  const isRunning = runStreaming && !result;
214
- 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: _jsx(ToolCallDetail, { input: command, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
215
+ 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 ? RUNNING_TITLE_CLASS : ''}`, children: t('Bash') }), desc ? _jsx("span", { className: "op-meta op-desc", children: desc }) : null] }), children: _jsx(ToolCallDetail, { input: command, result: result, runStreaming: runStreaming, runSucceeded: runSucceeded }) }));
215
216
  }
216
217
  function GlobCard({ input, result, runStreaming, runSucceeded }) {
217
218
  const t = useT();
@@ -1,4 +1,4 @@
1
- const SLASH_QUERY_RE = /^\/([^\s/]*)(?:(\s+)([\s\S]*))?$/;
1
+ const SLASH_QUERY_RE = /^\/([^\s/]*)(\s+)?/;
2
2
  /**
3
3
  * Open Design's active slash-token rule (`/^\/([^\s/]*)$/`), extended to admit one optional
4
4
  * trailing argument while staying anchored end-to-end. The command segment itself is unchanged —
@@ -9,13 +9,13 @@ export function parseComposerSlashQuery({ draft }) {
9
9
  const match = SLASH_QUERY_RE.exec(draft);
10
10
  if (!match)
11
11
  return null;
12
- // The `as string` casts are guaranteed by SLASH_QUERY_RE itself, not assumed: group 1 is a bare
13
- // `*` capture outside any optional group, so it always participates (possibly as ''); group 3 is
14
- // `*` inside the same optional group as group 2, so it participates whenever a separator does.
15
- // `noUncheckedIndexedAccess` cannot see either guarantee, and the `?? ''` fallbacks that used to
16
- // satisfy it were unreachable branches.
17
- const [, command, separator, rest] = match;
18
- return { command: command, argument: separator === undefined ? null : rest };
12
+ // Group 1 always participates, including the empty command; without a separator the entire
13
+ // draft must be the command. With one, the greedy separator consumes all leading whitespace.
14
+ // This preserves the whole-draft grammar without overlapping separator/rest repetitions.
15
+ const [, command, separator] = match;
16
+ if (separator === undefined && match[0].length !== draft.length)
17
+ return null;
18
+ return { command: command, argument: separator === undefined ? null : draft.slice(match[0].length) };
19
19
  }
20
20
  /**
21
21
  * Once a separator has been typed, only an item whose declared `command` exactly equals the typed
@@ -28,14 +28,13 @@ function resolveAgentPluginStrip({ messages }, { conversationId }) {
28
28
  /** One per-chat usage strip; removing a name persists its exclusion for future turns. */
29
29
  export function AgentPluginUsage({ conversationId, messages }) {
30
30
  const t = useT();
31
- const [revision, setRevision] = useState(0);
31
+ const [, setRevision] = useState(0);
32
32
  useEffect(() => {
33
33
  const refresh = () => setRevision(value => value + 1);
34
34
  window.addEventListener(AGENT_PLUGIN_STATE_CHANGED, refresh);
35
35
  window.addEventListener('storage', refresh);
36
36
  return () => { window.removeEventListener(AGENT_PLUGIN_STATE_CHANGED, refresh); window.removeEventListener('storage', refresh); };
37
37
  }, []);
38
- void revision;
39
38
  const strip = resolveAgentPluginStrip({ messages }, { conversationId });
40
39
  if (!strip)
41
40
  return null;
@@ -10,5 +10,5 @@ export declare function runtimeAgentStatus({ agent }: {
10
10
  * @complexity Time: O(n) in the runtime inventory; space: O(n) for ordering.
11
11
  * @overallScore 100/100
12
12
  */
13
- export declare function AgentRuntimePicker({ agents, value, onChange, onRescan, scanning, daemonOnline, placement, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, agentIconBasePath, }: AgentRuntimePickerProps): import("react").JSX.Element;
13
+ export declare function AgentRuntimePicker({ agents, nativeToolPolicy, value, onChange, onRescan, scanning, daemonOnline, placement, executionMode, apiModeAvailable, onExecutionModeChange, byokRuntime, onByokModelChange, agentIconBasePath, }: AgentRuntimePickerProps): import("react").JSX.Element;
14
14
  //# sourceMappingURL=AgentRuntimePicker.d.ts.map
@@ -1,7 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { pickerModelValue, pickerCatalogNote, pickerModelOptions, shortModelLabel } from '../model-options.js';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { AgentIcon, RemixIcon, SearchableModelSelect } from '@jini-ai/ui';
5
+ import { assessNativeToolPolicy } from '@jini-ai/agent-runtime/native-tool-policy';
5
6
  import { useT } from '../../../hooks/context.js';
6
7
  import { definedProps } from '../../../util/defined-props.js';
7
8
  import { RuntimeDropUp } from './RuntimeDropUp.js';
@@ -17,8 +18,17 @@ export function runtimeAgentStatus({ agent }) {
17
18
  return 'Installed · sign-in required';
18
19
  return agent.version ?? 'Installed';
19
20
  }
21
+ /** Selection preflight uses the launch assessor; it never substitutes for enforcement. */
22
+ function NativeToolPolicyWarning({ agent, policy, isApi }) {
23
+ const t = useT();
24
+ if (!agent || isApi)
25
+ return null;
26
+ const warning = policy ? assessNativeToolPolicy({ def: agent, policy }) : null;
27
+ return _jsxs(_Fragment, { children: [agent.fullCliAccessNote ? _jsx("p", { children: t(agent.fullCliAccessNote) }) : null, warning ? _jsx("p", { role: "alert", children: t(warning) }) : null] });
28
+ }
20
29
  function RuntimeModeButtons({ executionMode, daemonOnline, apiModeAvailable, onExecutionModeChange, t, }) {
21
- return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: `jini-runtime-mode${executionMode === 'local' ? ' is-active' : ''}`, disabled: !daemonOnline && executionMode !== 'local', onClick: () => onExecutionModeChange?.('local'), children: [_jsx(RemixIcon, { name: "terminal-box-line", size: 18 }), _jsx("span", { children: t('Use Local CLI') }), _jsx("span", { className: "jini-runtime-mode__meta", children: !daemonOnline ? t('offline') : executionMode === 'local' ? t('active') : '' }), executionMode === 'local' ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] }), _jsxs("button", { type: "button", className: `jini-runtime-mode${executionMode === 'api' ? ' is-active' : ''}`, disabled: !apiModeAvailable, onClick: () => onExecutionModeChange?.('api'), children: [_jsx(RemixIcon, { name: "link", size: 18 }), _jsx("span", { children: t('Use API · BYOK') }), !apiModeAvailable ? _jsx("span", { className: "jini-runtime-mode__meta", children: t('not configured') }) : null, executionMode === 'api' ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] })] }));
30
+ const localModeLabel = executionMode === 'local' ? t('active') : '';
31
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: `jini-runtime-mode${executionMode === 'local' ? ' is-active' : ''}`, disabled: !daemonOnline && executionMode !== 'local', onClick: () => onExecutionModeChange?.('local'), children: [_jsx(RemixIcon, { name: "terminal-box-line", size: 18 }), _jsx("span", { children: t('Use Local CLI') }), _jsx("span", { className: "jini-runtime-mode__meta", children: !daemonOnline ? t('offline') : localModeLabel }), executionMode === 'local' ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] }), _jsxs("button", { type: "button", className: `jini-runtime-mode${executionMode === 'api' ? ' is-active' : ''}`, disabled: !apiModeAvailable, onClick: () => onExecutionModeChange?.('api'), children: [_jsx(RemixIcon, { name: "link", size: 18 }), _jsx("span", { children: t('Use API · BYOK') }), !apiModeAvailable ? _jsx("span", { className: "jini-runtime-mode__meta", children: t('not configured') }) : null, executionMode === 'api' ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] })] }));
22
32
  }
23
33
  function RuntimeAgentList({ orderedAgents, value, onChange, agentIconBasePath, t, }) {
24
34
  if (orderedAgents.length === 0) {
@@ -27,7 +37,7 @@ function RuntimeAgentList({ orderedAgents, value, onChange, agentIconBasePath, t
27
37
  return (_jsx(_Fragment, { children: orderedAgents.map((agent) => {
28
38
  const active = agent.id === value.agentId;
29
39
  const status = runtimeAgentStatus({ agent: agent });
30
- return (_jsxs("button", { type: "button", role: "radio", "aria-checked": active, className: `jini-runtime-agent${active ? ' is-active' : ''}`, "aria-current": active ? 'true' : undefined, "aria-label": `${agent.name} · ${status}`, onClick: () => onChange(defaultChatPaneSelection({ agent: agent })), children: [_jsx(AgentIcon, { id: agent.id, size: 20, className: "jini-runtime-agent-icon", ...definedProps({ source: { basePath: agentIconBasePath } }) }), _jsx("span", { className: "jini-runtime-agent__copy", children: _jsx("strong", { children: agent.name }) }), agent.supportsTools === false ? (_jsx("span", { className: "jini-runtime-agent__badge", title: t("This CLI has no MCP support, so it cannot use this app's tools."), children: t('No tools') })) : null, _jsx("span", { className: "jini-runtime-agent__status", children: active ? t('selected') : status }), active ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] }, agent.id));
40
+ return (_jsxs("button", { type: "button", role: "radio", "aria-checked": active, className: `jini-runtime-agent${active ? ' is-active' : ''}`, "aria-current": active ? 'true' : undefined, "aria-label": `${agent.name} · ${status}`, onClick: () => onChange(defaultChatPaneSelection({ agent: agent })), children: [_jsx(AgentIcon, { id: agent.id, size: 20, className: "jini-runtime-agent-icon", ...definedProps({ source: { basePath: agentIconBasePath } }) }), _jsxs("span", { className: "jini-runtime-agent__copy", children: [_jsx("strong", { children: agent.name }), agent.fullCliAccessNote ? _jsx("small", { children: t(agent.fullCliAccessNote) }) : null] }), agent.supportsTools === false ? (_jsx("span", { className: "jini-runtime-agent__badge", title: t("This CLI has no MCP support, so it cannot use this app's tools."), children: t('No tools') })) : null, _jsx("span", { className: "jini-runtime-agent__status", children: active ? t('selected') : status }), active ? (_jsx(RemixIcon, { name: "check-line", size: 19, className: "jini-runtime-check" })) : null] }, agent.id));
31
41
  }) }));
32
42
  }
33
43
  /**
@@ -127,7 +137,7 @@ function RuntimePopover({ picker, value, onChange, onRescan, scanning, daemonOnl
127
137
  * @complexity Time: O(n) in the runtime inventory; space: O(n) for ordering.
128
138
  * @overallScore 100/100
129
139
  */
130
- export function AgentRuntimePicker({ agents, value, onChange, onRescan, scanning = false, daemonOnline = true, placement = 'up', executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange,
140
+ export function AgentRuntimePicker({ agents, nativeToolPolicy, value, onChange, onRescan, scanning = false, daemonOnline = true, placement = 'up', executionMode = 'local', apiModeAvailable = false, onExecutionModeChange, byokRuntime, onByokModelChange,
131
141
  // No default here (was '/agent-icons'): omitted means "let AgentIcon fall back to its own
132
142
  // bundled icon set" — see AgentIcon.tsx's BUNDLED_ICON_URLS. A host that still wants to vendor
133
143
  // its own asset directory can pass this explicitly, same as before.
@@ -140,6 +150,6 @@ agentIconBasePath, }) {
140
150
  // operator on BYOK saw a CLI named as "selected" and a model reading "Default (CLI config)"
141
151
  // while their messages went to a provider over an API key.
142
152
  const isApi = executionMode === 'api';
143
- return (_jsxs("div", { className: "jini-runtime-picker", children: [_jsx(RuntimeTrigger, { picker: picker, isApi: isApi, byokRuntime: byokRuntime, agentIconBasePath: agentIconBasePath, t: t }), isApi ? null : (_jsx(RuntimeBarMenus, { selectedAgent: picker.selectedAgent, value: value, onChange: onChange, modelLabel: picker.modelLabel, t: t })), _jsx(RuntimePopover, { picker: picker, value: value, onChange: onChange, onRescan: onRescan, scanning: scanning, daemonOnline: daemonOnline, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, byokRuntime: byokRuntime, onByokModelChange: onByokModelChange, agentIconBasePath: agentIconBasePath, t: t })] }));
153
+ return (_jsxs("div", { className: "jini-runtime-picker", children: [_jsx(RuntimeTrigger, { picker: picker, isApi: isApi, byokRuntime: byokRuntime, agentIconBasePath: agentIconBasePath, t: t }), isApi ? null : (_jsx(RuntimeBarMenus, { selectedAgent: picker.selectedAgent, value: value, onChange: onChange, modelLabel: picker.modelLabel, t: t })), _jsx(NativeToolPolicyWarning, { agent: picker.selectedAgent, policy: nativeToolPolicy, isApi: isApi }), _jsx(RuntimePopover, { picker: picker, value: value, onChange: onChange, onRescan: onRescan, scanning: scanning, daemonOnline: daemonOnline, executionMode: executionMode, apiModeAvailable: apiModeAvailable, onExecutionModeChange: onExecutionModeChange, byokRuntime: byokRuntime, onByokModelChange: onByokModelChange, agentIconBasePath: agentIconBasePath, t: t })] }));
144
154
  }
145
155
  //# sourceMappingURL=AgentRuntimePicker.js.map
@@ -24,13 +24,9 @@ const EMPTY_AGENTS = [];
24
24
  function defaultHeader(title, onReset, t) {
25
25
  return (_jsxs("div", { className: "jini-chat-pane__header", children: [_jsxs("div", { className: "jini-chat-pane__heading", children: [_jsx("span", { className: "jini-chat-pane__eyebrow", children: t('Workspace chat') }), _jsx("h1", { className: "jini-chat-pane__title", children: title ?? t('Chat') })] }), _jsx("button", { type: "button", className: "jini-chat-pane__new-thread", onClick: onReset, children: t('New thread') })] }));
26
26
  }
27
- /** Collapses `header ?? defaultHeader(...)` out of `ChatPane` itself — nesting is what taxes the
28
- * `??` in the caller's score, not the branch itself. */
29
- function resolveChatPaneHeader(header, title, onReset, t) {
30
- return header ?? defaultHeader(title, onReset, t);
31
- }
32
27
  function chatPaneClassName(variant, className) {
33
- return `jini-chat-pane jini-chat-pane--${variant}${className ? ` ${className}` : ''}`;
28
+ const suffix = className ? ' ' + className : '';
29
+ return `jini-chat-pane jini-chat-pane--${variant}${suffix}`;
34
30
  }
35
31
  function resolveRuntimeAccessView(runtimeAccess, injectedAgents, scanningAgents, daemonOnline, onRescanAgents, inventory) {
36
32
  if (runtimeAccess === undefined) {
@@ -273,7 +269,7 @@ function useComposerHandlePublisher({ pane }, { composerHandle }) {
273
269
  composerDraftRef.current = pane.composer.draft;
274
270
  useEffect(() => {
275
271
  if (!composerHandle)
276
- return;
272
+ return undefined;
277
273
  composerHandle.current = {
278
274
  insertText: (text) => {
279
275
  pane.composer.setDraft(appendComposerDiscovery({ draft: composerDraftRef.current, insertText: text }));
@@ -401,12 +397,13 @@ export function ChatPane(props) {
401
397
  onExecutionModeChange: p.onExecutionModeChange,
402
398
  byokRuntime: p.byokRuntime,
403
399
  onByokModelChange: p.onByokModelChange,
400
+ nativeToolPolicy: p.nativeToolPolicy,
404
401
  } }) })),
405
402
  }, { leadingAccessory: p.leadingAccessory });
406
403
  const { rootRef, controlsRef } = useChatPaneControlsHeight();
407
404
  const selectSuggestion = useChatPaneSuggestion({ rootRef, setDraft: pane.composer.setDraft });
408
405
  const openLinkInNewTab = useChatPaneNewTabLinks();
409
406
  const { pendingPrompts, projectFileNameSet } = useChatPaneTranscriptProps({ pane }, definedProps({ source: { projectFileNames: p.projectFileNames } }));
410
- return (_jsxs("section", { className: chatPaneClassName(p.variant, p.className), style: p.style, "data-activity": pane.activity, ref: rootRef, onClickCapture: openLinkInNewTab, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), resolveChatPaneHeader(p.header, p.title, pane.reset, t), p.agentPluginsMode !== 'off' && _jsx(AgentPluginUsage, { conversationId: pane.conversation.conversationId, messages: pane.conversation.messages }), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(ChatPaneTranscript, { pane: pane, emptyState: p.emptyState, embed: surface.embed, suggestions: p.suggestions, onSelectSuggestion: selectSuggestion, pendingPrompts: pendingPrompts, projectFileNameSet: projectFileNameSet, toolCallDisplay: p.toolCallDisplay, showUsageLine: p.showUsageLine, showThinking: p.showThinking, t: t }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [showsControlsSuggestions(surface.embed, pane.conversation.messages.length, p.emptyState) && (_jsx(ChatPaneSuggestionsRow, { suggestions: p.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: p.executionMode, apiModeAvailable: p.apiModeAvailable, onExecutionModeChange: p.onExecutionModeChange, sendError: pane.conversation.sendError, onRetrySend: () => void pane.retrySend(), attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, typedAnswerNotice: pane.typedAnswerNotice, t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: surface.uploadAttachments, pane: pane, disabled: p.disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: p.attachmentAccept, workingDirectoryAccess: p.workingDirectoryAccess, workingDirectoryControlPlacement: surface.workingDirectoryControlPlacement, t: t })] })] }), p.footer] }));
407
+ return (_jsxs("section", { className: chatPaneClassName(p.variant, p.className), style: p.style, "data-activity": pane.activity, ref: rootRef, onClickCapture: openLinkInNewTab, children: [_jsx("style", { "data-jini-chat-pane-styles": "true", children: CHAT_PANE_STYLES }), p.header ?? defaultHeader(p.title, pane.reset, t), p.agentPluginsMode !== 'off' && _jsx(AgentPluginUsage, { conversationId: pane.conversation.conversationId, messages: pane.conversation.messages }), _jsxs("div", { className: "jini-chat-pane__body", children: [_jsx(ChatPaneTranscript, { pane: pane, emptyState: p.emptyState, embed: surface.embed, suggestions: p.suggestions, onSelectSuggestion: selectSuggestion, pendingPrompts: pendingPrompts, projectFileNameSet: projectFileNameSet, toolCallDisplay: p.toolCallDisplay, showUsageLine: p.showUsageLine, showThinking: p.showThinking, t: t }), _jsxs("div", { className: "jini-chat-pane__controls", ref: controlsRef, children: [showsControlsSuggestions(surface.embed, pane.conversation.messages.length, p.emptyState) && (_jsx(ChatPaneSuggestionsRow, { suggestions: p.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: p.executionMode, apiModeAvailable: p.apiModeAvailable, onExecutionModeChange: p.onExecutionModeChange, sendError: pane.conversation.sendError, onRetrySend: () => void pane.retrySend(), attachmentError: pane.attachmentError, dropReadError: fileDrop.dropReadError, workingDirectoryError: pane.workingDirectoryError, workingDirectoryPending: pane.workingDirectoryPending, workingDirectoryInvalid: pane.workingDirectoryInvalid, runtimeInventoryError: inventory.runtimeInventoryError, typedAnswerNotice: pane.typedAnswerNotice, t: t }), _jsx(ChatPaneComposerArea, { fileDrop: fileDrop, uploadAttachments: surface.uploadAttachments, pane: pane, disabled: p.disabled, unavailable: unavailable, placeholder: composerPlaceholder, slots: slots, attachmentAccept: p.attachmentAccept, workingDirectoryAccess: p.workingDirectoryAccess, workingDirectoryControlPlacement: surface.workingDirectoryControlPlacement, t: t })] })] }), p.footer] }));
411
408
  }
412
409
  //# sourceMappingURL=ChatPane.js.map
@@ -33,7 +33,7 @@ export function RuntimeDropUp({ label, valueLabel, triggerLabel, value, options,
33
33
  if (tipVisible)
34
34
  tip.show();
35
35
  };
36
- return (_jsxs(_Fragment, { children: [_jsxs("button", { ref: triggerRef, type: "button", className: `jini-runtime-dropup-trigger${className ? ` ${className}` : ''}`, "aria-label": `${label}: ${valueLabel}`, "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": menuId, ...(shortened ? {} : { title: valueLabel }), onClick: () => {
36
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { ref: triggerRef, type: "button", className: 'jini-runtime-dropup-trigger' + (className ? ' ' + className : ''), "aria-label": `${label}: ${valueLabel}`, "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": menuId, ...(shortened ? {} : { title: valueLabel }), onClick: () => {
37
37
  tip.hide();
38
38
  toggleOpen();
39
39
  }, onKeyDown: (event) => {
@@ -179,7 +179,7 @@ export function createDaemonAttachmentUploader({ baseUrl, fetch }, options = {})
179
179
  const controller = new AbortController();
180
180
  const unlinkParentAbort = linkParentAbort({ controller, parent: uploadOptions?.signal });
181
181
  const timeout = globalThis.setTimeout(() => controller.abort(new DOMException('Attachment upload timed out', 'TimeoutError')), timeoutMs);
182
- const uploaded = new Array(files.length);
182
+ const uploaded = Array.from({ length: files.length }, () => undefined);
183
183
  const settled = () => uploaded.filter((attachment) => attachment !== undefined);
184
184
  // The failure is captured rather than rethrown from inside the `try`, so that the timer/listener
185
185
  // teardown in `finally` has exactly two reachable paths (the turn succeeded, or it did not)
@@ -175,7 +175,7 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
175
175
  }, []);
176
176
  useEffect(() => {
177
177
  if (!open)
178
- return;
178
+ return undefined;
179
179
  const closeOnOutsidePointer = (event) => {
180
180
  const target = event.target;
181
181
  if (triggerRef.current?.contains(target)
@@ -207,7 +207,7 @@ export function useAgentRuntimePicker({ agents, value, placement, t, executionMo
207
207
  }, [focusTrigger, open]);
208
208
  useEffect(() => {
209
209
  if (!open)
210
- return;
210
+ return undefined;
211
211
  const update = () => {
212
212
  const rect = triggerRef.current?.getBoundingClientRect();
213
213
  const bounds = triggerRef.current?.closest('.jini-composer')?.getBoundingClientRect();
@@ -20,15 +20,18 @@ function unconsumedQueuedInputs({ inputs, messages }) {
20
20
  const receipt = acceptedInputEvent({ event });
21
21
  if (receipt?.data.deliveryState !== 'consumed')
22
22
  continue;
23
- for (const input of [receipt.data, ...(receipt.data.inputs ?? [])]) {
24
- consumed.add(input.id);
25
- for (const alias of input.aliases ?? [])
26
- consumed.add(alias);
27
- }
23
+ recordConsumedInputs({ receipt: receipt.data, consumed });
28
24
  }
29
25
  }
30
26
  return inputs.filter(input => !consumed.has(input.liveMessageId ?? input.id));
31
27
  }
28
+ function recordConsumedInputs({ receipt, consumed }) {
29
+ for (const input of [receipt, ...(receipt.inputs ?? [])]) {
30
+ consumed.add(input.id);
31
+ for (const alias of input.aliases ?? [])
32
+ consumed.add(alias);
33
+ }
34
+ }
32
35
  /** The conversation copies run events onto its messages one effect after streaming stops, so a
33
36
  * consumed receipt can still be missing from the transcript when the send blocker clears. A
34
37
  * live-accepted copy therefore waits until the run's own message reaches a terminal status. */
@@ -390,8 +393,7 @@ export function useChatPane(options) {
390
393
  if (inputId === undefined)
391
394
  composer.addAttachment(attachment);
392
395
  else
393
- setQueuedInputs(inputs => inputs.map(input => input.id === inputId
394
- ? { ...input, attachments: [...input.attachments, attachment] } : input));
396
+ setQueuedInputs(inputs => attachQueuedInput({ inputs, inputId, attachment }));
395
397
  if (file)
396
398
  cacheAttachmentPreviewSource({ path: attachment.path, file });
397
399
  },
@@ -837,4 +839,7 @@ export function useChatPane(options) {
837
839
  reset,
838
840
  };
839
841
  }
842
+ function attachQueuedInput({ inputs, inputId, attachment }) {
843
+ return inputs.map(input => input.id === inputId ? { ...input, attachments: [...input.attachments, attachment] } : input);
844
+ }
840
845
  //# sourceMappingURL=useChatPane.hooks.js.map
@@ -1,18 +1,7 @@
1
1
  import { useEffect, useRef } from 'react';
2
+ import { getAgentModelContext } from '@jini-ai/agentic/dom';
2
3
  import { CHAT_PANE_AGENT_TOOLS } from '../agent-tools.js';
3
4
  import { definedProps } from '../../../util/defined-props.js';
4
- function getModelContext() {
5
- const doc = globalThis.document;
6
- if (isModelContext(doc?.modelContext))
7
- return doc.modelContext;
8
- const nav = globalThis.navigator;
9
- if (isModelContext(nav?.modelContext))
10
- return nav.modelContext;
11
- return undefined;
12
- }
13
- function isModelContext(value) {
14
- return typeof value === 'object' && value !== null && typeof value.registerTool === 'function';
15
- }
16
5
  function requireStringField(input, key) {
17
6
  const value = input[key];
18
7
  if (typeof value !== 'string' || value.length === 0)
@@ -78,7 +67,7 @@ async function selectAgentAction(pane, input) {
78
67
  }
79
68
  if (agent.available === false) {
80
69
  throw new Error(`agent "${agentId}" is not available on this machine`
81
- + `${agent.diagnostic === undefined ? '' : ` (${agent.diagnostic})`}`);
70
+ + (agent.diagnostic === undefined ? '' : ` (${agent.diagnostic})`));
82
71
  }
83
72
  const model = optionalStringField(input, 'model');
84
73
  const reasoning = optionalStringField(input, 'reasoning');
@@ -202,30 +191,37 @@ export function useChatPaneAgentControl({ pane }, options = {}) {
202
191
  const hasBridgeAccess = options.bridgeAccess !== undefined;
203
192
  useEffect(() => {
204
193
  if (!enabled)
205
- return;
194
+ return undefined;
206
195
  const cleanups = [];
207
196
  // Not registered unless the host opted in: see `webmcp`'s own doc for why this path cannot be
208
197
  // gated and therefore must not be on by default.
209
- const modelContext = options.webmcp === true ? getModelContext() : undefined;
198
+ // Feature-detect the draft surface through its shared owner; no `@mcp-b/*` dependency.
199
+ const modelContext = options.webmcp === true ? getAgentModelContext({
200
+ host: { candidates: () => [
201
+ globalThis.document?.modelContext,
202
+ globalThis.navigator?.modelContext,
203
+ ] },
204
+ }) : undefined;
210
205
  if (modelContext) {
211
206
  const controller = new AbortController();
212
207
  for (const tool of CHAT_PANE_AGENT_TOOLS) {
213
208
  // `registerTool` is async per the draft IDL, and a polyfill may return undefined instead.
214
209
  // Normalize both, then swallow: registration is best-effort, and a rejected one must not
215
210
  // surface as an unhandled rejection from a React effect.
216
- const registered = modelContext.registerTool({
217
- name: tool.id,
218
- description: tool.description,
219
- inputSchema: tool.inputSchema,
220
- execute: (args) => runChatPaneAction(paneRef.current, tool.id, args ?? {}),
221
- }, { signal: controller.signal });
211
+ const registered = modelContext.registerTool({ tool: {
212
+ name: tool.id,
213
+ description: tool.description,
214
+ inputSchema: tool.inputSchema,
215
+ execute: (args) => runChatPaneAction(paneRef.current, tool.id, args ?? {}),
216
+ } }, { signal: controller.signal });
222
217
  void Promise.resolve(registered).catch(() => undefined);
223
218
  }
224
219
  cleanups.push(() => {
220
+ // Aborting the signal is the spec cleanup path; unregisterTool is optional for polyfills.
225
221
  controller.abort();
226
222
  if (modelContext.unregisterTool) {
227
223
  for (const tool of CHAT_PANE_AGENT_TOOLS)
228
- modelContext.unregisterTool(tool.id);
224
+ modelContext.unregisterTool({ name: tool.id });
229
225
  }
230
226
  });
231
227
  }
@@ -42,7 +42,7 @@ export function useChatPaneControlsHeight() {
42
42
  const root = rootRef.current;
43
43
  const controls = controlsRef.current;
44
44
  if (!root || !controls || typeof ResizeObserver !== 'function')
45
- return;
45
+ return undefined;
46
46
  const observer = new ResizeObserver((entries) => {
47
47
  // Only `controls` is observed, so a notification carries at most one entry: `slice(0, 1)`
48
48
  // reads it without a branch for the empty list the ResizeObserver spec never delivers.
@@ -111,7 +111,7 @@ export function useChatPaneRuntimeInventory({ access, initialAgents = EMPTY_AGEN
111
111
  }, [access, health, reloadIfUnanswered, retry]);
112
112
  useEffect(() => {
113
113
  if (!access)
114
- return;
114
+ return undefined;
115
115
  retry.reset();
116
116
  void loadAgents(false);
117
117
  // Health polls only while the tab is visible: a hidden tab stops its timer, and showing it
@@ -157,7 +157,7 @@ export function useChatPaneRuntimeInventory({ access, initialAgents = EMPTY_AGEN
157
157
  }, [access, health, inventory, loadAgents, pollIntervalMs, refreshStatus, retry]);
158
158
  useEffect(() => {
159
159
  if (!access)
160
- return;
160
+ return undefined;
161
161
  // The status check on becoming visible belongs to the polling effect above; this one only
162
162
  // retries an unanswered inventory load (and, on focus, checks status too).
163
163
  const onFocus = () => {
@@ -43,7 +43,7 @@ export function useChatPaneWorkingDirectory(options) {
43
43
  }, []);
44
44
  useEffect(() => {
45
45
  if (observedDirectoryRef.current === workingDirectory)
46
- return;
46
+ return undefined;
47
47
  observedDirectoryRef.current = workingDirectory;
48
48
  operation.supersede();
49
49
  const access = options.workingDirectoryAccess;
@@ -51,7 +51,7 @@ export function useChatPaneWorkingDirectory(options) {
51
51
  if (workingDirectory === null || !access) {
52
52
  setWorkingDirectoryInvalid(false);
53
53
  setWorkingDirectoryPending(false);
54
- return;
54
+ return undefined;
55
55
  }
56
56
  let validationSettled = false;
57
57
  setWorkingDirectoryPending(true);
@@ -56,7 +56,7 @@ export function useRuntimeDropUp({ width }) {
56
56
  }, []);
57
57
  useEffect(() => {
58
58
  if (!open)
59
- return;
59
+ return undefined;
60
60
  const closeOnOutsidePointer = (event) => {
61
61
  const target = event.target;
62
62
  if (triggerRef.current?.contains(target) || menuRef.current?.contains(target))
@@ -77,7 +77,7 @@ export function useRuntimeDropUp({ width }) {
77
77
  }, [closeToTrigger, open]);
78
78
  useEffect(() => {
79
79
  if (!open)
80
- return;
80
+ return undefined;
81
81
  const update = () => {
82
82
  const rect = triggerRef.current?.getBoundingClientRect();
83
83
  const bounds = triggerRef.current?.closest('.jini-composer')?.getBoundingClientRect();
@@ -51,7 +51,8 @@ const RAW_MODEL_ID = /^(?:[a-z0-9._-]+\/)*[a-z0-9][a-z0-9._-]*(?:\[[^\]]*\])?$/;
51
51
  const BRAND_CASING = { gpt: 'GPT' };
52
52
  function prettifyRawModelId(id) {
53
53
  // A routing prefix ("anthropic/") is the full name's job; the bar names only the model.
54
- const tokens = id.slice(id.lastIndexOf('/') + 1).replace(/\[[^\]]*\]$/, '').split(/[-_]+/).filter((token) => token && !/^\d{8}$/.test(token));
54
+ // The caller proved RAW_MODEL_ID, so the first '[' can only start its trailing context suffix.
55
+ const tokens = id.slice(id.lastIndexOf('/') + 1).split('[', 1)[0].split(/[-_]+/).filter((token) => token && !/^\d{8}$/.test(token));
55
56
  const words = [];
56
57
  for (const token of tokens) {
57
58
  const previous = words[words.length - 1];
@@ -78,7 +79,8 @@ export function shortModelLabel({ label, agentId }, { max = 16 } = {}) {
78
79
  const vendors = (agentId && BAR_VENDOR_WORDS[agentId]) || [];
79
80
  const dropVendor = words.length > 1 && vendors.includes(words[0].toLowerCase());
80
81
  // A display name with no vendor word to drop is shown as the catalog wrote it.
81
- const short = dropVendor ? words.slice(1).join(' ') : raw ? words.join(' ') : trimmed;
82
+ const normalized = raw ? words.join(' ') : trimmed;
83
+ const short = dropVendor ? words.slice(1).join(' ') : normalized;
82
84
  return short.length > max ? `${short.slice(0, max - 1).trimEnd()}…` : short;
83
85
  }
84
86
  //# sourceMappingURL=model-options.js.map