Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Conversation

wonderwhy-er
Copy link
Owner

@wonderwhy-er wonderwhy-er commented Sep 30, 2025

Add new tool to capture and query recent tool call history, helps to restore lost chats or pass context between chats about work that was done recently

Summary by CodeRabbit

  • New Features

    • Added a “Get Recent Tool Calls” tool to view recent tool invocations with filters (tool name, since, max results), durations, and basic stats; included in the tools list.
    • Tool usage is now recorded and persisted across sessions with capped history and batched writes.
  • System Info

    • System info now includes current memory usage details.
  • Documentation

    • README updated to document the new tool.

…restore lost chats or pass context between chats about work that was done recently
Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

Walkthrough

Adds a persisted tool-invocation history, exposes a new get_recent_tool_calls tool and schema, integrates recording of tool calls (with duration) into the server flow (excluding the new tool), updates manifest/README, and augments system info with runtime memory metrics.

Changes

Cohort / File(s) Summary
Docs & Manifest
README.md, manifest.template.json
Document and declare new tool get_recent_tool_calls and its description in README and manifest.
Schemas
src/tools/schemas.ts
Add GetRecentToolCallsArgsSchema (fields: maxResults, toolName, since).
History Utility
src/utils/toolHistory.ts
New JSONL-backed toolHistory singleton: addCall, getRecentCalls, getRecentCallsFormatted, getStats, in-memory sliding cap, batched async writes, trimming, and cleanup.
Handlers: History
src/handlers/history-handlers.ts
New handleGetRecentToolCalls exported function: validate args, fetch formatted history and stats, return composed content or error.
Handlers Index
src/handlers/index.ts
Re-export history handlers (export * from './history-handlers.js').
Server Integration
src/server.ts
Register get_recent_tool_calls tool and schema; import toolHistory; record startTime per CallToolRequest; add post-call toolHistory.addCall(name, args, result, duration) for non-excluded tools; handle get_recent_tool_calls via handler and include tool in ListTools.
Tools Config
src/tools/config.ts
Add runtime memory metrics to systemInfo.memory (rss, heapTotal, heapUsed, external, arrayBuffers in MB).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant S as Server
  participant H as Handlers
  participant TH as toolHistory

  Note over C,S: CallToolRequest(name, args)
  C->>S: CallToolRequest(name, args)
  S->>S: startTime = Date.now()
  alt name == get_recent_tool_calls
    S->>H: handleGetRecentToolCalls(args)
    H->>TH: getRecentCallsFormatted(filters)
    TH-->>H: formattedRecords
    H->>TH: getStats()
    TH-->>H: stats
    H-->>S: ServerResult(content)
    Note over S,TH: Do not add history entry (avoids recursion)
  else other tool
    S->>S: execute tool handler
    S->>TH: addCall(name, args, result, duration)
    TH-->>S: write enqueued
  end
  S-->>C: ToolResponse
Loading
sequenceDiagram
  autonumber
  participant S as Server
  participant H as history-handlers
  participant TH as toolHistory

  S->>H: handleGetRecentToolCalls(args)
  H->>H: parse args with GetRecentToolCallsArgsSchema
  H->>TH: getRecentCallsFormatted({maxResults, toolName, since})
  TH-->>H: records[]
  H->>TH: getStats()
  TH-->>H: stats
  H-->>S: { content: summary + pretty JSON }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

size:M

Suggested reviewers

  • dmitry-ottic-ai

Poem

I hop through logs with whiskers bright,
I count each call by day and night.
Durations tucked in JSON lairs,
I nibble crumbs from tool-call fares.
Ask my history—I'll bring carrots and bytes! 🐇📜

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add tool call history" directly and concisely summarizes the primary change of introducing a mechanism for recording and querying recent tool call history, without unnecessary details or noise. It clearly conveys the addition of the new feature, matching the main intent described in the changeset. This concise phrasing ensures that teammates can quickly understand the PR’s focus.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tool-history

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d564b1 and e816bb5.

📒 Files selected for processing (1)
  • src/utils/toolHistory.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/toolHistory.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
src/tools/config.ts (1)

18-25: Expose raw bytes (non-breaking) and capture timestamp for observability

