-
-
Notifications
You must be signed in to change notification settings - Fork 519
Add tool call history #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…restore lost chats or pass context between chats about work that was done recently
WalkthroughAdds a persisted tool-invocation history, exposes a new Changes
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
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 }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
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. Comment |
There was a problem hiding this 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 observabilityKeep 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 adoptionAdd 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 datetimeSmall 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 likeget_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
📒 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 goodConsistent with existing wildcard exports; no issues.
src/tools/config.ts (1)
30-33
: Confirm uniqueness ofmemory
in getSystemInfo payload
Double-check whethergetSystemInfo()
already returns amemory
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 ensuressince
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.
{ | ||
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, | ||
}, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
{ | |
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
There was a problem hiding this 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 usingunknown
instead ofany
for the arguments field.The
any
type bypasses type checking. Usingunknown
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
andfs.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
📒 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.
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); | ||
} | ||
} |
There was a problem hiding this comment.
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.
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.
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); | ||
} | ||
} |
There was a problem hiding this comment.
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.
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.
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; | ||
} | ||
} |
There was a problem hiding this comment.
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.
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.
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
System Info
Documentation