@jini-ai/chat 0.6.15-rc.2 → 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 (91) hide show
  1. package/dist/core/accepted-input.js +20 -11
  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-form/normalize.js +1 -1
  17. package/dist/core/question-form/scan.js +25 -15
  18. package/dist/core/question-form/secret-answers.js +2 -1
  19. package/dist/core/run-activity.js +8 -4
  20. package/dist/core/run-events/entry.js +6 -2
  21. package/dist/core/transcript.js +13 -6
  22. package/dist/core/turn-usage.js +7 -2
  23. package/dist/core/user-text-redaction.d.ts +2 -5
  24. package/dist/core/user-text-redaction.js +3 -3
  25. package/dist/core/util/manifest.js +13 -11
  26. package/dist/core/util/markdown-context.d.ts +14 -0
  27. package/dist/core/util/markdown-context.js +42 -4
  28. package/dist/core/util/markup-attributes.js +1 -1
  29. package/dist/core/util/parser.js +7 -7
  30. package/dist/core/util/pointer.js +22 -6
  31. package/dist/core/util/recover.js +5 -8
  32. package/dist/core/util/strip.js +12 -15
  33. package/dist/core/util/validate.js +30 -18
  34. package/dist/embed-widget/browser/lazy-loader.js +16 -12
  35. package/dist/embed-widget/react/dialog.hooks.js +2 -2
  36. package/dist/embed-widget/react/embed.js +1 -1
  37. package/dist/embed-widget/react/header.hooks.js +1 -1
  38. package/dist/embed-widget/transports/fetch-sse.js +2 -1
  39. package/dist/embed-widget/vanilla/index.js +19 -12
  40. package/dist/embed-widget/vanilla/state.js +4 -2
  41. package/dist/react/components/AttachmentPreviewModal.hooks.js +2 -1
  42. package/dist/react/components/ChatSettingsDialog.js +18 -9
  43. package/dist/react/components/Composer.js +5 -4
  44. package/dist/react/components/ConversationList.js +5 -4
  45. package/dist/react/components/Markdown.js +29 -26
  46. package/dist/react/components/McpUiSurfaceCard.js +2 -1
  47. package/dist/react/components/MessageList.hooks.js +3 -2
  48. package/dist/react/components/MessageRow.js +23 -7
  49. package/dist/react/components/MessageRow.usage.js +3 -1
  50. package/dist/react/components/QuestionForm.js +4 -5
  51. package/dist/react/components/SentAttachment.js +4 -1
  52. package/dist/react/components/TodoCard.js +9 -1
  53. package/dist/react/components/ToolCard.js +8 -7
  54. package/dist/react/components/composer-discovery.js +8 -8
  55. package/dist/react/features/chat-pane/components/AgentPluginUsage.js +1 -2
  56. package/dist/react/features/chat-pane/components/AgentRuntimePicker.d.ts +1 -1
  57. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +15 -5
  58. package/dist/react/features/chat-pane/components/ChatPane.js +4 -2
  59. package/dist/react/features/chat-pane/components/RuntimeDropUp.js +1 -1
  60. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +1 -1
  61. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +2 -2
  62. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +4 -2
  63. package/dist/react/features/chat-pane/hooks/useChatPaneAgentControl.hooks.js +2 -2
  64. package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +1 -1
  65. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +2 -2
  66. package/dist/react/features/chat-pane/hooks/useChatPaneWorkingDirectory.hooks.js +2 -2
  67. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +2 -2
  68. package/dist/react/features/chat-pane/model-options.js +4 -2
  69. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  70. package/dist/react/features/chat-pane/styles.js +26 -3
  71. package/dist/react/features/chat-pane/types.d.ts +8 -0
  72. package/dist/react/hooks/assistant-chats.js +22 -8
  73. package/dist/react/hooks/useArtifactStream.js +2 -1
  74. package/dist/react/hooks/useComposer.js +2 -2
  75. package/dist/react/hooks/useComposerHistoryKeys.hooks.js +2 -2
  76. package/dist/react/hooks/useConversation.js +9 -4
  77. package/dist/react/hooks/useExtEventGroups.js +4 -2
  78. package/dist/react/hooks/useLatestOperation.js +2 -1
  79. package/dist/react/hooks/useQuestionForms.js +32 -22
  80. package/dist/react/hooks/useRunStream.js +4 -2
  81. package/dist/react/message-blocks.js +4 -2
  82. package/dist/react/styles/reference.css +9 -0
  83. package/dist/server/durable-runs/recover.js +13 -5
  84. package/dist/server/run-finalizer/entry.js +1 -0
  85. package/dist/store/runtime/chat-history-store.js +9 -3
  86. package/dist/store/runtime/durable-run-store.js +34 -24
  87. package/dist/store/runtime/run-ledger.js +7 -4
  88. package/dist/store/sql/boundary.js +3 -1
  89. package/dist/store/sql/cursor.js +8 -3
  90. package/dist/store/sql/store.js +36 -28
  91. 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
