@jini-ai/chat 0.6.5 → 0.6.7

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 (161) hide show
  1. package/CHANGELOG.md +12 -0
  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 +184 -97
  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 +35 -6
  21. package/dist/core/durable-runs/ledger-contracts.d.ts +2 -1
  22. package/dist/core/durable-runs/policies.d.ts +1 -0
  23. package/dist/core/durable-runs/policies.js +1 -0
  24. package/dist/core/durable-runs/ports.d.ts +6 -2
  25. package/dist/core/index.d.ts +2 -0
  26. package/dist/core/index.js +2 -0
  27. package/dist/core/messages.d.ts +2 -0
  28. package/dist/core/partial-json.d.ts +6 -7
  29. package/dist/core/partial-json.js +81 -47
  30. package/dist/core/persistence/title.js +4 -2
  31. package/dist/core/question-answer.d.ts +23 -0
  32. package/dist/core/question-answer.js +66 -0
  33. package/dist/core/question-form/format.d.ts +6 -6
  34. package/dist/core/question-form/format.js +16 -17
  35. package/dist/core/question-form/normalize.d.ts +18 -0
  36. package/dist/core/question-form/normalize.js +112 -122
  37. package/dist/core/question-form/scan.js +141 -203
  38. package/dist/core/run-activity.js +62 -49
  39. package/dist/core/run-events/entry.d.ts +10 -1
  40. package/dist/core/run-events/entry.js +117 -87
  41. package/dist/core/todos.d.ts +1 -1
  42. package/dist/core/todos.js +60 -85
  43. package/dist/core/tool-events.js +14 -12
  44. package/dist/core/transcript.js +62 -16
  45. package/dist/core/turn-usage.d.ts +2 -1
  46. package/dist/core/turn-usage.js +56 -27
  47. package/dist/core/user-text-redaction.js +3 -0
  48. package/dist/core/util/manifest.js +77 -82
  49. package/dist/core/util/markdown-context.js +51 -53
  50. package/dist/core/util/parser.js +136 -144
  51. package/dist/core/util/pointer.js +16 -16
  52. package/dist/core/util/strip.js +57 -67
  53. package/dist/core/util/validate.js +15 -17
  54. package/dist/embed-widget/browser/session-store.js +21 -12
  55. package/dist/embed-widget/transports/fetch-sse.js +108 -95
  56. package/dist/embed-widget/vanilla/index.js +81 -70
  57. package/dist/react/agent-bridge/frontend-session-bridge.js +80 -52
  58. package/dist/react/components/AttachmentPreviewModal.js +25 -1
  59. package/dist/react/components/ChatSettingsDialog.d.ts +13 -0
  60. package/dist/react/components/ChatSettingsDialog.hooks.d.ts +115 -0
  61. package/dist/react/components/ChatSettingsDialog.hooks.js +40 -0
  62. package/dist/react/components/ChatSettingsDialog.js +72 -0
  63. package/dist/react/components/ChatSettingsSegmented.d.ts +18 -0
  64. package/dist/react/components/ChatSettingsSegmented.js +11 -0
  65. package/dist/react/components/Composer.d.ts +5 -1
  66. package/dist/react/components/Composer.js +126 -82
  67. package/dist/react/components/ConversationList.js +25 -39
  68. package/dist/react/components/ConversationTitleEditor.d.ts +27 -0
  69. package/dist/react/components/ConversationTitleEditor.js +54 -0
  70. package/dist/react/components/Icon.d.ts +1 -1
  71. package/dist/react/components/Icon.js +25 -45
  72. package/dist/react/components/Markdown.js +134 -123
  73. package/dist/react/components/McpUiSurfaceCard.d.ts +2 -2
  74. package/dist/react/components/McpUiSurfaceCard.js +30 -9
  75. package/dist/react/components/MessageList.d.ts +13 -13
  76. package/dist/react/components/MessageList.hooks.d.ts +6 -1
  77. package/dist/react/components/MessageList.hooks.js +19 -3
  78. package/dist/react/components/MessageList.js +34 -10
  79. package/dist/react/components/MessageRow.js +48 -32
  80. package/dist/react/components/MessageRow.usage.d.ts +5 -2
  81. package/dist/react/components/MessageRow.usage.js +20 -11
  82. package/dist/react/components/MidRunUserMessage.js +16 -3
  83. package/dist/react/components/QuestionForm.js +148 -76
  84. package/dist/react/components/QuestionsPanel.js +1 -1
  85. package/dist/react/components/TodoCard.js +12 -6
  86. package/dist/react/components/ToolCard.js +47 -40
  87. package/dist/react/components/composer-discovery.js +6 -1
  88. package/dist/react/components/mcp-ui-surface-state.d.ts +6 -11
  89. package/dist/react/components/mcp-ui-surface-state.js +23 -8
  90. package/dist/react/ext-event-renderer-registry.d.ts +8 -0
  91. package/dist/react/ext-event-renderer-registry.js +5 -2
  92. package/dist/react/features/chat-pane/components/AgentPluginUsage.d.ts +7 -0
  93. package/dist/react/features/chat-pane/components/AgentPluginUsage.js +45 -0
  94. package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +55 -19
  95. package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
  96. package/dist/react/features/chat-pane/components/ChatPane.js +192 -74
  97. package/dist/react/features/chat-pane/components/RuntimeDropUp.d.ts +29 -0
  98. package/dist/react/features/chat-pane/components/RuntimeDropUp.js +45 -0
  99. package/dist/react/features/chat-pane/create-daemon-attachment-uploader.js +80 -47
  100. package/dist/react/features/chat-pane/create-mcp-ui-tool-caller.js +44 -40
  101. package/dist/react/features/chat-pane/create-mid-run-message-poster.d.ts +6 -6
  102. package/dist/react/features/chat-pane/create-mid-run-message-poster.js +8 -8
  103. package/dist/react/features/chat-pane/create-typed-answer-poster.d.ts +3 -3
  104. package/dist/react/features/chat-pane/create-typed-answer-poster.js +3 -3
  105. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.d.ts +6 -1
  106. package/dist/react/features/chat-pane/hooks/useAgentRuntimePicker.hooks.js +11 -5
  107. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +12 -7
  108. package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +340 -149
  109. package/dist/react/features/chat-pane/hooks/useChatPaneControlsHeight.hooks.js +7 -6
  110. package/dist/react/features/chat-pane/hooks/useChatPaneNewTabLinks.hooks.js +28 -10
  111. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.d.ts +3 -3
  112. package/dist/react/features/chat-pane/hooks/useChatPaneRuntimeInventory.hooks.js +60 -12
  113. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.d.ts +49 -0
  114. package/dist/react/features/chat-pane/hooks/useRuntimeDropUp.hooks.js +143 -0
  115. package/dist/react/features/chat-pane/model-options.d.ts +18 -1
  116. package/dist/react/features/chat-pane/model-options.js +49 -20
  117. package/dist/react/features/chat-pane/rules.d.ts +8 -9
  118. package/dist/react/features/chat-pane/rules.js +84 -46
  119. package/dist/react/features/chat-pane/styles.d.ts +1 -1
  120. package/dist/react/features/chat-pane/styles.js +156 -37
  121. package/dist/react/features/chat-pane/types.d.ts +17 -8
  122. package/dist/react/hooks/assistant-chats.js +12 -5
  123. package/dist/react/hooks/composer-draft-cache.js +116 -118
  124. package/dist/react/hooks/composer-history-storage.js +5 -5
  125. package/dist/react/hooks/fab-position.js +7 -4
  126. package/dist/react/hooks/useAnsweredCards.d.ts +21 -4
  127. package/dist/react/hooks/useAnsweredCards.js +38 -11
  128. package/dist/react/hooks/useArtifactStream.js +28 -22
  129. package/dist/react/hooks/useComposer.d.ts +20 -0
  130. package/dist/react/hooks/useComposer.js +22 -9
  131. package/dist/react/hooks/useComposerHistory.hooks.d.ts +6 -1
  132. package/dist/react/hooks/useComposerHistory.hooks.js +39 -19
  133. package/dist/react/hooks/useComposerHistoryKeys.hooks.js +24 -14
  134. package/dist/react/hooks/useConversation.d.ts +7 -0
  135. package/dist/react/hooks/useConversation.js +23 -10
  136. package/dist/react/hooks/useExtEventGroups.js +39 -39
  137. package/dist/react/hooks/useQuestionForms.js +21 -26
  138. package/dist/react/hooks/useRunStream.js +10 -2
  139. package/dist/react/index.d.ts +7 -1
  140. package/dist/react/index.js +9 -0
  141. package/dist/react/message-blocks.d.ts +6 -4
  142. package/dist/react/message-blocks.js +94 -72
  143. package/dist/react/slots.d.ts +2 -2
  144. package/dist/react/tool-call-display.js +19 -17
  145. package/dist/server/durable-runs/recover.d.ts +1 -1
  146. package/dist/server/durable-runs/recover.js +139 -76
  147. package/dist/server/run-finalizer/entry.js +25 -15
  148. package/dist/store/legacy/sqlite/conversations.d.ts +11 -11
  149. package/dist/store/legacy/sqlite/conversations.js +69 -98
  150. package/dist/store/legacy/sqlite/messages.d.ts +10 -2
  151. package/dist/store/legacy/sqlite/messages.js +113 -71
  152. package/dist/store/runtime/chat-history-store.js +9 -3
  153. package/dist/store/runtime/durable-run-store.d.ts +2 -2
  154. package/dist/store/runtime/durable-run-store.js +165 -117
  155. package/dist/store/runtime/history-tools.js +118 -82
  156. package/dist/store/runtime/run-ledger.d.ts +4 -1
  157. package/dist/store/runtime/run-ledger.js +56 -16
  158. package/dist/store/sql/cursor.js +38 -18
  159. package/dist/store/sql/store.d.ts +18 -0
  160. package/dist/store/sql/store.js +46 -15
  161. package/package.json +12 -10