Keep the human-friendly MB strings, but also include raw byte counters and a capture timestamp to enable machine use and time correlation.

-    const memory = {
-      rss: `${(memoryUsage.rss / 1024 / 1024).toFixed(2)} MB`,
-      heapTotal: `${(memoryUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`,
-      heapUsed: `${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`,
-      external: `${(memoryUsage.external / 1024 / 1024).toFixed(2)} MB`,
-      arrayBuffers: `${(memoryUsage.arrayBuffers / 1024 / 1024).toFixed(2)} MB`
-    };
+    const memory = {
+      // human-friendly
+      rss: `${(memoryUsage.rss / 1024 / 1024).toFixed(2)} MB`,
+      heapTotal: `${(memoryUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`,
+      heapUsed: `${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`,
+      external: `${(memoryUsage.external / 1024 / 1024).toFixed(2)} MB`,
+      arrayBuffers: `${(memoryUsage.arrayBuffers / 1024 / 1024).toFixed(2)} MB`,
+      // machine-friendly
+      raw: {
+        rss: memoryUsage.rss,
+        heapTotal: memoryUsage.heapTotal,
+        heapUsed: memoryUsage.heapUsed,
+        external: memoryUsage.external,
+        arrayBuffers: memoryUsage.arrayBuffers
+      },
+      capturedAt: new Date().toISOString()
+    };
README.md (1)

439-440: Document defaults/filters inline for quicker adoption

Add defaults/filters to the row so users know how to scope results without hunting.

-| | `get_recent_tool_calls` | Get recent tool call history with arguments and outputs for debugging and context recovery |
+| | `get_recent_tool_calls` | Get recent tool call history with arguments and outputs (default: last 50; filters: `toolName?`, `since?`, `maxResults<=1000`) for debugging and context recovery |
manifest.template.json (1)

125-128: Align description with persisted history (avoid “during this session”)

Since history is persisted, “during this session” can mislead. Use “recent” and mention persistence.

-      "description": "Get recent tool call history with their arguments and outputs. Returns chronological list of tool calls made during this session for debugging, context recovery, and onboarding new chats."
+      "description": "Get recent tool call history with their arguments and outputs. Returns a chronological list of recent (persisted) tool calls for debugging, context recovery, and onboarding new chats."
src/tools/schemas.ts (1)

141-146: Tighten schema: simpler default, trimmed toolName, timezone-aware datetime

Small Zod tweaks improve DX and reduce parsing edge cases.

-export const GetRecentToolCallsArgsSchema = z.object({
-  maxResults: z.number().min(1).max(1000).optional().default(50),
-  toolName: z.string().optional(),
-  since: z.string().datetime().optional(),
-});
+export const GetRecentToolCallsArgsSchema = z.object({
+  maxResults: z.number().min(1).max(1000).default(50),
+  toolName: z.string().trim().optional(),
+  // require/accept timezone in ISO-8601; keeps server-side comparisons unambiguous
+  since: z.string().datetime({ offset: true }).optional(),
+});

Optionally support numeric epoch and/or coerce to Date in the handler if needed.

src/utils/toolHistory.ts (2)

57-88: Use appropriate log level for non-error conditions.

Line 60 logs "No history file found, starting fresh" as an error via console.error, but this is an expected condition on first run and should be informational. Consider using a standard log or removing this message entirely since it's not actionable.

Apply this diff to use a more appropriate log level:

       if (!fs.existsSync(this.historyFile)) {
-        console.error('[ToolHistory] No history file found, starting fresh');
+        console.log('[ToolHistory] No history file found, starting fresh');
         return;
       }

124-142: Consider limiting write queue growth on persistent failures.

If disk writes repeatedly fail (lines 136-138), failed entries are continuously pushed back to the queue with unshift, potentially causing unbounded memory growth. While rare, persistent I/O failures (full disk, permissions) could accumulate entries over time.

