ui cleanup
This commit is contained in:
103
index.ts
103
index.ts
@@ -45,74 +45,58 @@ function looksLikePath(token: string): boolean {
|
|||||||
// ─── Extension Entry ────────────────────────────────────────────────────────
|
// ─── Extension Entry ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
||||||
// Register custom message renderer for ralph progress messages
|
// Register custom message renderer for ralph progress messages.
|
||||||
|
// Renders an expandable tool-call tree: collapsed shows last 3 + "N more",
|
||||||
|
// expanded (Ctrl+O) shows every tool call.
|
||||||
pi.registerMessageRenderer(
|
pi.registerMessageRenderer(
|
||||||
"ralph-progress",
|
"ralph-progress",
|
||||||
(message, { expanded }, theme) => {
|
(message, { expanded }, theme) => {
|
||||||
const details = message.details as
|
const details = message.details as
|
||||||
| {
|
| {
|
||||||
taskId?: string;
|
|
||||||
taskTitle?: string;
|
|
||||||
phase?: string;
|
phase?: string;
|
||||||
timestamp?: number;
|
toolCalls?: Array<{ name: string; label: string }>;
|
||||||
durationMs?: number;
|
|
||||||
toolUsage?: Record<string, number>;
|
|
||||||
commits?: number;
|
|
||||||
error?: string;
|
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
const phase = details?.phase ?? "info";
|
const MAX_COLLAPSED = 3;
|
||||||
const phaseLabel =
|
const lines: string[] = [];
|
||||||
phase === "starting"
|
|
||||||
? theme.fg("accent", "[RUNNING]")
|
|
||||||
: phase === "completed"
|
|
||||||
? theme.fg("success", "[DONE]")
|
|
||||||
: phase === "failed"
|
|
||||||
? theme.fg("error", "[FAIL]")
|
|
||||||
: phase === "batch_start"
|
|
||||||
? theme.fg("accent", "[BATCH]")
|
|
||||||
: phase === "retry"
|
|
||||||
? theme.fg("warning", "[RETRY]")
|
|
||||||
: phase === "progress"
|
|
||||||
? ""
|
|
||||||
: theme.fg("dim", "[INFO]");
|
|
||||||
|
|
||||||
let text = phaseLabel
|
// Header line — e.g. "✓ 05 · billing-subscriptions-trials (2m 14s)"
|
||||||
? `${phaseLabel} ${message.content}`
|
lines.push(String(message.content));
|
||||||
: String(message.content);
|
|
||||||
|
|
||||||
// Show expanded details
|
// Build tool-call tree
|
||||||
if (expanded && details) {
|
if (details?.toolCalls && details.toolCalls.length > 0) {
|
||||||
const lines: string[] = [];
|
const all = details.toolCalls;
|
||||||
if (details.taskId) lines.push(` Task: ${details.taskId}`);
|
|
||||||
if (details.durationMs) {
|
if (expanded) {
|
||||||
const dur = formatDuration(details.durationMs);
|
// Expanded: show ALL tool calls
|
||||||
lines.push(` Duration: ${dur}`);
|
for (let i = 0; i < all.length; i++) {
|
||||||
}
|
const entry = all[i];
|
||||||
if (details.toolUsage) {
|
const isLast = i === all.length - 1;
|
||||||
const tools = Object.entries(details.toolUsage)
|
const branch = isLast ? " └── " : " ├── ";
|
||||||
.filter(([, v]) => v > 0)
|
const tag = theme.fg("accent", `[${entry.name}]`);
|
||||||
.map(([k, v]) => `[${k}]: ${v}`)
|
lines.push(`${branch}${tag} ${entry.label}`);
|
||||||
.join(" ");
|
}
|
||||||
if (tools) lines.push(` Tools: ${tools}`);
|
} else {
|
||||||
}
|
// Collapsed: last N + "X more"
|
||||||
if (details.commits && details.commits > 0) {
|
const shown = all.slice(-MAX_COLLAPSED);
|
||||||
lines.push(` Commits: ${details.commits}`);
|
const remaining = all.length - shown.length;
|
||||||
}
|
|
||||||
if (details.error) {
|
if (remaining > 0) {
|
||||||
lines.push(` Error: ${details.error}`);
|
lines.push(theme.fg("dim", ` ├── ${remaining} more`));
|
||||||
}
|
}
|
||||||
if (details.timestamp) {
|
|
||||||
const time = new Date(details.timestamp).toLocaleTimeString();
|
for (let i = 0; i < shown.length; i++) {
|
||||||
lines.push(` Time: ${time}`);
|
const entry = shown[i];
|
||||||
}
|
const isLast = i === shown.length - 1;
|
||||||
if (lines.length > 0) {
|
const branch = isLast ? " └── " : " ├── ";
|
||||||
text += "\n" + lines.join("\n");
|
const tag = theme.fg("accent", `[${entry.name}]`);
|
||||||
|
lines.push(`${branch}${tag} ${entry.label}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use Box with customMessageBg for consistent styling
|
const text = lines.join("\n");
|
||||||
const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
|
const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
|
||||||
box.addChild(new Text(text, 0, 0));
|
box.addChild(new Text(text, 0, 0));
|
||||||
return box;
|
return box;
|
||||||
@@ -128,12 +112,16 @@ export default function ralphLoopExtension(pi: ExtensionAPI): void {
|
|||||||
// Wraps pi.sendMessage() for posting status to the chat history.
|
// Wraps pi.sendMessage() for posting status to the chat history.
|
||||||
// Uses "ralph-progress" customType with a "progress" phase so the
|
// Uses "ralph-progress" customType with a "progress" phase so the
|
||||||
// renderer omits the label prefix entirely (no [INFO] etc.).
|
// renderer omits the label prefix entirely (no [INFO] etc.).
|
||||||
const sendProgress = (content: string) => {
|
// Accepts an optional meta object with toolCalls for the expandable view.
|
||||||
|
const sendProgress = (
|
||||||
|
content: string,
|
||||||
|
meta?: { toolCalls?: Array<{ name: string; label: string }> },
|
||||||
|
) => {
|
||||||
pi.sendMessage({
|
pi.sendMessage({
|
||||||
customType: "ralph-progress",
|
customType: "ralph-progress",
|
||||||
content,
|
content,
|
||||||
display: true,
|
display: true,
|
||||||
details: { phase: "progress" },
|
details: { phase: "progress", toolCalls: meta?.toolCalls },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -274,6 +262,7 @@ async function handleRun(
|
|||||||
ctx as any,
|
ctx as any,
|
||||||
{ parallel: useParallel },
|
{ parallel: useParallel },
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
|
projectDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const task of batch.tasks) {
|
for (const task of batch.tasks) {
|
||||||
@@ -406,6 +395,7 @@ async function handleResume(
|
|||||||
ctx as any,
|
ctx as any,
|
||||||
{ parallel: useParallel },
|
{ parallel: useParallel },
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
|
projectDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const task of batch.tasks) {
|
for (const task of batch.tasks) {
|
||||||
@@ -481,6 +471,7 @@ async function handleNext(
|
|||||||
ctx as any,
|
ctx as any,
|
||||||
undefined,
|
undefined,
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
|
projectDir,
|
||||||
);
|
);
|
||||||
updateTaskInFile(taskFile, task.id, progress.getTaskStatus(task.id));
|
updateTaskInFile(taskFile, task.id, progress.getTaskStatus(task.id));
|
||||||
}
|
}
|
||||||
|
|||||||
173
src/executor.ts
173
src/executor.ts
@@ -14,9 +14,13 @@ import {
|
|||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
/** Optional callback to post a progress message into the chat history. */
|
/** Optional callback to post a progress message into the chat history. */
|
||||||
export type SendChatMessage = (content: string) => void;
|
export type SendChatMessage = (
|
||||||
|
content: string,
|
||||||
|
/** Extra data passed to the message renderer for the expanded view. */
|
||||||
|
meta?: { toolCalls?: ToolCallEntry[] },
|
||||||
|
) => void;
|
||||||
|
|
||||||
interface ToolCallEntry {
|
export interface ToolCallEntry {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
@@ -34,6 +38,7 @@ export async function runTask(
|
|||||||
depReflections: Reflection[],
|
depReflections: Reflection[],
|
||||||
ctx: ExtensionCommandContext,
|
ctx: ExtensionCommandContext,
|
||||||
sendChatMessage?: SendChatMessage,
|
sendChatMessage?: SendChatMessage,
|
||||||
|
projectDir: string = project.sourceDir,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
reflection?: Reflection;
|
reflection?: Reflection;
|
||||||
@@ -56,7 +61,7 @@ export async function runTask(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Write prompt to .ralph/ with timestamp (for debugging)
|
// Write prompt to .ralph/ with timestamp (for debugging)
|
||||||
const ralphDir = path.join(project.sourceDir, ".ralph");
|
const ralphDir = path.join(projectDir, ".ralph");
|
||||||
ensureDir(ralphDir);
|
ensureDir(ralphDir);
|
||||||
const promptFile = path.join(ralphDir, `prompt-${startMs}.md`);
|
const promptFile = path.join(ralphDir, `prompt-${startMs}.md`);
|
||||||
writeFileSafe(promptFile, prompt);
|
writeFileSafe(promptFile, prompt);
|
||||||
@@ -64,42 +69,68 @@ export async function runTask(
|
|||||||
// Footer shows just the task title (no batch prefix)
|
// Footer shows just the task title (no batch prefix)
|
||||||
ctx.ui.setStatus("ralph", task.title);
|
ctx.ui.setStatus("ralph", task.title);
|
||||||
|
|
||||||
// Animated spinner in Pi's streaming area — shows as actual spinner, not static text
|
|
||||||
const taskHeader = `${task.id} · ${task.title}`;
|
const taskHeader = `${task.id} · ${task.title}`;
|
||||||
ctx.ui.setWorkingMessage(taskHeader);
|
|
||||||
|
// Live progress widget above the editor — animated spinner + tool call updates
|
||||||
|
// Using setWidget instead of setWorkingMessage because the working message area
|
||||||
|
// is only visible during parent agent streaming, not during extension command execution.
|
||||||
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||||
|
let frameIndex = 0;
|
||||||
|
let lastToolLabel = "";
|
||||||
|
const theme = ctx.ui.theme;
|
||||||
|
|
||||||
|
const toolCalls: ToolCallEntry[] = [];
|
||||||
|
|
||||||
|
const updateWidget = () => {
|
||||||
|
const frame = theme.fg("accent", SPINNER_FRAMES[frameIndex]);
|
||||||
|
const lines = [`${frame} ${taskHeader}`];
|
||||||
|
if (toolCalls.length > 0) {
|
||||||
|
lines.push(
|
||||||
|
theme.fg(
|
||||||
|
"dim",
|
||||||
|
` ${toolCalls.length} tool${toolCalls.length !== 1 ? "s" : ""} · ${lastToolLabel}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ctx.ui.setWidget("ralph-task", lines);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Smooth spinner animation at 100ms intervals
|
||||||
|
const spinnerTimer = setInterval(() => {
|
||||||
|
frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
|
||||||
|
updateWidget();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Initial display
|
||||||
|
updateWidget();
|
||||||
|
|
||||||
// Use task-level timeout if set, otherwise fall back to config
|
// Use task-level timeout if set, otherwise fall back to config
|
||||||
const timeoutMs = task.timeoutMs ?? config.execution.timeoutMs;
|
const timeoutMs = task.timeoutMs ?? config.execution.timeoutMs;
|
||||||
|
|
||||||
// Collect tool call entries during execution
|
|
||||||
const toolCalls: ToolCallEntry[] = [];
|
|
||||||
let lastUpdateCount = 0;
|
|
||||||
const UPDATE_THROTTLE = 5;
|
|
||||||
|
|
||||||
// Run task asynchronously via Pi SDK — event loop stays responsive
|
// Run task asynchronously via Pi SDK — event loop stays responsive
|
||||||
const output = await runAgentSession(
|
const output = await runAgentSession(
|
||||||
prompt,
|
prompt,
|
||||||
project.sourceDir,
|
projectDir,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
(event) => {
|
(event) => {
|
||||||
if (event.type === "tool_execution_start") {
|
if (event.type === "tool_execution_start") {
|
||||||
|
const label = formatToolArg(event.toolName, event.args);
|
||||||
toolCalls.push({
|
toolCalls.push({
|
||||||
name: event.toolName,
|
name: event.toolName,
|
||||||
label: formatToolArg(event.toolName, event.args),
|
label,
|
||||||
});
|
});
|
||||||
// Send periodic chat update every N tool calls
|
// Update widget with latest tool call info
|
||||||
if (toolCalls.length - lastUpdateCount >= UPDATE_THROTTLE) {
|
lastToolLabel = `[${event.toolName}] ${label}`;
|
||||||
lastUpdateCount = toolCalls.length;
|
updateWidget();
|
||||||
sendChatMessage?.(buildRunningMessage(taskHeader, toolCalls));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const durationMs = Date.now() - startMs;
|
const durationMs = Date.now() - startMs;
|
||||||
|
|
||||||
// Clear working message after task finishes
|
// Clear progress widget and status after task finishes
|
||||||
ctx.ui.setWorkingMessage();
|
clearInterval(spinnerTimer);
|
||||||
|
ctx.ui.setWidget("ralph-task", undefined);
|
||||||
ctx.ui.setStatus("ralph", undefined);
|
ctx.ui.setStatus("ralph", undefined);
|
||||||
|
|
||||||
if (!output.success) {
|
if (!output.success) {
|
||||||
@@ -116,13 +147,11 @@ export async function runTask(
|
|||||||
const toolUsage = output.toolUsage;
|
const toolUsage = output.toolUsage;
|
||||||
|
|
||||||
// Capture git commits made during this task
|
// Capture git commits made during this task
|
||||||
const { commitMessages, commitSummary } = captureGitCommits(
|
const { commitMessages, commitSummary } = captureGitCommits(projectDir);
|
||||||
project.sourceDir,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Save full session transcript to .ralph/sessions/
|
// Save full session transcript to .ralph/sessions/
|
||||||
const sessionFile = saveSessionOutput(
|
const sessionFile = saveSessionOutput(
|
||||||
project.sourceDir,
|
projectDir,
|
||||||
task.id,
|
task.id,
|
||||||
JSON.stringify(output.events, null, 2),
|
JSON.stringify(output.events, null, 2),
|
||||||
);
|
);
|
||||||
@@ -136,10 +165,9 @@ export async function runTask(
|
|||||||
// Extract reflection from agent output
|
// Extract reflection from agent output
|
||||||
const reflection = extractReflection(agentText, task.id, task.title);
|
const reflection = extractReflection(agentText, task.id, task.title);
|
||||||
|
|
||||||
// Post completion chat message with tree format
|
// Post completion chat message — header only, renderer builds the expandable tree
|
||||||
const dur = formatDuration(durationMs);
|
const dur = formatDuration(durationMs);
|
||||||
const tree = formatToolCallTree(taskHeader, toolCalls, dur);
|
sendChatMessage?.(`✓ ${taskHeader} (${dur})`, { toolCalls });
|
||||||
sendChatMessage?.(tree);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -182,6 +210,7 @@ export async function executeBatch(
|
|||||||
ctx: ExtensionCommandContext,
|
ctx: ExtensionCommandContext,
|
||||||
options?: { parallel?: boolean },
|
options?: { parallel?: boolean },
|
||||||
sendChatMessage?: SendChatMessage,
|
sendChatMessage?: SendChatMessage,
|
||||||
|
projectDir?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Check if we should run parallel
|
// Check if we should run parallel
|
||||||
const shouldParallel =
|
const shouldParallel =
|
||||||
@@ -195,13 +224,22 @@ export async function executeBatch(
|
|||||||
progress,
|
progress,
|
||||||
ctx,
|
ctx,
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
|
projectDir,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute sequentially
|
// Execute sequentially
|
||||||
for (const task of tasks) {
|
for (const task of tasks) {
|
||||||
await executeTask(task, project, config, progress, ctx, sendChatMessage);
|
await executeTask(
|
||||||
|
task,
|
||||||
|
project,
|
||||||
|
config,
|
||||||
|
progress,
|
||||||
|
ctx,
|
||||||
|
sendChatMessage,
|
||||||
|
projectDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +253,7 @@ async function executeBatchParallel(
|
|||||||
progress: ProgressTracker,
|
progress: ProgressTracker,
|
||||||
ctx: ExtensionCommandContext,
|
ctx: ExtensionCommandContext,
|
||||||
sendChatMessage?: SendChatMessage,
|
sendChatMessage?: SendChatMessage,
|
||||||
|
projectDir?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const maxParallel = config.execution.maxParallel;
|
const maxParallel = config.execution.maxParallel;
|
||||||
const results: Array<{ task: Task; result: Promise<any> }> = [];
|
const results: Array<{ task: Task; result: Promise<any> }> = [];
|
||||||
@@ -229,6 +268,7 @@ async function executeBatchParallel(
|
|||||||
progress,
|
progress,
|
||||||
ctx,
|
ctx,
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
|
projectDir,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -254,6 +294,7 @@ async function executeTask(
|
|||||||
progress: ProgressTracker,
|
progress: ProgressTracker,
|
||||||
ctx: ExtensionCommandContext,
|
ctx: ExtensionCommandContext,
|
||||||
sendChatMessage?: SendChatMessage,
|
sendChatMessage?: SendChatMessage,
|
||||||
|
projectDir: string = project.sourceDir,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const maxRetries = config.execution.maxRetries;
|
const maxRetries = config.execution.maxRetries;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
@@ -276,12 +317,13 @@ async function executeTask(
|
|||||||
depReflections,
|
depReflections,
|
||||||
ctx,
|
ctx,
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
|
projectDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// Save reflection
|
// Save reflection
|
||||||
if (result.reflection) {
|
if (result.reflection) {
|
||||||
saveReflectionToFile(project.sourceDir, config, result.reflection);
|
saveReflectionToFile(projectDir, config, result.reflection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark completed with all metadata
|
// Mark completed with all metadata
|
||||||
@@ -343,8 +385,6 @@ function sleep(ms: number): Promise<void> {
|
|||||||
|
|
||||||
// ─── Tool Call Formatting ────────────────────────────────────────────────
|
// ─── Tool Call Formatting ────────────────────────────────────────────────
|
||||||
|
|
||||||
const MAX_DETAIL_TOOL_CALLS = 3;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a tool call argument into a short label.
|
* Format a tool call argument into a short label.
|
||||||
*/
|
*/
|
||||||
@@ -377,76 +417,3 @@ function truncateMiddle(s: string, maxLen: number): string {
|
|||||||
const half = Math.floor((maxLen - 3) / 2);
|
const half = Math.floor((maxLen - 3) / 2);
|
||||||
return s.slice(0, half) + "…" + s.slice(s.length - half);
|
return s.slice(0, half) + "…" + s.slice(s.length - half);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a brief running-status chat message (displayed during task execution).
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* ⠿ 05 · billing-subscriptions-trials
|
|
||||||
* ├── 12 tools
|
|
||||||
* └── [bash] find /path -name "*.tsx"
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
function buildRunningMessage(
|
|
||||||
header: string,
|
|
||||||
toolCalls: ToolCallEntry[],
|
|
||||||
): string {
|
|
||||||
const lines: string[] = [`⠿ ${header}`];
|
|
||||||
const last = toolCalls[toolCalls.length - 1];
|
|
||||||
if (last) {
|
|
||||||
lines.push(` ├── ${toolCalls.length} tools`);
|
|
||||||
lines.push(` └── [${last.name}] ${last.label}`);
|
|
||||||
}
|
|
||||||
return lines.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a tree-format chat message showing tool calls.
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* ✓ 05 · billing-subscriptions-trials (2m 14s)
|
|
||||||
* ├── 24 reads, 14 bash, 6 writes, 5 edits
|
|
||||||
* ├── [bash] find /path -name "*.tsx"
|
|
||||||
* ├── [write] /path/routes/pricing.tsx
|
|
||||||
* └── [bash] npm test ...
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Older calls are summarized as a tool-type breakdown above detailed entries.
|
|
||||||
* Newest calls appear at the bottom.
|
|
||||||
*/
|
|
||||||
function formatToolCallTree(
|
|
||||||
header: string,
|
|
||||||
toolCalls: ToolCallEntry[],
|
|
||||||
duration: string,
|
|
||||||
): string {
|
|
||||||
const lines: string[] = [`✓ ${header} (${duration})`];
|
|
||||||
|
|
||||||
if (toolCalls.length === 0) {
|
|
||||||
return lines.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show tool-type breakdown instead of "N more"
|
|
||||||
const typeCounts = new Map<string, number>();
|
|
||||||
for (const t of toolCalls) {
|
|
||||||
typeCounts.set(t.name, (typeCounts.get(t.name) ?? 0) + 1);
|
|
||||||
}
|
|
||||||
const summary = [...typeCounts.entries()]
|
|
||||||
.map(([name, count]) => `${count} ${name}`)
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
// Determine which entries to show in detail (last N)
|
|
||||||
const shown = toolCalls.slice(-MAX_DETAIL_TOOL_CALLS);
|
|
||||||
|
|
||||||
// Tool-type summary line BEFORE detailed entries
|
|
||||||
lines.push(` ├── ${summary}`);
|
|
||||||
|
|
||||||
// Detailed entries (newest at bottom)
|
|
||||||
for (let i = 0; i < shown.length; i++) {
|
|
||||||
const entry = shown[i];
|
|
||||||
const isLast = i === shown.length - 1;
|
|
||||||
const prefix = isLast ? " └──" : " ├──";
|
|
||||||
lines.push(`${prefix} [${entry.name}] ${entry.label}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.join("\n");
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user