@@ -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);
@@ -645,8 +681,11 @@ export const CHAT_PANE_STYLES = `
645
681
  }
646
682
  /* Phones: the 25px "x" gets an invisible 44px tap area; the chip keeps its size. The area ends at the
647
683
  chip's bottom edge and grows up into the tray's 8px top padding / row gap, because growing down
648
- past the last row would make the 88px tray scroll by a few pixels. */
684
+ past the last row would make the 88px tray scroll by a few pixels. Rows are 8px apart here, not
685
+ 6: at a 42px row pitch the second row's area covered the bottom 2.5px of the first row's, leaving
686
+ the top row a 41.5px target. 8px top padding + two 36px rows + an 8px gap is exactly the 88px cap. */
649
687
  @media (max-width: 640px), (pointer: coarse) {
688
+ .jini-chat-pane .jini-attachment-tray { row-gap: 8px; }
650
689
  .jini-chat-pane .jini-attachment-remove { position: relative; }
651
690
  .jini-chat-pane .jini-attachment-remove::before {
652
691
  content: '';
@@ -882,7 +921,7 @@ export const CHAT_PANE_STYLES = `
882
921
  }
883
922
  .jini-chat-pane .jini-composer-workdir-confirm {
884
923
  padding: 6px 10px;
885
- color: white;
924
+ color: var(--jini-chat-panel);
886
925
  white-space: nowrap;
887
926
  background: var(--jini-chat-text-strong);
888
927
  border: 0;
@@ -895,34 +934,52 @@ export const CHAT_PANE_STYLES = `
895
934
  .jini-runtime-spinner,
896
935
  .jini-icon-spin { animation: jini-chat-spin .8s linear infinite; }
897
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. */
898
940
  .jini-chat-pane .jini-composer-footer-accessories {
899
941
  display: flex;
900
- margin-left: auto;
901
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;
902
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. */
903
957
  .jini-chat-pane .jini-composer-send-group {
904
958
  display: flex;
905
959
  flex-shrink: 0;
906
960
  gap: 6px;
961
+ margin-left: auto;
907
962
  }
908
- /* A footer with no attach/accessory controls before the send group (a public visitor chat) would
909
- leave the lone send button at the start under the footer's space-between; keep it at the end. */
910
- .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. */
911
968
  .jini-chat-pane .jini-composer-send {
912
969
  display: grid;
913
970
  place-items: center;
914
971
  width: 32px;
915
972
  height: 32px;
916
973
  padding: 0;
917
- color: white;
974
+ color: var(--jini-chat-panel);
918
975
  background: var(--jini-chat-text-strong);
919
976
  border: 0;
920
977
  border-radius: 8px;
921
978
  cursor: pointer;
922
979
  }
923
980
  .jini-chat-pane .jini-composer-send:disabled {
924
- color: #aaa59e;
925
- background: #ece8e2;
981
+ color: var(--jini-chat-faint);
982
+ background: var(--jini-chat-subtle);
926
983
  cursor: default;
927
984
  }
928
985
  .jini-chat-pane__workdir {
@@ -1095,40 +1152,100 @@ export const CHAT_PANE_STYLES = `
1095
1152
  white-space: nowrap;
1096
1153
  opacity: .7;
1097
1154
  }
