@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.
- 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-XS67YG5R.js → chunk-4O7OE7ON.js} +45 -4
- package/dist/chunk-4O7OE7ON.js.map +1 -0
- 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-QMMCJGFI.js → chunk-6BAMPGBV.js} +6 -3
- package/dist/chunk-6BAMPGBV.js.map +1 -0
- package/dist/{chunk-I6OY3MWC.js → chunk-6FOTSVWU.js} +2 -2
- 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-YVFRBXST.js → chunk-GMNGUPMX.js} +2 -2
- 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-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-LHF6BVM4.js +83 -0
- package/dist/chunk-LHF6BVM4.js.map +1 -0
- package/dist/{chunk-C5BF4L26.js → chunk-LSMWZSCJ.js} +7 -7
- package/dist/chunk-LTKGTXUQ.js +174 -0
- package/dist/chunk-LTKGTXUQ.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-IYQCWGU5.js → chunk-RGTZ2VDR.js} +35 -62
- package/dist/chunk-RGTZ2VDR.js.map +1 -0
- package/dist/chunk-RI42H5IU.js +326 -0
- package/dist/chunk-RI42H5IU.js.map +1 -0
- package/dist/{chunk-EAOSEIXB.js → chunk-RRRS5D5T.js} +727 -1983
- package/dist/chunk-RRRS5D5T.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-IUHHPMAX.js → chunk-SZOCY5VG.js} +18 -87
- package/dist/chunk-SZOCY5VG.js.map +1 -0
- package/dist/{chunk-L2J3PU7A.js → chunk-VYIPNDUZ.js} +29 -100
- package/dist/chunk-VYIPNDUZ.js.map +1 -0
- package/dist/{chunk-G6YRCIYJ.js → chunk-YK6QMGVL.js} +55 -60
- package/dist/chunk-YK6QMGVL.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-K7SOZVVZ.js} +6 -4
- package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-K7SOZVVZ.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/hooks/use-new-session-draft.d.ts +74 -0
- 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-27HGRPVF.js} +3 -3
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/provider-recovery.d.ts +49 -0
- package/dist/lib/transient-retry.d.ts +25 -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-I5KPC3D6.js} +6 -4
- package/dist/model-policy-picker-menu-I5KPC3D6.js.map +1 -0
- package/dist/model-policy.d.ts +10 -0
- package/dist/model-policy.js +5 -1
- package/dist/new-session-draft.d.ts +7 -0
- package/dist/new-session-draft.js +639 -0
- package/dist/new-session-draft.js.map +1 -0
- 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-2DGDO6NI.js} +5 -5
- package/dist/sandbox-files-2DGDO6NI.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-agents-model.d.ts +10 -0
- package/dist/session-agents-model.js +25 -0
- package/dist/session-agents-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 +119 -0
- package/dist/session-list-model.js +244 -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 +55 -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 +8 -146
- 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 +20 -150
- 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/hooks/use-new-session-draft.ts +813 -0
- 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/lib/transient-retry.ts +99 -0
- package/src/model-mark-paths.ts +53 -0
- package/src/model-picker-model.ts +330 -0
- package/src/model-policy.ts +75 -5
- package/src/new-session-draft.ts +22 -0
- 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-agents-model.ts +33 -0
- package/src/session-attention-model.ts +59 -0
- package/src/session-feedback-model.ts +95 -0
- package/src/session-list-model.ts +377 -0
- package/src/session-status-model.ts +62 -0
- package/src/testing/session-scenarios.ts +326 -0
- package/src/timeline/activity-rail.tsx +54 -24
- package/src/timeline/genie-copy.ts +19 -0
- package/src/timeline/genie-loading.tsx +15 -19
- 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/projection.ts +12 -1
- 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 +254 -1024
- 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 +17 -4
- package/styles/connect.css +4 -0
- package/styles/responsive.css +11 -2
- 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-QMMCJGFI.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-I6OY3MWC.js.map → chunk-6FOTSVWU.js.map} +0 -0
- /package/dist/{chunk-YVFRBXST.js.map → chunk-GMNGUPMX.js.map} +0 -0
- /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
- /package/dist/{chunk-C5BF4L26.js.map → chunk-LSMWZSCJ.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-27HGRPVF.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,233 @@ 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
|
-
|
|
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
|
-
|
|
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>
|
|
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
|
-
|
|
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
|
-
);
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
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
|
-
);
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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={<
|
|
502
|
-
iconTone=
|
|
503
|
-
title={
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
<
|
|
342
|
+
<PresentedBody body={p.body} />
|
|
508
343
|
</ActivityDisclosure>
|
|
509
344
|
);
|
|
510
345
|
}
|
|
511
346
|
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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
|
|
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) {
|