fix: keep agent listings fresh after file edits

Agent definitions are cached across command invocations, so edits made through the shared file helper must invalidate both the agent definition cache and the underlying markdown file cache. This keeps the TUI creation path and /agents command output consistent after creating, updating, or deleting custom agents.

Constraint: The TUI agent wizard and /agents command share cached agent definition loaders
Rejected: Clear only getAgentDefinitionsWithOverrides | the nested markdown file memoization can still return stale files
Confidence: high
Scope-risk: narrow
Directive: Any future agent file mutation path must call clearAgentDefinitionsCache after a successful write or delete
Tested: bun test src/tools/AgentTool/loadAgentsDir.cache.test.ts
Tested: bun run check:server
Not-tested: Full verify still blocked by existing agent-utils global coverage baseline
This commit is contained in:
程序员阿江(Relakkes) 2026-05-09 21:47:58 +08:00
parent 3193e741c7
commit 571d771025
3 changed files with 103 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import type { SettingSource } from 'src/utils/settings/constants.js'
import { getManagedFilePath } from 'src/utils/settings/managedPath.js'
import type { AgentMemoryScope } from '../../tools/AgentTool/agentMemory.js'
import {
clearAgentDefinitionsCache,
type AgentDefinition,
isBuiltInAgent,
isPluginAgent,
@ -200,6 +201,7 @@ export async function saveAgentToFile(
}
throw e
}
clearAgentDefinitionsCache()
}
/**
@ -233,6 +235,7 @@ export async function updateAgentFile(
)
await writeFileAndFlush(filePath, content)
clearAgentDefinitionsCache()
}
/**
@ -255,6 +258,7 @@ export async function deleteAgentFromFile(
throw e
}
}
clearAgentDefinitionsCache()
}
async function writeFileAndFlush(

View File

@ -0,0 +1,98 @@
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
import * as fs from 'node:fs/promises'
import * as os from 'node:os'
import * as path from 'node:path'
import { getCwdState, setCwdState } from '../../bootstrap/state.js'
import { agentsHandler } from '../../cli/handlers/agents.js'
import { saveAgentToFile } from '../../components/agents/agentFileUtils.js'
import {
clearAgentDefinitionsCache,
getAgentDefinitionsWithOverrides,
} from './loadAgentsDir.js'
let tmpHome: string
let originalHome: string | undefined
let originalUserProfile: string | undefined
let originalClaudeConfigDir: string | undefined
let originalCwdState: string
describe('agent definition cache invalidation', () => {
beforeEach(async () => {
tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), 'agent-def-cache-'))
originalHome = process.env.HOME
originalUserProfile = process.env.USERPROFILE
originalClaudeConfigDir = process.env.CLAUDE_CONFIG_DIR
originalCwdState = getCwdState()
process.env.HOME = tmpHome
process.env.USERPROFILE = tmpHome
process.env.CLAUDE_CONFIG_DIR = path.join(tmpHome, '.claude')
clearAgentDefinitionsCache()
})
afterEach(async () => {
if (originalHome === undefined) {
delete process.env.HOME
} else {
process.env.HOME = originalHome
}
if (originalUserProfile === undefined) {
delete process.env.USERPROFILE
} else {
process.env.USERPROFILE = originalUserProfile
}
if (originalClaudeConfigDir === undefined) {
delete process.env.CLAUDE_CONFIG_DIR
} else {
process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir
}
setCwdState(originalCwdState)
clearAgentDefinitionsCache()
await fs.rm(tmpHome, { recursive: true, force: true })
})
test('shows a newly-created project agent in the /agents output after an initial cached read', async () => {
const projectRoot = path.join(tmpHome, 'project')
await fs.mkdir(projectRoot, { recursive: true })
setCwdState(projectRoot)
const agentType = 'cache-created-agent'
const before = await getAgentDefinitionsWithOverrides(projectRoot)
expect(before.allAgents.some(agent => agent.agentType === agentType)).toBe(false)
await saveAgentToFile(
'projectSettings',
agentType,
'Use this agent to verify cache invalidation.',
undefined,
'You verify cache invalidation.',
true,
)
const after = await getAgentDefinitionsWithOverrides(projectRoot)
expect(after.allAgents).toContainEqual(
expect.objectContaining({
agentType,
source: 'projectSettings',
}),
)
const logs: string[] = []
const originalLog = console.log
console.log = (...args: unknown[]) => {
logs.push(args.map(String).join(' '))
}
try {
await agentsHandler()
} finally {
console.log = originalLog
}
expect(logs.join('\n')).toContain(agentType)
})
})

View File

@ -394,6 +394,7 @@ export const getAgentDefinitionsWithOverrides = memoize(
export function clearAgentDefinitionsCache(): void {
getAgentDefinitionsWithOverrides.cache.clear?.()
loadMarkdownFilesForSubdir.cache.clear?.()
clearPluginAgentCache()
}