1098
- .jini-runtime-picker { position: relative; }
1099
- .jini-runtime-trigger {
1100
- display: inline-grid;
1101
- 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;
1102
1166
  align-items: center;
1103
- gap: 7px;
1167
+ gap: 4px;
1104
1168
  height: 32px;
1105
1169
  min-width: 0;
1106
- padding: 3px 7px 3px 5px;
1107
- color: var(--jini-chat-text);
1170
+ padding: 3px 6px;
1171
+ color: var(--jini-chat-muted);
1108
1172
  background: transparent;
1109
1173
  border: 0;
1110
1174
  border-radius: 8px;
1175
+ font: inherit;
1176
+ font-size: 13px;
1111
1177
  cursor: pointer;
1112
1178
  }
1113
- .jini-runtime-trigger:hover { background: var(--jini-chat-subtle); }
1114
- .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. */
1115
1193
  .jini-runtime-trigger__copy strong {
1194
+ position: absolute;
1195
+ width: 1px;
1196
+ height: 1px;
1116
1197
  overflow: hidden;
1117
- max-width: 122px;
1118
- font-size: 11px;
1119
- font-weight: 650;
1120
- text-overflow: ellipsis;
1198
+ clip: rect(0 0 0 0);
1121
1199
  white-space: nowrap;
1122
1200
  }
