fix: 为 sessionStorage existingSessionFiles Map 添加容量上限#1227
Conversation
Spinner.tsx 的 token 聚合循环仅统计 in_process_teammate 类型任务, 漏掉了 local_agent(后台代理/verification agent)类型。当后台代理 运行时,主界面 spinner 一直显示 "↓ 0 tokens",因为 background agent 的 token 消耗未被纳入 teammateTokens 聚合。 同时在 inProcessRunner.ts 中,进程内队友完成时计算并设置 result (含 totalTokens/totalToolUseCount/content/usage),使详情弹窗可以 正确展示累计 token 消耗,不再仅依赖 progress.tokenCount 间歇更新。 Co-Authored-By: deepseek-v4-pro[1m] <deepseek-ai@claude-code-best.win>
Map 以 querySource 为 key 存储每个来源的缓存命中率历史状态, 但 querySource 类型为 `any`,长时间会话中可能产生大量唯一值, Map 持续增长永不清理。 新增 MAX_SOURCE_ENTRIES = 50 上限,新增条目时若达到上限则 逐出最早插入的条目(Map 按插入顺序迭代)。 同时也新增 _resetCacheWarningStateForTest() 用于测试隔离。 Co-Authored-By: deepseek-v4-pro[1m] <deepseek-ai@claude-code-best.win>
existingSessionFiles Map 缓存 sessionId → 文件路径映射以避免重复 stat 调用,但在 coordinator/swarm 模式下,每个子代理产生独立 sessionId,长时间运行的 daemon 会话可能累积数千条目。 新增 MAX_CACHED_SESSION_FILES = 200 上限,新增条目时若达到上限则 逐出最早插入的条目。同时在 _resetFlushState() 中清除此缓存以保证 测试隔离。 Co-Authored-By: deepseek-v4-pro[1m] <deepseek-ai@claude-code-best.win>
📝 WalkthroughWalkthroughThis PR adds memory bounding safety improvements across two utilities, captures structured execution results for in-process teammate runs with duration and token accounting, and expands the spinner UI to aggregate tokens from both in-process and local-agent tasks. ChangesMemory Bounding Improvements
Teammate Execution Tracking and Reporting
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/utils/swarm/inProcessRunner.ts`:
- Around line 1472-1511: The loop uses "as any[]" for message blocks; replace
that with a safe typed form and runtime type guards: cast m.message?.content to
unknown[] or unknown and then narrow it (e.g., const blocksUnk =
m.message?.content ?? []; treat blocksUnk as unknown[] and validate each entry
with typeof checks or by asserting it as Record<string, unknown> before
accessing .type/.text), update the variables that depend on those fields
(blocks, textBlocks, lastAssistantContent) to use the narrowed types, and if a
static cast is required use the double assertion pattern (unknown as
SpecificType[]) rather than "as any"; keep AgentToolResult usage and
getTokenCountFromUsage calls unchanged but ensure lastUsage and completionTokens
derive from the properly typed m.message.usage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 73575530-2c56-4a34-9555-d125b33ce0d9
📒 Files selected for processing (4)
src/components/Spinner.tsxsrc/utils/cacheWarning.tssrc/utils/sessionStorage.tssrc/utils/swarm/inProcessRunner.ts
| // Compute result so the detail dialog can show token usage. | ||
| // Walk backwards for the last API usage (cumulative input_tokens from the | ||
| // Anthropic API already includes all prior context). | ||
| let completionTokens = 0 | ||
| let completionToolUseCount = 0 | ||
| let lastAssistantContent: AgentToolResult['content'] = [] | ||
| let lastUsage: AgentToolResult['usage'] | undefined | ||
| for (let i = allMessages.length - 1; i >= 0; i--) { | ||
| const m = allMessages[i]! | ||
| if (m.type === 'assistant') { | ||
| const blocks = (m.message?.content ?? []) as any[] | ||
| for (const b of blocks) { | ||
| if (b?.type === 'tool_use') completionToolUseCount++ | ||
| } | ||
| const textBlocks = blocks.filter((b: any) => b?.type === 'text') | ||
| if (textBlocks.length > 0 && lastAssistantContent.length === 0) { | ||
| lastAssistantContent = textBlocks.map((b: any) => ({ | ||
| type: 'text' as const, | ||
| text: b.text, | ||
| })) | ||
| } | ||
| if (!lastUsage && m.message?.usage) { | ||
| lastUsage = m.message.usage as AgentToolResult['usage'] | ||
| completionTokens = getTokenCountFromUsage( | ||
| m.message.usage as Parameters<typeof getTokenCountFromUsage>[0], | ||
| ) | ||
| } | ||
| if (completionTokens > 0 && lastAssistantContent.length > 0) break | ||
| } | ||
| } | ||
|
|
||
| const teammateResult: AgentToolResult = { | ||
| agentId: identity.agentId, | ||
| agentType: 'teammate', | ||
| content: lastAssistantContent, | ||
| totalToolUseCount: completionToolUseCount, | ||
| totalDurationMs: Date.now() - startTime, | ||
| totalTokens: completionTokens, | ||
| usage: lastUsage as AgentToolResult['usage'], | ||
| } as unknown as AgentToolResult |
There was a problem hiding this comment.
Replace as any with proper type handling.
Line 1482 uses as any[] which violates the coding guidelines. The guidelines prohibit as any in production code and recommend using double assertion or proper type guards instead.
As per coding guidelines, "Prohibit as any type assertions in production code; in test files, as any is permitted for mock data. Use as unknown as SpecificType double assertion or interface supplementation when type mismatches occur" and "Use Record<string, unknown> instead of any for objects with unknown structure".
🔧 Proposed fix using proper type handling
- const blocks = (m.message?.content ?? []) as any[]
+ const blocks = (m.message?.content ?? []) as unknown as Array<Record<string, unknown>>
for (const b of blocks) {
- if (b?.type === 'tool_use') completionToolUseCount++
+ if (typeof b === 'object' && b !== null && b.type === 'tool_use') completionToolUseCount++
}
- const textBlocks = blocks.filter((b: any) => b?.type === 'text')
+ const textBlocks = blocks.filter((b): b is Record<string, unknown> =>
+ typeof b === 'object' && b !== null && b.type === 'text'
+ )
if (textBlocks.length > 0 && lastAssistantContent.length === 0) {
- lastAssistantContent = textBlocks.map((b: any) => ({
+ lastAssistantContent = textBlocks.map(b => ({
type: 'text' as const,
- text: b.text,
+ text: typeof b.text === 'string' ? b.text : '',
}))
}📝 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.
| // Compute result so the detail dialog can show token usage. | |
| // Walk backwards for the last API usage (cumulative input_tokens from the | |
| // Anthropic API already includes all prior context). | |
| let completionTokens = 0 | |
| let completionToolUseCount = 0 | |
| let lastAssistantContent: AgentToolResult['content'] = [] | |
| let lastUsage: AgentToolResult['usage'] | undefined | |
| for (let i = allMessages.length - 1; i >= 0; i--) { | |
| const m = allMessages[i]! | |
| if (m.type === 'assistant') { | |
| const blocks = (m.message?.content ?? []) as any[] | |
| for (const b of blocks) { | |
| if (b?.type === 'tool_use') completionToolUseCount++ | |
| } | |
| const textBlocks = blocks.filter((b: any) => b?.type === 'text') | |
| if (textBlocks.length > 0 && lastAssistantContent.length === 0) { | |
| lastAssistantContent = textBlocks.map((b: any) => ({ | |
| type: 'text' as const, | |
| text: b.text, | |
| })) | |
| } | |
| if (!lastUsage && m.message?.usage) { | |
| lastUsage = m.message.usage as AgentToolResult['usage'] | |
| completionTokens = getTokenCountFromUsage( | |
| m.message.usage as Parameters<typeof getTokenCountFromUsage>[0], | |
| ) | |
| } | |
| if (completionTokens > 0 && lastAssistantContent.length > 0) break | |
| } | |
| } | |
| const teammateResult: AgentToolResult = { | |
| agentId: identity.agentId, | |
| agentType: 'teammate', | |
| content: lastAssistantContent, | |
| totalToolUseCount: completionToolUseCount, | |
| totalDurationMs: Date.now() - startTime, | |
| totalTokens: completionTokens, | |
| usage: lastUsage as AgentToolResult['usage'], | |
| } as unknown as AgentToolResult | |
| // Compute result so the detail dialog can show token usage. | |
| // Walk backwards for the last API usage (cumulative input_tokens from the | |
| // Anthropic API already includes all prior context). | |
| let completionTokens = 0 | |
| let completionToolUseCount = 0 | |
| let lastAssistantContent: AgentToolResult['content'] = [] | |
| let lastUsage: AgentToolResult['usage'] | undefined | |
| for (let i = allMessages.length - 1; i >= 0; i--) { | |
| const m = allMessages[i]! | |
| if (m.type === 'assistant') { | |
| const blocks = (m.message?.content ?? []) as unknown as Array<Record<string, unknown>> | |
| for (const b of blocks) { | |
| if (typeof b === 'object' && b !== null && b.type === 'tool_use') completionToolUseCount++ | |
| } | |
| const textBlocks = blocks.filter((b): b is Record<string, unknown> => | |
| typeof b === 'object' && b !== null && b.type === 'text' | |
| ) | |
| if (textBlocks.length > 0 && lastAssistantContent.length === 0) { | |
| lastAssistantContent = textBlocks.map(b => ({ | |
| type: 'text' as const, | |
| text: typeof b.text === 'string' ? b.text : '', | |
| })) | |
| } | |
| if (!lastUsage && m.message?.usage) { | |
| lastUsage = m.message.usage as AgentToolResult['usage'] | |
| completionTokens = getTokenCountFromUsage( | |
| m.message.usage as Parameters<typeof getTokenCountFromUsage>[0], | |
| ) | |
| } | |
| if (completionTokens > 0 && lastAssistantContent.length > 0) break | |
| } | |
| } | |
| const teammateResult: AgentToolResult = { | |
| agentId: identity.agentId, | |
| agentType: 'teammate', | |
| content: lastAssistantContent, | |
| totalToolUseCount: completionToolUseCount, | |
| totalDurationMs: Date.now() - startTime, | |
| totalTokens: completionTokens, | |
| usage: lastUsage as AgentToolResult['usage'], | |
| } as unknown as AgentToolResult |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/utils/swarm/inProcessRunner.ts` around lines 1472 - 1511, The loop uses
"as any[]" for message blocks; replace that with a safe typed form and runtime
type guards: cast m.message?.content to unknown[] or unknown and then narrow it
(e.g., const blocksUnk = m.message?.content ?? []; treat blocksUnk as unknown[]
and validate each entry with typeof checks or by asserting it as Record<string,
unknown> before accessing .type/.text), update the variables that depend on
those fields (blocks, textBlocks, lastAssistantContent) to use the narrowed
types, and if a static cast is required use the double assertion pattern
(unknown as SpecificType[]) rather than "as any"; keep AgentToolResult usage and
getTokenCountFromUsage calls unchanged but ensure lastUsage and completionTokens
derive from the properly typed m.message.usage.
Summary
existingSessionFilesMap 缓存 sessionId → 文件路径映射以避免重复stat 调用,但在 coordinator/swarm 模式下,每个子代理产生独立
sessionId,长时间运行的 daemon 会话可能累积数千条目导致内存泄漏。
新增
MAX_CACHED_SESSION_FILES = 200上限,新增条目时若达到上限则逐出最早插入的条目(利用 Map 按插入顺序迭代的特性)。
同时在
_resetFlushState()中清除此缓存以保证测试隔离。Test plan
🤖 Generated with Claude Code Best
Summary by CodeRabbit
Bug Fixes
Performance