@jini-ai/chat 0.6.6 → 0.6.10

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 (160) hide show
  1. package/CHANGELOG.md +129 -1
  2. package/dist/core/accepted-input.d.ts +54 -0
  3. package/dist/core/accepted-input.js +165 -0
  4. package/dist/core/agent-plugin-chat-state.d.ts +43 -0
  5. package/dist/core/agent-plugin-chat-state.js +48 -0
  6. package/dist/core/assistant-client/a2ui-action-poster.js +32 -28
  7. package/dist/core/assistant-client/messages.d.ts +1 -1
  8. package/dist/core/assistant-client/messages.js +2 -2
  9. package/dist/core/assistant-client/transport.d.ts +2 -2
  10. package/dist/core/assistant-client/transport.js +187 -98
  11. package/dist/core/assistant-content.d.ts +2 -1
  12. package/dist/core/assistant-content.js +9 -11
  13. package/dist/core/browser/page-actions.js +4 -2
  14. package/dist/core/composer-history.d.ts +16 -9
  15. package/dist/core/composer-history.js +28 -16
  16. package/dist/core/conversation-title.d.ts +3 -0
  17. package/dist/core/conversation-title.js +3 -0
  18. package/dist/core/durable-projection.js +20 -13
  19. package/dist/core/durable-runs/continuation.d.ts +4 -0
  20. package/dist/core/durable-runs/continuation.js +31 -6
  21. package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
  22. package/dist/core/durable-runs/ports.d.ts +6 -2
  23. package/dist/core/index.d.ts +2 -0
  24. package/dist/core/index.js +2 -0
  25. package/dist/core/messages.d.ts +2 -0
  26. package/dist/core/partial-json.d.ts +6 -7
  27. package/dist/core/partial-json.js +81 -47
  28. package/dist/core/persistence/title.js +4 -2
  29. package/dist/core/question-answer.d.ts +23 -0
  30. package/dist/core/question-answer.js +66 -0
  31. package/dist/core/question-form/format.d.ts +6 -6
  32. package/dist/core/question-form/format.js +16 -17
  33. package/dist/core/question-form/normalize.d.ts +18 -0
  34. package/dist/core/question-form/normalize.js +112 -122
  35. package/dist/core/question-form/scan.js +141 -203
  36. package/dist/core/run-activity.js +62 -49
  37. package/dist/core/run-events/entry.d.ts +10 -1
  38. package/dist/core/run-events/entry.js +117 -87
  39. package/dist/core/todos.d.ts +1 -1
  40. package/dist/core/todos.js +60 -85
  41. package/dist/core/tool-events.js +14 -12
  42. package/dist/core/transcript.js +62 -16
  43. package/dist/core/turn-usage.js +54 -32
  44. package/dist/core/user-text-redaction.js +3 -0
  45. package/dist/core/util/manifest.js +77 -82
  46. package/dist/core/util/markdown-context.js +51 -53
  47. package/dist/core/util/parser.js +136 -144
  48. package/dist/core/util/pointer.js +16 -16
  49. package/dist/core/util/strip.js +57 -67
  50. package/dist/core/util/validate.js +15 -17
  51. package/dist/embed-widget/browser/session-store.js +21 -12
  52. package/dist/embed-widget/transports/fetch-sse.js +108 -95
  53. package/dist/embed-widget/vanilla/index.js +81 -70
  54. package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
  55. package/dist/react/components/AttachmentPreviewModal.js +25 -1
  56. package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
  57. package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
  58. package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
  59. package/dist/react/components/ChatSettingsDialog.js +72 -0
  60. package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
  61. package/dist/react/components/ChatSettingsSegmented.js +11 -0
  62. package/dist/react/components/Composer.d.ts +5 -1
  63. package/dist/react/components/Composer.js +126 -82
  64. package/dist/react/components/ConversationList.js +25 -39
  65. package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
  66. package/dist/react/components/ConversationTitleEditor.js +54 -0
  67. package/dist/react/components/Icon.d.ts +1 -1
  68. package/dist/react/components/Icon.js +25 -45
  69. package/dist/react/components/Markdown.js +134 -123
  70. package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
  71. package/dist/react/components/McpUiSurfaceCard.js +30 -9
  72. package/dist/react/components/MessageList.d.ts +13 -13
  73. package/dist/react/components/MessageList.hooks.d.ts +6 -1
  74. package/dist/react/components/MessageList.hooks.js +19 -3
  75. package/dist/react/components/MessageList.js +34 -10
  76. package/dist/react/components/MessageRow.d.ts +1 -1
  77. package/dist/react/components/MessageRow.js +75 -47
  78. package/dist/react/components/MessageRow.usage.js +16 -11
  79. package/dist/react/components/MidRunUserMessage.js +16 -3
  80. package/dist/react/components/QuestionForm.js +148 -76
  81. package/dist/react/components/QuestionsPanel.js +1 -1
  82. package/dist/react/components/TodoCard.js +12 -6
  83. package/dist/react/components/ToolCallGroup.d.ts +4 -3
  84. package/dist/react/components/ToolCallGroup.js +7 -5
  85. package/dist/react/components/ToolCard.js +47 -40
  86. package/dist/react/components/composer-discovery.js +6 -1
  87. package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
  88. package/dist/react/components/mcp-ui-surface-state.js +23 -8
  89. package/dist/react/ext-event-renderer-registry.d.ts +8 -0
  90. package/dist/react/ext-event-renderer-registry.js +5 -2
  91. package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
  92. package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
  93. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
  94. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  95. package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
  96. package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
  97. package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
  98. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
  99. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
  100. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
  101. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
  102. package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
  103. package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
  104. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
  105. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
  106. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
  107. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
  108. package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
  109. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
  110. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
  111. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
  112. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
  113. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
  114. package/dist/react/features/chat-pane/model-options.d.ts +14 -0
  115. package/dist/react/features/chat-pane/model-options.js +45 -25
  116. package/dist/react/features/chat-pane/rules.d.ts +8 -9
  117. package/dist/react/features/chat-pane/rules.js +84 -46
  118. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  119. package/dist/react/features/chat-pane/styles.js +152 -36
  120. package/dist/react/features/chat-pane/types.d.ts +19 -9
  121. package/dist/react/hooks/assistant-chats.js +12 -5
  122. package/dist/react/hooks/composer-draft-cache.js +116 -118
  123. package/dist/react/hooks/composer-history-storage.js +5 -5
  124. package/dist/react/hooks/fab-position.js +7 -4
  125. package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
  126. package/dist/react/hooks/useAnsweredCards.js +38 -11
  127. package/dist/react/hooks/useArtifactStream.js +28 -22
  128. package/dist/react/hooks/useComposer.d.ts +20 -0
  129. package/dist/react/hooks/useComposer.js +22 -9
  130. package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
  131. package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
  132. package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
  133. package/dist/react/hooks/useConversation.d.ts +7 -0
  134. package/dist/react/hooks/useConversation.js +23 -10
  135. package/dist/react/hooks/useExtEventGroups.js +39 -39
  136. package/dist/react/hooks/useQuestionForms.js +21 -26
  137. package/dist/react/hooks/useRunStream.js +10 -2
  138. package/dist/react/index.d.ts +7 -1
  139. package/dist/react/index.js +9 -0
  140. package/dist/react/message-blocks.d.ts +6 -4
  141. package/dist/react/message-blocks.js +94 -72
  142. package/dist/react/slots.d.ts +2 -2
  143. package/dist/react/tool-call-display.d.ts +12 -6
  144. package/dist/react/tool-call-display.js +53 -24
  145. package/dist/server/durable-runs/recover.js +132 -80
  146. package/dist/server/run-finalizer/entry.js +25 -15
  147. package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
  148. package/dist/store/legacy/sqlite/conversations.js +69 -98
  149. package/dist/store/legacy/sqlite/messages.d.ts +10 -2
  150. package/dist/store/legacy/sqlite/messages.js +113 -71
  151. package/dist/store/runtime/chat-history-store.js +9 -3
  152. package/dist/store/runtime/durable-run-store.d.ts +2 -2
  153. package/dist/store/runtime/durable-run-store.js +165 -117
  154. package/dist/store/runtime/history-tools.js +118 -82
  155. package/dist/store/runtime/run-ledger.d.ts +4 -1
  156. package/dist/store/runtime/run-ledger.js +56 -16
  157. package/dist/store/sql/cursor.js +38 -18
  158. package/dist/store/sql/store.d.ts +18 -0
  159. package/dist/store/sql/store.js +46 -15
  160. package/package.json +18 -16
