mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 05:45:51 +00:00
* feat: 第一版大重构 * fix: 修复类型问题 * chore: 更新版本到 1.3.2 * Add brave as alternative WebSearchTool * fix: 修正顺序 * fix: 修复对穷鬼模式的 auto dream 和 session memory 越过 * feat: 穷鬼模式去除 session-summary * feat: 创建 builtin-tools 包,搬运所有工具实现 将 src/tools/ 下的全部 60 个工具目录迁移至 packages/builtin-tools/src/tools/, 内部导入路径已更新为 src/ alias 模式。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 更新 src/ 中所有工具引用至 builtin-tools 包,删除 src/tools/ - src/tools.ts 及 178 个 src/ 文件的 import 路径从 ./tools/ 改为 builtin-tools/tools/ - 删除 src/tools/ 整个目录(已迁移至 packages/builtin-tools/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: 添加 builtin-tools 路径别名至 tsconfig,更新 bun.lock - tsconfig.json 新增 builtin-tools/* 和 builtin-tools 路径映射 - 新增 packages/builtin-tools/src 至 include Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 为 builtin-tools、mcp-client、agent-tools 添加 @claude-code-best 作用域前缀 所有包名及 import 路径统一添加 @claude-code-best/ 前缀: - builtin-tools → @claude-code-best/builtin-tools - mcp-client → @claude-code-best/mcp-client - agent-tools → @claude-code-best/agent-tools Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复 node 环境没有 bun 的问题 --------- Co-authored-by: Eric-Guo <eric.guocz@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
187 lines
6.6 KiB
TypeScript
187 lines
6.6 KiB
TypeScript
/**
|
|
* Periodic background summarization for coordinator mode sub-agents.
|
|
*
|
|
* Forks the sub-agent's conversation every ~30s using runForkedAgent()
|
|
* to generate a 1-2 sentence progress summary. The summary is stored
|
|
* on AgentProgress for UI display.
|
|
*
|
|
* Cache sharing: uses the same CacheSafeParams as the parent agent
|
|
* to share the prompt cache. Tools are kept in the request for cache
|
|
* key matching but denied via canUseTool callback.
|
|
*/
|
|
|
|
import type { TaskContext } from '../../Task.js'
|
|
import { isPoorModeActive } from '../../commands/poor/poorMode.js'
|
|
import { updateAgentSummary } from '../../tasks/LocalAgentTask/LocalAgentTask.js'
|
|
import { filterIncompleteToolCalls } from '@claude-code-best/builtin-tools/tools/AgentTool/runAgent.js'
|
|
import type { AgentId } from '../../types/ids.js'
|
|
import { logForDebugging } from '../../utils/debug.js'
|
|
import {
|
|
type CacheSafeParams,
|
|
runForkedAgent,
|
|
} from '../../utils/forkedAgent.js'
|
|
import { logError } from '../../utils/log.js'
|
|
import { createUserMessage } from '../../utils/messages.js'
|
|
import { getAgentTranscript } from '../../utils/sessionStorage.js'
|
|
|
|
const SUMMARY_INTERVAL_MS = 30_000
|
|
|
|
function buildSummaryPrompt(previousSummary: string | null): string {
|
|
const prevLine = previousSummary
|
|
? `\nPrevious: "${previousSummary}" — say something NEW.\n`
|
|
: ''
|
|
|
|
return `Describe your most recent action in 3-5 words using present tense (-ing). Name the file or function, not the branch. Do not use tools.
|
|
${prevLine}
|
|
Good: "Reading runAgent.ts"
|
|
Good: "Fixing null check in validate.ts"
|
|
Good: "Running auth module tests"
|
|
Good: "Adding retry logic to fetchUser"
|
|
|
|
Bad (past tense): "Analyzed the branch diff"
|
|
Bad (too vague): "Investigating the issue"
|
|
Bad (too long): "Reviewing full branch diff and AgentTool.tsx integration"
|
|
Bad (branch name): "Analyzed adam/background-summary branch diff"`
|
|
}
|
|
|
|
export function startAgentSummarization(
|
|
taskId: string,
|
|
agentId: AgentId,
|
|
cacheSafeParams: CacheSafeParams,
|
|
setAppState: TaskContext['setAppState'],
|
|
): { stop: () => void } {
|
|
// Drop forkContextMessages from the closure — runSummary rebuilds it each
|
|
// tick from getAgentTranscript(). Without this, the original fork messages
|
|
// (passed from AgentTool.tsx) are pinned for the lifetime of the timer.
|
|
const { forkContextMessages: _drop, ...baseParams } = cacheSafeParams
|
|
let summaryAbortController: AbortController | null = null
|
|
let timeoutId: ReturnType<typeof setTimeout> | null = null
|
|
let stopped = false
|
|
let previousSummary: string | null = null
|
|
|
|
async function runSummary(): Promise<void> {
|
|
if (stopped) return
|
|
if (isPoorModeActive()) {
|
|
logForDebugging('[AgentSummary] Skipping summary — poor mode active')
|
|
scheduleNext()
|
|
return
|
|
}
|
|
|
|
logForDebugging(`[AgentSummary] Timer fired for agent ${agentId}`)
|
|
|
|
try {
|
|
// Read current messages from transcript
|
|
const transcript = await getAgentTranscript(agentId)
|
|
if (!transcript || transcript.messages.length < 3) {
|
|
// Not enough context yet — finally block will schedule next attempt
|
|
logForDebugging(
|
|
`[AgentSummary] Skipping summary for ${taskId}: not enough messages (${transcript?.messages.length ?? 0})`,
|
|
)
|
|
return
|
|
}
|
|
|
|
// Filter to clean message state
|
|
const cleanMessages = filterIncompleteToolCalls(transcript.messages)
|
|
|
|
// Build fork params with current messages
|
|
const forkParams: CacheSafeParams = {
|
|
...baseParams,
|
|
forkContextMessages: cleanMessages,
|
|
}
|
|
|
|
logForDebugging(
|
|
`[AgentSummary] Forking for summary, ${cleanMessages.length} messages in context`,
|
|
)
|
|
|
|
// Create abort controller for this summary
|
|
summaryAbortController = new AbortController()
|
|
|
|
// Deny tools via callback, NOT by passing tools:[] - that busts cache
|
|
const canUseTool = async () => ({
|
|
behavior: 'deny' as const,
|
|
message: 'No tools needed for summary',
|
|
decisionReason: { type: 'other' as const, reason: 'summary only' },
|
|
})
|
|
|
|
// DO NOT set maxOutputTokens here. The fork piggybacks on the main
|
|
// thread's prompt cache by sending identical cache-key params (system,
|
|
// tools, model, messages prefix, thinking config). Setting maxOutputTokens
|
|
// would clamp budget_tokens, creating a thinking config mismatch that
|
|
// invalidates the cache.
|
|
//
|
|
// ContentReplacementState is cloned by default in createSubagentContext
|
|
// from forkParams.toolUseContext (the subagent's LIVE state captured at
|
|
// onCacheSafeParams time). No explicit override needed.
|
|
const result = await runForkedAgent({
|
|
promptMessages: [
|
|
createUserMessage({ content: buildSummaryPrompt(previousSummary) }),
|
|
],
|
|
cacheSafeParams: forkParams,
|
|
canUseTool,
|
|
querySource: 'agent_summary',
|
|
forkLabel: 'agent_summary',
|
|
overrides: { abortController: summaryAbortController },
|
|
skipTranscript: true,
|
|
})
|
|
|
|
if (stopped) return
|
|
|
|
// Extract summary text from result
|
|
for (const msg of result.messages) {
|
|
if (msg.type !== 'assistant') continue
|
|
// Skip API error messages
|
|
if (msg.isApiErrorMessage) {
|
|
logForDebugging(
|
|
`[AgentSummary] Skipping API error message for ${taskId}`,
|
|
)
|
|
continue
|
|
}
|
|
const contentArr = Array.isArray(msg.message!.content) ? msg.message!.content : []
|
|
const textBlock = contentArr.find(b => b.type === 'text')
|
|
if (textBlock?.type === 'text' && textBlock.text.trim()) {
|
|
const summaryText = textBlock.text.trim()
|
|
logForDebugging(
|
|
`[AgentSummary] Summary result for ${taskId}: ${summaryText}`,
|
|
)
|
|
previousSummary = summaryText
|
|
updateAgentSummary(taskId, summaryText, setAppState)
|
|
break
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (!stopped && e instanceof Error) {
|
|
logError(e)
|
|
}
|
|
} finally {
|
|
summaryAbortController = null
|
|
// Reset timer on completion (not initiation) to prevent overlapping summaries
|
|
if (!stopped) {
|
|
scheduleNext()
|
|
}
|
|
}
|
|
}
|
|
|
|
function scheduleNext(): void {
|
|
if (stopped) return
|
|
timeoutId = setTimeout(runSummary, SUMMARY_INTERVAL_MS)
|
|
}
|
|
|
|
function stop(): void {
|
|
logForDebugging(`[AgentSummary] Stopping summarization for ${taskId}`)
|
|
stopped = true
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId)
|
|
timeoutId = null
|
|
}
|
|
if (summaryAbortController) {
|
|
summaryAbortController.abort()
|
|
summaryAbortController = null
|
|
}
|
|
}
|
|
|
|
// Start the first timer
|
|
scheduleNext()
|
|
|
|
return { stop }
|
|
}
|