@opengeni/react 1.4.4 → 1.4.5-canary.37485542607001

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 (249) hide show
  1. package/README.md +17 -6
  2. package/dist/artifacts.d.ts +1 -0
  3. package/dist/artifacts.js +22 -15
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/{browser-viewer-5EEAS3IU.js → browser-viewer-CV23VIHB.js} +8 -6
  6. package/dist/{chunk-XS67YG5R.js → chunk-4O7OE7ON.js} +45 -4
  7. package/dist/chunk-4O7OE7ON.js.map +1 -0
  8. package/dist/{chunk-JHTIMBOL.js → chunk-5JSUNNQN.js} +26 -4
  9. package/dist/{chunk-JHTIMBOL.js.map → chunk-5JSUNNQN.js.map} +1 -1
  10. package/dist/{chunk-QMMCJGFI.js → chunk-6BAMPGBV.js} +6 -3
  11. package/dist/chunk-6BAMPGBV.js.map +1 -0
  12. package/dist/{chunk-I6OY3MWC.js → chunk-6FOTSVWU.js} +2 -2
  13. package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
  14. package/dist/chunk-ATIRUHBZ.js.map +1 -0
  15. package/dist/chunk-BOFEQC6K.js +162 -0
  16. package/dist/chunk-BOFEQC6K.js.map +1 -0
  17. package/dist/{chunk-2OQA3CPB.js → chunk-CESBPQP5.js} +19 -5
  18. package/dist/chunk-CESBPQP5.js.map +1 -0
  19. package/dist/{chunk-YVFRBXST.js → chunk-GMNGUPMX.js} +2 -2
  20. package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
  21. package/dist/chunk-I5EGRXXG.js.map +1 -0
  22. package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
  23. package/dist/chunk-IZV5FTJC.js +55 -0
  24. package/dist/chunk-IZV5FTJC.js.map +1 -0
  25. package/dist/chunk-JDJJMBB3.js +1883 -0
  26. package/dist/chunk-JDJJMBB3.js.map +1 -0
  27. package/dist/chunk-LHF6BVM4.js +83 -0
  28. package/dist/chunk-LHF6BVM4.js.map +1 -0
  29. package/dist/{chunk-C5BF4L26.js → chunk-LSMWZSCJ.js} +7 -7
  30. package/dist/chunk-LTKGTXUQ.js +174 -0
  31. package/dist/chunk-LTKGTXUQ.js.map +1 -0
  32. package/dist/chunk-N7S72EXK.js +25 -0
  33. package/dist/chunk-N7S72EXK.js.map +1 -0
  34. package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
  35. package/dist/chunk-PEHTX3V7.js.map +1 -0
  36. package/dist/chunk-R5R5RQQD.js +51 -0
  37. package/dist/chunk-R5R5RQQD.js.map +1 -0
  38. package/dist/{chunk-IYQCWGU5.js → chunk-RGTZ2VDR.js} +35 -62
  39. package/dist/chunk-RGTZ2VDR.js.map +1 -0
  40. package/dist/chunk-RI42H5IU.js +326 -0
  41. package/dist/chunk-RI42H5IU.js.map +1 -0
  42. package/dist/{chunk-EAOSEIXB.js → chunk-RRRS5D5T.js} +727 -1983
  43. package/dist/chunk-RRRS5D5T.js.map +1 -0
  44. package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
  45. package/dist/chunk-SHLZMAJN.js.map +1 -0
  46. package/dist/{chunk-IUHHPMAX.js → chunk-SZOCY5VG.js} +18 -87
  47. package/dist/chunk-SZOCY5VG.js.map +1 -0
  48. package/dist/{chunk-L2J3PU7A.js → chunk-VYIPNDUZ.js} +29 -100
  49. package/dist/chunk-VYIPNDUZ.js.map +1 -0
  50. package/dist/{chunk-G6YRCIYJ.js → chunk-YK6QMGVL.js} +55 -60
  51. package/dist/chunk-YK6QMGVL.js.map +1 -0
  52. package/dist/chunk-ZIB4H5LN.js +101 -0
  53. package/dist/chunk-ZIB4H5LN.js.map +1 -0
  54. package/dist/client.d.ts +9 -3
  55. package/dist/clipboard.js +1 -1
  56. package/dist/components/browser-viewer.d.ts +6 -0
  57. package/dist/components/composer.d.ts +3 -69
  58. package/dist/components/human-input-form.d.ts +3 -32
  59. package/dist/components/markdown.d.ts +8 -0
  60. package/dist/components/model-policy-picker.d.ts +4 -37
  61. package/dist/components/opper-mark.d.ts +7 -0
  62. package/dist/components/provider-recovery-notice.d.ts +15 -0
  63. package/dist/components/remark-soft-line-breaks.d.ts +38 -0
  64. package/dist/components/sandbox-workspace.d.ts +13 -0
  65. package/dist/components/session-chrome.d.ts +5 -3
  66. package/dist/components/session-conversation.d.ts +15 -3
  67. package/dist/composer-messages.d.ts +74 -0
  68. package/dist/composer-messages.js +9 -0
  69. package/dist/composer.js +15 -10
  70. package/dist/{computer-viewer-ELNLZGHI.js → computer-viewer-K4CKYRX6.js} +4 -4
  71. package/dist/connect-setup.d.ts +2 -0
  72. package/dist/connect.d.ts +1 -1
  73. package/dist/connect.js +21 -6
  74. package/dist/connect.js.map +1 -1
  75. package/dist/{embedded-voice-NIL4RDX3.js → embedded-voice-K7SOZVVZ.js} +6 -4
  76. package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-K7SOZVVZ.js.map} +1 -1
  77. package/dist/hooks/shared-machines-list.d.ts +4 -0
  78. package/dist/hooks/use-client-config-flags.d.ts +6 -0
  79. package/dist/hooks/use-goal.d.ts +2 -1
  80. package/dist/hooks/use-new-session-draft.d.ts +74 -0
  81. package/dist/human-input-model.d.ts +40 -0
  82. package/dist/index.d.ts +4 -1
  83. package/dist/index.js +116 -73
  84. package/dist/index.js.map +1 -1
  85. package/dist/interaction.js +5 -5
  86. package/dist/{latest-question-2POFPYNB.js → latest-question-27HGRPVF.js} +3 -3
  87. package/dist/lib/clipboard.d.ts +7 -0
  88. package/dist/lib/provider-recovery.d.ts +49 -0
  89. package/dist/lib/transient-retry.d.ts +25 -0
  90. package/dist/machines.js +2 -2
  91. package/dist/model-mark-paths.d.ts +34 -0
  92. package/dist/model-mark-paths.js +16 -0
  93. package/dist/model-picker-model.d.ts +111 -0
  94. package/dist/model-picker-model.js +25 -0
  95. package/dist/model-picker-model.js.map +1 -0
  96. package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-I5KPC3D6.js} +6 -4
  97. package/dist/model-policy-picker-menu-I5KPC3D6.js.map +1 -0
  98. package/dist/model-policy.d.ts +10 -0
  99. package/dist/model-policy.js +5 -1
  100. package/dist/new-session-draft.d.ts +7 -0
  101. package/dist/new-session-draft.js +639 -0
  102. package/dist/new-session-draft.js.map +1 -0
  103. package/dist/organization-model.d.ts +32 -0
  104. package/dist/organization-model.js +65 -0
  105. package/dist/organization-model.js.map +1 -0
  106. package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
  107. package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
  108. package/dist/queue-presentation-model.d.ts +72 -0
  109. package/dist/queue-presentation-model.js +38 -0
  110. package/dist/queue-presentation-model.js.map +1 -0
  111. package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
  112. package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
  113. package/dist/realtime.js +5 -3
  114. package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-2DGDO6NI.js} +5 -5
  115. package/dist/sandbox-files-2DGDO6NI.js.map +1 -0
  116. package/dist/sandbox-label-model.d.ts +23 -0
  117. package/dist/sandbox-label-model.js +28 -0
  118. package/dist/sandbox-label-model.js.map +1 -0
  119. package/dist/session-agents-model.d.ts +10 -0
  120. package/dist/session-agents-model.js +25 -0
  121. package/dist/session-agents-model.js.map +1 -0
  122. package/dist/session-attention-model.d.ts +8 -0
  123. package/dist/session-attention-model.js +46 -0
  124. package/dist/session-attention-model.js.map +1 -0
  125. package/dist/session-feedback-model.d.ts +20 -0
  126. package/dist/session-feedback-model.js +56 -0
  127. package/dist/session-feedback-model.js.map +1 -0
  128. package/dist/session-list-model.d.ts +119 -0
  129. package/dist/session-list-model.js +244 -0
  130. package/dist/session-list-model.js.map +1 -0
  131. package/dist/session-status-model.d.ts +19 -0
  132. package/dist/session-ui.js +26 -17
  133. package/dist/session-ui.js.map +1 -1
  134. package/dist/session.js +8 -6
  135. package/dist/testing/session-scenarios.d.ts +10 -0
  136. package/dist/testing/session-scenarios.js +303 -0
  137. package/dist/testing/session-scenarios.js.map +1 -0
  138. package/dist/timeline/genie-copy.d.ts +3 -0
  139. package/dist/timeline/genie-loading.d.ts +11 -0
  140. package/dist/timeline/index.d.ts +1 -1
  141. package/dist/timeline/notice-presentation.d.ts +7 -0
  142. package/dist/timeline/patch-apply-command.d.ts +18 -0
  143. package/dist/timeline/platform-activity-presentation.d.ts +7 -0
  144. package/dist/timeline/presented-image.d.ts +6 -1
  145. package/dist/timeline/question-nav-model.d.ts +23 -0
  146. package/dist/timeline/registry.d.ts +10 -1
  147. package/dist/timeline/retained-loaders.d.ts +1 -1
  148. package/dist/timeline/tool-presentation.d.ts +172 -0
  149. package/dist/timeline/turn-summary-model.d.ts +90 -0
  150. package/dist/timeline/turn-summary.d.ts +4 -74
  151. package/dist/timeline/work-presentation.d.ts +42 -0
  152. package/dist/timeline-model.d.ts +24 -0
  153. package/dist/timeline-model.js +152 -0
  154. package/dist/timeline-model.js.map +1 -0
  155. package/package.json +55 -3
  156. package/src/artifacts.ts +1 -0
  157. package/src/client.ts +9 -3
  158. package/src/components/browser-viewer.tsx +23 -2
  159. package/src/components/claude-mark.tsx +2 -1
  160. package/src/components/composer.tsx +8 -146
  161. package/src/components/grok-mark.tsx +2 -1
  162. package/src/components/human-input-form.tsx +13 -124
  163. package/src/components/markdown.tsx +19 -1
  164. package/src/components/message-timeline.tsx +45 -99
  165. package/src/components/model-mark.tsx +2 -1
  166. package/src/components/model-policy-picker-menu.tsx +30 -85
  167. package/src/components/model-policy-picker.tsx +20 -150
  168. package/src/components/opper-mark.tsx +18 -0
  169. package/src/components/provider-recovery-notice.tsx +41 -0
  170. package/src/components/queue-surface-implementation.tsx +20 -349
  171. package/src/components/remark-soft-line-breaks.ts +166 -0
  172. package/src/components/sandbox-workspace.tsx +17 -1
  173. package/src/components/session-chrome.tsx +114 -99
  174. package/src/components/session-conversation.tsx +55 -6
  175. package/src/components/session-status.tsx +17 -16
  176. package/src/composer-messages.ts +150 -0
  177. package/src/connect-setup.tsx +45 -11
  178. package/src/connect.ts +1 -1
  179. package/src/hooks/shared-machines-list.ts +34 -1
  180. package/src/hooks/use-client-config-flags.ts +8 -0
  181. package/src/hooks/use-goal.ts +66 -10
  182. package/src/hooks/use-new-session-draft.ts +813 -0
  183. package/src/human-input-model.ts +147 -0
  184. package/src/index.ts +16 -0
  185. package/src/lib/clipboard.ts +29 -0
  186. package/src/lib/format.ts +7 -0
  187. package/src/lib/provider-recovery.ts +202 -0
  188. package/src/lib/transient-retry.ts +99 -0
  189. package/src/model-mark-paths.ts +53 -0
  190. package/src/model-picker-model.ts +330 -0
  191. package/src/model-policy.ts +75 -5
  192. package/src/new-session-draft.ts +22 -0
  193. package/src/organization-model.ts +118 -0
  194. package/src/queue-presentation-model.ts +464 -0
  195. package/src/sandbox-label-model.ts +41 -0
  196. package/src/session-agents-model.ts +33 -0
  197. package/src/session-attention-model.ts +59 -0
  198. package/src/session-feedback-model.ts +95 -0
  199. package/src/session-list-model.ts +377 -0
  200. package/src/session-status-model.ts +62 -0
  201. package/src/testing/session-scenarios.ts +326 -0
  202. package/src/timeline/activity-rail.tsx +54 -24
  203. package/src/timeline/genie-copy.ts +19 -0
  204. package/src/timeline/genie-loading.tsx +15 -19
  205. package/src/timeline/index.ts +1 -0
  206. package/src/timeline/notice-presentation.ts +22 -0
  207. package/src/timeline/patch-apply-command.tsx +135 -0
  208. package/src/timeline/platform-activity-presentation.ts +122 -0
  209. package/src/timeline/platform-activity-row.tsx +6 -96
  210. package/src/timeline/presented-image.ts +16 -2
  211. package/src/timeline/projection.ts +12 -1
  212. package/src/timeline/question-nav-model.ts +28 -0
  213. package/src/timeline/registry.ts +11 -1
  214. package/src/timeline/retained-loaders.ts +9 -4
  215. package/src/timeline/rolling-activity.tsx +7 -19
  216. package/src/timeline/tool-presentation.ts +1316 -0
  217. package/src/timeline/tool-renderers.tsx +254 -1024
  218. package/src/timeline/turn-summary-model.ts +338 -0
  219. package/src/timeline/turn-summary.tsx +21 -326
  220. package/src/timeline/work-presentation.ts +139 -0
  221. package/src/timeline-model.ts +108 -0
  222. package/styles/compiled.css +17 -4
  223. package/styles/connect.css +4 -0
  224. package/styles/responsive.css +11 -2
  225. package/dist/chunk-2OQA3CPB.js.map +0 -1
  226. package/dist/chunk-CVYCLFMH.js.map +0 -1
  227. package/dist/chunk-EAOSEIXB.js.map +0 -1
  228. package/dist/chunk-G6YRCIYJ.js.map +0 -1
  229. package/dist/chunk-HXYOSSQO.js +0 -117
  230. package/dist/chunk-HXYOSSQO.js.map +0 -1
  231. package/dist/chunk-IUHHPMAX.js.map +0 -1
  232. package/dist/chunk-IYQCWGU5.js.map +0 -1
  233. package/dist/chunk-JH7V627O.js.map +0 -1
  234. package/dist/chunk-L2J3PU7A.js.map +0 -1
  235. package/dist/chunk-LVS3MH5T.js.map +0 -1
  236. package/dist/chunk-PHBFJCEU.js.map +0 -1
  237. package/dist/chunk-QMMCJGFI.js.map +0 -1
  238. package/dist/chunk-XS67YG5R.js.map +0 -1
  239. package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
  240. package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
  241. /package/dist/{browser-viewer-5EEAS3IU.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
  242. /package/dist/{chunk-I6OY3MWC.js.map → chunk-6FOTSVWU.js.map} +0 -0
  243. /package/dist/{chunk-YVFRBXST.js.map → chunk-GMNGUPMX.js.map} +0 -0
  244. /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
  245. /package/dist/{chunk-C5BF4L26.js.map → chunk-LSMWZSCJ.js.map} +0 -0
  246. /package/dist/{computer-viewer-ELNLZGHI.js.map → composer-messages.js.map} +0 -0
  247. /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
  248. /package/dist/{latest-question-2POFPYNB.js.map → latest-question-27HGRPVF.js.map} +0 -0
  249. /package/dist/{sandbox-files-34CJJNOY.js.map → model-mark-paths.js.map} +0 -0
@@ -4,11 +4,7 @@ import type { ToolReviewStatus } from "@opengeni/sdk";
4
4
  import { KnowledgeReceiptRow } from "./knowledge-receipt";
5
5
  import { defaultUrlTransform } from "react-markdown";
6
6
  import { isRetainedImageContentType, useRetainedImageObjectUrl } from "./retained-image";
7
- import {
8
- parseSandboxFileArtifactReceipt,
9
- type GitFileDiff,
10
- type RetainedArtifactReference,
11
- } from "@opengeni/sdk";
7
+ import { parseSandboxFileArtifactReceipt, type RetainedArtifactReference } from "@opengeni/sdk";
12
8
  import {
13
9
  BoxIcon,
14
10
  BrainCircuitIcon,
@@ -39,28 +35,18 @@ import {
39
35
  TerminalIcon,
40
36
  VideoIcon,
41
37
  WrenchIcon,
38
+ type LucideIcon,
42
39
  } from "lucide-react";
43
40
  import { useContext, useState, type ReactNode } from "react";
44
- import { formatBytes, stringifyPayload, tryParseJson } from "../lib/format";
41
+ import { formatBytes, tryParseJson } from "../lib/format";
45
42
  import { useTimelineComputeLabel } from "./compute-label";
46
43
  import {
47
- applyPatchOpsFromToolItem,
48
- controlCaret,
49
- execTruncated,
50
44
  generatedImageReceipt,
51
- isExecSessionLostBanner,
52
- looksBinary,
53
45
  mediaPreviewFact,
54
- parseExecBannerSessionId,
55
46
  parseToolArgs,
56
47
  retainedScreenshotMetadata,
57
- sandboxCommandExitCode,
58
- stripExecBanner,
59
- tailPeek,
60
48
  unwrapMcpOutput,
61
- v4aToGitFileDiff,
62
49
  screenshotDataUrl,
63
- type ApplyPatchOperation,
64
50
  } from "./parsers";
65
51
  import {
66
52
  createToolRegistry,
@@ -81,16 +67,37 @@ import {
81
67
  type DisclosureChip,
82
68
  } from "./shared";
83
69
  import { RawPatch, ToolDiff } from "./tool-diff";
70
+ import {
71
+ applyPatchPresentation,
72
+ askPresentation,
73
+ execPresentation,
74
+ genericToolIconKind,
75
+ genericToolPresentation,
76
+ parseDisclosedTools,
77
+ parseSearchHits,
78
+ pathBasename,
79
+ pathDirname,
80
+ runOnPresentation,
81
+ toolSearchPreview,
82
+ toolSearchQuery,
83
+ truncatePreview,
84
+ webSearchPresentation,
85
+ writeStdinPresentation,
86
+ type ToolBody,
87
+ type ToolIconKind,
88
+ type ToolPreview,
89
+ type ToolRowPresentation,
90
+ type WebSearchResult,
91
+ } from "./tool-presentation";
92
+ import { isPatchFilename, PatchApplyCommand } from "./patch-apply-command";
84
93
  import { mcpToolLeaf, toolDisplayName } from "./tool-display-name";
85
94
  import { useOpenGeniLinkResolver } from "../components/open-geni-links";
86
95
 
87
96
  /* ----------------------------------------------------------------------------
88
97
  Per-tool renderers
89
-
90
98
  Each renderer takes one projected `ToolCallItem` and returns an `ActivityDisclosure`
91
99
  tuned for that tool's real wire shape. The defaults below populate the
92
100
  registry; the mapping is registered at the bottom of the file.
93
-
94
101
  Restraint is the rule: compact title + one quiet preview, secondary detail
95
102
  only on expand. No loud right-side badges — at most a single settle chip.
96
103
  -------------------------------------------------------------------------- */
@@ -115,218 +122,6 @@ function RunningPreview({ children }: { children: ReactNode }) {
115
122
  );
116
123
  }
117
124
 
118
- /** Prefix a collapsed preview with the host-supplied active compute label. */
119
- function withComputePreview(label: string | null, preview: string): string {
120
- if (!label) {
121
- return preview;
122
- }
123
- return `on ${label} · ${preview}`;
124
- }
125
-
126
- /* ---- exec_command ---------------------------------------------------------- */
127
-
128
- function ExecRenderer({ item }: ToolRendererProps) {
129
- const args = parseToolArgs(item.arguments);
130
- const cmd = typeof args.cmd === "string" ? args.cmd : "";
131
- const workdir = typeof args.workdir === "string" ? args.workdir : null;
132
- const running = item.status === "running";
133
- const out = item.output;
134
- const title = `$ ${cmd}`;
135
- const computeLabel = useTimelineComputeLabel();
136
-
137
- // No output event ever arrived (item.output stays undefined from creation):
138
- // the turn failed before the output insert — most likely a NUL byte in the
139
- // command output prevented storage. Surface the specific explanation.
140
- // (Cancelled items bypass this: a cancellation is not a NUL-storage failure.)
141
- if (item.status === "failed" && out === undefined) {
142
- return (
143
- <ActivityDisclosure
144
- icon={<TerminalIcon className={ICON_SIZE} />}
145
- iconTone="failed"
146
- title={title}
147
- titleMono
148
- chip={{ tone: "bad", text: "failed" }}
149
- preview={withComputePreview(computeLabel, "output lost — NUL byte could not be stored")}
150
- >
151
- <BodyNote tone="error">
152
- output contained a NUL byte and could not be stored; the turn failed on this tool&apos;s
153
- output insert — no output event ever arrived.
154
- </BodyNote>
155
- </ActivityDisclosure>
156
- );
157
- }
158
-
159
- // An output event arrived but the tool still failed (error:true / MCP isError)
160
- // and the output is empty — show a generic failure rather than claiming NUL.
161
- // (Cancelled items bypass this: a cancellation is not a tool-call failure.)
162
- if (item.status === "failed" && (out == null || out === "")) {
163
- return (
164
- <ActivityDisclosure
165
- icon={<TerminalIcon className={ICON_SIZE} />}
166
- iconTone="failed"
167
- title={title}
168
- titleMono
169
- chip={{ tone: "bad", text: "failed" }}
170
- preview={withComputePreview(computeLabel, "tool call failed")}
171
- >
172
- <BodyNote tone="error">the tool call failed with no output.</BodyNote>
173
- </ActivityDisclosure>
174
- );
175
- }
176
-
177
- if (running) {
178
- const streamed = typeof out === "string" ? stripExecBanner(out) : "";
179
- const runningPreview = streamed ? `${streamed.split("\n").length} lines` : "running…";
180
- return (
181
- <ActivityDisclosure
182
- icon={<TerminalIcon className={ICON_SIZE} />}
183
- iconTone="running"
184
- title={title}
185
- titleMono
186
- running
187
- preview={
188
- <RunningPreview>{withComputePreview(computeLabel, runningPreview)}</RunningPreview>
189
- }
190
- >
191
- {/* The row title is already `$ ${cmd}`; the TermBlock header drops the
192
- command (command={null}) so it never repeats above the output. */}
193
- <TermBlock command={null} workdir={workdir} output={streamed} live />
194
- </ActivityDisclosure>
195
- );
196
- }
197
-
198
- const text = typeof out === "string" ? out : stringifyPayload(out);
199
- const stripped = stripExecBanner(text);
200
- const bgSession = parseExecBannerSessionId(text);
201
- const exitCode = sandboxCommandExitCode(text);
202
- const binary = looksBinary(stripped);
203
-
204
- // Color is spent on the exception only: a clean exit (0) earns NO chip — the
205
- // absence of a red token is the success signal. Background sessions surface a
206
- // muted id; a non-zero exit is the one red token.
207
- let chip: DisclosureChip | undefined;
208
- let iconTone: "accent" | "failed" | "muted" = "muted";
209
- if (bgSession != null) {
210
- chip = { tone: "muted", text: `session ${bgSession}` };
211
- } else if (exitCode != null && exitCode !== 0) {
212
- chip = { tone: "bad", text: `exit ${exitCode}` };
213
- iconTone = "failed";
214
- }
215
-
216
- const peek = binary ? "binary output" : tailPeek(stripped) || "(no output)";
217
- const truncated = execTruncated(text);
218
- const preview = withComputePreview(computeLabel, truncated ? `⋯ truncated · ${peek}` : peek);
219
- // Hand TermBlock the FULL stripped output; it owns the tail/show-more slicing.
220
- const body = binary ? "(binary output suppressed)" : stripped;
221
-
222
- return (
223
- <ActivityDisclosure
224
- icon={<TerminalIcon className={ICON_SIZE} />}
225
- iconTone={iconTone}
226
- title={title}
227
- titleMono
228
- {...(chip ? { chip } : {})}
229
- failed={item.status === "failed"}
230
- cancelled={item.status === "cancelled"}
231
- preview={preview}
232
- >
233
- <TermBlock
234
- command={null}
235
- workdir={workdir}
236
- output={body}
237
- failed={item.status === "failed" || (exitCode != null && exitCode !== 0)}
238
- />
239
- {bgSession != null ? (
240
- <BodyNote>↳ session {bgSession} — a later write_stdin can target this PTY.</BodyNote>
241
- ) : null}
242
- </ActivityDisclosure>
243
- );
244
- }
245
-
246
- /* ---- write_stdin ----------------------------------------------------------- */
247
-
248
- function WriteStdinRenderer({ item }: ToolRendererProps) {
249
- const args = parseToolArgs(item.arguments);
250
- const sessionId =
251
- typeof args.session_id === "string" || typeof args.session_id === "number"
252
- ? args.session_id
253
- : undefined;
254
- const running = item.status === "running";
255
- const text = typeof item.output === "string" ? item.output : stringifyPayload(item.output);
256
- const lost = isExecSessionLostBanner(text);
257
- const keys = controlCaret(typeof args.chars === "string" ? args.chars : "");
258
- const exitCode = sandboxCommandExitCode(text);
259
- const stripped = stripExecBanner(text);
260
-
261
- if (running) {
262
- return (
263
- <ActivityDisclosure
264
- icon={<KeyboardIcon className={ICON_SIZE} />}
265
- iconTone="running"
266
- title={`session ${sessionId} ← ${keys || "∅"}`}
267
- titleMono
268
- running
269
- preview={<RunningPreview>sending…</RunningPreview>}
270
- >
271
- <BodyNote>sending input to session {sessionId}…</BodyNote>
272
- </ActivityDisclosure>
273
- );
274
- }
275
-
276
- // Success (exit 0 or a quiet ack) earns no chip; only a lost PTY / non-zero
277
- // exit gets the one red token.
278
- let chip: DisclosureChip | undefined;
279
- if (lost) {
280
- chip = { tone: "bad", text: "lost" };
281
- } else if (exitCode != null && exitCode !== 0) {
282
- chip = { tone: "bad", text: `exit ${exitCode}` };
283
- }
284
-
285
- return (
286
- <ActivityDisclosure
287
- icon={<KeyboardIcon className={ICON_SIZE} />}
288
- iconTone={lost ? "failed" : "muted"}
289
- title={`session ${sessionId} ← ${keys || "∅"}`}
290
- titleMono
291
- {...(chip ? { chip } : {})}
292
- failed={item.status === "failed"}
293
- cancelled={item.status === "cancelled"}
294
- preview={lost ? `session ${sessionId} PTY vanished` : tailPeek(stripped) || "sent"}
295
- >
296
- {lost ? (
297
- <BodyNote tone="error">{stripped || text}</BodyNote>
298
- ) : (
299
- <TermBlock command={`write_stdin → session ${sessionId}`} output={stripped} />
300
- )}
301
- </ActivityDisclosure>
302
- );
303
- }
304
-
305
- /* ---- apply_patch ----------------------------------------------------------- */
306
-
307
- function verbForOp(op: ApplyPatchOperation | undefined): string {
308
- if (!op) {
309
- return "Edited";
310
- }
311
- return op.type === "create_file"
312
- ? "Created"
313
- : op.type === "delete_file"
314
- ? "Deleted"
315
- : op.moveTo
316
- ? "Renamed"
317
- : "Edited";
318
- }
319
-
320
- function basename(path: string): string {
321
- const parts = path.split("/").filter(Boolean);
322
- return parts.length ? parts[parts.length - 1]! : path;
323
- }
324
-
325
- function dirname(path: string): string {
326
- const idx = path.lastIndexOf("/");
327
- return idx >= 0 ? path.slice(0, idx + 1) : "";
328
- }
329
-
330
125
  /**
331
126
  * The collapsed-row path preview. Diff magnitude is rendered as a SINGLE muted
332
127
  * "+N −M" glyph pair — the saturated add/del green/red is reserved exclusively
@@ -345,8 +140,8 @@ function PathPreview({
345
140
  return (
346
141
  <span className="inline-flex items-center gap-2 truncate font-og-mono">
347
142
  <span className="truncate">
348
- <span className="text-og-fg-subtle">{dirname(path)}</span>
349
- <span className="text-og-fg-muted">{basename(path)}</span>
143
+ <span className="text-og-fg-subtle">{pathDirname(path)}</span>
144
+ <span className="text-og-fg-muted">{pathBasename(path)}</span>
350
145
  </span>
351
146
  {add != null || del != null ? (
352
147
  <span className="shrink-0 text-og-fg-subtle">
@@ -359,162 +154,233 @@ function PathPreview({
359
154
  );
360
155
  }
361
156
 
362
- function ApplyPatchRenderer({ item }: ToolRendererProps) {
363
- const ops = applyPatchOpsFromToolItem(item);
364
- const failed = item.status === "failed";
365
- const cancelled = item.status === "cancelled";
366
- const running = item.status === "running";
367
- const firstOp = ops[0];
368
-
369
- if (running) {
370
- // Show the patch structure from the arguments (available immediately on
371
- // creation), but mark the row clearly as in-progress — not applied yet.
372
- const fileCount = ops.length;
373
- const titleVerb = firstOp ? `Applying ${basename(firstOp.path)}` : "Applying patch";
374
- return (
375
- <ActivityDisclosure
376
- icon={<FileDiffIcon className={ICON_SIZE} />}
377
- iconTone="running"
378
- title={fileCount > 1 ? `Applying ${fileCount} files` : titleVerb}
379
- running
380
- preview={
381
- <RunningPreview>
382
- {fileCount > 1 ? `${fileCount} files` : firstOp ? firstOp.path : "applying…"}
383
- </RunningPreview>
384
- }
385
- >
386
- {ops.map((op, index) => {
387
- const file = safeParseOp(op);
388
- const key = `${op.type}:${op.path}:${index}`;
389
- return file ? (
390
- <ToolDiff key={key} files={[file]} />
391
- ) : (
392
- <div key={key}>
393
- <p className="mb-1 font-og-mono text-og-xs text-og-fg-muted">{op.path}</p>
394
- <RawPatch diff={op.diff ?? ""} />
395
- </div>
396
- );
397
- })}
398
- </ActivityDisclosure>
399
- );
400
- }
157
+ /* ---- shared-model rows ------------------------------------------------------
158
+ exec_command, write_stdin, apply_patch, web search, request_human_input,
159
+ run_on and the generic fallback draw their row from the renderer-neutral
160
+ presentation model in ./tool-presentation, which non-DOM renderers share.
161
+ -------------------------------------------------------------------------- */
401
162
 
402
- if (failed) {
403
- return (
404
- <ActivityDisclosure
405
- icon={<FileDiffIcon className={ICON_SIZE} />}
406
- iconTone="failed"
407
- title={firstOp ? `${verbForOp(firstOp)} ${basename(firstOp.path)}` : "apply_patch"}
408
- chip={{ tone: "bad", text: "failed" }}
409
- preview={typeof item.output === "string" ? item.output : "patch failed"}
410
- >
411
- <PayloadBlock label="Error" value={item.output} failed />
412
- </ActivityDisclosure>
413
- );
163
+ /**
164
+ * The icon for a presentation kind, looked up when a row renders. A module-level
165
+ * table would capture the icons at load time; in a bundle where this module and
166
+ * the icon library's chunk import each other, an icon can still be undefined
167
+ * then, and the row would render an undefined component (React #130).
168
+ */
169
+ function toolIcon(kind: ToolIconKind): LucideIcon {
170
+ switch (kind) {
171
+ case "terminal":
172
+ return TerminalIcon;
173
+ case "keyboard":
174
+ return KeyboardIcon;
175
+ case "file-diff":
176
+ return FileDiffIcon;
177
+ case "search":
178
+ return SearchIcon;
179
+ case "question":
180
+ return MessageCircleQuestionIcon;
181
+ case "target":
182
+ return TargetIcon;
183
+ case "brain":
184
+ return BrainCircuitIcon;
185
+ case "sessions":
186
+ return MessagesSquareIcon;
187
+ case "server":
188
+ return ServerIcon;
189
+ case "server-cog":
190
+ return ServerCogIcon;
191
+ case "calendar":
192
+ return CalendarClockIcon;
193
+ case "panels":
194
+ return PanelsTopLeftIcon;
195
+ case "share":
196
+ return Share2Icon;
197
+ case "message":
198
+ return MessageSquareIcon;
199
+ case "git":
200
+ return FolderGitIcon;
201
+ case "box":
202
+ return BoxIcon;
203
+ case "key":
204
+ return KeyRoundIcon;
205
+ case "file-search":
206
+ return FileSearchIcon;
207
+ case "package-search":
208
+ return PackageSearchIcon;
209
+ case "plug":
210
+ return PlugIcon;
211
+ case "wrench":
212
+ return WrenchIcon;
414
213
  }
214
+ }
415
215
 
416
- // multi-file edit — magnitude stays a single muted glyph; the per-file
417
- // green/red lives only inside the expanded DiffView gutter.
418
- if (ops.length > 1) {
419
- // Parse every op: successfully parsed ones go into ToolDiff; malformed ops
420
- // fall back to a RawPatch display (mirroring the single-op fallback path).
421
- // The count in the title/preview equals ops.length so it is always truthful
422
- // regardless of how many ops parsed successfully.
423
- const parsed = ops.map((op) => safeParseOp(op));
424
- const goodFiles = parsed.filter((f): f is GitFileDiff => f !== null);
425
- const add = goodFiles.reduce((n, f) => n + f.additions, 0);
426
- const del = goodFiles.reduce((n, f) => n + f.deletions, 0);
427
- return (
428
- <ActivityDisclosure
429
- icon={<FileDiffIcon className={ICON_SIZE} />}
430
- iconTone="accent"
431
- title={`Edited ${ops.length} files`}
432
- cancelled={cancelled}
433
- preview={
434
- <span className="inline-flex items-center gap-2 font-og-mono">
435
- <span className="text-og-fg-muted">{ops.length} files</span>
436
- <span className="text-og-fg-subtle">
437
- +{add} −{del}
438
- </span>
216
+ function presentedPreviewNode(preview: ToolPreview): ReactNode {
217
+ switch (preview.kind) {
218
+ case "text":
219
+ return preview.running ? <RunningPreview>{preview.text}</RunningPreview> : preview.text;
220
+ case "path":
221
+ return <PathPreview path={preview.path} add={preview.add} del={preview.del} />;
222
+ case "files":
223
+ return (
224
+ <span className="inline-flex items-center gap-2 font-og-mono">
225
+ <span className="text-og-fg-muted">{preview.count} files</span>
226
+ <span className="text-og-fg-subtle">
227
+ +{preview.add} −{preview.del}
439
228
  </span>
440
- }
441
- >
442
- {ops.map((op, index) => {
443
- const file = parsed[index];
444
- const key = `${op.type}:${op.path}:${index}`;
445
- return file ? (
446
- <ToolDiff key={key} files={[file]} />
447
- ) : (
448
- <div key={key}>
449
- <p className="mb-1 font-og-mono text-og-xs text-og-fg-muted">{op.path}</p>
450
- <RawPatch diff={op.diff ?? ""} />
451
- </div>
452
- );
453
- })}
454
- </ActivityDisclosure>
455
- );
229
+ </span>
230
+ );
231
+ case "malformed":
232
+ return (
233
+ <span className="inline-flex items-center gap-2 font-og-mono">
234
+ <span className="text-og-fg-muted">{preview.name}</span>
235
+ <span className="text-og-fg-subtle">malformed V4A</span>
236
+ </span>
237
+ );
456
238
  }
239
+ }
457
240
 
458
- // single op
459
- if (!firstOp) {
460
- return <GenericRenderer item={item} />;
461
- }
462
- if (firstOp.type === "delete_file") {
463
- return (
464
- <ActivityDisclosure
465
- icon={<FileDiffIcon className={ICON_SIZE} />}
466
- iconTone="failed"
467
- title={`Deleted ${basename(firstOp.path)}`}
468
- cancelled={cancelled}
469
- preview={<PathPreview path={firstOp.path} />}
470
- >
471
- <BodyNote>File deleted — no diff to show.</BodyNote>
472
- </ActivityDisclosure>
473
- );
241
+ function PresentedBody({ body }: { body: ToolBody }) {
242
+ switch (body.kind) {
243
+ case "term":
244
+ return (
245
+ <>
246
+ {/* Exec rows pass command={null}: the row title already carries `$ cmd`. */}
247
+ <TermBlock
248
+ command={body.command}
249
+ {...(body.workdir !== null || body.command === null ? { workdir: body.workdir } : {})}
250
+ output={body.output}
251
+ {...(body.live ? { live: true } : {})}
252
+ {...(body.failed !== undefined ? { failed: body.failed } : {})}
253
+ />
254
+ {body.note ? <BodyNote>{body.note}</BodyNote> : null}
255
+ </>
256
+ );
257
+ case "note":
258
+ return body.tone ? (
259
+ <BodyNote tone={body.tone}>{body.text}</BodyNote>
260
+ ) : (
261
+ <BodyNote>{body.text}</BodyNote>
262
+ );
263
+ case "payloads":
264
+ return (
265
+ <>
266
+ {body.note ? <p className="m-0 py-1 text-og-sm text-og-fg-muted">{body.note}</p> : null}
267
+ {body.blocks.map((block) =>
268
+ block.failed ? (
269
+ <PayloadBlock key={block.label} label={block.label} value={block.value} failed />
270
+ ) : (
271
+ <PayloadBlock key={block.label} label={block.label} value={block.value} />
272
+ ),
273
+ )}
274
+ </>
275
+ );
276
+ case "patch":
277
+ if (body.bare) {
278
+ return <RawPatch diff={body.files[0]?.diff ?? ""} />;
279
+ }
280
+ return (
281
+ <>
282
+ {body.files.map((entry) =>
283
+ entry.file ? (
284
+ <ToolDiff key={entry.key} files={[entry.file]} />
285
+ ) : (
286
+ <div key={entry.key}>
287
+ <p className="mb-1 font-og-mono text-og-xs text-og-fg-muted">{entry.path}</p>
288
+ <RawPatch diff={entry.diff} />
289
+ </div>
290
+ ),
291
+ )}
292
+ </>
293
+ );
294
+ case "web-results":
295
+ return <WebSearchResults results={body.results} />;
474
296
  }
297
+ }
475
298
 
476
- const file = safeParseOp(firstOp);
477
- if (!file) {
478
- return (
479
- <ActivityDisclosure
480
- icon={<FileDiffIcon className={ICON_SIZE} />}
481
- iconTone="accent"
482
- title={`${verbForOp(firstOp)} ${basename(firstOp.path)}`}
483
- cancelled={cancelled}
484
- preview={
485
- <span className="inline-flex items-center gap-2 font-og-mono">
486
- <span className="text-og-fg-muted">{basename(firstOp.path)}</span>
487
- <span className="text-og-fg-subtle">malformed V4A</span>
488
- </span>
489
- }
490
- >
491
- <RawPatch diff={firstOp.diff ?? ""} />
492
- </ActivityDisclosure>
493
- );
299
+ function WebSearchResults({ results }: { results: WebSearchResult[] | null }) {
300
+ const resultOccurrences = new Map<string, number>();
301
+ const keyedResults = results?.map((result) => {
302
+ const contentKey = `${result.domain}\u0000${result.title}\u0000${result.snippet}`;
303
+ const occurrence = (resultOccurrences.get(contentKey) ?? 0) + 1;
304
+ resultOccurrences.set(contentKey, occurrence);
305
+ return { key: `${contentKey}\u0000${occurrence}`, result };
306
+ });
307
+ if (!keyedResults || !keyedResults.length) {
308
+ return <BodyNote>results folded into model context — no list available.</BodyNote>;
494
309
  }
310
+ return (
311
+ <ul className="flex flex-col gap-2">
312
+ {keyedResults.map(({ key, result }) => (
313
+ <li key={key} className="flex gap-2.5">
314
+ <GlobeIcon className="mt-0.5 size-3.5 shrink-0 text-og-fg-subtle" />
315
+ <div className="min-w-0">
316
+ <p className="truncate text-og-base text-og-fg">
317
+ {result.title} <span className="text-og-fg-subtle">{result.domain}</span>
318
+ </p>
319
+ <p className="text-og-sm leading-5 text-og-fg-muted">{result.snippet}</p>
320
+ </div>
321
+ </li>
322
+ ))}
323
+ </ul>
324
+ );
325
+ }
495
326
 
496
- // The collapsed row shows verb + basename (title) and a muted "+N −M"
497
- // (preview); on expand the preview is hidden and the DiffView header carries
498
- // the path + churn — so the filename/stat never appears twice at once.
327
+ /** Draw one shared-model row with the web disclosure primitives. */
328
+ function PresentedToolRow({ presentation: p }: { presentation: ToolRowPresentation }) {
329
+ const Icon = toolIcon(p.icon);
499
330
  return (
500
331
  <ActivityDisclosure
501
- icon={<FileDiffIcon className={ICON_SIZE} />}
502
- iconTone="accent"
503
- title={`${verbForOp(firstOp)} ${basename(file.path)}`}
504
- cancelled={cancelled}
505
- preview={<PathPreview path={file.path} add={file.additions} del={file.deletions} />}
332
+ icon={<Icon className={ICON_SIZE} />}
333
+ iconTone={p.iconTone}
334
+ title={p.title}
335
+ {...(p.titleMono ? { titleMono: true } : {})}
336
+ {...(p.running ? { running: true } : {})}
337
+ {...(p.chip ? { chip: p.chip } : {})}
338
+ {...(p.failed !== undefined ? { failed: p.failed } : {})}
339
+ {...(p.cancelled !== undefined ? { cancelled: p.cancelled } : {})}
340
+ preview={p.preview ? presentedPreviewNode(p.preview) : undefined}
506
341
  >
507
- <ToolDiff files={[file]} />
342
+ <PresentedBody body={p.body} />
508
343
  </ActivityDisclosure>
509
344
  );
510
345
  }
511
346
 
512
- function safeParseOp(op: ApplyPatchOperation): GitFileDiff | null {
513
- try {
514
- return v4aToGitFileDiff(op);
515
- } catch {
516
- return null;
517
- }
347
+ function ExecRenderer({ item }: ToolRendererProps) {
348
+ const computeLabel = useTimelineComputeLabel();
349
+ return <PresentedToolRow presentation={execPresentation(item, { computeLabel })} />;
350
+ }
351
+
352
+ function WriteStdinRenderer({ item }: ToolRendererProps) {
353
+ return <PresentedToolRow presentation={writeStdinPresentation(item)} />;
354
+ }
355
+
356
+ function ApplyPatchRenderer({ item }: ToolRendererProps) {
357
+ const presentation = applyPatchPresentation(item);
358
+ return presentation ? (
359
+ <PresentedToolRow presentation={presentation} />
360
+ ) : (
361
+ <GenericRenderer item={item} />
362
+ );
363
+ }
364
+
365
+ function WebSearchRenderer({ item }: ToolRendererProps) {
366
+ return <PresentedToolRow presentation={webSearchPresentation(item)} />;
367
+ }
368
+
369
+ function AskRenderer({ item }: ToolRendererProps) {
370
+ return <PresentedToolRow presentation={askPresentation(item)} />;
371
+ }
372
+
373
+ function RunOnRenderer({ item }: ToolRendererProps) {
374
+ return <PresentedToolRow presentation={runOnPresentation(item)} />;
375
+ }
376
+
377
+ /**
378
+ * Baseline craft for unmatched tools: family icon + title-cased leaf + honest
379
+ * status preview (Running… / Done / error snippet). No argument-field sniffing —
380
+ * JSON stays in the expandable body only.
381
+ */
382
+ function UnreviewedGenericRenderer({ item }: ToolRendererProps) {
383
+ return <PresentedToolRow presentation={genericToolPresentation(item)} />;
518
384
  }
519
385
 
520
386
  /* ---- computer_call --------------------------------------------------------- */
@@ -1151,6 +1017,13 @@ function SandboxFilePublishRenderer({ item, loadRetainedArtifact }: ToolRenderer
1151
1017
  >
1152
1018
  {downloadButton}
1153
1019
  {openLink}
1020
+ {isPatchFilename(receipt.filename) ? (
1021
+ <PatchApplyCommand
1022
+ artifact={receipt.artifact}
1023
+ filename={receipt.filename}
1024
+ load={loadRetainedArtifact}
1025
+ />
1026
+ ) : null}
1154
1027
  </ActivityDisclosure>
1155
1028
  );
1156
1029
  }
@@ -1370,145 +1243,6 @@ function retainedImageFilename(artifact: RetainedArtifactReference): string {
1370
1243
 
1371
1244
  /* ---- web_search ------------------------------------------------------------ */
1372
1245
 
1373
- type WebSearchResult = { title: string; domain: string; snippet: string };
1374
-
1375
- /** Pull a search string from tool-call arguments when providerData.action is sparse. */
1376
- function webSearchQueryFromArguments(args: unknown): string | null {
1377
- if (typeof args === "string") {
1378
- const trimmed = args.trim();
1379
- if (!trimmed) {
1380
- return null;
1381
- }
1382
- try {
1383
- return webSearchQueryFromArguments(JSON.parse(trimmed));
1384
- } catch {
1385
- return trimmed;
1386
- }
1387
- }
1388
- if (!args || typeof args !== "object") {
1389
- return null;
1390
- }
1391
- const record = args as Record<string, unknown>;
1392
- if (typeof record.query === "string" && record.query.trim().length > 0) {
1393
- return record.query;
1394
- }
1395
- if (Array.isArray(record.queries)) {
1396
- const first = record.queries.find(
1397
- (value): value is string => typeof value === "string" && value.trim().length > 0,
1398
- );
1399
- if (first) {
1400
- return first;
1401
- }
1402
- }
1403
- return null;
1404
- }
1405
-
1406
- function WebSearchRenderer({ item }: ToolRendererProps) {
1407
- const raw = (item.raw ?? {}) as {
1408
- providerData?: {
1409
- action?: {
1410
- type?: string;
1411
- query?: string;
1412
- queries?: string[];
1413
- url?: string;
1414
- pattern?: string;
1415
- };
1416
- };
1417
- };
1418
- const action = raw.providerData?.action ?? {};
1419
- const actionType = action.type ?? "search";
1420
- // Responses API deprecated singular `query` in favor of `queries[]`.
1421
- // Codex/current OpenAI often only populate the array.
1422
- const queries = (action.queries ?? []).filter(
1423
- (value): value is string => typeof value === "string" && value.trim().length > 0,
1424
- );
1425
- const searchQuery =
1426
- (typeof action.query === "string" && action.query.trim().length > 0 ? action.query : null) ??
1427
- queries[0] ??
1428
- webSearchQueryFromArguments(item.arguments);
1429
- const running = item.status === "running";
1430
- const query =
1431
- actionType === "open_page"
1432
- ? (action.url ?? "(page unavailable)")
1433
- : actionType === "find_in_page"
1434
- ? action.pattern && action.url
1435
- ? `"${action.pattern}" in ${action.url}`
1436
- : (action.pattern ?? action.url ?? "(page unavailable)")
1437
- : // Codex often emits the live card before action.query/queries land;
1438
- // don't flash the scary unavailable copy while still searching.
1439
- (searchQuery ?? (running ? "…" : "(query unavailable)"));
1440
- const variants = queries.length > 1 ? ` +${queries.length - 1} variants` : "";
1441
- const runningTitle =
1442
- actionType === "open_page"
1443
- ? "Opening web page"
1444
- : actionType === "find_in_page"
1445
- ? "Searching within page"
1446
- : "Searching the web";
1447
- const completedTitle =
1448
- actionType === "open_page"
1449
- ? "Opened web page"
1450
- : actionType === "find_in_page"
1451
- ? "Searched within page"
1452
- : "Searched the web";
1453
- // web_search may surface a results array on the output when the host enriches it.
1454
- // Filter out null/undefined/non-object entries before casting: host-provided
1455
- // data is untrusted and a null element would throw on result.title access.
1456
- const rawResults = (item.output as { results?: unknown } | undefined)?.results;
1457
- const results = Array.isArray(rawResults)
1458
- ? (rawResults as unknown[]).filter((r): r is WebSearchResult => !!r && typeof r === "object")
1459
- : undefined;
1460
- const resultOccurrences = new Map<string, number>();
1461
- const keyedResults = results?.map((result) => {
1462
- const contentKey = `${result.domain}\u0000${result.title}\u0000${result.snippet}`;
1463
- const occurrence = (resultOccurrences.get(contentKey) ?? 0) + 1;
1464
- resultOccurrences.set(contentKey, occurrence);
1465
- return { key: `${contentKey}\u0000${occurrence}`, result };
1466
- });
1467
-
1468
- if (running) {
1469
- return (
1470
- <ActivityDisclosure
1471
- icon={<SearchIcon className={ICON_SIZE} />}
1472
- iconTone="running"
1473
- title={runningTitle}
1474
- running
1475
- preview={<RunningPreview>{`${query}${variants}`}</RunningPreview>}
1476
- >
1477
- <BodyNote>searching… results fold into the model context (no output event).</BodyNote>
1478
- </ActivityDisclosure>
1479
- );
1480
- }
1481
-
1482
- return (
1483
- <ActivityDisclosure
1484
- icon={<SearchIcon className={ICON_SIZE} />}
1485
- iconTone="muted"
1486
- title={completedTitle}
1487
- preview={`${query}${variants}`}
1488
- failed={item.status === "failed"}
1489
- cancelled={item.status === "cancelled"}
1490
- >
1491
- {keyedResults && keyedResults.length ? (
1492
- <ul className="flex flex-col gap-2">
1493
- {keyedResults.map(({ key, result }) => (
1494
- <li key={key} className="flex gap-2.5">
1495
- <GlobeIcon className="mt-0.5 size-3.5 shrink-0 text-og-fg-subtle" />
1496
- <div className="min-w-0">
1497
- <p className="truncate text-og-base text-og-fg">
1498
- {result.title} <span className="text-og-fg-subtle">{result.domain}</span>
1499
- </p>
1500
- <p className="text-og-sm leading-5 text-og-fg-muted">{result.snippet}</p>
1501
- </div>
1502
- </li>
1503
- ))}
1504
- </ul>
1505
- ) : (
1506
- <BodyNote>results folded into model context — no list available.</BodyNote>
1507
- )}
1508
- </ActivityDisclosure>
1509
- );
1510
- }
1511
-
1512
1246
  /* ---- view_image ------------------------------------------------------------ */
1513
1247
 
1514
1248
  const VIEW_IMAGE_ERRORS = [
@@ -1532,7 +1266,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
1532
1266
  <ActivityDisclosure
1533
1267
  icon={<ImageIcon className={ICON_SIZE} />}
1534
1268
  iconTone="running"
1535
- title={`View ${basename(path)}`}
1269
+ title={`View ${pathBasename(path)}`}
1536
1270
  running
1537
1271
  preview={<RunningPreview>reading…</RunningPreview>}
1538
1272
  media={<MediaSkeleton />}
@@ -1546,7 +1280,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
1546
1280
  const viewCancelled = item.status === "cancelled";
1547
1281
 
1548
1282
  if (retained) {
1549
- const title = `Viewed ${basename(path)}`;
1283
+ const title = `Viewed ${pathBasename(path)}`;
1550
1284
  if (!retained.available) {
1551
1285
  const state =
1552
1286
  retained.reason === "expired" || retained.reason === "deleted"
@@ -1591,7 +1325,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
1591
1325
  <ActivityDisclosure
1592
1326
  icon={<ImageIcon className={ICON_SIZE} />}
1593
1327
  iconTone="failed"
1594
- title={`View ${basename(path)}`}
1328
+ title={`View ${pathBasename(path)}`}
1595
1329
  chip={{ tone: "bad", text: tooBig ? "too large" : "error" }}
1596
1330
  preview={text}
1597
1331
  >
@@ -1604,7 +1338,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
1604
1338
  <ActivityDisclosure
1605
1339
  icon={<ImageIcon className={ICON_SIZE} />}
1606
1340
  iconTone={viewFailed ? "failed" : "muted"}
1607
- title={`Viewed ${basename(path)}`}
1341
+ title={`Viewed ${pathBasename(path)}`}
1608
1342
  failed={viewFailed}
1609
1343
  cancelled={viewCancelled}
1610
1344
  preview={path}
@@ -1618,7 +1352,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
1618
1352
  <ActivityDisclosure
1619
1353
  icon={<ImageIcon className={ICON_SIZE} />}
1620
1354
  iconTone={viewFailed ? "failed" : "muted"}
1621
- title={`Viewed ${basename(path)} · image omitted · not retained`}
1355
+ title={`Viewed ${pathBasename(path)} · image omitted · not retained`}
1622
1356
  failed={viewFailed}
1623
1357
  cancelled={viewCancelled}
1624
1358
  preview="inline image omitted · not retained"
@@ -1636,7 +1370,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
1636
1370
  <ActivityDisclosure
1637
1371
  icon={<ImageIcon className={ICON_SIZE} />}
1638
1372
  iconTone={viewFailed ? "failed" : "muted"}
1639
- title={`Viewed ${basename(path)}`}
1373
+ title={`Viewed ${pathBasename(path)}`}
1640
1374
  failed={viewFailed}
1641
1375
  cancelled={viewCancelled}
1642
1376
  preview="(no image)"
@@ -1656,7 +1390,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
1656
1390
  <ActivityDisclosure
1657
1391
  icon={<ImageIcon className={ICON_SIZE} />}
1658
1392
  iconTone={viewFailed ? "failed" : "accent"}
1659
- title={`Viewed ${basename(path)}`}
1393
+ title={`Viewed ${pathBasename(path)}`}
1660
1394
  failed={viewFailed}
1661
1395
  cancelled={viewCancelled}
1662
1396
  media={<Thumbnail src={text} caption={path} alt={path} />}
@@ -1726,123 +1460,6 @@ function SecretSetRenderer({ item }: ToolRendererProps) {
1726
1460
 
1727
1461
  /* ---- tool_search (progressive MCP disclosure) ------------------------------ */
1728
1462
 
1729
- type DisclosedTool = {
1730
- /** Full wire name (`server__leaf` or bare). */
1731
- name: string;
1732
- /** Server / namespace prefix before `__`, when present. */
1733
- source: string | null;
1734
- /** Leaf tool name after `__`. */
1735
- leaf: string;
1736
- };
1737
-
1738
- function splitToolWireName(name: string): DisclosedTool {
1739
- const boundary = name.indexOf("__");
1740
- if (boundary <= 0) {
1741
- return { name, source: null, leaf: name };
1742
- }
1743
- return {
1744
- name,
1745
- source: name.slice(0, boundary),
1746
- leaf: name.slice(boundary + 2),
1747
- };
1748
- }
1749
-
1750
- /** Capability query from live tool_search args (object or JSON string). */
1751
- function toolSearchQuery(item: ToolRendererProps["item"]): string {
1752
- const fromArgs = parseToolArgs(item.arguments);
1753
- if (typeof fromArgs.query === "string" && fromArgs.query.trim()) {
1754
- return fromArgs.query.trim();
1755
- }
1756
- const raw = item.raw;
1757
- if (raw && typeof raw === "object") {
1758
- const rawArgs = (raw as { arguments?: unknown }).arguments;
1759
- if (typeof rawArgs === "string" && rawArgs.trim()) {
1760
- const parsed = tryParseJson(rawArgs);
1761
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1762
- const query = (parsed as { query?: unknown }).query;
1763
- if (typeof query === "string" && query.trim()) {
1764
- return query.trim();
1765
- }
1766
- }
1767
- } else if (rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)) {
1768
- const query = (rawArgs as { query?: unknown }).query;
1769
- if (typeof query === "string" && query.trim()) {
1770
- return query.trim();
1771
- }
1772
- }
1773
- }
1774
- return "";
1775
- }
1776
-
1777
- /**
1778
- * Parse disclosed tools from the runtime event shape.
1779
- * `normalizeSdkEvent` collapses `tool_search_output.tools[]` into text:
1780
- * "Disclosed tools: a, b" | "No matching tools found."
1781
- * Also accept a structured `tools` array when a host/enricher preserves it.
1782
- */
1783
- function parseDisclosedTools(output: unknown): DisclosedTool[] | null {
1784
- if (output && typeof output === "object" && !Array.isArray(output)) {
1785
- const tools = (output as { tools?: unknown }).tools;
1786
- if (Array.isArray(tools)) {
1787
- return tools
1788
- .map((tool) => {
1789
- if (typeof tool === "string" && tool.trim()) {
1790
- return splitToolWireName(tool.trim());
1791
- }
1792
- if (
1793
- tool &&
1794
- typeof tool === "object" &&
1795
- typeof (tool as { name?: unknown }).name === "string"
1796
- ) {
1797
- const name = (tool as { name: string }).name.trim();
1798
- return name ? splitToolWireName(name) : null;
1799
- }
1800
- return null;
1801
- })
1802
- .filter((tool): tool is DisclosedTool => tool != null);
1803
- }
1804
- }
1805
-
1806
- const { text } = unwrapMcpOutput(output);
1807
- const trimmed = text.trim();
1808
- if (!trimmed) {
1809
- return null;
1810
- }
1811
- if (/^no matching tools found\.?$/i.test(trimmed)) {
1812
- return [];
1813
- }
1814
- const disclosed = trimmed.match(/^disclosed tools:\s*(.+)$/i);
1815
- if (disclosed?.[1]) {
1816
- return disclosed[1]
1817
- .split(",")
1818
- .map((part) => part.trim())
1819
- .filter(Boolean)
1820
- .map(splitToolWireName);
1821
- }
1822
- const parsed = tryParseJson(trimmed);
1823
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1824
- return parseDisclosedTools(parsed);
1825
- }
1826
- return null;
1827
- }
1828
-
1829
- function toolSearchPreview(tools: DisclosedTool[] | null, cancelled: boolean): string | undefined {
1830
- if (cancelled) {
1831
- return undefined;
1832
- }
1833
- if (!tools) {
1834
- return "Done";
1835
- }
1836
- if (tools.length === 0) {
1837
- return "No matches";
1838
- }
1839
- if (tools.length === 1) {
1840
- return tools[0]!.leaf;
1841
- }
1842
- const head = tools[0]!.leaf;
1843
- return `${tools.length} tools · ${truncatePreview(head, 28)}`;
1844
- }
1845
-
1846
1463
  function ToolSearchRenderer({ item }: ToolRendererProps) {
1847
1464
  const query = toolSearchQuery(item);
1848
1465
  const icon = <PackageSearchIcon className={ICON_SIZE} />;
@@ -2069,43 +1686,6 @@ function SetSessionTitleRenderer({ item }: ToolRendererProps) {
2069
1686
  );
2070
1687
  }
2071
1688
 
2072
- type SearchHit = { title: string; snippet: string };
2073
-
2074
- function parseSearchHits(outText: string): SearchHit[] | null {
2075
- const parsed = tryParseJson(outText);
2076
- if (parsed == null) {
2077
- return null;
2078
- }
2079
- const list = Array.isArray(parsed)
2080
- ? parsed
2081
- : parsed &&
2082
- typeof parsed === "object" &&
2083
- Array.isArray((parsed as { results?: unknown }).results)
2084
- ? (parsed as { results: unknown[] }).results
2085
- : parsed && typeof parsed === "object" && Array.isArray((parsed as { hits?: unknown }).hits)
2086
- ? (parsed as { hits: unknown[] }).hits
2087
- : null;
2088
- if (!list) {
2089
- return null;
2090
- }
2091
- return list.map((row) => {
2092
- const r = row && typeof row === "object" ? (row as Record<string, unknown>) : {};
2093
- const title =
2094
- (typeof r.title === "string" && r.title) ||
2095
- (typeof r.name === "string" && r.name) ||
2096
- (typeof r.documentTitle === "string" && r.documentTitle) ||
2097
- (typeof r.path === "string" && r.path) ||
2098
- (typeof r.id === "string" && r.id) ||
2099
- "Result";
2100
- const snippet =
2101
- (typeof r.snippet === "string" && r.snippet) ||
2102
- (typeof r.text === "string" && r.text) ||
2103
- (typeof r.content === "string" && r.content) ||
2104
- "";
2105
- return { title, snippet: truncatePreview(snippet, 160) };
2106
- });
2107
- }
2108
-
2109
1689
  /* ---- company memory propose (docs MCP) ------------------------------------- */
2110
1690
 
2111
1691
  function MemoryProposeRenderer({ item }: ToolRendererProps) {
@@ -2160,177 +1740,8 @@ function MemoryProposeRenderer({ item }: ToolRendererProps) {
2160
1740
 
2161
1741
  /* ---- request_human_input --------------------------------------------------- */
2162
1742
 
2163
- function askToolPreview(args: unknown): string | null {
2164
- const record = args && typeof args === "object" ? (args as Record<string, unknown>) : null;
2165
- const questions = Array.isArray(record?.questions) ? record.questions : null;
2166
- if (!questions || questions.length === 0) {
2167
- return null;
2168
- }
2169
- const first = questions[0];
2170
- if (!first || typeof first !== "object") {
2171
- return null;
2172
- }
2173
- const q = first as Record<string, unknown>;
2174
- const text =
2175
- typeof q.label === "string" && q.label.trim()
2176
- ? q.label.trim()
2177
- : typeof q.prompt === "string" && q.prompt.trim()
2178
- ? q.prompt.trim()
2179
- : null;
2180
- if (!text) {
2181
- return null;
2182
- }
2183
- const preview = truncatePreview(text, 90);
2184
- return questions.length > 1 ? `${preview} · ${questions.length} questions` : preview;
2185
- }
2186
-
2187
- function AskRenderer({ item }: ToolRendererProps) {
2188
- const args = parseToolArgs(item.arguments);
2189
- const preview = askToolPreview(args);
2190
- const icon = <MessageCircleQuestionIcon className={ICON_SIZE} />;
2191
- const title = "Ask";
2192
-
2193
- if (item.status === "running") {
2194
- return (
2195
- <ActivityDisclosure
2196
- icon={icon}
2197
- iconTone="running"
2198
- title={title}
2199
- running
2200
- preview={
2201
- preview ? (
2202
- <RunningPreview>{preview}</RunningPreview>
2203
- ) : (
2204
- <RunningPreview>Waiting…</RunningPreview>
2205
- )
2206
- }
2207
- >
2208
- <PayloadBlock label="Arguments" value={args} />
2209
- </ActivityDisclosure>
2210
- );
2211
- }
2212
-
2213
- const { text: outText, isError } = unwrapMcpOutput(item.output);
2214
- if ((isError || item.status === "failed") && item.status !== "cancelled") {
2215
- return (
2216
- <ActivityDisclosure
2217
- icon={icon}
2218
- iconTone="failed"
2219
- title={title}
2220
- chip={{ tone: "bad", text: "error" }}
2221
- preview={truncatePreview(outText, 80) || preview || "Error"}
2222
- >
2223
- <PayloadBlock label="Arguments" value={args} />
2224
- <PayloadBlock label="Error" value={outText} failed />
2225
- </ActivityDisclosure>
2226
- );
2227
- }
2228
-
2229
- return (
2230
- <ActivityDisclosure
2231
- icon={icon}
2232
- iconTone="muted"
2233
- title={title}
2234
- cancelled={item.status === "cancelled"}
2235
- preview={item.status === "cancelled" ? undefined : (preview ?? undefined)}
2236
- >
2237
- <PayloadBlock label="Arguments" value={args} />
2238
- {outText ? <PayloadBlock label="Result" value={outText} /> : null}
2239
- </ActivityDisclosure>
2240
- );
2241
- }
2242
-
2243
1743
  /* ---- run_on ---------------------------------------------------------------- */
2244
1744
 
2245
- function runOnTargetName(output: unknown): string | null {
2246
- const { text } = unwrapMcpOutput(output);
2247
- const parsed = tryParseJson(text);
2248
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2249
- const name = (parsed as { targetName?: unknown }).targetName;
2250
- if (typeof name === "string" && name.trim()) {
2251
- return name.trim();
2252
- }
2253
- }
2254
- return null;
2255
- }
2256
-
2257
- function runOnOpPreview(args: Record<string, unknown>): string | null {
2258
- const op = args.op;
2259
- if (!op || typeof op !== "object" || Array.isArray(op)) {
2260
- return null;
2261
- }
2262
- const record = op as Record<string, unknown>;
2263
- if (record.kind === "exec" && typeof record.cmd === "string" && record.cmd.trim()) {
2264
- return `$ ${record.cmd.trim()}`;
2265
- }
2266
- if (
2267
- (record.kind === "read" || record.kind === "write") &&
2268
- typeof record.path === "string" &&
2269
- record.path.trim()
2270
- ) {
2271
- return truncatePreview(record.path.trim(), 72);
2272
- }
2273
- return null;
2274
- }
2275
-
2276
- function RunOnRenderer({ item }: ToolRendererProps) {
2277
- const parsedArgs = parseToolArgs(item.arguments);
2278
- const args = parsedArgs;
2279
- const targetName = runOnTargetName(item.output);
2280
- const title = targetName ? `Run on ${targetName}` : "Run on";
2281
- const opPreview = runOnOpPreview(parsedArgs);
2282
- const icon = <ServerIcon className={ICON_SIZE} />;
2283
-
2284
- if (item.status === "running") {
2285
- return (
2286
- <ActivityDisclosure
2287
- icon={icon}
2288
- iconTone="running"
2289
- title={title}
2290
- running
2291
- preview={
2292
- opPreview ? (
2293
- <RunningPreview>{opPreview}</RunningPreview>
2294
- ) : (
2295
- <RunningPreview>Running…</RunningPreview>
2296
- )
2297
- }
2298
- >
2299
- <PayloadBlock label="Arguments" value={args} />
2300
- </ActivityDisclosure>
2301
- );
2302
- }
2303
-
2304
- const { text: outText, isError } = unwrapMcpOutput(item.output);
2305
- if ((isError || item.status === "failed") && item.status !== "cancelled") {
2306
- return (
2307
- <ActivityDisclosure
2308
- icon={icon}
2309
- iconTone="failed"
2310
- title={title}
2311
- chip={{ tone: "bad", text: "error" }}
2312
- preview={truncatePreview(outText, 80) || opPreview || "Error"}
2313
- >
2314
- <PayloadBlock label="Arguments" value={args} />
2315
- <PayloadBlock label="Error" value={outText} failed />
2316
- </ActivityDisclosure>
2317
- );
2318
- }
2319
-
2320
- return (
2321
- <ActivityDisclosure
2322
- icon={icon}
2323
- iconTone="muted"
2324
- title={title}
2325
- cancelled={item.status === "cancelled"}
2326
- preview={item.status === "cancelled" ? undefined : (opPreview ?? undefined)}
2327
- >
2328
- <PayloadBlock label="Arguments" value={args} />
2329
- {outText ? <PayloadBlock label="Result" value={outText} /> : null}
2330
- </ActivityDisclosure>
2331
- );
2332
- }
2333
-
2334
1745
  /* ---- generic fallback (first-party MCP, external MCP, unknown) ------------- */
2335
1746
 
2336
1747
  /**
@@ -2364,7 +1775,8 @@ const REVIEW_CHIP: Partial<Record<ToolReviewStatus, DisclosureChip>> = {
2364
1775
  function ReviewedGenericRenderer({ item }: ToolRendererProps) {
2365
1776
  const { review, onViewDetails } = useRecordedToolReview(item.callId);
2366
1777
  if (!review) return <UnreviewedGenericRenderer item={item} />;
2367
- const icon = <GenericToolIcon name={item.name} />;
1778
+ const ReviewIcon = toolIcon(genericToolIconKind(item.name));
1779
+ const icon = <ReviewIcon className={ICON_SIZE} />;
2368
1780
  const waiting = review.status === "pending";
2369
1781
  const running =
2370
1782
  !waiting &&
@@ -2403,188 +1815,6 @@ function ReviewedGenericRenderer({ item }: ToolRendererProps) {
2403
1815
  );
2404
1816
  }
2405
1817
 
2406
- /** The runtime's exact refusal for a tool whose effective permission is Block. */
2407
- const BLOCKED_OUTPUT = /Connector action was not executed: blocked\b/;
2408
- /** The runtime's refusal to guess whether an approved action ran. */
2409
- const UNCERTAIN_OUTPUT = /Connector action outcome is uncertain\b/;
2410
-
2411
- function UnreviewedGenericRenderer({ item }: ToolRendererProps) {
2412
- const running = item.status === "running";
2413
- const args = parseToolArgs(item.arguments);
2414
- const display = toolDisplayName(item.name, item.display);
2415
- const icon = <GenericToolIcon name={item.name} />;
2416
- // Goal tools: surface the objective text on the collapsed row so the in-cluster
2417
- // tool replaces the old breakaway GoalRow pill without losing the gist.
2418
- const goalPreview = goalToolPreview(item.name, args);
2419
-
2420
- if (running) {
2421
- return (
2422
- <ActivityDisclosure
2423
- icon={icon}
2424
- iconTone="running"
2425
- title={display}
2426
- running
2427
- preview={goalPreview ?? <RunningPreview>Running…</RunningPreview>}
2428
- >
2429
- <PayloadBlock label="Arguments" value={args} />
2430
- </ActivityDisclosure>
2431
- );
2432
- }
2433
-
2434
- const { text: outText, isError } = unwrapMcpOutput(item.output);
2435
- // A permission Block is a decision, not a tool failure: say so plainly instead
2436
- // of the generic "error occurred, please try again" wrapper.
2437
- if (isError && BLOCKED_OUTPUT.test(outText)) {
2438
- return (
2439
- <ActivityDisclosure
2440
- icon={icon}
2441
- iconTone="muted"
2442
- title={display}
2443
- chip={{ tone: "interrupted", text: "blocked" }}
2444
- preview="Blocked by your permission settings"
2445
- >
2446
- <p className="m-0 py-1 text-og-sm text-og-fg-muted">
2447
- Your permission settings block this action, so it did not run. You can change this in the
2448
- integration's tool permissions.
2449
- </p>
2450
- <PayloadBlock label="Arguments" value={args} />
2451
- </ActivityDisclosure>
2452
- );
2453
- }
2454
- // An interrupted approved action may have run. Never invite a blind retry.
2455
- if (isError && UNCERTAIN_OUTPUT.test(outText)) {
2456
- return (
2457
- <ActivityDisclosure
2458
- icon={icon}
2459
- iconTone="failed"
2460
- title={display}
2461
- chip={{ tone: "bad", text: "outcome unknown" }}
2462
- preview="It may have run"
2463
- >
2464
- <p className="m-0 py-1 text-og-sm text-og-fg-muted">
2465
- This action may have run. Check the result in the connected app before trying again.
2466
- </p>
2467
- <PayloadBlock label="Arguments" value={args} />
2468
- </ActivityDisclosure>
2469
- );
2470
- }
2471
- // Cancelled is NOT an error — a user-cancelled tool should not surface the red
2472
- // error chip even if the output payload carries an isError flag (the error may be
2473
- // a consequence of the cancellation, not the tool's own failure).
2474
- if ((isError || item.status === "failed") && item.status !== "cancelled") {
2475
- return (
2476
- <ActivityDisclosure
2477
- icon={icon}
2478
- iconTone="failed"
2479
- title={display}
2480
- chip={{ tone: "bad", text: "error" }}
2481
- preview={truncatePreview(outText, 80) || "Error"}
2482
- >
2483
- <PayloadBlock label="Arguments" value={args} />
2484
- <PayloadBlock label="Error" value={outText} failed />
2485
- </ActivityDisclosure>
2486
- );
2487
- }
2488
-
2489
- return (
2490
- <ActivityDisclosure
2491
- icon={icon}
2492
- iconTone="muted"
2493
- title={display}
2494
- cancelled={item.status === "cancelled"}
2495
- preview={item.status === "cancelled" ? undefined : (goalPreview ?? "Done")}
2496
- >
2497
- <PayloadBlock label="Arguments" value={args} />
2498
- <PayloadBlock label="Result" value={outText} />
2499
- </ActivityDisclosure>
2500
- );
2501
- }
2502
-
2503
- function goalToolPreview(name: string, args: unknown): string | null {
2504
- const leaf = mcpToolLeaf(name);
2505
- if (
2506
- leaf !== "goal_set" &&
2507
- leaf !== "goal_update" &&
2508
- leaf !== "goal_complete" &&
2509
- leaf !== "goal_pause" &&
2510
- leaf !== "wait_for_input"
2511
- ) {
2512
- return null;
2513
- }
2514
- const record = args && typeof args === "object" ? (args as Record<string, unknown>) : null;
2515
- if (!record) {
2516
- return null;
2517
- }
2518
- const text =
2519
- typeof record.text === "string"
2520
- ? record.text
2521
- : typeof record.evidence === "string"
2522
- ? record.evidence
2523
- : typeof record.rationale === "string"
2524
- ? record.rationale
2525
- : typeof record.reason === "string"
2526
- ? record.reason
2527
- : typeof record.progressNote === "string"
2528
- ? record.progressNote
2529
- : null;
2530
- return text ? truncatePreview(text, 90) : null;
2531
- }
2532
-
2533
- function truncatePreview(text: string, max: number): string {
2534
- const cleaned = text.replace(/\s+/g, " ").trim();
2535
- if (!cleaned) {
2536
- return "";
2537
- }
2538
- return cleaned.length > max ? `${cleaned.slice(0, max - 1)}…` : cleaned;
2539
- }
2540
-
2541
- /** Explicit first-party leaf/prefix → canonical product icons (match nav/pages). */
2542
- function GenericToolIcon({ name }: { name: string }) {
2543
- const leaf = mcpToolLeaf(name);
2544
- const Icon =
2545
- leaf === "request_human_input"
2546
- ? MessageCircleQuestionIcon
2547
- : leaf.startsWith("goal_")
2548
- ? TargetIcon
2549
- : leaf.startsWith("memory_") ||
2550
- leaf === "preference_registry_summary" ||
2551
- leaf === "preference_registry_get"
2552
- ? BrainCircuitIcon
2553
- : leaf.startsWith("session_") ||
2554
- leaf === "sessions_list" ||
2555
- leaf === "set_session_title" ||
2556
- leaf === "set_other_session_title"
2557
- ? MessagesSquareIcon
2558
- : leaf.startsWith("sandbox") || leaf === "sandboxes_list" || leaf === "run_on"
2559
- ? ServerIcon
2560
- : leaf.startsWith("rig_")
2561
- ? ServerCogIcon
2562
- : leaf.startsWith("scheduled_")
2563
- ? CalendarClockIcon
2564
- : leaf.startsWith("artifacts_")
2565
- ? PanelsTopLeftIcon
2566
- : leaf.startsWith("social_")
2567
- ? Share2Icon
2568
- : leaf.startsWith("slack_")
2569
- ? MessageSquareIcon
2570
- : leaf.startsWith("github_")
2571
- ? FolderGitIcon
2572
- : leaf.startsWith("variable_")
2573
- ? BoxIcon
2574
- : leaf.startsWith("environment_")
2575
- ? KeyRoundIcon
2576
- : leaf.includes("document") ||
2577
- leaf.includes("knowledge") ||
2578
- leaf === "list_document_bases"
2579
- ? FileSearchIcon
2580
- : leaf === "tool_search"
2581
- ? PackageSearchIcon
2582
- : leaf.startsWith("skill_")
2583
- ? PlugIcon
2584
- : WrenchIcon;
2585
- return <Icon className={ICON_SIZE} />;
2586
- }
2587
-
2588
1818
  /* ---- the default registry -------------------------------------------------- */
2589
1819
 
2590
1820
  function KnowledgeSaveRenderer({ item }: ToolRendererProps) {