@opengeni/react 1.4.4 → 1.4.5-canary.37458084490001
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.
- package/README.md +17 -6
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +22 -15
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-5EEAS3IU.js → browser-viewer-CV23VIHB.js} +8 -6
- package/dist/chunk-3ZC6QPOM.js +174 -0
- package/dist/chunk-3ZC6QPOM.js.map +1 -0
- package/dist/{chunk-YVFRBXST.js → chunk-4BCVCJSU.js} +2 -2
- package/dist/{chunk-JHTIMBOL.js → chunk-5JSUNNQN.js} +26 -4
- package/dist/{chunk-JHTIMBOL.js.map → chunk-5JSUNNQN.js.map} +1 -1
- package/dist/{chunk-I6OY3MWC.js → chunk-6FOTSVWU.js} +2 -2
- package/dist/{chunk-IUHHPMAX.js → chunk-7RRBX5EM.js} +14 -85
- package/dist/chunk-7RRBX5EM.js.map +1 -0
- package/dist/{chunk-L2J3PU7A.js → chunk-7ZB7I3KP.js} +24 -98
- package/dist/chunk-7ZB7I3KP.js.map +1 -0
- package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
- package/dist/chunk-ATIRUHBZ.js.map +1 -0
- package/dist/chunk-BOFEQC6K.js +162 -0
- package/dist/chunk-BOFEQC6K.js.map +1 -0
- package/dist/{chunk-2OQA3CPB.js → chunk-CESBPQP5.js} +19 -5
- package/dist/chunk-CESBPQP5.js.map +1 -0
- package/dist/{chunk-IYQCWGU5.js → chunk-GHJU7XG7.js} +35 -62
- package/dist/chunk-GHJU7XG7.js.map +1 -0
- package/dist/{chunk-G6YRCIYJ.js → chunk-HNVWMDNN.js} +55 -60
- package/dist/chunk-HNVWMDNN.js.map +1 -0
- package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
- package/dist/chunk-I5EGRXXG.js.map +1 -0
- package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
- package/dist/{chunk-XS67YG5R.js → chunk-IX3K4THH.js} +24 -3
- package/dist/chunk-IX3K4THH.js.map +1 -0
- package/dist/chunk-IZV5FTJC.js +55 -0
- package/dist/chunk-IZV5FTJC.js.map +1 -0
- package/dist/chunk-JDJJMBB3.js +1883 -0
- package/dist/chunk-JDJJMBB3.js.map +1 -0
- package/dist/{chunk-C5BF4L26.js → chunk-KHF2VRXF.js} +7 -7
- package/dist/{chunk-QMMCJGFI.js → chunk-L5OKM2VX.js} +2 -2
- package/dist/chunk-LHF6BVM4.js +83 -0
- package/dist/chunk-LHF6BVM4.js.map +1 -0
- package/dist/chunk-N7S72EXK.js +25 -0
- package/dist/chunk-N7S72EXK.js.map +1 -0
- package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
- package/dist/chunk-PEHTX3V7.js.map +1 -0
- package/dist/chunk-R5R5RQQD.js +51 -0
- package/dist/chunk-R5R5RQQD.js.map +1 -0
- package/dist/chunk-RI42H5IU.js +326 -0
- package/dist/chunk-RI42H5IU.js.map +1 -0
- package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
- package/dist/chunk-SHLZMAJN.js.map +1 -0
- package/dist/{chunk-EAOSEIXB.js → chunk-TZQSHCZN.js} +692 -1996
- package/dist/chunk-TZQSHCZN.js.map +1 -0
- package/dist/chunk-ZIB4H5LN.js +101 -0
- package/dist/chunk-ZIB4H5LN.js.map +1 -0
- package/dist/client.d.ts +9 -3
- package/dist/clipboard.js +1 -1
- package/dist/components/browser-viewer.d.ts +6 -0
- package/dist/components/composer.d.ts +3 -69
- package/dist/components/human-input-form.d.ts +3 -32
- package/dist/components/markdown.d.ts +8 -0
- package/dist/components/model-policy-picker.d.ts +4 -37
- package/dist/components/opper-mark.d.ts +7 -0
- package/dist/components/provider-recovery-notice.d.ts +15 -0
- package/dist/components/remark-soft-line-breaks.d.ts +38 -0
- package/dist/components/sandbox-workspace.d.ts +13 -0
- package/dist/components/session-chrome.d.ts +5 -3
- package/dist/components/session-conversation.d.ts +15 -3
- package/dist/composer-messages.d.ts +74 -0
- package/dist/composer-messages.js +9 -0
- package/dist/composer.js +15 -10
- package/dist/{computer-viewer-ELNLZGHI.js → computer-viewer-K4CKYRX6.js} +4 -4
- package/dist/connect-setup.d.ts +2 -0
- package/dist/connect.d.ts +1 -1
- package/dist/connect.js +21 -6
- package/dist/connect.js.map +1 -1
- package/dist/{embedded-voice-NIL4RDX3.js → embedded-voice-3ZFWWJ6O.js} +6 -4
- package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-3ZFWWJ6O.js.map} +1 -1
- package/dist/hooks/shared-machines-list.d.ts +4 -0
- package/dist/hooks/use-client-config-flags.d.ts +6 -0
- package/dist/hooks/use-goal.d.ts +2 -1
- package/dist/human-input-model.d.ts +40 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +116 -73
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +5 -5
- package/dist/{latest-question-2POFPYNB.js → latest-question-SWC6QEVN.js} +3 -3
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/provider-recovery.d.ts +49 -0
- package/dist/machines.js +2 -2
- package/dist/model-mark-paths.d.ts +34 -0
- package/dist/model-mark-paths.js +16 -0
- package/dist/model-picker-model.d.ts +111 -0
- package/dist/model-picker-model.js +25 -0
- package/dist/model-picker-model.js.map +1 -0
- package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-NM4RPTY6.js} +6 -4
- package/dist/model-policy-picker-menu-NM4RPTY6.js.map +1 -0
- package/dist/model-policy.d.ts +10 -0
- package/dist/model-policy.js +5 -1
- package/dist/organization-model.d.ts +32 -0
- package/dist/organization-model.js +65 -0
- package/dist/organization-model.js.map +1 -0
- package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
- package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
- package/dist/queue-presentation-model.d.ts +72 -0
- package/dist/queue-presentation-model.js +38 -0
- package/dist/queue-presentation-model.js.map +1 -0
- package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
- package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
- package/dist/realtime.js +5 -3
- package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-RCVCMXG7.js} +5 -5
- package/dist/sandbox-files-RCVCMXG7.js.map +1 -0
- package/dist/sandbox-label-model.d.ts +23 -0
- package/dist/sandbox-label-model.js +28 -0
- package/dist/sandbox-label-model.js.map +1 -0
- package/dist/session-attention-model.d.ts +8 -0
- package/dist/session-attention-model.js +46 -0
- package/dist/session-attention-model.js.map +1 -0
- package/dist/session-feedback-model.d.ts +20 -0
- package/dist/session-feedback-model.js +56 -0
- package/dist/session-feedback-model.js.map +1 -0
- package/dist/session-list-model.d.ts +96 -0
- package/dist/session-list-model.js +219 -0
- package/dist/session-list-model.js.map +1 -0
- package/dist/session-status-model.d.ts +19 -0
- package/dist/session-ui.js +26 -17
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +8 -6
- package/dist/testing/session-scenarios.d.ts +10 -0
- package/dist/testing/session-scenarios.js +303 -0
- package/dist/testing/session-scenarios.js.map +1 -0
- package/dist/timeline/genie-copy.d.ts +3 -0
- package/dist/timeline/genie-loading.d.ts +11 -0
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/notice-presentation.d.ts +7 -0
- package/dist/timeline/patch-apply-command.d.ts +18 -0
- package/dist/timeline/platform-activity-presentation.d.ts +7 -0
- package/dist/timeline/presented-image.d.ts +6 -1
- package/dist/timeline/question-nav-model.d.ts +23 -0
- package/dist/timeline/registry.d.ts +10 -1
- package/dist/timeline/retained-loaders.d.ts +1 -1
- package/dist/timeline/tool-presentation.d.ts +172 -0
- package/dist/timeline/turn-summary-model.d.ts +90 -0
- package/dist/timeline/turn-summary.d.ts +4 -74
- package/dist/timeline/work-presentation.d.ts +42 -0
- package/dist/timeline-model.d.ts +24 -0
- package/dist/timeline-model.js +152 -0
- package/dist/timeline-model.js.map +1 -0
- package/package.json +47 -3
- package/src/artifacts.ts +1 -0
- package/src/client.ts +9 -3
- package/src/components/browser-viewer.tsx +23 -2
- package/src/components/claude-mark.tsx +2 -1
- package/src/components/composer.tsx +4 -144
- package/src/components/grok-mark.tsx +2 -1
- package/src/components/human-input-form.tsx +13 -124
- package/src/components/markdown.tsx +19 -1
- package/src/components/message-timeline.tsx +45 -99
- package/src/components/model-mark.tsx +2 -1
- package/src/components/model-policy-picker-menu.tsx +30 -85
- package/src/components/model-policy-picker.tsx +15 -148
- package/src/components/opper-mark.tsx +18 -0
- package/src/components/provider-recovery-notice.tsx +41 -0
- package/src/components/queue-surface-implementation.tsx +20 -349
- package/src/components/remark-soft-line-breaks.ts +166 -0
- package/src/components/sandbox-workspace.tsx +17 -1
- package/src/components/session-chrome.tsx +114 -99
- package/src/components/session-conversation.tsx +55 -6
- package/src/components/session-status.tsx +17 -16
- package/src/composer-messages.ts +150 -0
- package/src/connect-setup.tsx +45 -11
- package/src/connect.ts +1 -1
- package/src/hooks/shared-machines-list.ts +34 -1
- package/src/hooks/use-client-config-flags.ts +8 -0
- package/src/hooks/use-goal.ts +66 -10
- package/src/human-input-model.ts +147 -0
- package/src/index.ts +16 -0
- package/src/lib/clipboard.ts +29 -0
- package/src/lib/format.ts +7 -0
- package/src/lib/provider-recovery.ts +202 -0
- package/src/model-mark-paths.ts +53 -0
- package/src/model-picker-model.ts +330 -0
- package/src/model-policy.ts +43 -2
- package/src/organization-model.ts +118 -0
- package/src/queue-presentation-model.ts +464 -0
- package/src/sandbox-label-model.ts +41 -0
- package/src/session-attention-model.ts +59 -0
- package/src/session-feedback-model.ts +95 -0
- package/src/session-list-model.ts +327 -0
- package/src/session-status-model.ts +62 -0
- package/src/testing/session-scenarios.ts +326 -0
- package/src/timeline/activity-rail.tsx +5 -17
- package/src/timeline/genie-copy.ts +19 -0
- package/src/timeline/genie-loading.tsx +13 -18
- package/src/timeline/index.ts +1 -0
- package/src/timeline/notice-presentation.ts +22 -0
- package/src/timeline/patch-apply-command.tsx +135 -0
- package/src/timeline/platform-activity-presentation.ts +122 -0
- package/src/timeline/platform-activity-row.tsx +6 -96
- package/src/timeline/presented-image.ts +16 -2
- package/src/timeline/question-nav-model.ts +28 -0
- package/src/timeline/registry.ts +11 -1
- package/src/timeline/retained-loaders.ts +9 -4
- package/src/timeline/rolling-activity.tsx +7 -19
- package/src/timeline/tool-presentation.ts +1316 -0
- package/src/timeline/tool-renderers.tsx +226 -1025
- package/src/timeline/turn-summary-model.ts +338 -0
- package/src/timeline/turn-summary.tsx +21 -326
- package/src/timeline/work-presentation.ts +139 -0
- package/src/timeline-model.ts +108 -0
- package/styles/compiled.css +3 -0
- package/styles/connect.css +4 -0
- package/dist/chunk-2OQA3CPB.js.map +0 -1
- package/dist/chunk-CVYCLFMH.js.map +0 -1
- package/dist/chunk-EAOSEIXB.js.map +0 -1
- package/dist/chunk-G6YRCIYJ.js.map +0 -1
- package/dist/chunk-HXYOSSQO.js +0 -117
- package/dist/chunk-HXYOSSQO.js.map +0 -1
- package/dist/chunk-IUHHPMAX.js.map +0 -1
- package/dist/chunk-IYQCWGU5.js.map +0 -1
- package/dist/chunk-JH7V627O.js.map +0 -1
- package/dist/chunk-L2J3PU7A.js.map +0 -1
- package/dist/chunk-LVS3MH5T.js.map +0 -1
- package/dist/chunk-PHBFJCEU.js.map +0 -1
- package/dist/chunk-XS67YG5R.js.map +0 -1
- package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
- package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
- /package/dist/{browser-viewer-5EEAS3IU.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
- /package/dist/{chunk-YVFRBXST.js.map → chunk-4BCVCJSU.js.map} +0 -0
- /package/dist/{chunk-I6OY3MWC.js.map → chunk-6FOTSVWU.js.map} +0 -0
- /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
- /package/dist/{chunk-C5BF4L26.js.map → chunk-KHF2VRXF.js.map} +0 -0
- /package/dist/{chunk-QMMCJGFI.js.map → chunk-L5OKM2VX.js.map} +0 -0
- /package/dist/{computer-viewer-ELNLZGHI.js.map → composer-messages.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
- /package/dist/{latest-question-2POFPYNB.js.map → latest-question-SWC6QEVN.js.map} +0 -0
- /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,
|
|
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'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">{
|
|
349
|
-
<span className="text-og-fg-muted">{
|
|
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,204 @@ function PathPreview({
|
|
|
359
154
|
);
|
|
360
155
|
}
|
|
361
156
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
163
|
+
const TOOL_ICONS: Record<ToolIconKind, LucideIcon> = {
|
|
164
|
+
terminal: TerminalIcon,
|
|
165
|
+
keyboard: KeyboardIcon,
|
|
166
|
+
"file-diff": FileDiffIcon,
|
|
167
|
+
search: SearchIcon,
|
|
168
|
+
question: MessageCircleQuestionIcon,
|
|
169
|
+
target: TargetIcon,
|
|
170
|
+
brain: BrainCircuitIcon,
|
|
171
|
+
sessions: MessagesSquareIcon,
|
|
172
|
+
server: ServerIcon,
|
|
173
|
+
"server-cog": ServerCogIcon,
|
|
174
|
+
calendar: CalendarClockIcon,
|
|
175
|
+
panels: PanelsTopLeftIcon,
|
|
176
|
+
share: Share2Icon,
|
|
177
|
+
message: MessageSquareIcon,
|
|
178
|
+
git: FolderGitIcon,
|
|
179
|
+
box: BoxIcon,
|
|
180
|
+
key: KeyRoundIcon,
|
|
181
|
+
"file-search": FileSearchIcon,
|
|
182
|
+
"package-search": PackageSearchIcon,
|
|
183
|
+
plug: PlugIcon,
|
|
184
|
+
wrench: WrenchIcon,
|
|
185
|
+
};
|
|
415
186
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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>
|
|
187
|
+
function presentedPreviewNode(preview: ToolPreview): ReactNode {
|
|
188
|
+
switch (preview.kind) {
|
|
189
|
+
case "text":
|
|
190
|
+
return preview.running ? <RunningPreview>{preview.text}</RunningPreview> : preview.text;
|
|
191
|
+
case "path":
|
|
192
|
+
return <PathPreview path={preview.path} add={preview.add} del={preview.del} />;
|
|
193
|
+
case "files":
|
|
194
|
+
return (
|
|
195
|
+
<span className="inline-flex items-center gap-2 font-og-mono">
|
|
196
|
+
<span className="text-og-fg-muted">{preview.count} files</span>
|
|
197
|
+
<span className="text-og-fg-subtle">
|
|
198
|
+
+{preview.add} −{preview.del}
|
|
439
199
|
</span>
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
);
|
|
200
|
+
</span>
|
|
201
|
+
);
|
|
202
|
+
case "malformed":
|
|
203
|
+
return (
|
|
204
|
+
<span className="inline-flex items-center gap-2 font-og-mono">
|
|
205
|
+
<span className="text-og-fg-muted">{preview.name}</span>
|
|
206
|
+
<span className="text-og-fg-subtle">malformed V4A</span>
|
|
207
|
+
</span>
|
|
208
|
+
);
|
|
456
209
|
}
|
|
210
|
+
}
|
|
457
211
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
212
|
+
function PresentedBody({ body }: { body: ToolBody }) {
|
|
213
|
+
switch (body.kind) {
|
|
214
|
+
case "term":
|
|
215
|
+
return (
|
|
216
|
+
<>
|
|
217
|
+
{/* Exec rows pass command={null}: the row title already carries `$ cmd`. */}
|
|
218
|
+
<TermBlock
|
|
219
|
+
command={body.command}
|
|
220
|
+
{...(body.workdir !== null || body.command === null ? { workdir: body.workdir } : {})}
|
|
221
|
+
output={body.output}
|
|
222
|
+
{...(body.live ? { live: true } : {})}
|
|
223
|
+
{...(body.failed !== undefined ? { failed: body.failed } : {})}
|
|
224
|
+
/>
|
|
225
|
+
{body.note ? <BodyNote>{body.note}</BodyNote> : null}
|
|
226
|
+
</>
|
|
227
|
+
);
|
|
228
|
+
case "note":
|
|
229
|
+
return body.tone ? (
|
|
230
|
+
<BodyNote tone={body.tone}>{body.text}</BodyNote>
|
|
231
|
+
) : (
|
|
232
|
+
<BodyNote>{body.text}</BodyNote>
|
|
233
|
+
);
|
|
234
|
+
case "payloads":
|
|
235
|
+
return (
|
|
236
|
+
<>
|
|
237
|
+
{body.note ? <p className="m-0 py-1 text-og-sm text-og-fg-muted">{body.note}</p> : null}
|
|
238
|
+
{body.blocks.map((block) =>
|
|
239
|
+
block.failed ? (
|
|
240
|
+
<PayloadBlock key={block.label} label={block.label} value={block.value} failed />
|
|
241
|
+
) : (
|
|
242
|
+
<PayloadBlock key={block.label} label={block.label} value={block.value} />
|
|
243
|
+
),
|
|
244
|
+
)}
|
|
245
|
+
</>
|
|
246
|
+
);
|
|
247
|
+
case "patch":
|
|
248
|
+
if (body.bare) {
|
|
249
|
+
return <RawPatch diff={body.files[0]?.diff ?? ""} />;
|
|
250
|
+
}
|
|
251
|
+
return (
|
|
252
|
+
<>
|
|
253
|
+
{body.files.map((entry) =>
|
|
254
|
+
entry.file ? (
|
|
255
|
+
<ToolDiff key={entry.key} files={[entry.file]} />
|
|
256
|
+
) : (
|
|
257
|
+
<div key={entry.key}>
|
|
258
|
+
<p className="mb-1 font-og-mono text-og-xs text-og-fg-muted">{entry.path}</p>
|
|
259
|
+
<RawPatch diff={entry.diff} />
|
|
260
|
+
</div>
|
|
261
|
+
),
|
|
262
|
+
)}
|
|
263
|
+
</>
|
|
264
|
+
);
|
|
265
|
+
case "web-results":
|
|
266
|
+
return <WebSearchResults results={body.results} />;
|
|
474
267
|
}
|
|
268
|
+
}
|
|
475
269
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
);
|
|
270
|
+
function WebSearchResults({ results }: { results: WebSearchResult[] | null }) {
|
|
271
|
+
const resultOccurrences = new Map<string, number>();
|
|
272
|
+
const keyedResults = results?.map((result) => {
|
|
273
|
+
const contentKey = `${result.domain}\u0000${result.title}\u0000${result.snippet}`;
|
|
274
|
+
const occurrence = (resultOccurrences.get(contentKey) ?? 0) + 1;
|
|
275
|
+
resultOccurrences.set(contentKey, occurrence);
|
|
276
|
+
return { key: `${contentKey}\u0000${occurrence}`, result };
|
|
277
|
+
});
|
|
278
|
+
if (!keyedResults || !keyedResults.length) {
|
|
279
|
+
return <BodyNote>results folded into model context — no list available.</BodyNote>;
|
|
494
280
|
}
|
|
281
|
+
return (
|
|
282
|
+
<ul className="flex flex-col gap-2">
|
|
283
|
+
{keyedResults.map(({ key, result }) => (
|
|
284
|
+
<li key={key} className="flex gap-2.5">
|
|
285
|
+
<GlobeIcon className="mt-0.5 size-3.5 shrink-0 text-og-fg-subtle" />
|
|
286
|
+
<div className="min-w-0">
|
|
287
|
+
<p className="truncate text-og-base text-og-fg">
|
|
288
|
+
{result.title} <span className="text-og-fg-subtle">{result.domain}</span>
|
|
289
|
+
</p>
|
|
290
|
+
<p className="text-og-sm leading-5 text-og-fg-muted">{result.snippet}</p>
|
|
291
|
+
</div>
|
|
292
|
+
</li>
|
|
293
|
+
))}
|
|
294
|
+
</ul>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
495
297
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
298
|
+
/** Draw one shared-model row with the web disclosure primitives. */
|
|
299
|
+
function PresentedToolRow({ presentation: p }: { presentation: ToolRowPresentation }) {
|
|
300
|
+
const Icon = TOOL_ICONS[p.icon];
|
|
499
301
|
return (
|
|
500
302
|
<ActivityDisclosure
|
|
501
|
-
icon={<
|
|
502
|
-
iconTone=
|
|
503
|
-
title={
|
|
504
|
-
|
|
505
|
-
|
|
303
|
+
icon={<Icon className={ICON_SIZE} />}
|
|
304
|
+
iconTone={p.iconTone}
|
|
305
|
+
title={p.title}
|
|
306
|
+
{...(p.titleMono ? { titleMono: true } : {})}
|
|
307
|
+
{...(p.running ? { running: true } : {})}
|
|
308
|
+
{...(p.chip ? { chip: p.chip } : {})}
|
|
309
|
+
{...(p.failed !== undefined ? { failed: p.failed } : {})}
|
|
310
|
+
{...(p.cancelled !== undefined ? { cancelled: p.cancelled } : {})}
|
|
311
|
+
preview={p.preview ? presentedPreviewNode(p.preview) : undefined}
|
|
506
312
|
>
|
|
507
|
-
<
|
|
313
|
+
<PresentedBody body={p.body} />
|
|
508
314
|
</ActivityDisclosure>
|
|
509
315
|
);
|
|
510
316
|
}
|
|
511
317
|
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
318
|
+
function ExecRenderer({ item }: ToolRendererProps) {
|
|
319
|
+
const computeLabel = useTimelineComputeLabel();
|
|
320
|
+
return <PresentedToolRow presentation={execPresentation(item, { computeLabel })} />;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function WriteStdinRenderer({ item }: ToolRendererProps) {
|
|
324
|
+
return <PresentedToolRow presentation={writeStdinPresentation(item)} />;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function ApplyPatchRenderer({ item }: ToolRendererProps) {
|
|
328
|
+
const presentation = applyPatchPresentation(item);
|
|
329
|
+
return presentation ? (
|
|
330
|
+
<PresentedToolRow presentation={presentation} />
|
|
331
|
+
) : (
|
|
332
|
+
<GenericRenderer item={item} />
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function WebSearchRenderer({ item }: ToolRendererProps) {
|
|
337
|
+
return <PresentedToolRow presentation={webSearchPresentation(item)} />;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function AskRenderer({ item }: ToolRendererProps) {
|
|
341
|
+
return <PresentedToolRow presentation={askPresentation(item)} />;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function RunOnRenderer({ item }: ToolRendererProps) {
|
|
345
|
+
return <PresentedToolRow presentation={runOnPresentation(item)} />;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Baseline craft for unmatched tools: family icon + title-cased leaf + honest
|
|
350
|
+
* status preview (Running… / Done / error snippet). No argument-field sniffing —
|
|
351
|
+
* JSON stays in the expandable body only.
|
|
352
|
+
*/
|
|
353
|
+
function UnreviewedGenericRenderer({ item }: ToolRendererProps) {
|
|
354
|
+
return <PresentedToolRow presentation={genericToolPresentation(item)} />;
|
|
518
355
|
}
|
|
519
356
|
|
|
520
357
|
/* ---- computer_call --------------------------------------------------------- */
|
|
@@ -1151,6 +988,13 @@ function SandboxFilePublishRenderer({ item, loadRetainedArtifact }: ToolRenderer
|
|
|
1151
988
|
>
|
|
1152
989
|
{downloadButton}
|
|
1153
990
|
{openLink}
|
|
991
|
+
{isPatchFilename(receipt.filename) ? (
|
|
992
|
+
<PatchApplyCommand
|
|
993
|
+
artifact={receipt.artifact}
|
|
994
|
+
filename={receipt.filename}
|
|
995
|
+
load={loadRetainedArtifact}
|
|
996
|
+
/>
|
|
997
|
+
) : null}
|
|
1154
998
|
</ActivityDisclosure>
|
|
1155
999
|
);
|
|
1156
1000
|
}
|
|
@@ -1370,145 +1214,6 @@ function retainedImageFilename(artifact: RetainedArtifactReference): string {
|
|
|
1370
1214
|
|
|
1371
1215
|
/* ---- web_search ------------------------------------------------------------ */
|
|
1372
1216
|
|
|
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
1217
|
/* ---- view_image ------------------------------------------------------------ */
|
|
1513
1218
|
|
|
1514
1219
|
const VIEW_IMAGE_ERRORS = [
|
|
@@ -1532,7 +1237,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
|
|
|
1532
1237
|
<ActivityDisclosure
|
|
1533
1238
|
icon={<ImageIcon className={ICON_SIZE} />}
|
|
1534
1239
|
iconTone="running"
|
|
1535
|
-
title={`View ${
|
|
1240
|
+
title={`View ${pathBasename(path)}`}
|
|
1536
1241
|
running
|
|
1537
1242
|
preview={<RunningPreview>reading…</RunningPreview>}
|
|
1538
1243
|
media={<MediaSkeleton />}
|
|
@@ -1546,7 +1251,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
|
|
|
1546
1251
|
const viewCancelled = item.status === "cancelled";
|
|
1547
1252
|
|
|
1548
1253
|
if (retained) {
|
|
1549
|
-
const title = `Viewed ${
|
|
1254
|
+
const title = `Viewed ${pathBasename(path)}`;
|
|
1550
1255
|
if (!retained.available) {
|
|
1551
1256
|
const state =
|
|
1552
1257
|
retained.reason === "expired" || retained.reason === "deleted"
|
|
@@ -1591,7 +1296,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
|
|
|
1591
1296
|
<ActivityDisclosure
|
|
1592
1297
|
icon={<ImageIcon className={ICON_SIZE} />}
|
|
1593
1298
|
iconTone="failed"
|
|
1594
|
-
title={`View ${
|
|
1299
|
+
title={`View ${pathBasename(path)}`}
|
|
1595
1300
|
chip={{ tone: "bad", text: tooBig ? "too large" : "error" }}
|
|
1596
1301
|
preview={text}
|
|
1597
1302
|
>
|
|
@@ -1604,7 +1309,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
|
|
|
1604
1309
|
<ActivityDisclosure
|
|
1605
1310
|
icon={<ImageIcon className={ICON_SIZE} />}
|
|
1606
1311
|
iconTone={viewFailed ? "failed" : "muted"}
|
|
1607
|
-
title={`Viewed ${
|
|
1312
|
+
title={`Viewed ${pathBasename(path)}`}
|
|
1608
1313
|
failed={viewFailed}
|
|
1609
1314
|
cancelled={viewCancelled}
|
|
1610
1315
|
preview={path}
|
|
@@ -1618,7 +1323,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
|
|
|
1618
1323
|
<ActivityDisclosure
|
|
1619
1324
|
icon={<ImageIcon className={ICON_SIZE} />}
|
|
1620
1325
|
iconTone={viewFailed ? "failed" : "muted"}
|
|
1621
|
-
title={`Viewed ${
|
|
1326
|
+
title={`Viewed ${pathBasename(path)} · image omitted · not retained`}
|
|
1622
1327
|
failed={viewFailed}
|
|
1623
1328
|
cancelled={viewCancelled}
|
|
1624
1329
|
preview="inline image omitted · not retained"
|
|
@@ -1636,7 +1341,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
|
|
|
1636
1341
|
<ActivityDisclosure
|
|
1637
1342
|
icon={<ImageIcon className={ICON_SIZE} />}
|
|
1638
1343
|
iconTone={viewFailed ? "failed" : "muted"}
|
|
1639
|
-
title={`Viewed ${
|
|
1344
|
+
title={`Viewed ${pathBasename(path)}`}
|
|
1640
1345
|
failed={viewFailed}
|
|
1641
1346
|
cancelled={viewCancelled}
|
|
1642
1347
|
preview="(no image)"
|
|
@@ -1656,7 +1361,7 @@ function ViewImageRenderer({ item, loadRetainedScreenshot }: ToolRendererProps)
|
|
|
1656
1361
|
<ActivityDisclosure
|
|
1657
1362
|
icon={<ImageIcon className={ICON_SIZE} />}
|
|
1658
1363
|
iconTone={viewFailed ? "failed" : "accent"}
|
|
1659
|
-
title={`Viewed ${
|
|
1364
|
+
title={`Viewed ${pathBasename(path)}`}
|
|
1660
1365
|
failed={viewFailed}
|
|
1661
1366
|
cancelled={viewCancelled}
|
|
1662
1367
|
media={<Thumbnail src={text} caption={path} alt={path} />}
|
|
@@ -1726,123 +1431,6 @@ function SecretSetRenderer({ item }: ToolRendererProps) {
|
|
|
1726
1431
|
|
|
1727
1432
|
/* ---- tool_search (progressive MCP disclosure) ------------------------------ */
|
|
1728
1433
|
|
|
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
1434
|
function ToolSearchRenderer({ item }: ToolRendererProps) {
|
|
1847
1435
|
const query = toolSearchQuery(item);
|
|
1848
1436
|
const icon = <PackageSearchIcon className={ICON_SIZE} />;
|
|
@@ -2069,43 +1657,6 @@ function SetSessionTitleRenderer({ item }: ToolRendererProps) {
|
|
|
2069
1657
|
);
|
|
2070
1658
|
}
|
|
2071
1659
|
|
|
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
1660
|
/* ---- company memory propose (docs MCP) ------------------------------------- */
|
|
2110
1661
|
|
|
2111
1662
|
function MemoryProposeRenderer({ item }: ToolRendererProps) {
|
|
@@ -2160,177 +1711,8 @@ function MemoryProposeRenderer({ item }: ToolRendererProps) {
|
|
|
2160
1711
|
|
|
2161
1712
|
/* ---- request_human_input --------------------------------------------------- */
|
|
2162
1713
|
|
|
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
1714
|
/* ---- run_on ---------------------------------------------------------------- */
|
|
2244
1715
|
|
|
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
1716
|
/* ---- generic fallback (first-party MCP, external MCP, unknown) ------------- */
|
|
2335
1717
|
|
|
2336
1718
|
/**
|
|
@@ -2364,7 +1746,8 @@ const REVIEW_CHIP: Partial<Record<ToolReviewStatus, DisclosureChip>> = {
|
|
|
2364
1746
|
function ReviewedGenericRenderer({ item }: ToolRendererProps) {
|
|
2365
1747
|
const { review, onViewDetails } = useRecordedToolReview(item.callId);
|
|
2366
1748
|
if (!review) return <UnreviewedGenericRenderer item={item} />;
|
|
2367
|
-
const
|
|
1749
|
+
const ReviewIcon = TOOL_ICONS[genericToolIconKind(item.name)];
|
|
1750
|
+
const icon = <ReviewIcon className={ICON_SIZE} />;
|
|
2368
1751
|
const waiting = review.status === "pending";
|
|
2369
1752
|
const running =
|
|
2370
1753
|
!waiting &&
|
|
@@ -2403,188 +1786,6 @@ function ReviewedGenericRenderer({ item }: ToolRendererProps) {
|
|
|
2403
1786
|
);
|
|
2404
1787
|
}
|
|
2405
1788
|
|
|
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
1789
|
/* ---- the default registry -------------------------------------------------- */
|
|
2589
1790
|
|
|
2590
1791
|
function KnowledgeSaveRenderer({ item }: ToolRendererProps) {
|