@@ -15,6 +15,30 @@ import { looksLikeImageAttachment, useAttachmentPreviewModal } from './Attachmen
15
15
  export function AttachmentPreviewModal({ attachment, onClose, navigation }) {
16
16
  const t = useT();
17
17
  const c = useAttachmentPreviewModal({ attachment: attachment, onClose: onClose, navigation });
18
- return (_jsxs("dialog", { ref: c.dialogRef, className: `jini-attachment-preview-dialog${navigation ? ' has-nav' : ''}`, role: "dialog", "aria-modal": "true", "aria-label": t('{name} preview', { name: attachment.name }), onCancel: c.handleNativeCancel, onClick: c.handleBackdropClick, onKeyDown: c.handleKeyDown, children: [_jsxs("div", { className: "jini-attachment-preview-header", children: [_jsx("span", { className: "jini-attachment-preview-name", title: attachment.name, children: attachment.name }), _jsx("button", { type: "button", className: "jini-attachment-preview-close", onClick: onClose, title: t('Close'), "aria-label": t('Close'), children: _jsx(Icon, { name: "close", size: 16 }) })] }), _jsx("div", { className: "jini-attachment-preview-body", children: c.status === 'image' && c.imageUrl ? (_jsx("img", { src: c.imageUrl, alt: attachment.name, className: "jini-attachment-preview-image", onError: c.onImageError })) : c.status === 'text' ? (c.textLoading ? (_jsx("div", { className: "jini-attachment-preview-loading", children: t('Loading…') })) : (_jsxs(_Fragment, { children: [_jsx("pre", { className: "jini-attachment-preview-text", children: c.text?.content ?? '' }), c.text?.truncated ? (_jsx("div", { className: "jini-attachment-preview-truncated", children: t('Preview truncated — this file is larger than the preview limit.') })) : null] }))) : (_jsxs("div", { className: "jini-attachment-preview-meta", children: [_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Name') }), _jsx("span", { children: attachment.name })] }), _jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Type') }), _jsx("span", { children: looksLikeImageAttachment({ attachment: attachment }) ? t('Image') : t('File') })] }), c.sizeLabel ? (_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Size') }), _jsx("span", { children: c.sizeLabel })] })) : null, _jsx("div", { className: "jini-attachment-preview-meta-note", children: t('A preview is not available for this file.') })] })) }), navigation ? (_jsxs("div", { className: "jini-attachment-preview-nav", children: [_jsx("span", { className: "jini-attachment-preview-count", "aria-live": "polite", children: t('{index} / {count}', { index: navigation.index + 1, count: navigation.count }) }), _jsx("button", { type: "button", className: "jini-attachment-preview-step", onClick: navigation.onPrevious, title: t('Previous image'), "aria-label": t('Previous image'), children: _jsx(Icon, { name: "chevron-left", size: 18 }) }), _jsx("button", { type: "button", className: "jini-attachment-preview-step", onClick: navigation.onNext, title: t('Next image'), "aria-label": t('Next image'), children: _jsx(Icon, { name: "chevron-right", size: 18 }) })] })) : null] }));
18
+ return (_jsxs("dialog", { ref: c.dialogRef, className: `jini-attachment-preview-dialog${navigation ? ' has-nav' : ''}`, role: "dialog", "aria-modal": "true", "aria-label": t('{name} preview', { name: attachment.name }), onCancel: c.handleNativeCancel, onClick: c.handleBackdropClick, onKeyDown: c.handleKeyDown, children: [_jsxs("div", { className: "jini-attachment-preview-header", children: [_jsx("span", { className: "jini-attachment-preview-name", title: attachment.name, children: attachment.name }), _jsx("button", { type: "button", className: "jini-attachment-preview-close", onClick: onClose, title: t('Close'), "aria-label": t('Close'), children: _jsx(Icon, { name: "close", size: 16 }) })] }), _jsx("div", { className: "jini-attachment-preview-body", children: _jsx(AttachmentPreviewBody, { attachment: attachment, preview: c }) }), navigation ? _jsx(AttachmentPreviewNav, { navigation: navigation }) : null] }));
19
+ }
20
+ /** The image, the text pane, or - for anything else - the filename/type/size card. */
21
+ function AttachmentPreviewBody({ attachment, preview }) {
22
+ if (preview.status === 'image' && preview.imageUrl) {
23
+ return (_jsx("img", { src: preview.imageUrl, alt: attachment.name, className: "jini-attachment-preview-image", onError: preview.onImageError }));
24
+ }
25
+ if (preview.status === 'text')
26
+ return _jsx(AttachmentTextPane, { preview: preview });
27
+ return _jsx(AttachmentMetaCard, { attachment: attachment, sizeLabel: preview.sizeLabel });
28
+ }
29
+ function AttachmentTextPane({ preview }) {
30
+ const t = useT();
31
+ if (preview.textLoading)
32
+ return _jsx("div", { className: "jini-attachment-preview-loading", children: t('Loading…') });
33
+ return (_jsxs(_Fragment, { children: [_jsx("pre", { className: "jini-attachment-preview-text", children: preview.text?.content ?? '' }), preview.text?.truncated ? (_jsx("div", { className: "jini-attachment-preview-truncated", children: t('Preview truncated — this file is larger than the preview limit.') })) : null] }));
34
+ }
35
+ function AttachmentMetaCard({ attachment, sizeLabel }) {
36
+ const t = useT();
37
+ return (_jsxs("div", { className: "jini-attachment-preview-meta", children: [_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Name') }), _jsx("span", { children: attachment.name })] }), _jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Type') }), _jsx("span", { children: looksLikeImageAttachment({ attachment: attachment }) ? t('Image') : t('File') })] }), sizeLabel ? (_jsxs("div", { className: "jini-attachment-preview-meta-row", children: [_jsx("span", { className: "jini-attachment-preview-meta-label", children: t('Size') }), _jsx("span", { children: sizeLabel })] })) : null, _jsx("div", { className: "jini-attachment-preview-meta-note", children: t('A preview is not available for this file.') })] }));
38
+ }
39
+ /** Previous/next side by side in the bottom-right corner, with the "2 / 5" position before them. */
40
+ function AttachmentPreviewNav({ navigation }) {
41
+ const t = useT();
42
+ return (_jsxs("div", { className: "jini-attachment-preview-nav", children: [_jsx("span", { className: "jini-attachment-preview-count", "aria-live": "polite", children: t('{index} / {count}', { index: navigation.index + 1, count: navigation.count }) }), _jsx("button", { type: "button", className: "jini-attachment-preview-step", onClick: navigation.onPrevious, title: t('Previous image'), "aria-label": t('Previous image'), children: _jsx(Icon, { name: "chevron-left", size: 18 }) }), _jsx("button", { type: "button", className: "jini-attachment-preview-step", onClick: navigation.onNext, title: t('Next image'), "aria-label": t('Next image'), children: _jsx(Icon, { name: "chevron-right", size: 18 }) })] }));
19
43
  }
