@buoy-gg/ask-buoy 7.0.35 → 7.0.36

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 (217) hide show
  1. package/README.md +35 -8
  2. package/lib/commonjs/blocks/BlockRenderer.js +13 -1
  3. package/lib/commonjs/blocks/BlockRenderer.js.map +1 -1
  4. package/lib/commonjs/blocks/DataBlocks.js +126 -33
  5. package/lib/commonjs/blocks/DataBlocks.js.map +1 -1
  6. package/lib/commonjs/blocks/ExtraBlocks.js +12 -6
  7. package/lib/commonjs/blocks/ExtraBlocks.js.map +1 -1
  8. package/lib/commonjs/blocks/MediaBlocks.js +22 -6
  9. package/lib/commonjs/blocks/MediaBlocks.js.map +1 -1
  10. package/lib/commonjs/blocks/blockText.js +60 -0
  11. package/lib/commonjs/blocks/blockText.js.map +1 -0
  12. package/lib/commonjs/blocks/blockText.test.js +53 -0
  13. package/lib/commonjs/blocks/blockText.test.js.map +1 -0
  14. package/lib/commonjs/blocks/primitives.js +58 -2
  15. package/lib/commonjs/blocks/primitives.js.map +1 -1
  16. package/lib/commonjs/components/ApprovalCard.js +111 -7
  17. package/lib/commonjs/components/ApprovalCard.js.map +1 -1
  18. package/lib/commonjs/components/AskBuoyHeader.js +36 -4
  19. package/lib/commonjs/components/AskBuoyHeader.js.map +1 -1
  20. package/lib/commonjs/components/AskBuoyModal.js +160 -34
  21. package/lib/commonjs/components/AskBuoyModal.js.map +1 -1
  22. package/lib/commonjs/components/AskBuoySettings.js +50 -0
  23. package/lib/commonjs/components/AskBuoySettings.js.map +1 -1
  24. package/lib/commonjs/components/Composer.js +94 -14
  25. package/lib/commonjs/components/Composer.js.map +1 -1
  26. package/lib/commonjs/components/EmptyState.js +12 -5
  27. package/lib/commonjs/components/EmptyState.js.map +1 -1
  28. package/lib/commonjs/components/KeyboardPad.js +80 -0
  29. package/lib/commonjs/components/KeyboardPad.js.map +1 -0
  30. package/lib/commonjs/components/Markdown.js +148 -90
  31. package/lib/commonjs/components/Markdown.js.map +1 -1
  32. package/lib/commonjs/components/MessageList.js +339 -92
  33. package/lib/commonjs/components/MessageList.js.map +1 -1
  34. package/lib/commonjs/components/QueuedMessages.js +145 -0
  35. package/lib/commonjs/components/QueuedMessages.js.map +1 -0
  36. package/lib/commonjs/components/markdownParse.js +131 -0
  37. package/lib/commonjs/components/markdownParse.js.map +1 -0
  38. package/lib/commonjs/components/markdownParse.test.js +99 -0
  39. package/lib/commonjs/components/markdownParse.test.js.map +1 -0
  40. package/lib/commonjs/store/activityGroups.js +110 -0
  41. package/lib/commonjs/store/activityGroups.js.map +1 -0
  42. package/lib/commonjs/store/activityGroups.test.js +78 -0
  43. package/lib/commonjs/store/activityGroups.test.js.map +1 -0
  44. package/lib/commonjs/store/askBuoySettings.js +7 -2
  45. package/lib/commonjs/store/askBuoySettings.js.map +1 -1
  46. package/lib/commonjs/store/askBuoyStore.js +192 -10
  47. package/lib/commonjs/store/askBuoyStore.js.map +1 -1
  48. package/lib/commonjs/store/blockPlacement.js +64 -0
  49. package/lib/commonjs/store/blockPlacement.js.map +1 -0
  50. package/lib/commonjs/store/blockPlacement.test.js +123 -0
  51. package/lib/commonjs/store/blockPlacement.test.js.map +1 -0
  52. package/lib/commonjs/store/queue.test.js +150 -0
  53. package/lib/commonjs/store/queue.test.js.map +1 -0
  54. package/lib/commonjs/store/resetConversation.js +77 -0
  55. package/lib/commonjs/store/resetConversation.js.map +1 -0
  56. package/lib/commonjs/store/restorePending.test.js +63 -0
  57. package/lib/commonjs/store/restorePending.test.js.map +1 -0
  58. package/lib/commonjs/store/suggestions.js +54 -0
  59. package/lib/commonjs/store/suggestions.js.map +1 -0
  60. package/lib/commonjs/store/suggestions.test.js +58 -0
  61. package/lib/commonjs/store/suggestions.test.js.map +1 -0
  62. package/lib/commonjs/store/systemLine.test.js +77 -0
  63. package/lib/commonjs/store/systemLine.test.js.map +1 -0
  64. package/lib/commonjs/store/ticketPrompt.js +17 -0
  65. package/lib/commonjs/store/ticketPrompt.js.map +1 -0
  66. package/lib/commonjs/store/transcriptPersistence.js +92 -3
  67. package/lib/commonjs/store/transcriptPersistence.js.map +1 -1
  68. package/lib/commonjs/store/transcriptPersistence.test.js +148 -0
  69. package/lib/commonjs/store/transcriptPersistence.test.js.map +1 -1
  70. package/lib/commonjs/store/turnRendering.test.js +151 -0
  71. package/lib/commonjs/store/turnRendering.test.js.map +1 -0
  72. package/lib/commonjs/store/useAskBuoy.js +271 -49
  73. package/lib/commonjs/store/useAskBuoy.js.map +1 -1
  74. package/lib/commonjs/sync/askBuoySyncAdapter.js +25 -4
  75. package/lib/commonjs/sync/askBuoySyncAdapter.js.map +1 -1
  76. package/lib/module/blocks/BlockRenderer.js +13 -1
  77. package/lib/module/blocks/BlockRenderer.js.map +1 -1
  78. package/lib/module/blocks/DataBlocks.js +128 -35
  79. package/lib/module/blocks/DataBlocks.js.map +1 -1
  80. package/lib/module/blocks/ExtraBlocks.js +12 -6
  81. package/lib/module/blocks/ExtraBlocks.js.map +1 -1
  82. package/lib/module/blocks/MediaBlocks.js +22 -6
  83. package/lib/module/blocks/MediaBlocks.js.map +1 -1
  84. package/lib/module/blocks/blockText.js +55 -0
  85. package/lib/module/blocks/blockText.js.map +1 -0
  86. package/lib/module/blocks/blockText.test.js +53 -0
  87. package/lib/module/blocks/blockText.test.js.map +1 -0
  88. package/lib/module/blocks/primitives.js +58 -2
  89. package/lib/module/blocks/primitives.js.map +1 -1
  90. package/lib/module/components/ApprovalCard.js +113 -9
  91. package/lib/module/components/ApprovalCard.js.map +1 -1
  92. package/lib/module/components/AskBuoyHeader.js +37 -5
  93. package/lib/module/components/AskBuoyHeader.js.map +1 -1
  94. package/lib/module/components/AskBuoyModal.js +163 -37
  95. package/lib/module/components/AskBuoyModal.js.map +1 -1
  96. package/lib/module/components/AskBuoySettings.js +52 -2
  97. package/lib/module/components/AskBuoySettings.js.map +1 -1
  98. package/lib/module/components/Composer.js +95 -15
  99. package/lib/module/components/Composer.js.map +1 -1
  100. package/lib/module/components/EmptyState.js +12 -5
  101. package/lib/module/components/EmptyState.js.map +1 -1
  102. package/lib/module/components/KeyboardPad.js +75 -0
  103. package/lib/module/components/KeyboardPad.js.map +1 -0
  104. package/lib/module/components/Markdown.js +149 -90
  105. package/lib/module/components/Markdown.js.map +1 -1
  106. package/lib/module/components/MessageList.js +341 -94
  107. package/lib/module/components/MessageList.js.map +1 -1
  108. package/lib/module/components/QueuedMessages.js +140 -0
  109. package/lib/module/components/QueuedMessages.js.map +1 -0
  110. package/lib/module/components/markdownParse.js +125 -0
  111. package/lib/module/components/markdownParse.js.map +1 -0
  112. package/lib/module/components/markdownParse.test.js +98 -0
  113. package/lib/module/components/markdownParse.test.js.map +1 -0
  114. package/lib/module/store/activityGroups.js +103 -0
  115. package/lib/module/store/activityGroups.js.map +1 -0
  116. package/lib/module/store/activityGroups.test.js +77 -0
  117. package/lib/module/store/activityGroups.test.js.map +1 -0
  118. package/lib/module/store/askBuoySettings.js +7 -2
  119. package/lib/module/store/askBuoySettings.js.map +1 -1
  120. package/lib/module/store/askBuoyStore.js +190 -10
  121. package/lib/module/store/askBuoyStore.js.map +1 -1
  122. package/lib/module/store/blockPlacement.js +59 -0
  123. package/lib/module/store/blockPlacement.js.map +1 -0
  124. package/lib/module/store/blockPlacement.test.js +122 -0
  125. package/lib/module/store/blockPlacement.test.js.map +1 -0
  126. package/lib/module/store/queue.test.js +149 -0
  127. package/lib/module/store/queue.test.js.map +1 -0
  128. package/lib/module/store/resetConversation.js +72 -0
  129. package/lib/module/store/resetConversation.js.map +1 -0
  130. package/lib/module/store/restorePending.test.js +62 -0
  131. package/lib/module/store/restorePending.test.js.map +1 -0
  132. package/lib/module/store/suggestions.js +49 -0
  133. package/lib/module/store/suggestions.js.map +1 -0
  134. package/lib/module/store/suggestions.test.js +57 -0
  135. package/lib/module/store/suggestions.test.js.map +1 -0
  136. package/lib/module/store/systemLine.test.js +76 -0
  137. package/lib/module/store/systemLine.test.js.map +1 -0
  138. package/lib/module/store/ticketPrompt.js +13 -0
  139. package/lib/module/store/ticketPrompt.js.map +1 -0
  140. package/lib/module/store/transcriptPersistence.js +92 -3
  141. package/lib/module/store/transcriptPersistence.js.map +1 -1
  142. package/lib/module/store/transcriptPersistence.test.js +148 -0
  143. package/lib/module/store/transcriptPersistence.test.js.map +1 -1
  144. package/lib/module/store/turnRendering.test.js +150 -0
  145. package/lib/module/store/turnRendering.test.js.map +1 -0
  146. package/lib/module/store/useAskBuoy.js +275 -53
  147. package/lib/module/store/useAskBuoy.js.map +1 -1
  148. package/lib/module/sync/askBuoySyncAdapter.js +25 -4
  149. package/lib/module/sync/askBuoySyncAdapter.js.map +1 -1
  150. package/lib/typescript/blocks/BlockRenderer.d.ts.map +1 -1
  151. package/lib/typescript/blocks/DataBlocks.d.ts +11 -0
  152. package/lib/typescript/blocks/DataBlocks.d.ts.map +1 -1
  153. package/lib/typescript/blocks/ExtraBlocks.d.ts +18 -2
  154. package/lib/typescript/blocks/ExtraBlocks.d.ts.map +1 -1
  155. package/lib/typescript/blocks/MediaBlocks.d.ts.map +1 -1
  156. package/lib/typescript/blocks/blockText.d.ts +14 -0
  157. package/lib/typescript/blocks/blockText.d.ts.map +1 -0
  158. package/lib/typescript/blocks/blockText.test.d.ts +2 -0
  159. package/lib/typescript/blocks/blockText.test.d.ts.map +1 -0
  160. package/lib/typescript/blocks/primitives.d.ts +16 -1
  161. package/lib/typescript/blocks/primitives.d.ts.map +1 -1
  162. package/lib/typescript/components/ApprovalCard.d.ts.map +1 -1
  163. package/lib/typescript/components/AskBuoyHeader.d.ts +31 -1
  164. package/lib/typescript/components/AskBuoyHeader.d.ts.map +1 -1
  165. package/lib/typescript/components/AskBuoyModal.d.ts.map +1 -1
  166. package/lib/typescript/components/AskBuoySettings.d.ts.map +1 -1
  167. package/lib/typescript/components/Composer.d.ts +16 -1
  168. package/lib/typescript/components/Composer.d.ts.map +1 -1
  169. package/lib/typescript/components/EmptyState.d.ts +5 -1
  170. package/lib/typescript/components/EmptyState.d.ts.map +1 -1
  171. package/lib/typescript/components/KeyboardPad.d.ts +33 -0
  172. package/lib/typescript/components/KeyboardPad.d.ts.map +1 -0
  173. package/lib/typescript/components/Markdown.d.ts +0 -10
  174. package/lib/typescript/components/Markdown.d.ts.map +1 -1
  175. package/lib/typescript/components/MessageList.d.ts +7 -2
  176. package/lib/typescript/components/MessageList.d.ts.map +1 -1
  177. package/lib/typescript/components/QueuedMessages.d.ts +10 -0
  178. package/lib/typescript/components/QueuedMessages.d.ts.map +1 -0
  179. package/lib/typescript/components/markdownParse.d.ts +49 -0
  180. package/lib/typescript/components/markdownParse.d.ts.map +1 -0
  181. package/lib/typescript/components/markdownParse.test.d.ts +2 -0
  182. package/lib/typescript/components/markdownParse.test.d.ts.map +1 -0
  183. package/lib/typescript/store/activityGroups.d.ts +29 -0
  184. package/lib/typescript/store/activityGroups.d.ts.map +1 -0
  185. package/lib/typescript/store/activityGroups.test.d.ts +2 -0
  186. package/lib/typescript/store/activityGroups.test.d.ts.map +1 -0
  187. package/lib/typescript/store/askBuoySettings.d.ts +22 -0
  188. package/lib/typescript/store/askBuoySettings.d.ts.map +1 -1
  189. package/lib/typescript/store/askBuoyStore.d.ts +142 -5
  190. package/lib/typescript/store/askBuoyStore.d.ts.map +1 -1
  191. package/lib/typescript/store/blockPlacement.d.ts +30 -0
  192. package/lib/typescript/store/blockPlacement.d.ts.map +1 -0
  193. package/lib/typescript/store/blockPlacement.test.d.ts +2 -0
  194. package/lib/typescript/store/blockPlacement.test.d.ts.map +1 -0
  195. package/lib/typescript/store/queue.test.d.ts +2 -0
  196. package/lib/typescript/store/queue.test.d.ts.map +1 -0
  197. package/lib/typescript/store/resetConversation.d.ts +38 -0
  198. package/lib/typescript/store/resetConversation.d.ts.map +1 -0
  199. package/lib/typescript/store/restorePending.test.d.ts +2 -0
  200. package/lib/typescript/store/restorePending.test.d.ts.map +1 -0
  201. package/lib/typescript/store/suggestions.d.ts +32 -0
  202. package/lib/typescript/store/suggestions.d.ts.map +1 -0
  203. package/lib/typescript/store/suggestions.test.d.ts +2 -0
  204. package/lib/typescript/store/suggestions.test.d.ts.map +1 -0
  205. package/lib/typescript/store/systemLine.test.d.ts +2 -0
  206. package/lib/typescript/store/systemLine.test.d.ts.map +1 -0
  207. package/lib/typescript/store/ticketPrompt.d.ts +11 -0
  208. package/lib/typescript/store/ticketPrompt.d.ts.map +1 -0
  209. package/lib/typescript/store/transcriptPersistence.d.ts +41 -2
  210. package/lib/typescript/store/transcriptPersistence.d.ts.map +1 -1
  211. package/lib/typescript/store/turnRendering.test.d.ts +2 -0
  212. package/lib/typescript/store/turnRendering.test.d.ts.map +1 -0
  213. package/lib/typescript/store/useAskBuoy.d.ts +23 -1
  214. package/lib/typescript/store/useAskBuoy.d.ts.map +1 -1
  215. package/lib/typescript/sync/askBuoySyncAdapter.d.ts +26 -6
  216. package/lib/typescript/sync/askBuoySyncAdapter.d.ts.map +1 -1
  217. package/package.json +9 -9