1123
- .jini-runtime-trigger__copy small {
1201
+ .jini-runtime-trigger__copy small,
1202
+ .jini-runtime-dropup-trigger__label {
1124
1203
  overflow: hidden;
1125
- max-width: 122px;
1126
- color: var(--jini-chat-muted);
1127
- font-size: 9px;
1204
+ max-width: 180px;
1205
+ font-size: inherit;
1128
1206
  text-overflow: ellipsis;
1129
1207
  white-space: nowrap;
1130
1208
  }
1131
- .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; }
1132
1249
  .jini-runtime-agent-icon.agent-icon { flex: 0 0 auto; object-fit: contain; }
1133
1250
  .jini-runtime-agent-icon.agent-icon-mono {
1134
1251
  display: inline-block;
@@ -1165,8 +1282,8 @@ export const CHAT_PANE_STYLES = `
1165
1282
  background: var(--jini-chat-panel);
1166
1283
  border: 1px solid var(--jini-chat-border);
1167
1284
  border-radius: 12px;
1168
- box-shadow: 0 18px 46px rgba(13, 12, 10, .15), 0 2px 8px rgba(13, 12, 10, .06);
1169
- 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);
1170
1287
  font-size: 13.5px;
1171
1288
  line-height: 1.5;
1172
1289
  -webkit-font-smoothing: antialiased;
@@ -1359,7 +1476,7 @@ export const CHAT_PANE_STYLES = `
1359
1476
  .jini-chat-pane .op-path,
1360
1477
  .jini-chat-pane .op-command,
1361
1478
  .jini-chat-pane .op-output {
1362
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1479
+ font-family: var(--jini-chat-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1363
1480
  font-size: 12px;
1364
1481
  background: var(--jini-chat-panel);
1365
1482
  border: 1px solid var(--jini-chat-border);
@@ -1444,7 +1561,7 @@ export const CHAT_PANE_STYLES = `
1444
1561
  background: var(--jini-chat-subtle);
1445
1562
  border: 1px solid var(--jini-chat-border);
1446
1563
  border-radius: 8px;
1447
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1564
+ font-family: var(--jini-chat-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1448
1565
  font-size: 12.5px;
1449
1566
  line-height: 1.5;
1450
1567
  }
@@ -1460,7 +1577,7 @@ export const CHAT_PANE_STYLES = `
1460
1577
  background: var(--jini-chat-subtle);
1461
1578
  border: 1px solid var(--jini-chat-border);
1462
1579
  border-radius: 4px;
1463
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1580
+ font-family: var(--jini-chat-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1464
1581
  font-size: .9em;
1465
1582
  }
1466
1583
  /*
@@ -1593,7 +1710,7 @@ export const CHAT_PANE_STYLES = `
1593
1710
  background: var(--jini-chat-panel);
1594
1711
  border: 1px solid var(--jini-chat-border);
1595
1712
  border-radius: 12px;
1596
- 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));
1597
1714
  }
1598
1715
  .jini-md-table-modal::backdrop {
1599
1716
  background: rgba(0, 0, 0, .4);
@@ -1638,7 +1755,7 @@ export const CHAT_PANE_STYLES = `
1638
1755
  background: var(--jini-chat-panel);
1639
1756
  border: 1px solid var(--jini-chat-border);
1640
1757
  border-radius: 12px;
1641
- 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));
1642
1759
  }
1643
1760
  .jini-attachment-preview-dialog::backdrop {
1644
1761
  background: rgba(0, 0, 0, .4);
@@ -1694,6 +1811,8 @@ export const CHAT_PANE_STYLES = `
1694
1811
  margin: auto;
1695
1812
  }
1696
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);
1697
1816
  width: 100%;
1698
1817
  max-height: calc(80vh - 90px);
1699
1818
  margin: 0;
@@ -1703,7 +1822,7 @@ export const CHAT_PANE_STYLES = `
1703
1822
  background: var(--jini-chat-subtle);
1704
1823
  border: 1px solid var(--jini-chat-border-soft);
1705
1824
  border-radius: 8px;
1706
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1825
+ font-family: var(--jini-chat-font-mono);
1707
1826
  font-size: 12px;
1708
1827
  line-height: 1.5;
1709
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
  *
@@ -311,6 +318,8 @@ export interface ChatPaneProps extends UserTextRedactionOptions {
311
318
  toolCallDisplay?: ToolCallDisplay;
312
319
  showUsageLine?: boolean;
313
320
  showThinking?: boolean;
321
+ /** Hide prior automatic-usage chips immediately when the host selects Off. Loading policy stays server-owned. */
322
+ agentPluginsMode?: 'auto' | 'ask' | 'off';
314
323
  uploadAttachments?: (files: File[], options?: ChatPaneAttachmentUploadOptions) => Promise<ChatAttachment[]>;
315
324
  attachmentAccept?: string;
316
325
  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