20
44
  //# sourceMappingURL=AttachmentPreviewModal.js.map
@@ -0,0 +1,13 @@
1
+ import { type ChatPermissionsPort, type ChatSettingsSection } from './ChatSettingsDialog.hooks.js';
2
+ /** Advanced chat settings; the host supplies display actions, capabilities and persistence.
3
+ * Native dialog semantics own modal focus containment; closing restores the opener.
4
+ * Header and tabs stay fixed while only the active panel scrolls.
5
+ */
6
+ export declare function ChatSettingsDialog(required: {
7
+ open: boolean;
8
+ onClose: () => void;
9
+ displaySections: readonly ChatSettingsSection[];
10
+ permissions: ChatPermissionsPort;
11
+ t?: (key: string) => string;
12
+ }, _optional?: Record<string, never>): import("react").JSX.Element;
13
+ //# sourceMappingURL=ChatSettingsDialog.d.ts.map
@@ -0,0 +1,115 @@
1
+ /** Structural display port: hosts pass the very same actions used by their quick settings menu. */
2
+ export interface ChatSettingsSection {
3
+ /** One-line explanation for a whole radio group; falls back to its first item's hint. */
4
+ key: string;
5
+ label?: string;
6
+ description?: string;
7
+ /** One help tip beside a radio group's legend, e.g. a bulleted line per option, in place of one per option. */
8
+ tooltip?: string;
9
+ items: readonly {
10
+ key: string;
11
+ label: string;
12
+ role?: string;
13
+ checked?: boolean;
14
+ hint?: string;
15
+ /** Interactive help for a switch row. Radio options share their section's `tooltip` instead. */
16
+ tooltip?: string;
17
+ disabled?: boolean;
18
+ onSelect?: () => void;
19
+ }[];
20
+ }
21
+ export interface ChatPermissionsPort {
22
+ enabled: boolean;
23
+ canEdit: boolean;
24
+ busy?: boolean;
25
+ error?: string | null;
26
+ unavailableReason?: string;
27
+ capabilities: readonly {
28
+ key: string;
29
+ label: string;
30
+ description: string;
31
+ enabled: boolean;
32
+ }[];
33
+ onChange(input: {
34
+ key?: string;
35
+ enabled: boolean;
36
+ }): void;
37
+ }
38
+ export type ChatSettingsTab = 'display' | 'permissions';
39
+ /** Modal visibility only; display and permission state remain owned by the injected ports. */
40
+ export declare function useChatSettingsDialog(_required: Record<string, never>, _optional?: Record<string, never>): {
41
+ open: boolean;
42
+ show: (_input?: Record<string, never>) => void;
43
+ close: (_input?: Record<string, never>) => void;
44
+ };
45
+ /** Bind existing menu actions and a host permission port without local preference copies.
46
+ * The active tab is the only local state: it is view navigation, not a setting.
47
+ * @complexity O(d + c) for the bounded display rows and host capabilities; no I/O here.
48
+ */
49
+ export declare function useChatSettingsView(required: {
50
+ displaySections: readonly ChatSettingsSection[];
51
+ permissions: ChatPermissionsPort;
52
+ t?: (key: string) => string;
53
+ }, _optional?: Record<string, never>): {
54
+ t: (key: string) => string;
55
+ permissions: ChatPermissionsPort;
56
+ tab: ChatSettingsTab;
57
+ setTab: (next: string) => void;
58
+ tabs: {
59
+ id: string;
60
+ label: string;
61
+ }[];
62
+ display: {
63
+ radio: boolean;
64
+ name: string;
65
+ hint: string | undefined;
66
+ hintId: string;
67
+ labelId: string;
68
+ tooltipId: string;
69
+ items: {
70
+ hintId: string;
71
+ change: () => void;
72
+ key: string;
73
+ label: string;
74
+ role?: string;
75
+ checked?: boolean;
76
+ hint?: string;
77
+ /** Interactive help for a switch row. Radio options share their section's `tooltip` instead. */
78
+ tooltip?: string;
79
+ disabled?: boolean;
80
+ onSelect?: () => void;
81
+ }[];
82
+ /** One-line explanation for a whole radio group; falls back to its first item's hint. */
83
+ key: string;
84
+ label?: string;
85
+ description?: string;
86
+ /** One help tip beside a radio group's legend, e.g. a bulleted line per option, in place of one per option. */
87
+ tooltip?: string;
88
+ }[];
89
+ master: {
90
+ label: string;
91
+ checked: boolean;
92
+ disabled: boolean;
93
+ descriptionId: string;
94
+ description: string;
95
+ onCheckedChange: ({ checked }: {
96
+ checked: boolean;
97
+ }) => void;
98
+ };
99
+ capabilities: {
100
+ descriptionId: string;
101
+ control: {
102
+ label: string;
103
+ checked: boolean;
104
+ disabled: boolean;
105
+ onCheckedChange: ({ checked }: {
106
+ checked: boolean;
107
+ }) => void;
108
+ };
109
+ key: string;
110
+ label: string;
111
+ description: string;
112
+ enabled: boolean;
113
+ }[];
114
+ };
115
+ //# sourceMappingURL=ChatSettingsDialog.hooks.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { useId, useState } from 'react';
2
+ /** Modal visibility only; display and permission state remain owned by the injected ports. */
3
+ export function useChatSettingsDialog(_required, _optional = {}) {
4
+ const [open, setOpen] = useState(false);
5
+ return { open, show: (_input = {}) => setOpen(true), close: (_input = {}) => setOpen(false) };
6
+ }
7
+ /** Bind existing menu actions and a host permission port without local preference copies.
8
+ * The active tab is the only local state: it is view navigation, not a setting.
9
+ * @complexity O(d + c) for the bounded display rows and host capabilities; no I/O here.
10
+ */
11
+ export function useChatSettingsView(required, _optional = {}) {
12
+ const t = required.t ?? ((key) => key), permissions = required.permissions;
13
+ const id = useId();
14
+ const [tab, setTab] = useState('display');
15
+ const disabled = !permissions.canEdit || !!permissions.busy;
16
+ return { t, permissions,
17
+ tab, setTab: (next) => setTab(next === 'permissions' ? 'permissions' : 'display'),
18
+ tabs: [{ id: 'display', label: t('Display') }, { id: 'permissions', label: t('Agent permissions') }],
19
+ display: required.displaySections.map(section => ({ ...section,
20
+ radio: section.items[0]?.role === 'menuitemradio',
21
+ name: `${id}-${section.key}`,
22
+ // Radio rows share one line explaining the whole choice, so it sits under the group label.
23
+ hint: section.items[0]?.role === 'menuitemradio' ? section.description ?? section.items[0]?.hint : undefined,
24
+ hintId: `${id}-${section.key}`, labelId: `${id}-${section.key}-label`, tooltipId: `${id}-${section.key}-tooltip`,
25
+ items: section.items.map(item => ({ ...item, hintId: `${id}-${section.key}-${item.key}`,
26
+ change: () => { if (!item.disabled)
27
+ item.onSelect?.(); } })),
28
+ })),
29
+ master: { label: t('Full CLI access'), checked: permissions.enabled, disabled, descriptionId: `${id}-master`,
30
+ description: t('Turn every capability below on or off at once.'),
31
+ onCheckedChange: ({ checked }) => { if (!disabled)
32
+ permissions.onChange({ enabled: checked }); } },
33
+ capabilities: permissions.capabilities.map(item => ({ ...item, descriptionId: `${id}-${item.key}`,
34
+ control: { label: item.label, checked: item.enabled, disabled,
35
+ onCheckedChange: ({ checked }) => { if (!disabled)
36
+ permissions.onChange({ key: item.key, enabled: checked }); } },
37
+ })),
38
+ };
39
+ }
40
+ //# sourceMappingURL=ChatSettingsDialog.hooks.js.map
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useId, useLayoutEffect, useRef } from 'react';
3
+ import { TabBar } from '@jini-ai/ui/tab-strip';
4
+ import { InfoTip } from '@jini-ai/ui/admin-widgets';
5
+ import { useChatSettingsView } from './ChatSettingsDialog.hooks.js';
6
+ import { ChatSettingsSegmented } from './ChatSettingsSegmented.js';
7
+ /** One switch row, with optional inline description and help beside the control and label.
8
+ * The help trigger stays outside the label so tapping it never toggles the switch.
9
+ * Earlier (owner) ⓘ tips floated away from their icon, so this row showed visible text only; on 2026-10-18 the owner approved per-option ⓘ help, which InfoTip now anchors to the icon.
10
+ * Later that day radio groups moved to one ⓘ beside their legend; a switch keeps its own, which now
11
+ * sits right after its label like the legend's (owner, 2026-10-21) instead of at the row's far edge. */
12
+ function SettingSwitch(required, _optional = {}) {
13
+ const inputId = useId();
14
+ return _jsxs("div", { className: ['jini-chat-settings-row', required.className].filter(Boolean).join(' '), children: [_jsx("input", { id: inputId, type: "checkbox", role: "switch", className: "jini-checkbox jini-chat-settings-switch", checked: required.checked, disabled: required.disabled, "aria-describedby": [required.description ? required.descriptionId : '', required.tooltip ? `${required.descriptionId}-tooltip` : ''].filter(Boolean).join(' ') || undefined, onChange: event => required.onChange(event.currentTarget.checked) }), _jsxs("div", { className: "jini-chat-settings-text", children: [_jsxs("span", { className: "jini-chat-settings-label-row", children: [_jsx("label", { htmlFor: inputId, className: "jini-chat-settings-label", children: required.label }), required.tooltip && _jsx(InfoTip, { label: required.tooltip, descriptionId: `${required.descriptionId}-tooltip` })] }), required.description && _jsx("p", { id: required.descriptionId, className: "jini-chat-settings-description", children: required.description })] })] });
15
+ }
16
+ /** Advanced chat settings; the host supplies display actions, capabilities and persistence.
17
+ * Native dialog semantics own modal focus containment; closing restores the opener.
18
+ * Header and tabs stay fixed while only the active panel scrolls.
19
+ */
20
+ export function ChatSettingsDialog(required, _optional = {}) {
21
+ const vm = useChatSettingsView(required);
22
+ const dialogRef = useRef(null), closeRef = useRef(null), titleId = useId();
23
+ const openerRef = useRef(null);
24
+ useLayoutEffect(() => {
25
+ const dialog = dialogRef.current;
26
+ if (!dialog || !required.open)
27
+ return;
28
+ openerRef.current = document.activeElement;
29
+ if (typeof dialog.showModal === 'function') {
30
+ if (!dialog.open)
31
+ dialog.showModal();
32
+ }
33
+ else
34
+ dialog.setAttribute('open', '');
35
+ closeRef.current?.focus({ preventScroll: true });
36
+ dialog.scrollTop = 0;
37
+ return () => {
38
+ if (typeof dialog.close === 'function') {
39
+ if (dialog.open)
40
+ dialog.close();
41
+ }
42
+ else
43
+ dialog.removeAttribute('open');
44
+ };
45
+ }, [required.open]);
46
+ // Return focus to the opener from a passive cleanup: React's commit-time focus restore runs after
47
+ // layout cleanups and would pull focus straight back to the closing dialog's × button.
48
+ useEffect(() => {
49
+ if (!required.open)
50
+ return;
51
+ return () => {
52
+ const opener = openerRef.current;
53
+ if (opener instanceof HTMLElement && opener.isConnected)
54
+ opener.focus({ preventScroll: true });
55
+ };
56
+ }, [required.open]);
57
+ // Its own tip bounds: hosts mount this inside a narrow dock marked `data-jini-tip-bounds`, which
58
+ // pushed the ⓘ bubbles of options outside the dock across the dialog, away from their icon.
59
+ return _jsxs("dialog", { ref: dialogRef, "aria-labelledby": titleId, "aria-modal": "true", tabIndex: -1, "data-jini-tip-bounds": "", className: "jini-dialog jini-chat-settings-dialog", onCancel: event => { event.preventDefault(); required.onClose(); }, onClick: event => {
60
+ if (event.target !== event.currentTarget)
61
+ return;
62
+ const { clientX: x, clientY: y } = event, box = event.currentTarget.getBoundingClientRect();
63
+ if ((x === 0 && y === 0) || x < box.left || x > box.right || y < box.top || y > box.bottom)
64
+ required.onClose();
65
+ }, children: [_jsxs("header", { className: "jini-chat-settings-header", children: [_jsx("h2", { id: titleId, className: "jini-dialog-title", children: vm.t('Chat settings') }), _jsx("button", { ref: closeRef, type: "button", className: "jini-dialog-close jini-chat-settings-close", "aria-label": vm.t('Close'), onClick: required.onClose, children: _jsx("span", { "aria-hidden": "true", children: "\u00D7" }) })] }), _jsx(TabBar, { tabs: vm.tabs, activeId: vm.tab, onChange: vm.setTab, ariaLabel: vm.t('Chat settings') }), _jsx("div", { className: "jini-chat-settings-body", role: "tabpanel", "aria-label": vm.tab === 'display' ? vm.t('Display') : vm.t('Agent permissions'), children: vm.tab === 'display' ? vm.display.map(section => section.radio
66
+ // One ⓘ for the whole choice, beside the legend (owner, 2026-10-18), not one per option. The
67
+ // fieldset is named by the label span alone, so the tip's text never joins the group's name.
68
+ ? _jsxs("fieldset", { className: "jini-chat-settings-group", "aria-labelledby": section.label ? section.labelId : undefined, "aria-describedby": [section.hint ? section.hintId : '', section.tooltip ? section.tooltipId : ''].filter(Boolean).join(' ') || undefined, children: [section.label && _jsxs("legend", { className: "jini-chat-settings-legend", children: [_jsx("span", { id: section.labelId, children: section.label }), section.tooltip && _jsx(InfoTip, { label: section.tooltip, descriptionId: section.tooltipId })] }), section.hint && _jsx("p", { id: section.hintId, className: "jini-chat-settings-description jini-chat-settings-description--group", children: section.hint }), _jsx(ChatSettingsSegmented, { name: section.name, items: section.items })] }, section.key)
69
+ : _jsxs("fieldset", { className: "jini-chat-settings-group", children: [section.label && _jsx("legend", { className: "jini-chat-settings-legend", children: section.label }), section.items.map(item => _jsx(SettingSwitch, { label: item.label, checked: !!item.checked, disabled: item.disabled, onChange: item.change, description: item.hint, descriptionId: item.hintId, tooltip: item.tooltip }, item.key))] }, section.key))
70
+ : _jsxs(_Fragment, { children: [_jsx("p", { className: "jini-chat-settings-note", children: vm.t('Permission changes apply to the next run.') }), vm.permissions.unavailableReason && _jsx("p", { className: "jini-chat-settings-note", children: vm.t(vm.permissions.unavailableReason) }), _jsx(SettingSwitch, { className: "jini-chat-settings-row--master", label: vm.master.label, checked: vm.master.checked, disabled: vm.master.disabled, onChange: checked => vm.master.onCheckedChange({ checked }), description: vm.master.description, descriptionId: vm.master.descriptionId }), _jsx("div", { className: "jini-chat-settings-capabilities", children: vm.capabilities.map(item => _jsx(SettingSwitch, { label: item.control.label, checked: item.control.checked, disabled: item.control.disabled, onChange: checked => item.control.onCheckedChange({ checked }), description: item.description, descriptionId: item.descriptionId }, item.key)) }), vm.permissions.busy && _jsx("p", { role: "status", className: "jini-chat-settings-note", children: vm.t('Saving…') }), vm.permissions.error && _jsx("p", { role: "alert", className: "jini-chat-settings-error", children: vm.permissions.error })] }) })] });
71
+ }
72
+ //# sourceMappingURL=ChatSettingsDialog.js.map
@@ -0,0 +1,18 @@
1
+ /** One choice drawn as a segmented pill (the admin's segmented-control look), built on real radios
2
+ * for keyboard and AT. The Chat settings dialog and a host's quick settings menu share it (owner,
3
+ * 2026-10-21: the gear's Tool calls and Agent plugins match the dialog's control), so both look alike.
4
+ * Inside a `role="menu"` host, `inputRole="menuitemradio"` keeps each option a valid menu child, and
5
+ * `checkedRef` gets the checked option (else the first) so the host's roving focus can land on it. */
6
+ export declare function ChatSettingsSegmented(required: {
7
+ name: string;
8
+ items: readonly {
9
+ key: string;
10
+ label: string;
11
+ checked?: boolean | undefined;
12
+ disabled?: boolean | undefined;
13
+ change: () => void;
14
+ }[];
15
+ inputRole?: 'menuitemradio' | undefined;
16
+ checkedRef?: ((input: HTMLInputElement | null) => void) | undefined;
17
+ }, _optional?: Record<string, never>): import("react").JSX.Element;
18
+ //# sourceMappingURL=ChatSettingsSegmented.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** One choice drawn as a segmented pill (the admin's segmented-control look), built on real radios
3
+ * for keyboard and AT. The Chat settings dialog and a host's quick settings menu share it (owner,
4
+ * 2026-10-21: the gear's Tool calls and Agent plugins match the dialog's control), so both look alike.
5
+ * Inside a `role="menu"` host, `inputRole="menuitemradio"` keeps each option a valid menu child, and
6
+ * `checkedRef` gets the checked option (else the first) so the host's roving focus can land on it. */
7
+ export function ChatSettingsSegmented(required, _optional = {}) {
8
+ const focusKey = (required.items.find(item => item.checked) ?? required.items[0])?.key;
9
+ return _jsx("div", { className: "jini-chat-settings-segmented", children: required.items.map(item => _jsxs("label", { className: "jini-chat-settings-segment", children: [_jsx("input", { type: "radio", name: required.name, checked: !!item.checked, disabled: item.disabled, onChange: item.change, role: required.inputRole, "aria-checked": required.inputRole ? !!item.checked : undefined, ref: item.key === focusKey ? required.checkedRef : undefined }), _jsx("span", { children: item.label })] }, item.key)) });
10
+ }
11
+ //# sourceMappingURL=ChatSettingsSegmented.js.map
@@ -6,6 +6,10 @@ export interface ComposerProps {
6
6
  disabled?: boolean;
7
7
  /** Disables submission without disabling draft editing or attachment controls. */
8
8
  sendDisabled?: boolean;
9
+ /** Human-readable submission blocker, exposed on the Send button. */
10
+ sendDisabledReason?: string;
11
+ /** ID of the rendered blocker hint used as the Send button's description. */
12
+ sendDisabledReasonId?: string;
9
13
  placeholder?: string;
10
14
  slots?: ComposerSlots;
11
15
  attachmentPicker?: {
@@ -67,5 +71,5 @@ export interface ComposerProps {
67
71
  * @complexity Time/space: O(n) in rendered attachments and supplied menu items.
68
72
  * @overallScore 100/100
69
73
  */
70
- export declare function Composer({ composer, onSend, disabled, sendDisabled, placeholder, slots, attachmentPicker, running, onCancel, onInterrupt, workingDirectory, onChangeWorkingDirectory, onPickWorkingDirectory, }: ComposerProps): import("react").JSX.Element;
74
+ export declare function Composer({ composer, onSend, disabled, sendDisabled, sendDisabledReason, sendDisabledReasonId, placeholder, slots, attachmentPicker, running, onCancel, onInterrupt, workingDirectory, onChangeWorkingDirectory, onPickWorkingDirectory, }: ComposerProps): import("react").JSX.Element;
71
75
  //# sourceMappingURL=Composer.d.ts.map