mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 05:45:51 +00:00
* fix: 修复settings.json内存状态溢出的问题 * fix: 修复auto mode gate check未处理的promise rejection 在 bypassPermissionsKillswitch.ts 的 useKickOffCheckAndDisableAutoModeIfNeeded 中,void fire-and-forget 调用缺少 .catch() 处理,导致 verifyAutoModeGateAccess 失败时产生 unhandled promise rejection。同时移除 permissionSetup.ts 中冗余的 null check。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: 开放 auto mode 和 bypass mode 给所有用户 通过 Shift+Tab 统一循环:default → acceptEdits → plan → auto → bypassPermissions → default - 移除 USER_TYPE 分支判断,所有用户使用同一循环路径 - isBypassPermissionsModeAvailable 始终为 true - isAutoModeAvailable 初始化直接为 true - 移除 AutoModeOptInDialog 确认流程 - 简化 isAutoModeGateEnabled 仅保留快模式熔断器 - 简化 verifyAutoModeGateAccess 仅检查快模式 - 移除 GrowthBook/Statsig 远程门控 - bypass permissions killswitch 改为 no-op - 新增 24 个测试覆盖循环逻辑和门控不变量 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: 为sideQuery添加Langfuse追踪 sideQuery 绕过了 claude.ts 的主 API 路径,导致所有走 sideQuery 的调用 (auto mode classifier、permission explainer、session search 等)都没有 Langfuse 记录。现在为每次 sideQuery 调用创建独立 trace 并记录 LLM observation, 未配置 Langfuse 时全部 no-op。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: ACP availableModes 补齐 bypassPermissions 并修正测试 import 路径 - ACP agent availableModes 按条件包含 bypassPermissions(非 root/sandbox) - 顺序对齐 REPL 循环:default → acceptEdits → plan → auto → bypassPermissions - 新增 2 个测试验证 availableModes 包含 bypassPermissions 及模式切换 - 修正 getNextPermissionMode.test.ts 和 permissionSetup.test.ts 的 import 路径 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
208 lines
6.3 KiB
TypeScript
208 lines
6.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
import {
|
|
buildTool,
|
|
toolMatchesName,
|
|
findToolByName,
|
|
getEmptyToolPermissionContext,
|
|
filterToolProgressMessages,
|
|
} from '../Tool'
|
|
|
|
// Minimal tool definition for testing buildTool
|
|
function makeMinimalToolDef(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
name: 'TestTool',
|
|
inputSchema: { type: 'object' as const } as any,
|
|
maxResultSizeChars: 10000,
|
|
call: async () => ({ data: 'ok' }),
|
|
description: async () => 'A test tool',
|
|
prompt: async () => 'test prompt',
|
|
mapToolResultToToolResultBlockParam: (
|
|
content: unknown,
|
|
toolUseID: string,
|
|
) => ({
|
|
type: 'tool_result' as const,
|
|
tool_use_id: toolUseID,
|
|
content: String(content),
|
|
}),
|
|
renderToolUseMessage: () => null,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
describe('buildTool', () => {
|
|
test('fills in default isEnabled as true', () => {
|
|
const tool = buildTool(makeMinimalToolDef())
|
|
expect(tool.isEnabled()).toBe(true)
|
|
})
|
|
|
|
test('fills in default isConcurrencySafe as false', () => {
|
|
const tool = buildTool(makeMinimalToolDef())
|
|
expect(tool.isConcurrencySafe({})).toBe(false)
|
|
})
|
|
|
|
test('fills in default isReadOnly as false', () => {
|
|
const tool = buildTool(makeMinimalToolDef())
|
|
expect(tool.isReadOnly({})).toBe(false)
|
|
})
|
|
|
|
test('fills in default isDestructive as false', () => {
|
|
const tool = buildTool(makeMinimalToolDef())
|
|
expect(tool.isDestructive!({})).toBe(false)
|
|
})
|
|
|
|
test('fills in default checkPermissions as allow', async () => {
|
|
const tool = buildTool(makeMinimalToolDef())
|
|
const input = { foo: 'bar' }
|
|
const result = await tool.checkPermissions(input, {} as any)
|
|
expect(result).toEqual({ behavior: 'allow', updatedInput: input })
|
|
})
|
|
|
|
test('fills in default userFacingName from tool name', () => {
|
|
const tool = buildTool(makeMinimalToolDef())
|
|
expect(tool.userFacingName(undefined)).toBe('TestTool')
|
|
})
|
|
|
|
test('fills in default toAutoClassifierInput as empty string', () => {
|
|
const tool = buildTool(makeMinimalToolDef())
|
|
expect(tool.toAutoClassifierInput({})).toBe('')
|
|
})
|
|
|
|
test('preserves explicitly provided methods', () => {
|
|
const tool = buildTool(
|
|
makeMinimalToolDef({
|
|
isEnabled: () => false,
|
|
isConcurrencySafe: () => true,
|
|
isReadOnly: () => true,
|
|
}),
|
|
)
|
|
expect(tool.isEnabled()).toBe(false)
|
|
expect(tool.isConcurrencySafe({})).toBe(true)
|
|
expect(tool.isReadOnly({})).toBe(true)
|
|
})
|
|
|
|
test('preserves all non-defaultable properties', () => {
|
|
const tool = buildTool(makeMinimalToolDef())
|
|
expect(tool.name).toBe('TestTool')
|
|
expect(tool.maxResultSizeChars).toBe(10000)
|
|
expect(typeof tool.call).toBe('function')
|
|
expect(typeof tool.description).toBe('function')
|
|
expect(typeof tool.prompt).toBe('function')
|
|
})
|
|
})
|
|
|
|
describe('toolMatchesName', () => {
|
|
test('returns true for exact name match', () => {
|
|
expect(toolMatchesName({ name: 'Bash' }, 'Bash')).toBe(true)
|
|
})
|
|
|
|
test('returns false for non-matching name', () => {
|
|
expect(toolMatchesName({ name: 'Bash' }, 'Read')).toBe(false)
|
|
})
|
|
|
|
test('returns true when name matches an alias', () => {
|
|
expect(
|
|
toolMatchesName(
|
|
{ name: 'Bash', aliases: ['BashTool', 'Shell'] },
|
|
'BashTool',
|
|
),
|
|
).toBe(true)
|
|
})
|
|
|
|
test('returns false when aliases is undefined', () => {
|
|
expect(toolMatchesName({ name: 'Bash' }, 'BashTool')).toBe(false)
|
|
})
|
|
|
|
test('returns false when aliases is empty', () => {
|
|
expect(toolMatchesName({ name: 'Bash', aliases: [] }, 'BashTool')).toBe(
|
|
false,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('findToolByName', () => {
|
|
const mockTools = [
|
|
buildTool(makeMinimalToolDef({ name: 'Bash' })),
|
|
buildTool(makeMinimalToolDef({ name: 'Read', aliases: ['FileRead'] })),
|
|
buildTool(makeMinimalToolDef({ name: 'Edit' })),
|
|
]
|
|
|
|
test('finds tool by primary name', () => {
|
|
const tool = findToolByName(mockTools, 'Bash')
|
|
expect(tool).toBeDefined()
|
|
expect(tool!.name).toBe('Bash')
|
|
})
|
|
|
|
test('finds tool by alias', () => {
|
|
const tool = findToolByName(mockTools, 'FileRead')
|
|
expect(tool).toBeDefined()
|
|
expect(tool!.name).toBe('Read')
|
|
})
|
|
|
|
test('returns undefined when no match', () => {
|
|
expect(findToolByName(mockTools, 'NonExistent')).toBeUndefined()
|
|
})
|
|
|
|
test('returns first match when duplicates exist', () => {
|
|
const dupeTools = [
|
|
buildTool(makeMinimalToolDef({ name: 'Bash', maxResultSizeChars: 100 })),
|
|
buildTool(makeMinimalToolDef({ name: 'Bash', maxResultSizeChars: 200 })),
|
|
]
|
|
const tool = findToolByName(dupeTools, 'Bash')
|
|
expect(tool!.maxResultSizeChars).toBe(100)
|
|
})
|
|
})
|
|
|
|
describe('getEmptyToolPermissionContext', () => {
|
|
test('returns default permission mode', () => {
|
|
const ctx = getEmptyToolPermissionContext()
|
|
expect(ctx.mode).toBe('default')
|
|
})
|
|
|
|
test('returns empty maps and arrays', () => {
|
|
const ctx = getEmptyToolPermissionContext()
|
|
expect(ctx.additionalWorkingDirectories.size).toBe(0)
|
|
expect(ctx.alwaysAllowRules).toEqual({})
|
|
expect(ctx.alwaysDenyRules).toEqual({})
|
|
expect(ctx.alwaysAskRules).toEqual({})
|
|
})
|
|
|
|
test('returns isBypassPermissionsModeAvailable as true', () => {
|
|
const ctx = getEmptyToolPermissionContext()
|
|
expect(ctx.isBypassPermissionsModeAvailable).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('filterToolProgressMessages', () => {
|
|
test('filters out hook_progress messages', () => {
|
|
const messages = [
|
|
{ data: { type: 'hook_progress', hookName: 'pre' } },
|
|
{ data: { type: 'tool_progress', toolName: 'Bash' } },
|
|
] as any[]
|
|
const result = filterToolProgressMessages(messages)
|
|
expect(result).toHaveLength(1)
|
|
expect((result[0]!.data as any).type).toBe('tool_progress')
|
|
})
|
|
|
|
test('keeps tool progress messages', () => {
|
|
const messages = [
|
|
{ data: { type: 'tool_progress', toolName: 'Bash' } },
|
|
{ data: { type: 'tool_progress', toolName: 'Read' } },
|
|
] as any[]
|
|
const result = filterToolProgressMessages(messages)
|
|
expect(result).toHaveLength(2)
|
|
})
|
|
|
|
test('returns empty array for empty input', () => {
|
|
expect(filterToolProgressMessages([])).toEqual([])
|
|
})
|
|
|
|
test('handles messages without type field', () => {
|
|
const messages = [
|
|
{ data: { toolName: 'Bash' } },
|
|
{ data: { type: 'hook_progress' } },
|
|
] as any[]
|
|
const result = filterToolProgressMessages(messages)
|
|
expect(result).toHaveLength(1)
|
|
})
|
|
})
|