@@ -10,6 +10,10 @@ var _reactNative = require("react-native");
10
10
  var _sharedUi = require("@buoy-gg/shared-ui");
11
11
  var _agentCore = require("@buoy-gg/agent-core");
12
12
  var _BlockRenderer = require("../blocks/BlockRenderer");
13
+ var _blockText = require("../blocks/blockText");
14
+ var _activityGroups = require("../store/activityGroups");
15
+ var _blockPlacement = require("../store/blockPlacement");
16
+ var _askBuoyStore = require("../store/askBuoyStore");
13
17
  var _Markdown = require("./Markdown");
14
18
  var _ThinkingStrip = require("./ThinkingStrip");
15
19
  var _TypingDots = require("./TypingDots");
@@ -41,6 +45,21 @@ var _jsxRuntime = require("react/jsx-runtime");
41
45
  * The previous version showed the model-facing sentence cut off at two
42
46
  * lines with the result's JSON key names beside it, which was unreadable
43
47
  * AND uninformative.
48
+ * - What a READ step returned (the receipt — "10 cached queries", a store's
49
+ * key/values) lives INSIDE that step's row and shows only when the row is
50
+ * expanded. Rendered as a standalone card under the activity list it read
51
+ * as part of the answer: a question about one Pokémon produced a wall of
52
+ * ten cache entries that a non-technical tester had no way to relate to
53
+ * what they asked. The step row already says "10 items"; tapping it is the
54
+ * ask for the detail. Receipts of WRITES (a diff of what changed) are drawn
55
+ * in full — that is the thing the user must see without asking.
56
+ * - CARDS COME LAST, AFTER THE PROSE. A turn reads activity → explanation →
57
+ * the thing itself, which is the order a person would say it in. It used to
58
+ * be the other way round, and on a long turn the answer — a grid of 28 shop
59
+ * items — landed at the TOP with five paragraphs of narration underneath it,
60
+ * so the finished answer arrived before the sentence explaining it. Ask
61
+ * blocks (choice/confirm/input) stay last of all: they are the question the
62
+ * turn ends on.
44
63
  */