Consider adding a maximum retry count or queue size limit:

   } catch (error) {
     console.error('[ToolHistory] Failed to write to disk:', error);
-    // Put back in queue on failure
-    this.writeQueue.unshift(...toWrite);
+    // Put back in queue on failure (with limit to prevent unbounded growth)
+    if (this.writeQueue.length + toWrite.length < this.MAX_ENTRIES * 2) {
+      this.writeQueue.unshift(...toWrite);
+    } else {
+      console.error('[ToolHistory] Write queue full, dropping oldest entries');
+    }
   } finally {
src/server.ts (1)

1182-1190: Clarify which tools are excluded from history.

The tool description (line 827) states "Does not track its own calls or other meta/query tools", but the exclusion list only contains get_recent_tool_calls. Other query-oriented tools like get_config, get_usage_stats, list_sessions, etc. are recorded in history.

This might be intentional for context restoration purposes, but the description should be clarified to match the implementation.

If the current behavior is intended (only excluding get_recent_tool_calls to prevent recursion), update the tool description:

-                        Note: Does not track its own calls or other meta/query tools.
+                        Note: Does not track its own calls (to prevent recursion).

Alternatively, if other meta tools should also be excluded, add them to the EXCLUDED_TOOLS array and verify this doesn't break context restoration use cases.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9407fd and 35b2059.

📒 Files selected for processing (8)
  • README.md (1 hunks)
  • manifest.template.json (1 hunks)
  • src/handlers/history-handlers.ts (1 hunks)
  • src/handlers/index.ts (1 hunks)
  • src/server.ts (6 hunks)
  • src/tools/config.ts (1 hunks)
  • src/tools/schemas.ts (1 hunks)
  • src/utils/toolHistory.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/handlers/history-handlers.ts (3)
src/types.ts (1)
  • ServerResult (52-56)
src/tools/schemas.ts (1)
  • GetRecentToolCallsArgsSchema (142-146)
src/utils/toolHistory.ts (1)
  • toolHistory (229-229)
src/utils/toolHistory.ts (1)
src/types.ts (1)
  • ServerResult (52-56)
src/server.ts (2)
src/tools/schemas.ts (1)
  • GetRecentToolCallsArgsSchema (142-146)
src/utils/toolHistory.ts (1)
  • toolHistory (229-229)
🔇 Additional comments (13)
src/handlers/index.ts (1)

7-7: Re-export looks good

Consistent with existing wildcard exports; no issues.

src/tools/config.ts (1)

30-33: Confirm uniqueness of memory in getSystemInfo payload
Double-check whether getSystemInfo() already returns a memory field; if so, namespace or rename (e.g. processMemory) to prevent collisions.

src/handlers/history-handlers.ts (4)

1-3: LGTM!

Clean imports with proper typing and modular organization.


5-8: LGTM!

Function signature follows the established handler pattern with runtime validation via Zod.


9-30: LGTM!

The implementation correctly retrieves and formats tool history with appropriate filtering and pagination. The response format is clear and user-friendly.

Note: Line 21 mentions "excluding file path per user request" but the current output doesn't include file path anyway, so this comment is just historical context.


31-39: LGTM!

Proper error handling that returns a well-formed ServerResult with appropriate error flag and message extraction.

src/utils/toolHistory.ts (4)

1-12: LGTM!

Clean interface definition with appropriate types. Using any for arguments is pragmatic given the variety of tool schemas.


14-26: LGTM!

Clean timestamp formatting utility. The hardcoded en-US locale ensures consistent formatting across environments, which is appropriate for this tool history feature.


90-108: LGTM!

The trim operation appropriately uses synchronous I/O since it's called during initialization. The approach of rewriting the file with the last 1000 entries is straightforward and effective.


147-229: LGTM!

The filtering logic is correct and well-structured. The use of slice(-limit) ensures the most recent entries are returned in chronological order. Schema validation upstream ensures since is a valid ISO datetime string, preventing invalid Date parsing.

The singleton export pattern is appropriate for this utility.

src/server.ts (3)

48-48: LGTM!

New imports are correctly added and follow the existing import organization pattern.

Also applies to: 57-57


921-921: LGTM!

Proper placement of timestamp capture for duration measurement at the start of request processing.


1069-1079: LGTM!

The handler follows the established pattern for tool request processing with proper error handling and telemetry capture.

Comment on lines +816 to +836
{
name: "get_recent_tool_calls",
description: `
Get recent tool call history with their arguments and outputs.
Returns chronological list of tool calls made during this session.
Useful for:
- Onboarding new chats about work already done
- Recovering context after chat history loss
- Debugging tool call sequences
Note: Does not track its own calls or other meta/query tools.
History kept in memory (last 1000 calls, lost on restart).
${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(GetRecentToolCallsArgsSchema),
annotations: {
title: "Get Recent Tool Calls",
readOnlyHint: true,
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Correct the documentation about history persistence.

Line 828 states "History kept in memory (last 1000 calls, lost on restart)", but according to src/utils/toolHistory.ts, history is actually persisted to disk in ~/.claude-server-commander/tool-history.jsonl and reloaded on startup. Only the in-memory cache is limited to 1000 entries, but the full history survives restarts.

Apply this diff to correct the documentation:

                     description: `
                         Get recent tool call history with their arguments and outputs.
                         Returns chronological list of tool calls made during this session.
                         
                         Useful for:
                         - Onboarding new chats about work already done
                         - Recovering context after chat history loss
                         - Debugging tool call sequences
                         
                         Note: Does not track its own calls or other meta/query tools.
-                        History kept in memory (last 1000 calls, lost on restart).
+                        History persisted to disk and survives restarts (last 1000 calls kept in memory).
                         
                         ${CMD_PREFIX_DESCRIPTION}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
name: "get_recent_tool_calls",
description: `
Get recent tool call history with their arguments and outputs.
Returns chronological list of tool calls made during this session.
Useful for:
- Onboarding new chats about work already done
- Recovering context after chat history loss
- Debugging tool call sequences
Note: Does not track its own calls or other meta/query tools.
History kept in memory (last 1000 calls, lost on restart).
${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(GetRecentToolCallsArgsSchema),
annotations: {
title: "Get Recent Tool Calls",
readOnlyHint: true,
},
},
{
name: "get_recent_tool_calls",
description: `
Get recent tool call history with their arguments and outputs.
Returns chronological list of tool calls made during this session.
Useful for:
- Onboarding new chats about work already done
- Recovering context after chat history loss
- Debugging tool call sequences
Note: Does not track its own calls or other meta/query tools.
History persisted to disk and survives restarts (last 1000 calls kept in memory).
${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(GetRecentToolCallsArgsSchema),
annotations: {
title: "Get Recent Tool Calls",
readOnlyHint: true,
},
},
🤖 Prompt for AI Agents
In src/server.ts around lines 816 to 836, the description for
"get_recent_tool_calls" incorrectly states the full history is lost on restart;
update the text to note that the history is persisted to disk at
~/.claude-server-commander/tool-history.jsonl and reloaded on startup, while
only the in-memory cache is limited to the last 1000 entries (which may be
cleared on restart), and keep the rest of the description and
CMD_PREFIX_DESCRIPTION interpolation unchanged.

- Add writeInterval property to track setInterval reference
- Use unref() to allow graceful process shutdown
- Add cleanup() method to properly dispose resources
- Fixes test hang issue where Node process never exited
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/utils/toolHistory.ts (3)

6-12: Consider using unknown instead of any for the arguments field.

The any type bypasses type checking. Using unknown would preserve type safety while maintaining flexibility—consumers would need to validate/narrow the type before use.

Apply this diff:

 export interface ToolCallRecord {
   timestamp: string;
   toolName: string;
-  arguments: any;
+  arguments: unknown;
   output: ServerResult;
   duration?: number;
 }

14-26: Consider respecting user's locale preference.

The function hardcodes 'en-US' locale. Using the default locale (omit the locale parameter) would respect the user's system settings and provide a more localized experience.

Apply this diff:

 function formatLocalTimestamp(isoTimestamp: string): string {
   const date = new Date(isoTimestamp);
-  return date.toLocaleString('en-US', {
+  return date.toLocaleString(undefined, {
     year: 'numeric',
     month: '2-digit',
     day: '2-digit',
     hour: '2-digit',
     minute: '2-digit',
     second: '2-digit',
     hour12: false
   });
 }

36-53: Synchronous file operations block initialization.

The constructor uses fs.existsSync and fs.mkdirSync, which block the event loop during startup. For a singleton initialized at module load, this is generally acceptable, but consider documenting this trade-off or providing an async initialization method if startup performance becomes a concern.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35b2059 and 5d564b1.

📒 Files selected for processing (1)
  • src/utils/toolHistory.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/utils/toolHistory.ts (1)
src/types.ts (1)
  • ServerResult (52-56)
🔇 Additional comments (6)
src/utils/toolHistory.ts (6)

1-4: LGTM!

The imports are appropriate for the file operations and type definitions needed.


151-174: LGTM!

The addCall method correctly maintains the in-memory sliding window and queues writes asynchronously.


179-200: LGTM!

The filtering and limiting logic is correct. Using slice(-limit) ensures the most recent entries are returned.


222-230: LGTM!

The getStats method correctly exposes runtime metrics.


236-247: LGTM!

The cleanup method properly clears the interval and flushes pending writes. Good practice for graceful shutdown.


250-251: LGTM!

Exporting a singleton instance is appropriate for this utility.

Comment on lines +58 to +89
private loadFromDisk(): void {
try {
if (!fs.existsSync(this.historyFile)) {
console.error('[ToolHistory] No history file found, starting fresh');
return;
}

const content = fs.readFileSync(this.historyFile, 'utf-8');
const lines = content.trim().split('\n').filter(line => line.trim());

// Parse each line as JSON
const records: ToolCallRecord[] = [];
for (const line of lines) {
try {
records.push(JSON.parse(line));
} catch (e) {
console.error('[ToolHistory] Failed to parse line:', line);
}
}

// Keep only last 1000 entries
this.history = records.slice(-this.MAX_ENTRIES);
console.error(`[ToolHistory] Loaded ${this.history.length} entries from disk`);

// If file is getting too large, trim it
if (lines.length > this.MAX_ENTRIES * 2) {
this.trimHistoryFile();
}
} catch (error) {
console.error('[ToolHistory] Failed to load history:', error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use appropriate logging levels.

Lines 61 and 80 use console.error for informational messages ("No history file found, starting fresh" and "Loaded N entries"). Reserve console.error for actual errors; use console.log or console.info for informational messages. This ensures proper log filtering and monitoring.

Apply this diff:

     try {
       if (!fs.existsSync(this.historyFile)) {
-        console.error('[ToolHistory] No history file found, starting fresh');
+        console.log('[ToolHistory] No history file found, starting fresh');
         return;
       }
 
       // ... parsing logic ...
       
       // Keep only last 1000 entries
       this.history = records.slice(-this.MAX_ENTRIES);
-      console.error(`[ToolHistory] Loaded ${this.history.length} entries from disk`);
+      console.log(`[ToolHistory] Loaded ${this.history.length} entries from disk`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private loadFromDisk(): void {
try {
if (!fs.existsSync(this.historyFile)) {
console.error('[ToolHistory] No history file found, starting fresh');
return;
}
const content = fs.readFileSync(this.historyFile, 'utf-8');
const lines = content.trim().split('\n').filter(line => line.trim());
// Parse each line as JSON
const records: ToolCallRecord[] = [];
for (const line of lines) {
try {
records.push(JSON.parse(line));
} catch (e) {
console.error('[ToolHistory] Failed to parse line:', line);
}
}
// Keep only last 1000 entries
this.history = records.slice(-this.MAX_ENTRIES);
console.error(`[ToolHistory] Loaded ${this.history.length} entries from disk`);
// If file is getting too large, trim it
if (lines.length > this.MAX_ENTRIES * 2) {
this.trimHistoryFile();
}
} catch (error) {
console.error('[ToolHistory] Failed to load history:', error);
}
}
private loadFromDisk(): void {
try {
if (!fs.existsSync(this.historyFile)) {
console.log('[ToolHistory] No history file found, starting fresh');
return;
}
const content = fs.readFileSync(this.historyFile, 'utf-8');
const lines = content.trim().split('\n').filter(line => line.trim());
// Parse each line as JSON
const records: ToolCallRecord[] = [];
for (const line of lines) {
try {
records.push(JSON.parse(line));
} catch (e) {
console.error('[ToolHistory] Failed to parse line:', line);
}
}
// Keep only last 1000 entries
this.history = records.slice(-this.MAX_ENTRIES);
console.log(`[ToolHistory] Loaded ${this.history.length} entries from disk`);
// If file is getting too large, trim it
if (lines.length > this.MAX_ENTRIES * 2) {
this.trimHistoryFile();
}
} catch (error) {
console.error('[ToolHistory] Failed to load history:', error);
}
}
🤖 Prompt for AI Agents
In src/utils/toolHistory.ts around lines 58 to 89, change the two informational
console.error calls to use an appropriate info-level logger: replace the
console.error at the "No history file found, starting fresh" message with
console.info (or console.log) and replace the console.error after loading
entries ("Loaded N entries from disk") with console.info (or console.log); keep
other console.error usages for actual error cases (parse failures and catch
block) unchanged so only true errors remain at error level.

Comment on lines +94 to +109
private trimHistoryFile(): void {
try {
console.error('[ToolHistory] Trimming history file...');

// Keep last 1000 entries in memory
const keepEntries = this.history.slice(-this.MAX_ENTRIES);

// Write them back
const lines = keepEntries.map(entry => JSON.stringify(entry)).join('\n') + '\n';
fs.writeFileSync(this.historyFile, lines, 'utf-8');

console.error(`[ToolHistory] Trimmed to ${keepEntries.length} entries`);
} catch (error) {
console.error('[ToolHistory] Failed to trim history file:', error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use appropriate logging levels.

Lines 96 and 105 use console.error for informational messages. Use console.log or console.info instead.

Apply this diff:

   private trimHistoryFile(): void {
     try {
-      console.error('[ToolHistory] Trimming history file...');
+      console.log('[ToolHistory] Trimming history file...');
       
       // Keep last 1000 entries in memory
       const keepEntries = this.history.slice(-this.MAX_ENTRIES);
       
       // Write them back
       const lines = keepEntries.map(entry => JSON.stringify(entry)).join('\n') + '\n';
       fs.writeFileSync(this.historyFile, lines, 'utf-8');
       
-      console.error(`[ToolHistory] Trimmed to ${keepEntries.length} entries`);
+      console.log(`[ToolHistory] Trimmed to ${keepEntries.length} entries`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private trimHistoryFile(): void {
try {
console.error('[ToolHistory] Trimming history file...');
// Keep last 1000 entries in memory
const keepEntries = this.history.slice(-this.MAX_ENTRIES);
// Write them back
const lines = keepEntries.map(entry => JSON.stringify(entry)).join('\n') + '\n';
fs.writeFileSync(this.historyFile, lines, 'utf-8');
console.error(`[ToolHistory] Trimmed to ${keepEntries.length} entries`);
} catch (error) {
console.error('[ToolHistory] Failed to trim history file:', error);
}
}
private trimHistoryFile(): void {
try {
console.log('[ToolHistory] Trimming history file...');
// Keep last 1000 entries in memory
const keepEntries = this.history.slice(-this.MAX_ENTRIES);
// Write them back
const lines = keepEntries.map(entry => JSON.stringify(entry)).join('\n') + '\n';
fs.writeFileSync(this.historyFile, lines, 'utf-8');
console.log(`[ToolHistory] Trimmed to ${keepEntries.length} entries`);
} catch (error) {
console.error('[ToolHistory] Failed to trim history file:', error);
}
}
🤖 Prompt for AI Agents
In src/utils/toolHistory.ts around lines 94 to 109, the method trimHistoryFile
uses console.error for informational messages on lines 96 and 105; change those
calls to console.info (or console.log) so only actual errors are logged with
console.error in the catch block, leaving the error handling unchanged; update
the two console.error invocations that print progress to console.info and keep
the final catch's console.error as-is.

Comment on lines +114 to +146
private startWriteProcessor(): void {
this.writeInterval = setInterval(() => {
if (this.writeQueue.length > 0 && !this.isWriting) {
this.flushToDisk();
}
}, 1000); // Flush every second

// Prevent interval from keeping process alive during shutdown/tests
this.writeInterval.unref();
}

/**
* Flush queued writes to disk
*/
private async flushToDisk(): Promise<void> {
if (this.isWriting || this.writeQueue.length === 0) return;

this.isWriting = true;
const toWrite = [...this.writeQueue];
this.writeQueue = [];

try {
// Append to file (atomic append operation)
const lines = toWrite.map(entry => JSON.stringify(entry)).join('\n') + '\n';
fs.appendFileSync(this.historyFile, lines, 'utf-8');
} catch (error) {
console.error('[ToolHistory] Failed to write to disk:', error);
// Put back in queue on failure
this.writeQueue.unshift(...toWrite);
} finally {
this.isWriting = false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use async I/O in the async flush method.

Line 138 uses fs.appendFileSync in an async method. This blocks the event loop and defeats the purpose of async processing. Use fs.promises.appendFile instead for non-blocking I/O.

Additionally, line 142's unshift could reorder failed writes relative to newer queued writes, though this may be acceptable depending on requirements.

Apply this diff:

+import { promises as fsPromises } from 'fs';
+
 // ... later in the file ...

   private async flushToDisk(): Promise<void> {
     if (this.isWriting || this.writeQueue.length === 0) return;
     
     this.isWriting = true;
     const toWrite = [...this.writeQueue];
     this.writeQueue = [];
     
     try {
       // Append to file (atomic append operation)
       const lines = toWrite.map(entry => JSON.stringify(entry)).join('\n') + '\n';
-      fs.appendFileSync(this.historyFile, lines, 'utf-8');
+      await fsPromises.appendFile(this.historyFile, lines, 'utf-8');
     } catch (error) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private startWriteProcessor(): void {
this.writeInterval = setInterval(() => {
if (this.writeQueue.length > 0 && !this.isWriting) {
this.flushToDisk();
}
}, 1000); // Flush every second
// Prevent interval from keeping process alive during shutdown/tests
this.writeInterval.unref();
}
/**
* Flush queued writes to disk
*/
private async flushToDisk(): Promise<void> {
if (this.isWriting || this.writeQueue.length === 0) return;
this.isWriting = true;
const toWrite = [...this.writeQueue];
this.writeQueue = [];
try {
// Append to file (atomic append operation)
const lines = toWrite.map(entry => JSON.stringify(entry)).join('\n') + '\n';
fs.appendFileSync(this.historyFile, lines, 'utf-8');
} catch (error) {
console.error('[ToolHistory] Failed to write to disk:', error);
// Put back in queue on failure
this.writeQueue.unshift(...toWrite);
} finally {
this.isWriting = false;
}
}
// At the top of src/utils/toolHistory.ts, alongside your other fs imports:
import { promises as fsPromises } from 'fs';
// ... later in the file ...
/**
* Flush queued writes to disk
*/
private async flushToDisk(): Promise<void> {
if (this.isWriting || this.writeQueue.length === 0) return;
this.isWriting = true;
const toWrite = [...this.writeQueue];
this.writeQueue = [];
try {
// Append to file (atomic append operation)
const lines = toWrite.map(entry => JSON.stringify(entry)).join('\n') + '\n';
await fsPromises.appendFile(this.historyFile, lines, 'utf-8');
} catch (error) {
console.error('[ToolHistory] Failed to write to disk:', error);
// Put back in queue on failure
this.writeQueue.unshift(...toWrite);
} finally {
this.isWriting = false;
}
}
🤖 Prompt for AI Agents
In src/utils/toolHistory.ts around lines 114 to 146, flushToDisk is async but
uses the blocking fs.appendFileSync and re-inserts failed batches with unshift
which can reorder items; change to use await
fs.promises.appendFile(this.historyFile, lines, 'utf-8') inside the try block so
I/O is non-blocking, and on failure restore the queue without reordering by
setting this.writeQueue = [...toWrite, ...this.writeQueue] (or otherwise choose
intended ordering) so failed entries remain before newer ones, keeping isWriting
toggling and error handling otherwise the same.

src/utils/toolHistory.ts Show resolved Hide resolved
@wonderwhy-er wonderwhy-er merged commit 4fb8d28 into main Oct 1, 2025
2 checks passed
@wonderwhy-er wonderwhy-er deleted the tool-history branch October 1, 2025 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.