@@ -25,7 +25,8 @@ 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
27
  function chatPaneClassName(variant, className) {
28
- 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}`;
29
30
  }
30
31
  function resolveRuntimeAccessView(runtimeAccess, injectedAgents, scanningAgents, daemonOnline, onRescanAgents, inventory) {
31
32
  if (runtimeAccess === undefined) {
@@ -268,7 +269,7 @@ function useComposerHandlePublisher({ pane }, { composerHandle }) {
268
269
  composerDraftRef.current = pane.composer.draft;
269
270
  useEffect(() => {
270
271
  if (!composerHandle)
271
- return;
272
+ return undefined;
272
273
  composerHandle.current = {
273
274
  insertText: (text) => {
274
275
  pane.composer.setDraft(appendComposerDiscovery({ draft: composerDraftRef.current, insertText: text }));
@@ -396,6 +397,7 @@ export function ChatPane(props) {
396
397
  onExecutionModeChange: p.onExecutionModeChange,
397
398
  byokRuntime: p.byokRuntime,
398
399
  onByokModelChange: p.onByokModelChange,
400
+ nativeToolPolicy: p.nativeToolPolicy,
399
401
  } }) })),
400
402
  }, { leadingAccessory: p.leadingAccessory });
401
403
  const { rootRef, controlsRef } = useChatPaneControlsHeight();
@@ -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();
@@ -393,8 +393,7 @@ export function useChatPane(options) {
393
393
  if (inputId === undefined)
394
394
  composer.addAttachment(attachment);
395
395
  else
396
- setQueuedInputs(inputs => inputs.map(input => input.id === inputId
397
- ? { ...input, attachments: [...input.attachments, attachment] } : input));
396
+ setQueuedInputs(inputs => attachQueuedInput({ inputs, inputId, attachment }));
398
397
  if (file)
399
398
  cacheAttachmentPreviewSource({ path: attachment.path, file });
400
399
  },
@@ -840,4 +839,7 @@ export function useChatPane(options) {
840
839
  reset,
841
840
  };
842
841
  }
842
+ function attachQueuedInput({ inputs, inputId, attachment }) {
843
+ return inputs.map(input => input.id === inputId ? { ...input, attachments: [...input.attachments, attachment] } : input);
844
+ }
843
845
  //# sourceMappingURL=useChatPane.hooks.js.map
@@ -67,7 +67,7 @@ async function selectAgentAction(pane, input) {
67
67
  }
68
68
  if (agent.available === false) {
69
69
  throw new Error(`agent "${agentId}" is not available on this machine`
70
- + `${agent.diagnostic === undefined ? '' : ` (${agent.diagnostic})`}`);
70
+ + (agent.diagnostic === undefined ? '' : ` (${agent.diagnostic})`));
71
71
  }
72
72
  const model = optionalStringField(input, 'model');
73
73
  const reasoning = optionalStringField(input, 'reasoning');
@@ -191,7 +191,7 @@ export function useChatPaneAgentControl({ pane }, options = {}) {
191
191
  const hasBridgeAccess = options.bridgeAccess !== undefined;
192
192
  useEffect(() => {
193
193
  if (!enabled)
194
- return;
194
+ return undefined;
195
195
  const cleanups = [];
196
196
  // Not registered unless the host opted in: see `webmcp`'s own doc for why this path cannot be
197
197
  // gated and therefore must not be on by default.
@@ -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