45
64
 
46
65
  function StatusMark({
@@ -76,6 +95,18 @@ function StatusMark({
76
95
  color: _sharedUi.night.color.onAccent
77
96
  })
78
97
  });
98
+ // A grey ring with a bar — drawn, not an icon, so the shape is distinct
99
+ // from every outcome the MODEL produced. `declined` had no case at all:
100
+ // the row rendered a blank slot, and later nobody could tell whether the
101
+ // person or the policy had stopped it.
102
+ case "declined":
103
+ case "stopped":
104
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
105
+ style: [styles.mark, styles.markNotRun],
106
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
107
+ style: styles.markNotRunBar
108
+ })
109
+ });
79
110
  }
80
111
  }
81
112
 
@@ -93,102 +124,161 @@ function statusText(call) {
93
124
  text: "Failed",
94
125
  tone: "danger"
95
126
  };
127
+ if (call.status === "declined") return {
128
+ text: "You declined",
129
+ tone: "muted"
130
+ };
131
+ if (call.status === "stopped") return {
132
+ text: "Not run — stopped",
133
+ tone: "muted"
134
+ };
96
135
  return {
97
136
  text: call.summary || "Done",
98
137
  tone: "muted"
99
138
  };
100
139
  }
140
+
141
+ /** Rows that never fold: anything that did not simply succeed. */
142
+ const isExceptional = c => c.status === "failed" || c.status === "refused" || c.status === "declined" || c.status === "stopped";
101
143
  function ActivityRow({
102
144
  call,
145
+ receipts,
103
146
  last
104
147
  }) {
105
148
  const [open, setOpen] = (0, _react.useState)(false);
106
149
  const status = statusText(call);
107
150
  const Chevron = open ? _sharedUi.ChevronDown : _sharedUi.ChevronRight;
108
151
  const failureDetail = call.status === "failed" && call.summary && call.summary !== "Failed" ? call.summary : null;
109
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
110
- testID: `buoy-ask-buoy-tool-${call.toolId}`,
111
- accessibilityRole: "button",
112
- accessibilityLabel: `${call.label}, ${status.text || "running"}`,
113
- accessibilityState: {
114
- expanded: open
115
- },
116
- onPress: () => setOpen(v => !v),
152
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
117
153
  style: [styles.row, !last && styles.rowDivider],
118
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
119
- style: styles.rowLine,
120
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
121
- style: styles.markSlot,
122
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusMark, {
123
- status: call.status
124
- })
125
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
126
- style: styles.rowTitle,
127
- children: call.label
128
- }), status.text ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
129
- style: [styles.rowStatus, status.tone === "danger" && styles.rowStatusDanger, status.tone === "warning" && styles.rowStatusWarning],
130
- numberOfLines: 1,
131
- children: status.text
132
- }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(Chevron, {
133
- size: 12,
134
- color: _sharedUi.night.color.textTertiary
154
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
155
+ testID: `buoy-ask-buoy-tool-${call.toolId}`,
156
+ accessibilityRole: "button",
157
+ accessibilityLabel: `${call.label}, ${status.text || "running"}`,
158
+ accessibilityState: {
159
+ expanded: open
160
+ },
161
+ onPress: () => setOpen(v => !v),
162
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
163
+ style: styles.rowLine,
164
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
165
+ style: styles.markSlot,
166
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusMark, {
167
+ status: call.status
168
+ })
169
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
170
+ style: styles.rowTitle,
171
+ children: call.label
172
+ }), status.text ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
173
+ style: [styles.rowStatus, status.tone === "danger" && styles.rowStatusDanger, status.tone === "warning" && styles.rowStatusWarning],
174
+ numberOfLines: 1,
175
+ children: status.text
176
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(Chevron, {
177
+ size: 12,
178
+ color: _sharedUi.night.color.textTertiary
179
+ })]
180
+ }), open && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
181
+ style: styles.rowDetail,
182
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
183
+ style: styles.rowTool,
184
+ children: (0, _agentCore.toolTitle)(call.toolId).toUpperCase()
185
+ }), call.description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
186
+ style: styles.rowDescription,
187
+ children: call.description
188
+ }) : null, failureDetail ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
189
+ style: styles.rowFailure,
190
+ children: failureDetail
191
+ }) : null]
135
192
  })]
