@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
@@ -37,7 +37,7 @@ export const CHAT_PANE_STYLES = `
37
37
  overflow: clip;
38
38
  color: var(--jini-chat-text);
39
39
  background: var(--jini-chat-bg);
40
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei UI", "Noto Sans", Roboto, "Helvetica Neue", Arial, sans-serif;
40
+ font-family: var(--jini-chat-font-body, -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei UI", "Noto Sans", Roboto, "Helvetica Neue", Arial, sans-serif);
41
41
  font-size: 13.5px;
42
42
  line-height: 1.5;
43
43
  -webkit-font-smoothing: antialiased;
@@ -61,7 +61,7 @@ export const CHAT_PANE_STYLES = `
61
61
  display: block;
62
62
  margin-bottom: 4px;
63
63
  color: var(--jini-chat-muted);
64
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
64
+ font-family: var(--jini-chat-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
65
65
  font-size: 10px;
66
66
  letter-spacing: .1em;
67
67
  text-transform: uppercase;
@@ -109,6 +109,39 @@ export const CHAT_PANE_STYLES = `
109
109
  padding: 20px 22px calc(var(--jini-chat-controls-height, 240px) + 24px);
110
110
  scrollbar-width: thin;
111
111
  }
112
+ /*
113
+ * 'Jump to latest' (MessageList.tsx's JumpToLatest): a zero-height sticky last child, so it floats
114
+ * centred just above the composer overlay without taking layout space. Sticky offsets measure from
115
+ * the scrollport edge, not the padding, hence the controls height in 'bottom'.
116
+ */
117
+ .jini-message-list__jump {
118
+ position: sticky;
119
+ bottom: calc(var(--jini-chat-controls-height, 240px) + 8px);
120
+ height: 0;
121
+ display: flex;
122
+ justify-content: center;
123
+ align-items: flex-end;
124
+ z-index: 2;
125
+ pointer-events: none;
126
+ }
127
+ .jini-message-list__jump-btn {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ gap: 6px;
131
+ padding: 5px 12px;
132
+ border: 1px solid var(--jini-chat-border);
133
+ border-radius: 999px;
134
+ background: var(--jini-chat-panel);
135
+ color: var(--jini-chat-muted);
136
+ box-shadow: var(--jini-chat-popover-shadow);
137
+ font-size: 12px;
138
+ line-height: 1.2;
139
+ white-space: nowrap;
140
+ cursor: pointer;
141
+ pointer-events: auto;
142
+ }
143
+ .jini-message-list__jump-btn:hover { color: var(--jini-chat-text-strong); }
144
+ .jini-message-list__jump-btn:focus-visible { outline: 2px solid var(--jini-chat-accent); outline-offset: 2px; }
112
145
  .jini-chat-pane .jini-message {
113
146
  margin-bottom: 20px;
114
147
  color: var(--jini-chat-text);
@@ -152,6 +185,9 @@ export const CHAT_PANE_STYLES = `
152
185
  padding: 10px 13px;
153
186
  width: fit-content;
154
187
  max-width: 88%;
188
+ /* MOVE: Answers may contain long labels or pasted URLs; wrap them within narrow chat panes. */
189
+ white-space: pre-wrap;
190
+ overflow-wrap: anywhere;
155
191
  /* User bubbles share this style at every nesting depth, regardless of message length. */
156
192
  background: var(--jini-chat-user-bubble, #1a1b1c);
157
193
  color: var(--jini-chat-user-bubble-ink, #fff);
@@ -885,7 +921,7 @@ export const CHAT_PANE_STYLES = `
885
921
  }
886
922
  .jini-chat-pane .jini-composer-workdir-confirm {
887
923
  padding: 6px 10px;
888
- color: white;
924
+ color: var(--jini-chat-panel);
889
925
  white-space: nowrap;
890
926
  background: var(--jini-chat-text-strong);
891
927
  border: 0;
@@ -898,34 +934,52 @@ export const CHAT_PANE_STYLES = `
898
934
  .jini-runtime-spinner,
899
935
  .jini-icon-spin { animation: jini-chat-spin .8s linear infinite; }
900
936
  @keyframes jini-chat-spin { to { transform: rotate(360deg); } }
937
+ /* The model/effort cluster sits at the trailing edge beside send (owner, 2026-10-18); the "+"
938
+ stays at the start. The auto margin takes the free space, and gives it back first when a narrow
939
+ bar needs the room for the model label. */
901
940
  .jini-chat-pane .jini-composer-footer-accessories {
902
941
  display: flex;
903
- margin-left: auto;
904
942
  min-width: 0;
943
+ margin-left: auto;
944
+ }
945
+ /* Basis 0: the hint takes only the room the runtime row leaves and wraps into it, instead of
946
+ claiming its one-line width and squeezing the picker under its icon/effort labels. */
947
+ .jini-chat-pane .jini-composer-send-blocker {
948
+ flex: 1 1 0;
949
+ align-self: center;
950
+ margin-right: 8px;
951
+ color: var(--jini-chat-muted);
952
+ font-size: 12px;
905
953
  }
954
+ /* Always pushed to the trailing edge. This also covers a footer with no attach/accessory controls
955
+ before the send group (a public visitor chat), which would otherwise leave the lone send button
956
+ at the start under the footer's space-between. */
906
957
  .jini-chat-pane .jini-composer-send-group {
907
958
  display: flex;
908
959
  flex-shrink: 0;
909
960
  gap: 6px;
961
+ margin-left: auto;
910
962
  }
911
- /* A footer with no attach/accessory controls before the send group (a public visitor chat) would
912
- leave the lone send button at the start under the footer's space-between; keep it at the end. */
913
- .jini-chat-pane .jini-composer-send-group:first-child { margin-left: auto; }
963
+ /* After the cluster, the send group sits right beside it instead of splitting the free space. */
964
+ .jini-chat-pane .jini-composer-footer-accessories ~ .jini-composer-send-group { margin-left: 0; }
965
+ /* Inverse ink: the fill is the strong text colour, so the icon takes the panel colour rather than a
966
+ fixed white. A host dark theme flips text-strong to near-white, and fixed white icons then drew a
967
+ blank white square (owner report, 2026-10-21). Disabled uses the theme's subtle/faint pair too. */
914
968
  .jini-chat-pane .jini-composer-send {
915
969
  display: grid;
916
970
  place-items: center;
917
971
  width: 32px;
918
972
  height: 32px;
919
973
  padding: 0;
920
- color: white;
974
+ color: var(--jini-chat-panel);
921
975
  background: var(--jini-chat-text-strong);
922
976
  border: 0;
923
977
  border-radius: 8px;
924
978
  cursor: pointer;
925
979
  }
926
980
  .jini-chat-pane .jini-composer-send:disabled {
927
- color: #aaa59e;
928
- background: #ece8e2;
981
+ color: var(--jini-chat-faint);
982
+ background: var(--jini-chat-subtle);
929
983
  cursor: default;
930
984
  }
931
985
  .jini-chat-pane__workdir {
@@ -1098,40 +1152,100 @@ export const CHAT_PANE_STYLES = `
1098
1152
  white-space: nowrap;
1099
1153
  opacity: .7;
1100
1154
  }
1101
- .jini-runtime-picker { position: relative; }
1102
- .jini-runtime-trigger {
1103
- display: inline-grid;
1104
- grid-template-columns: 24px 14px;
1155
+ /* Composer-bar runtime row: [agent icon] [model ⌄] [effort ⌄] -- compact, borderless, muted text.
1156
+ The icon opens the runtime popover; model and effort each open a drop-up (RuntimeDropUp). */
1157
+ .jini-runtime-picker {
1158
+ position: relative;
1159
+ display: flex;
1160
+ align-items: center;
1161
+ min-width: 0;
1162
+ }
1163
+ .jini-runtime-trigger,
1164
+ .jini-runtime-dropup-trigger {
1165
+ display: inline-flex;
1105
1166
  align-items: center;
1106
- gap: 7px;
1167
+ gap: 4px;
1107
1168
  height: 32px;
1108
1169
  min-width: 0;
1109
- padding: 3px 7px 3px 5px;
1110
- color: var(--jini-chat-text);
1170
+ padding: 3px 6px;
1171
+ color: var(--jini-chat-muted);
1111
1172
  background: transparent;
1112
1173
  border: 0;
1113
1174
  border-radius: 8px;
1175
+ font: inherit;
1176
+ font-size: 13px;
1114
1177
  cursor: pointer;
1115
1178
  }
1116
- .jini-runtime-trigger:hover { background: var(--jini-chat-subtle); }
1117
- .jini-runtime-trigger__copy { display: none; }
1179
+ .jini-runtime-trigger { gap: 6px; padding: 3px 4px; }
1180
+ /* Local mode's trigger is the icon alone, so it has nothing to give way: shrunk under the shared
1181
+ \`min-width: 0\` on a narrow bar, the 22px icon spilled over the model/effort labels beside it
1182
+ (owner report, 2026-10-21). API mode keeps shrinking -- its model label ellipsizes. */
1183
+ .jini-runtime-trigger--icon-only { flex: none; }
1184
+ .jini-runtime-trigger:hover,
1185
+ .jini-runtime-dropup-trigger:hover,
1186
+ .jini-runtime-dropup-trigger[aria-expanded="true"] { color: var(--jini-chat-text); background: var(--jini-chat-subtle); }
1187
+ .jini-runtime-trigger:focus-visible,
1188
+ .jini-runtime-dropup-trigger:focus-visible { outline: 2px solid var(--jini-chat-accent); outline-offset: 1px; }
1189
+ /* 'contents' so the hidden name (absolutely positioned) adds no flex gap beside the icon-only
1190
+ local trigger, and the API-mode model label is a direct, shrinkable flex item. */
1191
+ .jini-runtime-trigger__copy { display: contents; }
1192
+ /* Kept in the text (see the component), not shown: the icon already names the agent. */
1118
1193
  .jini-runtime-trigger__copy strong {
1194
+ position: absolute;
1195
+ width: 1px;
1196
+ height: 1px;
1119
1197
  overflow: hidden;
1120
- max-width: 122px;
1121
- font-size: 11px;
1122
- font-weight: 650;
1123
- text-overflow: ellipsis;
1198
+ clip: rect(0 0 0 0);
1124
1199
  white-space: nowrap;
1125
1200
  }
1126
- .jini-runtime-trigger__copy small {
1201
+ .jini-runtime-trigger__copy small,
1202
+ .jini-runtime-dropup-trigger__label {
1127
1203
  overflow: hidden;
1128
- max-width: 122px;
1129
- color: var(--jini-chat-muted);
1130
- font-size: 9px;
1204
+ max-width: 180px;
1205
+ font-size: inherit;
1131
1206
  text-overflow: ellipsis;
1132
1207
  white-space: nowrap;
1133
1208
  }
1134
- .jini-runtime-trigger__chevron { color: var(--jini-chat-muted); font-size: 12px; }
1209
+ .jini-runtime-trigger__chevron { flex: none; }
1210
+ /* The effort label is short; the model label is the one that gives way on a narrow bar. */
1211
+ .jini-runtime-dropup-trigger--effort { flex: none; }
1212
+ .jini-runtime-dropup {
1213
+ display: flex;
1214
+ flex-direction: column;
1215
+ gap: 1px;
1216
+ padding: 4px;
1217
+ color: var(--jini-chat-text);
1218
+ background: var(--jini-chat-panel);
1219
+ border: 1px solid var(--jini-chat-border);
1220
+ border-radius: 10px;
1221
+ box-shadow: var(--jini-chat-popover-shadow, 0 18px 46px rgba(13, 12, 10, .15), 0 2px 8px rgba(13, 12, 10, .06));
1222
+ font-size: 13px;
1223
+ line-height: 1.4;
1224
+ }
1225
+ .jini-runtime-dropup__note {
1226
+ padding: 4px 10px 6px;
1227
+ color: var(--jini-chat-muted);
1228
+ font-size: 11.5px;
1229
+ }
1230
+ .jini-runtime-dropup__item {
1231
+ display: flex;
1232
+ align-items: center;
1233
+ gap: 8px;
1234
+ width: 100%;
1235
+ min-height: 32px;
1236
+ padding: 6px 10px;
1237
+ color: inherit;
1238
+ background: transparent;
1239
+ border: 0;
1240
+ border-radius: 6px;
1241
+ font: inherit;
1242
+ text-align: left;
1243
+ cursor: pointer;
1244
+ }
1245
+ .jini-runtime-dropup__item:hover,
1246
+ .jini-runtime-dropup__item:focus-visible { background: var(--jini-chat-subtle); outline: none; }
1247
+ .jini-runtime-dropup__item.is-active { font-weight: 600; }
1248
+ .jini-runtime-dropup__item-label { flex: 1; min-width: 0; overflow-wrap: anywhere; }
1135
1249
  .jini-runtime-agent-icon.agent-icon { flex: 0 0 auto; object-fit: contain; }
1136
1250
  .jini-runtime-agent-icon.agent-icon-mono {
1137
1251
  display: inline-block;
@@ -1168,8 +1282,8 @@ export const CHAT_PANE_STYLES = `
1168
1282
  background: var(--jini-chat-panel);
1169
1283
  border: 1px solid var(--jini-chat-border);
1170
1284
  border-radius: 12px;
1171
- box-shadow: 0 18px 46px rgba(13, 12, 10, .15), 0 2px 8px rgba(13, 12, 10, .06);
1172
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei UI", "Noto Sans", Roboto, "Helvetica Neue", Arial, sans-serif;
1285
+ box-shadow: var(--jini-chat-popover-shadow, 0 18px 46px rgba(13, 12, 10, .15), 0 2px 8px rgba(13, 12, 10, .06));
1286
+ font-family: var(--jini-chat-font-body, -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei UI", "Noto Sans", Roboto, "Helvetica Neue", Arial, sans-serif);
1173
1287
  font-size: 13.5px;
1174
1288
  line-height: 1.5;
1175
1289
  -webkit-font-smoothing: antialiased;
@@ -1362,7 +1476,7 @@ export const CHAT_PANE_STYLES = `
1362
1476
  .jini-chat-pane .op-path,
1363
1477
  .jini-chat-pane .op-command,
1364
1478
  .jini-chat-pane .op-output {
1365
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1479
+ font-family: var(--jini-chat-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1366
1480
  font-size: 12px;
1367
1481
  background: var(--jini-chat-panel);
1368
1482
  border: 1px solid var(--jini-chat-border);
@@ -1447,7 +1561,7 @@ export const CHAT_PANE_STYLES = `
1447
1561
  background: var(--jini-chat-subtle);
1448
1562
  border: 1px solid var(--jini-chat-border);
1449
1563
  border-radius: 8px;
1450
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1564
+ font-family: var(--jini-chat-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1451
1565
  font-size: 12.5px;
1452
1566
  line-height: 1.5;
1453
1567
  }
@@ -1463,7 +1577,7 @@ export const CHAT_PANE_STYLES = `
1463
1577
  background: var(--jini-chat-subtle);
1464
1578
  border: 1px solid var(--jini-chat-border);
1465
1579
  border-radius: 4px;
1466
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1580
+ font-family: var(--jini-chat-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1467
1581
  font-size: .9em;
1468
1582
  }
1469
1583
  /*
@@ -1596,7 +1710,7 @@ export const CHAT_PANE_STYLES = `
1596
1710
  background: var(--jini-chat-panel);
1597
1711
  border: 1px solid var(--jini-chat-border);
1598
1712
  border-radius: 12px;
1599
- box-shadow: 0 18px 46px rgba(13, 12, 10, .15), 0 2px 8px rgba(13, 12, 10, .06);
1713
+ box-shadow: var(--jini-chat-popover-shadow, 0 18px 46px rgba(13, 12, 10, .15), 0 2px 8px rgba(13, 12, 10, .06));
1600
1714
  }
1601
1715
  .jini-md-table-modal::backdrop {
1602
1716
  background: rgba(0, 0, 0, .4);
@@ -1641,7 +1755,7 @@ export const CHAT_PANE_STYLES = `
1641
1755
  background: var(--jini-chat-panel);
1642
1756
  border: 1px solid var(--jini-chat-border);
1643
1757
  border-radius: 12px;
1644
- box-shadow: 0 18px 46px rgba(13, 12, 10, .15), 0 2px 8px rgba(13, 12, 10, .06);
1758
+ box-shadow: var(--jini-chat-popover-shadow, 0 18px 46px rgba(13, 12, 10, .15), 0 2px 8px rgba(13, 12, 10, .06));
1645
1759
  }
1646
1760
  .jini-attachment-preview-dialog::backdrop {
1647
1761
  background: rgba(0, 0, 0, .4);
@@ -1697,6 +1811,8 @@ export const CHAT_PANE_STYLES = `
1697
1811
  margin: auto;
1698
1812
  }
1699
1813
  .jini-attachment-preview-text {
1814
+ /* Preserve this preview's wider fallback stack when the host does not supply a chat font. */
1815
+ --jini-chat-font-mono: var(--jini-theme-chat-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
1700
1816
  width: 100%;
1701
1817
  max-height: calc(80vh - 90px);
1702
1818
  margin: 0;
@@ -1706,7 +1822,7 @@ export const CHAT_PANE_STYLES = `
1706
1822
  background: var(--jini-chat-subtle);
1707
1823
  border: 1px solid var(--jini-chat-border-soft);
1708
1824
  border-radius: 8px;
1709
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1825
+ font-family: var(--jini-chat-font-mono);
1710
1826
  font-size: 12px;
1711
1827
  line-height: 1.5;
1712
1828
  white-space: pre-wrap;
@@ -66,6 +66,10 @@ export interface ChatPaneAgentSelection {
66
66
  /** Host I/O effects used by the package-owned runtime inventory controller. */
67
67
  export interface ChatPaneRuntimeAccess {
68
68
  listAgents: () => Promise<readonly ChatPaneAgent[]>;
69
+ /** Earliest time a cached inventory read can refresh (epoch ms); read after listAgents settles.
70
+ * Pending retries honor this deadline so a remounted pane does not spend its retry budget on
71
+ * cached answers. Omit for hosts without a cooldown. Example: () => cachedInventory.expiresAt. */
72
+ getAgentsRefreshAt?: () => number | undefined;
69
73
  rescanAgents: () => Promise<readonly ChatPaneAgent[]>;
70
74
  daemonOnline: () => Promise<boolean>;
71
75
  }
@@ -177,10 +181,11 @@ export type DeliverTypedAnswer = ((input: {
177
181
  * any more (it is ending); `'failed'` — the host could not tell (network error, unexpected status).
178
182
  */
179
183
  export type MidRunMessageDelivery = 'delivered' | 'unsupported' | 'not-running' | 'failed';
180
- /** Delivers text typed while a run streams to that run's live agent. `createMidRunMessagePoster` builds one. */
184
+ /** Delivers a complete turn to the live agent; unsupported attachments must reject the whole turn. */
181
185
  export type DeliverMidRunMessage = (input: {
182
186
  runId: string;
183
187
  text: string;
188
+ attachments?: readonly ChatAttachment[];
184
189
  }) => Promise<MidRunMessageDelivery>;
185
190
  export interface ChatPaneProps extends UserTextRedactionOptions {
186
191
  transport: ChatTransport;
@@ -217,6 +222,8 @@ export interface ChatPaneProps extends UserTextRedactionOptions {
217
222
  composerHistoryScope?: string;
218
223
  /** Optional storage effects; omitted uses bounded browser storage with memory fallback. */
219
224
  composerHistoryStorage?: ComposerHistoryStoragePort;
225
+ /** Recall retention; defaults to 100. */
226
+ composerHistoryCap?: number;
220
227
  /**
221
228
  * Confirms which previously staged attachments still exist, so a draft restored after a reload
222
229
  * never shows a chip for a file the backend has since garbage-collected — that looks intact and
@@ -241,19 +248,19 @@ export interface ChatPaneProps extends UserTextRedactionOptions {
241
248
  * Called instead of queueing when the human types while the running agent is holding a question
242
249
  * open in the transcript (an interactive surface whose call to `deliverTypedAnswer.toolName` has
243
250
  * not returned — `isAwaitingTypedAnswer`) and the turn carries no attachments. `'delivered'` clears the draft
244
- * (only if unchanged since it went out); any other outcome, or a throw, leaves the draft in place with a
245
- * notice and NEVER queues it — a queued answer would go out after the run ends as a new, paid run
246
- * answering nothing. A draft typed while the question waited stays held after it closes, until the
247
- * human clears it or picks "Send as a new message" (`useChatPane`'s `sendAsNewMessage`).
251
+ * (only if unchanged since it went out). A definitive `'not-pending'` refusal falls back once to
252
+ * the ordinary message queue without a notice; a failure or throw retains the draft with a notice.
253
+ * Terminal runs and answered or superseded cards do not accept typed answers.
248
254
  *
249
255
  * Omit it and typing while a run streams queues exactly as before.
250
256
  */
251
257
  deliverTypedAnswer?: DeliverTypedAnswer;
252
258
  /**
253
- * Called instead of queueing when the human sends text (no attachments) while a run streams, so
259
+ * Called instead of queueing when the human sends a message while a run streams, so
254
260
  * the message reaches the running agent now rather than after it finishes. The draft clears at
255
- * once. `'delivered'` leaves the run going; `'unsupported'` stops the run and sends the text as
256
- * the next turn (a resuming agent keeps its session); `'not-running'`, `'failed'` or a throw
261
+ * once. `'delivered'` leaves the run going; `'unsupported'` queues attachment turns until the
262
+ * reply finishes, or stops the run for a text-only turn (a resuming agent keeps its session).
263
+ * `'not-running'`, `'failed'` or a throw
257
264
  * queue it behind the ending run. Never silently dropped. A held-open question still goes to
258
265
  * {@link deliverTypedAnswer} first.
259
266
  *
@@ -305,12 +312,15 @@ export interface ChatPaneProps extends UserTextRedactionOptions {
305
312
  /**
306
313
  * Transcript display choices (a host's chat settings, 2026-10-11), passed straight to
307
314
  * `MessageList`: how tool calls show (`'show'` default, `'collapsed'` one line per turn, `'hidden'`
308
- * all but failed ones — see `tool-call-display.ts`), whether the "Done · time · cost" lines show
315
+ * hides recovered calls and folds unrecovered failures — see `tool-call-display.ts`), whether the
316
+ * "Done · time · cost" lines show
309
317
  * (default `true`), and whether the model's reasoning shows (default `false`).
310
318
  */
311
319
  toolCallDisplay?: ToolCallDisplay;
312
320
  showUsageLine?: boolean;
313
321
  showThinking?: boolean;
322
+ /** Hide prior automatic-usage chips immediately when the host selects Off. Loading policy stays server-owned. */
323
+ agentPluginsMode?: 'auto' | 'ask' | 'off';
314
324
  uploadAttachments?: (files: File[], options?: ChatPaneAttachmentUploadOptions) => Promise<ChatAttachment[]>;
315
325
  attachmentAccept?: string;
316
326
  disabled?: boolean;
@@ -143,13 +143,20 @@ export function createAssistantChatsClient({ ports }, _options = {}) {
143
143
  * {@link attemptSave}.
144
144
  *
145
145
  * Never rejects, so a caller can treat the result as data rather than wrapping every call.
146
+ *
147
+ * A `do…while` on the attempt's own verdict rather than `for (;;)` with an inner `return`: the
148
+ * unconditional loop had an exit path no input could ever reach (it only ever left by `return`),
149
+ * while this loop exits exactly when an attempt is done. Termination is unchanged — every
150
+ * attempt past the end of the delay ladder is done.
146
151
  */
147
152
  async function saveWithRetry(port, conversationId, message, isDisposed) {
148
- for (let attempt = 0;; attempt += 1) {
149
- const result = await attemptSave(port, conversationId, message, attempt, isDisposed);
150
- if (result.done)
151
- return result.outcome;
152
- }
153
+ let attempt = 0;
154
+ let result;
155
+ do {
156
+ result = await attemptSave(port, conversationId, message, attempt, isDisposed);
157
+ attempt += 1;
158
+ } while (!result.done);
159
+ return result.outcome;
153
160
  }
154
161
  /**
155
162
  * Decides what a settled batch of message-save outcomes means for `flush`'s bookkeeping: which