@makefinks/daemon 0.7.2 → 0.9.0
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/package.json +2 -1
- package/src/ai/agent-turn-runner.ts +5 -0
- package/src/ai/daemon-ai.ts +74 -24
- package/src/ai/mcp/mcp-manager.ts +348 -0
- package/src/ai/memory/memory-manager.ts +90 -2
- package/src/ai/model-config.ts +1 -1
- package/src/ai/system-prompt.ts +47 -41
- package/src/ai/tools/fetch-urls.ts +153 -125
- package/src/ai/tools/index.ts +14 -12
- package/src/ai/tools/subagents.ts +17 -13
- package/src/app/components/AppOverlays.tsx +2 -0
- package/src/components/SettingsMenu.tsx +49 -27
- package/src/components/ToolCallView.tsx +51 -12
- package/src/components/ToolsMenu.tsx +81 -10
- package/src/components/UrlMenu.tsx +2 -7
- package/src/components/tool-layouts/layouts/subagent.tsx +16 -0
- package/src/components/tool-layouts/layouts/url-tools.ts +142 -80
- package/src/hooks/daemon-event-handlers.ts +9 -0
- package/src/hooks/keyboard-handlers.ts +26 -11
- package/src/hooks/use-app-context-builder.ts +2 -0
- package/src/hooks/use-app-controller.ts +5 -0
- package/src/hooks/use-app-preferences-bootstrap.ts +11 -0
- package/src/hooks/use-app-settings.ts +6 -0
- package/src/hooks/use-daemon-events.ts +4 -0
- package/src/index.tsx +3 -0
- package/src/state/app-context.tsx +2 -0
- package/src/state/daemon-events.ts +2 -0
- package/src/state/daemon-state.ts +10 -0
- package/src/types/index.ts +10 -1
- package/src/utils/config.ts +33 -0
- package/src/utils/derive-url-menu-items.ts +197 -37
- package/src/utils/preferences.ts +6 -0
- package/src/utils/tool-output-preview.ts +215 -27
|
@@ -35,6 +35,7 @@ interface SettingsMenuProps {
|
|
|
35
35
|
canEnableVoiceOutput: boolean;
|
|
36
36
|
showFullReasoning: boolean;
|
|
37
37
|
showToolOutput: boolean;
|
|
38
|
+
memoryEnabled: boolean;
|
|
38
39
|
onClose: () => void;
|
|
39
40
|
toggleInteractionMode: () => void;
|
|
40
41
|
setVoiceInteractionType: (type: VoiceInteractionType) => void;
|
|
@@ -43,6 +44,7 @@ interface SettingsMenuProps {
|
|
|
43
44
|
setBashApprovalLevel: (level: BashApprovalLevel) => void;
|
|
44
45
|
setShowFullReasoning: (show: boolean) => void;
|
|
45
46
|
setShowToolOutput: (show: boolean) => void;
|
|
47
|
+
setMemoryEnabled: (enabled: boolean) => void;
|
|
46
48
|
persistPreferences: (updates: Partial<AppPreferences>) => void;
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -56,6 +58,7 @@ export function SettingsMenu({
|
|
|
56
58
|
canEnableVoiceOutput,
|
|
57
59
|
showFullReasoning,
|
|
58
60
|
showToolOutput,
|
|
61
|
+
memoryEnabled,
|
|
59
62
|
onClose,
|
|
60
63
|
toggleInteractionMode,
|
|
61
64
|
setVoiceInteractionType,
|
|
@@ -64,6 +67,7 @@ export function SettingsMenu({
|
|
|
64
67
|
setBashApprovalLevel,
|
|
65
68
|
setShowFullReasoning,
|
|
66
69
|
setShowToolOutput,
|
|
70
|
+
setMemoryEnabled,
|
|
67
71
|
persistPreferences,
|
|
68
72
|
}: SettingsMenuProps) {
|
|
69
73
|
const [selectedIdx, setSelectedIdx] = useState(0);
|
|
@@ -115,24 +119,33 @@ export function SettingsMenu({
|
|
|
115
119
|
description: "Require approval for bash commands (NONE / DANGEROUS / ALL)",
|
|
116
120
|
isCyclic: true,
|
|
117
121
|
},
|
|
122
|
+
{
|
|
123
|
+
id: "memory-enabled",
|
|
124
|
+
label: "Memory",
|
|
125
|
+
value: memoryEnabled ? "ON" : "OFF",
|
|
126
|
+
description: "Auto-save messages + inject relevant memories",
|
|
127
|
+
isToggle: true,
|
|
128
|
+
},
|
|
118
129
|
];
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
const audioSettingsDisabled = interactionMode !== "voice";
|
|
132
|
+
items.push(
|
|
133
|
+
{
|
|
134
|
+
id: "header-audio",
|
|
135
|
+
label: "AUDIO PARAMETERS",
|
|
136
|
+
isHeader: true,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "speech-speed",
|
|
140
|
+
label: "Speech Speed",
|
|
141
|
+
value: audioSettingsDisabled ? "N/A" : `${speechSpeed.toFixed(2)}x`,
|
|
142
|
+
description: audioSettingsDisabled
|
|
143
|
+
? "Enable voice mode to adjust speech rate"
|
|
144
|
+
: "Adjust speech rate (1.0x - 2.0x)",
|
|
145
|
+
isCyclic: !audioSettingsDisabled,
|
|
146
|
+
disabled: audioSettingsDisabled,
|
|
147
|
+
}
|
|
148
|
+
);
|
|
136
149
|
|
|
137
150
|
items.push(
|
|
138
151
|
{
|
|
@@ -159,6 +172,7 @@ export function SettingsMenu({
|
|
|
159
172
|
// Filter out headers for selection logic
|
|
160
173
|
const selectableItems = items.filter((item) => !item.isHeader);
|
|
161
174
|
const selectableCount = selectableItems.length;
|
|
175
|
+
const labelWidth = Math.max(0, ...selectableItems.map((item) => item.label.length)) + 4;
|
|
162
176
|
|
|
163
177
|
useEffect(() => {
|
|
164
178
|
if (selectableCount === 0) {
|
|
@@ -181,6 +195,7 @@ export function SettingsMenu({
|
|
|
181
195
|
canEnableVoiceOutput,
|
|
182
196
|
showFullReasoning,
|
|
183
197
|
showToolOutput,
|
|
198
|
+
memoryEnabled,
|
|
184
199
|
setSelectedIdx,
|
|
185
200
|
toggleInteractionMode,
|
|
186
201
|
setVoiceInteractionType,
|
|
@@ -189,6 +204,7 @@ export function SettingsMenu({
|
|
|
189
204
|
setBashApprovalLevel,
|
|
190
205
|
setShowFullReasoning,
|
|
191
206
|
setShowToolOutput,
|
|
207
|
+
setMemoryEnabled,
|
|
192
208
|
persistPreferences,
|
|
193
209
|
onClose,
|
|
194
210
|
manager,
|
|
@@ -263,19 +279,25 @@ export function SettingsMenu({
|
|
|
263
279
|
paddingRight={1}
|
|
264
280
|
flexDirection="column"
|
|
265
281
|
>
|
|
266
|
-
<box>
|
|
267
|
-
<
|
|
268
|
-
<
|
|
269
|
-
{
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
282
|
+
<box flexDirection="row">
|
|
283
|
+
<box width={labelWidth}>
|
|
284
|
+
<text>
|
|
285
|
+
<span fg={labelColor}>
|
|
286
|
+
{isSelected ? "▶ " : " "}
|
|
287
|
+
{item.label}:{" "}
|
|
288
|
+
</span>
|
|
289
|
+
</text>
|
|
290
|
+
</box>
|
|
291
|
+
<box>
|
|
292
|
+
<text>
|
|
293
|
+
<span fg={valueColor}>{item.value}</span>
|
|
294
|
+
{item.isToggle && !item.disabled && <span fg={COLORS.USER_LABEL}></span>}
|
|
295
|
+
{item.isCyclic && !item.disabled && <span fg={COLORS.USER_LABEL}></span>}
|
|
296
|
+
</text>
|
|
297
|
+
</box>
|
|
276
298
|
</box>
|
|
277
299
|
{item.description && (
|
|
278
|
-
<box marginLeft={
|
|
300
|
+
<box marginLeft={labelWidth}>
|
|
279
301
|
<text>
|
|
280
302
|
<span fg={COLORS.REASONING_DIM}>{item.description}</span>
|
|
281
303
|
</text>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
+
import { getMcpManager } from "../ai/mcp/mcp-manager";
|
|
2
3
|
import { useToolApprovalForCall } from "../hooks/use-tool-approval";
|
|
3
4
|
import type { ToolCall } from "../types";
|
|
4
5
|
import { COLORS } from "../ui/constants";
|
|
6
|
+
import { formatToolInputLines } from "../utils/formatters";
|
|
7
|
+
import { formatGenericToolOutputPreview } from "../utils/tool-output-preview";
|
|
5
8
|
import { ApprovalPicker } from "./ApprovalPicker";
|
|
6
9
|
import {
|
|
7
10
|
ErrorPreviewView,
|
|
@@ -37,8 +40,19 @@ function ApprovalResultBadge({ result }: { result: "approved" | "denied" }) {
|
|
|
37
40
|
);
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
function ToolSectionDivider({ label }: { label: string }) {
|
|
44
|
+
return (
|
|
45
|
+
<box flexDirection="column" paddingLeft={2} marginTop={1}>
|
|
46
|
+
<text>
|
|
47
|
+
<span fg={COLORS.REASONING_DIM}>{`--- ${label} ---`}</span>
|
|
48
|
+
</text>
|
|
49
|
+
</box>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
export function ToolCallView({ call, result, showOutput = true }: ToolCallViewProps) {
|
|
41
54
|
const layout = getToolLayout(call.name) ?? defaultToolLayout;
|
|
55
|
+
const mcpMeta = useMemo(() => getMcpManager().getToolMeta(call.name), [call.name]);
|
|
42
56
|
const isAwaitingApproval = call.status === "awaiting_approval";
|
|
43
57
|
const isRunning = call.status === "running" || call.status === "streaming";
|
|
44
58
|
const isFailed = call.status === "failed";
|
|
@@ -47,17 +61,42 @@ export function ToolCallView({ call, result, showOutput = true }: ToolCallViewPr
|
|
|
47
61
|
call.toolCallId
|
|
48
62
|
);
|
|
49
63
|
|
|
50
|
-
const header = useMemo(() =>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
const header = useMemo(() => {
|
|
65
|
+
const base = layout.getHeader?.(call.input, result) ?? null;
|
|
66
|
+
if (base) return base;
|
|
67
|
+
if (mcpMeta) {
|
|
68
|
+
return {
|
|
69
|
+
primary: mcpMeta.serverId,
|
|
70
|
+
secondary: mcpMeta.originalToolName,
|
|
71
|
+
secondaryStyle: "dim" as const,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}, [call.input, result, layout, mcpMeta]);
|
|
76
|
+
|
|
77
|
+
const body = useMemo(() => {
|
|
78
|
+
const base = layout.getBody?.(call.input, result, call) ?? null;
|
|
79
|
+
if (base) return base;
|
|
80
|
+
if (!mcpMeta) return null;
|
|
81
|
+
const lines = formatToolInputLines(call.input);
|
|
82
|
+
const normalized = lines.length > 0 ? lines : ["(no input)"];
|
|
83
|
+
return {
|
|
84
|
+
lines: normalized.map((text) => ({
|
|
85
|
+
text,
|
|
86
|
+
color: COLORS.REASONING_DIM,
|
|
87
|
+
})),
|
|
88
|
+
};
|
|
89
|
+
}, [call.input, result, call, layout, mcpMeta]);
|
|
56
90
|
|
|
57
91
|
const resultPreviewLines = useMemo(() => {
|
|
58
92
|
if (!showOutput) return null;
|
|
59
|
-
|
|
60
|
-
|
|
93
|
+
const formatted = layout.formatResult?.(result) ?? null;
|
|
94
|
+
if (formatted) return formatted;
|
|
95
|
+
if (mcpMeta) return formatGenericToolOutputPreview(result);
|
|
96
|
+
return null;
|
|
97
|
+
}, [result, showOutput, layout, mcpMeta]);
|
|
98
|
+
|
|
99
|
+
const hasResultPreview = Boolean(showOutput && resultPreviewLines && resultPreviewLines.length > 0);
|
|
61
100
|
|
|
62
101
|
const toolColor =
|
|
63
102
|
call.status === "completed"
|
|
@@ -65,7 +104,7 @@ export function ToolCallView({ call, result, showOutput = true }: ToolCallViewPr
|
|
|
65
104
|
: isAwaitingApproval
|
|
66
105
|
? COLORS.STATUS_APPROVAL
|
|
67
106
|
: COLORS.TOOLS;
|
|
68
|
-
const toolName = layout.abbreviation ?? getDefaultAbbreviation(call.name);
|
|
107
|
+
const toolName = mcpMeta ? "mcp" : (layout.abbreviation ?? getDefaultAbbreviation(call.name));
|
|
69
108
|
const borderColor = getStatusBorderColor(call.status);
|
|
70
109
|
|
|
71
110
|
const customBody = layout.renderBody ? layout.renderBody({ call, result, showOutput }) : null;
|
|
@@ -98,10 +137,10 @@ export function ToolCallView({ call, result, showOutput = true }: ToolCallViewPr
|
|
|
98
137
|
/>
|
|
99
138
|
)}
|
|
100
139
|
|
|
101
|
-
{
|
|
102
|
-
|
|
103
|
-
)}
|
|
140
|
+
{hasResultPreview && <ToolSectionDivider label="OUTPUT" />}
|
|
141
|
+
{hasResultPreview && <ResultPreviewView lines={resultPreviewLines ?? []} />}
|
|
104
142
|
|
|
143
|
+
{isFailed && call.error && <ToolSectionDivider label="ERROR" />}
|
|
105
144
|
{isFailed && call.error && <ErrorPreviewView error={call.error} />}
|
|
106
145
|
|
|
107
146
|
{call.approvalResult && <ApprovalResultBadge result={call.approvalResult} />}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
|
|
3
|
+
import { type McpServerStatus, getMcpManager } from "../ai/mcp/mcp-manager";
|
|
3
4
|
import { invalidateDaemonToolsCache } from "../ai/tools/index";
|
|
4
5
|
import { invalidateSubagentToolsCache } from "../ai/tools/subagents";
|
|
5
6
|
import {
|
|
@@ -13,6 +14,7 @@ import { getDaemonManager } from "../state/daemon-state";
|
|
|
13
14
|
import type { ToolToggleId, ToolToggles } from "../types";
|
|
14
15
|
import { DEFAULT_TOOL_TOGGLES } from "../types";
|
|
15
16
|
import { COLORS } from "../ui/constants";
|
|
17
|
+
import { getManualConfigPath } from "../utils/config";
|
|
16
18
|
|
|
17
19
|
interface ToolsMenuProps {
|
|
18
20
|
persistPreferences: (updates: Partial<{ toolToggles: ToolToggles }>) => void;
|
|
@@ -52,9 +54,21 @@ function getToolLabel(id: ToolToggleId): string {
|
|
|
52
54
|
export function ToolsMenu({ persistPreferences, onClose }: ToolsMenuProps) {
|
|
53
55
|
const manager = getDaemonManager();
|
|
54
56
|
const [toggles, setToggles] = useState<ToolToggles>(manager.toolToggles ?? { ...DEFAULT_TOOL_TOGGLES });
|
|
57
|
+
const [mcpServers, setMcpServers] = useState<McpServerStatus[]>(() => getMcpManager().getServersSnapshot());
|
|
55
58
|
|
|
56
59
|
const [toolAvailability, setToolAvailability] = useState<Record<ToolToggleId, MenuToolItem> | null>(null);
|
|
57
60
|
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const mcp = getMcpManager();
|
|
63
|
+
const handleUpdate = () => {
|
|
64
|
+
setMcpServers(mcp.getServersSnapshot());
|
|
65
|
+
};
|
|
66
|
+
mcp.on("update", handleUpdate);
|
|
67
|
+
return () => {
|
|
68
|
+
mcp.off("update", handleUpdate);
|
|
69
|
+
};
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
58
72
|
useEffect(() => {
|
|
59
73
|
let cancelled = false;
|
|
60
74
|
const loadAvailability = async () => {
|
|
@@ -134,12 +148,20 @@ export function ToolsMenu({ persistPreferences, onClose }: ToolsMenuProps) {
|
|
|
134
148
|
}, [items, showReasonColumn]);
|
|
135
149
|
|
|
136
150
|
const statusWidth = 8;
|
|
151
|
+
const mcpStatusWidth = 8;
|
|
137
152
|
|
|
138
153
|
function truncateText(text: string, maxLen: number): string {
|
|
139
154
|
if (text.length <= maxLen) return text;
|
|
140
155
|
return text.slice(0, Math.max(0, maxLen - 1)) + "…";
|
|
141
156
|
}
|
|
142
157
|
|
|
158
|
+
const mcpConfigPath = useMemo(() => getManualConfigPath(), []);
|
|
159
|
+
|
|
160
|
+
const mcpIdWidth = useMemo(() => {
|
|
161
|
+
const raw = mcpServers.reduce((max, server) => Math.max(max, server.id.length), 0);
|
|
162
|
+
return Math.min(Math.max(raw, 10), 28);
|
|
163
|
+
}, [mcpServers]);
|
|
164
|
+
|
|
143
165
|
return (
|
|
144
166
|
<box
|
|
145
167
|
position="absolute"
|
|
@@ -176,15 +198,13 @@ export function ToolsMenu({ persistPreferences, onClose }: ToolsMenuProps) {
|
|
|
176
198
|
</text>
|
|
177
199
|
</box>
|
|
178
200
|
|
|
179
|
-
{
|
|
180
|
-
<
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
</box>
|
|
187
|
-
)}
|
|
201
|
+
<box marginBottom={1}>
|
|
202
|
+
<text>
|
|
203
|
+
<span fg={COLORS.REASONING_DIM}>
|
|
204
|
+
{` ${"TOOL".padEnd(labelWidth)} ${"STATUS".padEnd(statusWidth)}${showReasonColumn ? " REASON" : ""}`}
|
|
205
|
+
</span>
|
|
206
|
+
</text>
|
|
207
|
+
</box>
|
|
188
208
|
|
|
189
209
|
<box flexDirection="column">
|
|
190
210
|
{items.map((item, idx) => {
|
|
@@ -218,7 +238,7 @@ export function ToolsMenu({ persistPreferences, onClose }: ToolsMenuProps) {
|
|
|
218
238
|
<span fg={labelColor}>{labelText}</span>
|
|
219
239
|
<span fg={COLORS.REASONING_DIM}> </span>
|
|
220
240
|
<span fg={statusColor}>{statusText}</span>
|
|
221
|
-
{showReasonColumn
|
|
241
|
+
{showReasonColumn ? (
|
|
222
242
|
<>
|
|
223
243
|
<span fg={COLORS.REASONING_DIM}> </span>
|
|
224
244
|
<span fg={reasonColor}>{reasonText}</span>
|
|
@@ -229,6 +249,57 @@ export function ToolsMenu({ persistPreferences, onClose }: ToolsMenuProps) {
|
|
|
229
249
|
);
|
|
230
250
|
})}
|
|
231
251
|
</box>
|
|
252
|
+
|
|
253
|
+
<box flexDirection="column" marginTop={1}>
|
|
254
|
+
<box marginBottom={1}>
|
|
255
|
+
<text>
|
|
256
|
+
<span fg={COLORS.DAEMON_LABEL}>[ MCP ]</span>
|
|
257
|
+
<span fg={COLORS.REASONING_DIM}>{` ${truncateText(mcpConfigPath, 80)}`}</span>
|
|
258
|
+
</text>
|
|
259
|
+
</box>
|
|
260
|
+
|
|
261
|
+
{mcpServers.length === 0 ? (
|
|
262
|
+
<text>
|
|
263
|
+
<span fg={COLORS.REASONING_DIM}>No MCP servers configured.</span>
|
|
264
|
+
</text>
|
|
265
|
+
) : (
|
|
266
|
+
<box flexDirection="column">
|
|
267
|
+
<box marginBottom={1}>
|
|
268
|
+
<text>
|
|
269
|
+
<span fg={COLORS.REASONING_DIM}>
|
|
270
|
+
{`SERVER`.padEnd(mcpIdWidth)} {`STATUS`.padEnd(mcpStatusWidth)} TOOLS
|
|
271
|
+
</span>
|
|
272
|
+
</text>
|
|
273
|
+
</box>
|
|
274
|
+
{mcpServers.map((server) => {
|
|
275
|
+
const statusLabel = server.status.toUpperCase();
|
|
276
|
+
const statusColor =
|
|
277
|
+
server.status === "ready"
|
|
278
|
+
? COLORS.STATUS_COMPLETED
|
|
279
|
+
: server.status === "loading"
|
|
280
|
+
? COLORS.STATUS_RUNNING
|
|
281
|
+
: server.status === "error"
|
|
282
|
+
? COLORS.STATUS_FAILED
|
|
283
|
+
: COLORS.REASONING_DIM;
|
|
284
|
+
const idText = truncateText(server.id, mcpIdWidth).padEnd(mcpIdWidth);
|
|
285
|
+
const toolsText = String(server.toolCount).padStart(4);
|
|
286
|
+
const errorText = server.error ? truncateText(server.error, 60) : "";
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<box key={server.id} flexDirection="column">
|
|
290
|
+
<text>
|
|
291
|
+
<span fg={COLORS.MENU_TEXT}>{idText}</span>
|
|
292
|
+
<span fg={COLORS.REASONING_DIM}> </span>
|
|
293
|
+
<span fg={statusColor}>{statusLabel.padEnd(mcpStatusWidth)}</span>
|
|
294
|
+
<span fg={COLORS.REASONING_DIM}> {toolsText}</span>
|
|
295
|
+
{errorText ? <span fg={COLORS.REASONING_DIM}>{` ${errorText}`}</span> : null}
|
|
296
|
+
</text>
|
|
297
|
+
</box>
|
|
298
|
+
);
|
|
299
|
+
})}
|
|
300
|
+
</box>
|
|
301
|
+
)}
|
|
302
|
+
</box>
|
|
232
303
|
</box>
|
|
233
304
|
</box>
|
|
234
305
|
);
|
|
@@ -139,7 +139,7 @@ export function UrlMenu({ items, onClose }: UrlMenuProps) {
|
|
|
139
139
|
</span>
|
|
140
140
|
</text>
|
|
141
141
|
<text>
|
|
142
|
-
<span fg={COLORS.REASONING_DIM}>(G=grounded, READ=%
|
|
142
|
+
<span fg={COLORS.REASONING_DIM}>(G=grounded, READ=%)</span>
|
|
143
143
|
</text>
|
|
144
144
|
</box>
|
|
145
145
|
|
|
@@ -152,12 +152,7 @@ export function UrlMenu({ items, onClose }: UrlMenuProps) {
|
|
|
152
152
|
sortedItems.map((item, idx) => {
|
|
153
153
|
const { origin, path } = splitUrl(item.url);
|
|
154
154
|
const grounded = item.groundedCount > 0;
|
|
155
|
-
const readLabel =
|
|
156
|
-
item.readPercent !== undefined
|
|
157
|
-
? `${item.readPercent}%`
|
|
158
|
-
: item.highlightsCount !== undefined
|
|
159
|
-
? `HL:${item.highlightsCount}`
|
|
160
|
-
: "—";
|
|
155
|
+
const readLabel = item.readPercent !== undefined ? `${item.readPercent}%` : "—";
|
|
161
156
|
|
|
162
157
|
return (
|
|
163
158
|
<box key={idx} flexDirection="row" marginBottom={0}>
|
|
@@ -34,6 +34,12 @@ function extractUrl(input: unknown): string | null {
|
|
|
34
34
|
if ("url" in input && typeof input.url === "string") {
|
|
35
35
|
return input.url;
|
|
36
36
|
}
|
|
37
|
+
if ("requests" in input && Array.isArray(input.requests)) {
|
|
38
|
+
const first = input.requests.find((item: unknown) => isRecord(item) && typeof item.url === "string");
|
|
39
|
+
if (isRecord(first) && typeof first.url === "string") {
|
|
40
|
+
return first.url;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
37
43
|
return null;
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -88,6 +94,16 @@ function formatStepLabel(step: { toolName: string; input?: unknown }): string {
|
|
|
88
94
|
|
|
89
95
|
if (step.toolName === "fetchUrls" || step.toolName === "renderUrl") {
|
|
90
96
|
const url = extractUrl(step.input);
|
|
97
|
+
if (step.toolName === "fetchUrls" && isRecord(step.input) && Array.isArray(step.input.requests)) {
|
|
98
|
+
const count = step.input.requests.filter(
|
|
99
|
+
(item: unknown) => isRecord(item) && typeof item.url === "string"
|
|
100
|
+
).length;
|
|
101
|
+
if (url) {
|
|
102
|
+
const suffix = count > 1 ? ` (+${count - 1})` : "";
|
|
103
|
+
return `${toolLabel}: ${truncateLabel(url, MAX_URL_LENGTH)}${suffix}`;
|
|
104
|
+
}
|
|
105
|
+
return toolLabel;
|
|
106
|
+
}
|
|
91
107
|
if (url) {
|
|
92
108
|
return `${toolLabel}: ${truncateLabel(url, MAX_URL_LENGTH)}`;
|
|
93
109
|
}
|