136
- }), open && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
137
- style: styles.rowDetail,
138
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
139
- style: styles.rowTool,
140
- children: (0, _agentCore.toolTitle)(call.toolId).toUpperCase()
141
- }), call.description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
142
- style: styles.rowDescription,
143
- children: call.description
144
- }) : null, failureDetail ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
145
- style: styles.rowFailure,
146
- children: failureDetail
147
- }) : null]
193
+ }), open && receipts.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
194
+ style: styles.rowReceipts,
195
+ testID: `buoy-ask-buoy-tool-receipt-${call.toolId}`,
196
+ children: receipts.map(block => /*#__PURE__*/(0, _jsxRuntime.jsx)(_BlockRenderer.BlockRenderer, {
197
+ block: block
198
+ }, block.id))
148
199
  })]
149
200
  });
150
201
  }
151
- const COLLAPSE_AFTER = 4;
152
202
 
153
203
  /**
154
- * More than four steps collapse to one line — "Did 15 things · 5 failed" —
155
- * with the failed and refused rows pinned so the bad news is never hidden.
204
+ * Consecutive successful reads fold into one row — "Looked at Network and
205
+ * Storage · 3 reads" — expandable to the individual rows. Everything else
206
+ * (running, failed, refused, declined, stopped, any write) is always its own
207
+ * row, and a group never crosses a write. See store/activityGroups.ts for the
208
+ * rule and why the old "Did 7 things" fold was replaced.
156
209
  */
