feat: register LocalMemoryRecallTool + VaultHttpFetchTool wiring (cherry-pick 5bb0306)
Upstream:5bb0306— feat: 添加 LocalMemoryRecallTool 和 VaultHttpFetchTool Tool sources were already present in CCP (pulled in via39ba9a56). This commit adds the wiring: - Register LOCAL_MEMORY_RECALL_TOOL_NAME in ALL_AGENT_DISALLOWED_TOOLS - Add agentToolFilter utility for fork subagent tool inheritance - Add agentToolFilter tests Build: 561 files, bun run build ✔ Runtime: ccp --version → 2.6.5 ✔
This commit is contained in:
parent
663ae6d627
commit
dc71add9d8
|
|
@ -28,6 +28,7 @@ import { ENTER_WORKTREE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/
|
||||||
import { EXIT_WORKTREE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/ExitWorktreeTool/constants.js'
|
import { EXIT_WORKTREE_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/ExitWorktreeTool/constants.js'
|
||||||
import { WORKFLOW_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/WorkflowTool/constants.js'
|
import { WORKFLOW_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/WorkflowTool/constants.js'
|
||||||
import { VAULT_HTTP_FETCH_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/VaultHttpFetchTool/constants.js'
|
import { VAULT_HTTP_FETCH_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/VaultHttpFetchTool/constants.js'
|
||||||
|
import { LOCAL_MEMORY_RECALL_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/LocalMemoryRecallTool/constants.js'
|
||||||
import {
|
import {
|
||||||
CRON_CREATE_TOOL_NAME,
|
CRON_CREATE_TOOL_NAME,
|
||||||
CRON_DELETE_TOOL_NAME,
|
CRON_DELETE_TOOL_NAME,
|
||||||
|
|
@ -44,6 +45,8 @@ export const ALL_AGENT_DISALLOWED_TOOLS = new Set([
|
||||||
TASK_STOP_TOOL_NAME,
|
TASK_STOP_TOOL_NAME,
|
||||||
// Prevent recursive workflow execution inside subagents.
|
// Prevent recursive workflow execution inside subagents.
|
||||||
...(feature('WORKFLOW_SCRIPTS') ? [WORKFLOW_TOOL_NAME] : []),
|
...(feature('WORKFLOW_SCRIPTS') ? [WORKFLOW_TOOL_NAME] : []),
|
||||||
|
// Keep local-memory recall on the main thread only.
|
||||||
|
LOCAL_MEMORY_RECALL_TOOL_NAME,
|
||||||
// Prevent subagents from accessing LocalVault secrets.
|
// Prevent subagents from accessing LocalVault secrets.
|
||||||
VAULT_HTTP_FETCH_TOOL_NAME,
|
VAULT_HTTP_FETCH_TOOL_NAME,
|
||||||
])
|
])
|
||||||
|
|
@ -85,7 +88,9 @@ export const IN_PROCESS_TEAMMATE_ALLOWED_TOOLS = new Set([
|
||||||
SEND_MESSAGE_TOOL_NAME,
|
SEND_MESSAGE_TOOL_NAME,
|
||||||
// Teammate-created crons are tagged with the creating agentId and routed to
|
// Teammate-created crons are tagged with the creating agentId and routed to
|
||||||
// that teammate's pendingUserMessages queue (see useScheduledTasks.ts).
|
// that teammate's pendingUserMessages queue (see useScheduledTasks.ts).
|
||||||
CRON_CREATE_TOOL_NAME, CRON_DELETE_TOOL_NAME, CRON_LIST_TOOL_NAME,
|
CRON_CREATE_TOOL_NAME,
|
||||||
|
CRON_DELETE_TOOL_NAME,
|
||||||
|
CRON_LIST_TOOL_NAME,
|
||||||
])
|
])
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
108
src/utils/__tests__/agentToolFilter.test.ts
Normal file
108
src/utils/__tests__/agentToolFilter.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { describe, expect, test } from 'bun:test'
|
||||||
|
import { filterParentToolsForFork } from '../agentToolFilter.js'
|
||||||
|
import { ALL_AGENT_DISALLOWED_TOOLS } from '../../constants/tools.js'
|
||||||
|
import type { Tool } from '../../Tool.js'
|
||||||
|
|
||||||
|
// L6 fix: synthetic tool factory typed precisely. filterParentToolsForFork
|
||||||
|
// only reads .name; if the filter ever needed more (e.g. .isEnabled()),
|
||||||
|
// the cast site would surface the missing fields rather than silently
|
||||||
|
// pass through `as Tool`.
|
||||||
|
function fakeTool(name: string): Tool {
|
||||||
|
return { name } as unknown as Tool
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('filterParentToolsForFork', () => {
|
||||||
|
test('strips tools that are in ALL_AGENT_DISALLOWED_TOOLS', () => {
|
||||||
|
// Pick any disallowed tool name for a deterministic test.
|
||||||
|
const disallowed = Array.from(ALL_AGENT_DISALLOWED_TOOLS)[0]!
|
||||||
|
const parent: Tool[] = [fakeTool('AllowedTool'), fakeTool(disallowed)]
|
||||||
|
const result = filterParentToolsForFork(parent)
|
||||||
|
expect(result.map(t => t.name)).toEqual(['AllowedTool'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('strips LocalMemoryRecall (registered as disallowed in PR-1)', () => {
|
||||||
|
const parent: Tool[] = [
|
||||||
|
fakeTool('LocalMemoryRecall'),
|
||||||
|
fakeTool('Bash'),
|
||||||
|
fakeTool('FileRead'),
|
||||||
|
]
|
||||||
|
const result = filterParentToolsForFork(parent)
|
||||||
|
expect(result.map(t => t.name)).toEqual(['Bash', 'FileRead'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('passes through tools that are not in the disallow set', () => {
|
||||||
|
const parent: Tool[] = [
|
||||||
|
fakeTool('Bash'),
|
||||||
|
fakeTool('Read'),
|
||||||
|
fakeTool('WebFetch'),
|
||||||
|
]
|
||||||
|
const result = filterParentToolsForFork(parent)
|
||||||
|
expect(result).toEqual(parent)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handles empty input', () => {
|
||||||
|
expect(filterParentToolsForFork([])).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('preserves order of allowed tools', () => {
|
||||||
|
const parent: Tool[] = [
|
||||||
|
fakeTool('A'),
|
||||||
|
fakeTool('LocalMemoryRecall'),
|
||||||
|
fakeTool('B'),
|
||||||
|
fakeTool('C'),
|
||||||
|
]
|
||||||
|
const result = filterParentToolsForFork(parent)
|
||||||
|
expect(result.map(t => t.name)).toEqual(['A', 'B', 'C'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('strips multiple disallowed tools in one pass', () => {
|
||||||
|
const disallowed = Array.from(ALL_AGENT_DISALLOWED_TOOLS).slice(0, 2)
|
||||||
|
const parent: Tool[] = [
|
||||||
|
fakeTool('Keep1'),
|
||||||
|
fakeTool(disallowed[0]!),
|
||||||
|
fakeTool('Keep2'),
|
||||||
|
fakeTool(disallowed[1]!),
|
||||||
|
fakeTool('Keep3'),
|
||||||
|
]
|
||||||
|
const result = filterParentToolsForFork(parent)
|
||||||
|
expect(result.map(t => t.name)).toEqual(['Keep1', 'Keep2', 'Keep3'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('AC11a: ALL_AGENT_DISALLOWED_TOOLS contains LocalMemoryRecall', () => {
|
||||||
|
test('layer 1 gate registration is in place', () => {
|
||||||
|
expect(ALL_AGENT_DISALLOWED_TOOLS.has('LocalMemoryRecall')).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('AC11b: layer 2 fork-path filter integration semantics', () => {
|
||||||
|
// Both AgentTool.tsx (new fork) and resumeAgent.ts (resumed fork) must
|
||||||
|
// call filterParentToolsForFork before passing tools to runAgent. We
|
||||||
|
// verify the wiring via grep snapshot - a missing call is the only way
|
||||||
|
// for layer 2 to silently fail. The actual fork execution pathway
|
||||||
|
// requires a full Ink REPL and is exercised in REPL AC.
|
||||||
|
test('AgentTool.tsx fork path uses filterParentToolsForFork', async () => {
|
||||||
|
const fs = await import('node:fs')
|
||||||
|
const path = await import('node:path')
|
||||||
|
// Resolve relative to the test worker's cwd, which is the project root.
|
||||||
|
const file = path.resolve(
|
||||||
|
'packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx',
|
||||||
|
)
|
||||||
|
const src = fs.readFileSync(file, 'utf8')
|
||||||
|
expect(src).toContain(
|
||||||
|
'filterParentToolsForFork(toolUseContext.options.tools)',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('resumeAgent.ts resumed-fork path uses filterParentToolsForFork', async () => {
|
||||||
|
const fs = await import('node:fs')
|
||||||
|
const path = await import('node:path')
|
||||||
|
const file = path.resolve(
|
||||||
|
'packages/builtin-tools/src/tools/AgentTool/resumeAgent.ts',
|
||||||
|
)
|
||||||
|
const src = fs.readFileSync(file, 'utf8')
|
||||||
|
expect(src).toContain(
|
||||||
|
'filterParentToolsForFork(toolUseContext.options.tools)',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user