157
- function ActivityCard({
158
- calls
210
+ function ReadsGroup({
211
+ label,
212
+ count,
213
+ calls,
214
+ receipts,
215
+ last
159
216
  }) {
160
- const [expanded, setExpanded] = (0, _react.useState)(false);
161
- const running = calls.some(c => c.status === "running");
162
- const collapse = calls.length > COLLAPSE_AFTER && !expanded && !running;
163
- const failed = calls.filter(c => c.status === "failed").length;
164
- const refused = calls.filter(c => c.status === "refused").length;
165
- const shown = collapse ? calls.filter(c => c.status === "failed" || c.status === "refused") : calls;
217
+ const [open, setOpen] = (0, _react.useState)(false);
218
+ const Chevron = open ? _sharedUi.ChevronDown : _sharedUi.ChevronRight;
166
219
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
167
- style: styles.card,
168
- testID: "buoy-ask-buoy-activity",
169
- children: [collapse && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
170
- onPress: () => setExpanded(true),
220
+ style: !last && !open && styles.rowDivider,
221
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
222
+ testID: "buoy-ask-buoy-reads-group",
171
223
  accessibilityRole: "button",
172
- style: [styles.row, shown.length > 0 && styles.rowDivider],
173
- testID: "buoy-ask-buoy-activity-expand",
224
+ accessibilityLabel: `${label}, ${count} reads`,
225
+ accessibilityState: {
226
+ expanded: open
227
+ },
228
+ onPress: () => setOpen(v => !v),
229
+ style: [styles.row, open && styles.rowDivider],
174
230
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
175
231
  style: styles.rowLine,
176
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
232
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
233
+ style: styles.markSlot,
234
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusMark, {
235
+ status: "ok"
236
+ })
237
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
177
238
  style: styles.rowTitle,
178
- children: ["Did ", calls.length, " things", failed ? ` · ${failed} failed` : "", refused ? ` · ${refused} refused` : ""]
179
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
239
+ children: label
240
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
241
+ style: styles.rowStatus,
242
+ numberOfLines: 1,
243
+ children: [count, " reads"]
244
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(Chevron, {
180
245
  size: 12,
181
246
  color: _sharedUi.night.color.textTertiary
182
247
  })]
183
248
  })
184
- }), shown.map((c, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(ActivityRow, {
185
- call: c,
186
- last: i === shown.length - 1
187
- }, c.id))]
249
+ }), open && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
250
+ style: styles.groupBody,
251
+ children: calls.map((c, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(ActivityRow, {
252
+ call: c,
253
+ receipts: receipts.get(c.id) ?? [],
254
+ last: last && i === calls.length - 1
255
+ }, c.id))
256
+ })]
257
+ });
258
+ }
259
+ function ActivityCard({
260
+ calls,
261
+ receipts
262
+ }) {
263
+ const items = (0, _activityGroups.groupCalls)(calls);
264
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
265
+ style: styles.card,
266
+ testID: "buoy-ask-buoy-activity",
267
+ children: items.map((item, i) => item.kind === "reads" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ReadsGroup, {
268
+ label: item.label,
269
+ count: item.count,
270
+ calls: item.calls,
271
+ receipts: receipts,
272
+ last: i === items.length - 1
273
+ }, item.id) : /*#__PURE__*/(0, _jsxRuntime.jsx)(ActivityRow, {
274
+ call: item.call,
275
+ receipts: receipts.get(item.call.id) ?? [],
276
+ last: i === items.length - 1
277
+ }, item.call.id))
188
278
  });
189
279
  }
190
280
 
191
- /** Receipts and model blocks render between the activity card and the prose; Ask blocks last. */
281
+ /** Write receipts and model blocks render between the activity card and the prose; Ask blocks last. */
192
282
  function Blocks({
193
283
  blocks
194
284
  }) {
@@ -202,6 +292,20 @@ function Blocks({
202
292
  }, block.id))
203
293
  });
204
294
  }
295
+
296
+ /** A line from Buoy, not the model. Dim, centred, no footer, no copy pill. */
297
+ function SystemLine({
298
+ message
299
+ }) {
300
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
301
+ style: styles.systemLine,
302
+ testID: "buoy-ask-buoy-system-line",
303
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
304
+ style: styles.systemText,
305
+ children: message.text
306
+ })
307
+ });
308
+ }
205
309
  function UserMessage({
206
310
  message
207
311
  }) {
@@ -219,24 +323,43 @@ function UserMessage({
219
323
  }
220
324
  function AssistantMessage({
221
325
  message,
222
- onRetry
326
+ onRetry,
327
+ waitingOnUser
223
328
  }) {
224
329
  const {
225
330
  showThinking
226
331
  } = (0, _useAskBuoySettings.useAskBuoySettings)();
227
- const running = message.toolCalls.some(c => c.status === "running");
228
- const waiting = !!message.streaming && message.text.length === 0 && !running;
229
- const askBlocks = message.blocks.filter(b => (0, _agentCore.isAskBlock)(b.block));
230
- const showBlocks = message.blocks.filter(b => !(0, _agentCore.isAskBlock)(b.block));
332
+ /**
333
+ * The dots run for the WHOLE turn, not just the wait for the first token.
334
+ *
335
+ * They used to hide as soon as any prose arrived and while a tool call was
336
+ * running. A turn that says "pulling the catalogue up — one moment" and
337
+ * then works for another minute is the normal shape of this tool, and it
338
+ * spent that minute with nothing moving at the bottom of the chat: the
339
+ * activity card had already collapsed to "Did 16 things", so the only clue
340
+ * anything was still happening was the Stop button and the header
341
+ * subtitle. The row spinner in the activity card says which STEP is
342
+ * running; this says the TURN is.
343
+ *
344
+ * The one state it stays out of is a turn waiting on the user — an
345
+ * approval or an Ask block. Nothing is loading there.
346
+ */
347
+ const waiting = !!message.streaming && !waitingOnUser;
348
+ const {
349
+ nested,
350
+ afterText,
351
+ ask: askBlocks
352
+ } = (0, _blockPlacement.placeBlocks)(message.blocks, message.toolCalls.map(c => c.id));
231
353
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
232
354
  style: styles.assistantWrap,
233
355
  children: [message.toolCalls.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(ActivityCard, {
234
- calls: message.toolCalls
235
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(Blocks, {
236
- blocks: showBlocks
356
+ calls: message.toolCalls,
357
+ receipts: nested
237
358
  }), message.text.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Markdown.Markdown, {
238
359
  text: message.text,
239
360
  style: styles.assistantText
361
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(Blocks, {
362
+ blocks: afterText
240
363
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(Blocks, {
241
364
  blocks: askBlocks
242
365
  }), showThinking && !message.streaming && /*#__PURE__*/(0, _jsxRuntime.jsx)(_ThinkingStrip.ThinkingStrip, {
@@ -244,6 +367,45 @@ function AssistantMessage({
244
367
  }), waiting && /*#__PURE__*/(0, _jsxRuntime.jsx)(_TypingDots.TypingDots, {}), message.error && /*#__PURE__*/(0, _jsxRuntime.jsx)(ErrorNote, {
245
368
  error: message.error,
246
369
  onRetry: onRetry
370
+ }), message.stopped && !message.error && /*#__PURE__*/(0, _jsxRuntime.jsx)(StoppedNote, {
371
+ onRetry: onRetry,
372
+ changed: message.toolCalls.some(c => c.status === "ok" && !!c.effect && c.effect !== "read")
373
+ })]
374
+ });
375
+ }
376
+
377
+ /**
378
+ * A turn the user stopped.
379
+ *
380
+ * Deliberately not the error box: they pressed the button. The whole point is
381
+ * that it is quiet — a muted line, the same Try-again affordance, and none of
382
+ * the red "check your connection" advice that a cancelled fetch used to
383
+ * produce. When a write had already landed before the tap, the line says so:
384
+ * Stop cannot pull a dispatched change back out of the app, and "Stopped."
385
+ * alone read as if nothing had happened.
386
+ */
387
+ function StoppedNote({
388
+ onRetry,
389
+ changed
390
+ }) {
391
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
392
+ style: styles.stoppedBox,
393
+ testID: "buoy-ask-buoy-stopped",
394
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
395
+ style: styles.stoppedText,
396
+ children: changed ? "Stopped. Changes already made are listed above." : "Stopped."
397
+ }), onRetry && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
398
+ testID: "buoy-ask-buoy-retry",
399
+ accessibilityRole: "button",
400
+ accessibilityLabel: "Try again",
401
+ onPress: onRetry,
402
+ style: ({
403
+ pressed
404
+ }) => [styles.retry, pressed && styles.retryPressed],
405
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
406
+ style: styles.retryText,
407
+ children: "Try again"
408
+ })
247
409
  })]
248
410
  });
249
411
  }
@@ -258,7 +420,7 @@ function ErrorNote({
258
420
  error,
259
421
  onRetry
260
422
  }) {
261
- const network = /fetch failed|network|ECONN|timed? ?out|socket|abort/i.test(error);
423
+ const network = /fetch failed|network|ECONN|timed? ?out|socket/i.test(error);
262
424
  const headline = network ? "Couldn't reach your AI endpoint. Check the connection and try again." : "Something went wrong with that turn.";
263
425
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
264
426
  style: styles.errorBox,
@@ -278,13 +440,14 @@ function ErrorNote({
278
440
  }), onRetry && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
279
441
  testID: "buoy-ask-buoy-retry",
280
442
  accessibilityRole: "button",
443
+ accessibilityLabel: "Try again",
281
444
  onPress: onRetry,
282
445
  style: ({
283
446
  pressed
284
447
  }) => [styles.retry, pressed && styles.retryPressed],
285
448
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
286
449
  style: styles.retryText,
287
- children: "Retry"
450
+ children: "Try again"
288
451
  })
289
452
  })]
290
453
  })]
@@ -311,16 +474,24 @@ function transcriptToText(messages, withTrace = false) {
311
474
  lines.push(`You: ${m.text}`);
312
475
  continue;
313
476
  }
477
+ if (m.system) {
478
+ lines.push(` — ${m.text}`);
479
+ continue;
480
+ }
314
481
  if (withTrace) {
315
482
  lines.push(...(0, _traceText.traceToText)(m));
316
483
  } else {
317
484
  for (const c of m.toolCalls) {
318
- const status = c.status === "failed" || c.status === "refused" ? ` (${c.status})` : "";
485
+ const status = isExceptional(c) ? ` (${c.status})` : "";
319
486
  lines.push(` · ${c.label}${c.summary ? ` — ${c.summary}` : ""}${status}`);
320
487
  }
321
488
  }
322
489
  if (m.text) lines.push(`Ask Buoy: ${m.text}`);
490
+ // The cards, too. A finding is the thing a ticket wants and it used to be
491
+ // the one thing the paste left out.
492
+ for (const t of (0, _blockText.blocksToText)(m.blocks.map(p => p.block))) lines.push(t);
323
493
  if (m.error) lines.push(` ! ${m.error}`);
494
+ if (m.stopped && !m.error) lines.push(" ! Stopped by the user.");
324
495
  }
325
496
  return lines.join("\n");
326
497
  }
@@ -357,54 +528,79 @@ function CopyPill({
357
528
  }
358
529
 
359
530
  /**
360
- * Under a finished answer: one Copy, and nothing else.
531
+ * Under a finished answer: the turn's footer and one Copy.
532
+ *
533
+ * The footer — "48s · 6 actions · 1 declined" — is always on. Duration and
534
+ * counts used to exist only inside the opt-in thinking strip, so nothing
535
+ * separated one turn from the next and "how long did that take?" had no
536
+ * answer without a setting. See `turnFooter` for what is counted and why.
361
537
  *
362
- * It is on EVERY answer rather than only the newest, because the moment you
538
+ * Copy is on EVERY answer rather than only the newest, because the moment you
363
539
  * decide to keep a conversation is usually a few replies after the one that
364
540
  * went wrong, and scrolling to the bottom to find the button is friction at
365
- * exactly the wrong time. Every one of them copies the WHOLE chat — one button
366
- * with one meaning beats four pills the reader has to choose between.
541
+ * exactly the wrong time. It copies the WHOLE chat, and now says so: labelled
542
+ * "Copy" it read as "copy this answer", and a paste into Slack brought the
543
+ * full transcript. The text is built on press from the live store, so this
544
+ * component takes no `messages` prop and a streaming answer no longer
545
+ * re-renders every finished one.
367
546
  */
368
547
  function TranscriptActions({
369
- messages
548
+ message
370
549
  }) {
371
550
  const {
372
551
  showThinking
373
552
  } = (0, _useAskBuoySettings.useAskBuoySettings)();
374
- const getText = (0, _react.useCallback)(() => transcriptToText(messages, showThinking), [messages, showThinking]);
375
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
553
+ const getText = (0, _react.useCallback)(() => transcriptToText(_askBuoyStore.askBuoyStore.getState().messages, showThinking), [showThinking]);
554
+ const footer = (0, _activityGroups.turnFooter)(message);
555
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
376
556
  style: styles.transcriptActions,
377
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(CopyPill, {
557
+ children: [footer ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
558
+ style: styles.turnFooter,
559
+ testID: "buoy-ask-buoy-turn-footer",
560
+ children: footer
561
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(CopyPill, {
378
562
  testID: "buoy-ask-buoy-copy",
379
- label: "Copy",
563
+ label: "Copy conversation",
380
564
  getText: getText
381
- })
565
+ })]
382
566
  });
383
567
  }
384
- function Row({
568
+
569
+ /**
570
+ * Memoised: a streaming answer changes the store on every token, and every
571
+ * row re-rendered for it. Props are the message itself (a new object when it
572
+ * changes), the retry handler (only the newest row gets one) and the waiting
573
+ * flag — nothing that changes when a DIFFERENT message streams.
574
+ */
575
+ const Row = /*#__PURE__*/(0, _react.memo)(function Row({
385
576
  message,
386
- messages,
387
- onRetry
577
+ onRetry,
578
+ waitingOnUser
388
579
  }) {
389
580
  if (message.role === "user") return /*#__PURE__*/(0, _jsxRuntime.jsx)(UserMessage, {
390
581
  message: message
391
582
  });
583
+ if (message.system) return /*#__PURE__*/(0, _jsxRuntime.jsx)(SystemLine, {
584
+ message: message
585
+ });
392
586
  const finished = !message.streaming && !message.error;
393
587
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
394
588
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(AssistantMessage, {
395
589
  message: message,
396
- onRetry: onRetry
590
+ onRetry: onRetry,
591
+ waitingOnUser: waitingOnUser
397
592
  }), finished && /*#__PURE__*/(0, _jsxRuntime.jsx)(TranscriptActions, {
398
- messages: messages
593
+ message: message
399
594
  })]
400
595
  });
401
- }
596
+ });
402
597
  function MessageList({
403
598
  messages,
404
599
  ListHeaderComponent,
405
600
  ListEmptyComponent,
406
601
  ListFooterComponent,
407
- onRetry
602
+ onRetry,
603
+ waitingOnUser
408
604
  }) {
409
605
  const ref = (0, _react.useRef)(null);
410
606
  // Only auto-scroll when the user is already at the bottom. Yanking them back
@@ -466,8 +662,8 @@ function MessageList({
466
662
  item
467
663
  }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(Row, {
468
664
  message: item,
469
- messages: messages,
470
- onRetry: item.id === last?.id ? onRetry : undefined
665
+ onRetry: item.id === last?.id ? onRetry : undefined,
666
+ waitingOnUser: item.id === last?.id ? waitingOnUser : false
471
667
  }),
472
668
  onScroll: onScroll,
473
669
  onScrollBeginDrag: onScrollBeginDrag,
@@ -516,6 +712,20 @@ const styles = _reactNative.StyleSheet.create({
516
712
  fontSize: _sharedUi.night.font.row,
517
713
  lineHeight: 21
518
714
  },
715
+ // Buoy's own lines
716
+ systemLine: {
717
+ alignSelf: "stretch",
718
+ paddingHorizontal: 12,
719
+ paddingVertical: 7,
720
+ borderRadius: _sharedUi.night.radius.row,
721
+ backgroundColor: _sharedUi.night.color.fill
722
+ },
723
+ systemText: {
724
+ color: _sharedUi.night.color.textSecondary,
725
+ fontSize: _sharedUi.night.font.micro,
726
+ lineHeight: 16,
727
+ textAlign: "center"
728
+ },
519
729
  // Assistant
520
730
  assistantWrap: {
521
731
  alignItems: "stretch",
@@ -573,6 +783,16 @@ const styles = _reactNative.StyleSheet.create({
573
783
  markRefused: {
574
784
  backgroundColor: _sharedUi.night.color.warning
575
785
  },
786
+ markNotRun: {
787
+ borderWidth: 1.5,
788
+ borderColor: _sharedUi.night.color.textTertiary
789
+ },
790
+ markNotRunBar: {
791
+ width: 7,
792
+ height: 1.5,
793
+ borderRadius: 1,
794
+ backgroundColor: _sharedUi.night.color.textSecondary
795
+ },
576
796
  rowTitle: {
577
797
  flex: 1,
578
798
  color: _sharedUi.night.color.text,
@@ -611,6 +831,25 @@ const styles = _reactNative.StyleSheet.create({
611
831
  fontSize: _sharedUi.night.font.caption,
612
832
  lineHeight: 18
613
833
  },
834
+ rowReceipts: {
835
+ paddingTop: 10,
836
+ gap: 10
837
+ },
838
+ groupBody: {
839
+ paddingLeft: 14,
840
+ backgroundColor: _sharedUi.night.color.fill
841
+ },
842
+ stoppedBox: {
843
+ alignItems: "flex-start",
844
+ gap: 2,
845
+ paddingHorizontal: 12,
846
+ paddingVertical: 8
847
+ },
848
+ stoppedText: {
849
+ color: _sharedUi.night.color.textTertiary,
850
+ fontSize: _sharedUi.night.font.caption,
851
+ lineHeight: 18
852
+ },
614
853
  // Error
615
854
  errorBox: {
616
855
  flexDirection: "row",
@@ -654,8 +893,16 @@ const styles = _reactNative.StyleSheet.create({
654
893
  },
655
894
  transcriptActions: {
656
895
  flexDirection: "row",
896
+ alignItems: "center",
897
+ gap: 10,
657
898
  marginTop: 8
658
899
  },
900
+ turnFooter: {
901
+ flex: 1,
902
+ color: _sharedUi.night.color.textTertiary,
903
+ fontSize: _sharedUi.night.font.micro,
904
+ fontVariant: ["tabular-nums"]
905
+ },
659
906
  copyBtn: {
660
907
  paddingHorizontal: 10,
661
908
  paddingVertical: 5,