mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
docs: 添加 ToolSearch 设计指南 + 禁用 turn-zero 工具推荐弹窗
- 新增 docs/design/tool-search-design-guide.md,涵盖架构、搜索算法、执行管道、演进历史 - 禁用 getTurnZeroSearchExtraToolsPrefetch,消除用户输入时的频繁弹窗 - inter-turn 发现机制保持不变 Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
This commit is contained in:
@@ -6,7 +6,7 @@ import type { Tools } from '../Tool.js';
|
||||
import type { RenderableMessage } from '../types/message.js';
|
||||
import {
|
||||
getDisplayMessageFromCollapsed,
|
||||
getToolSearchOrReadInfo,
|
||||
getSearchExtraToolsOrReadInfo,
|
||||
getToolUseIdsFromCollapsedGroup,
|
||||
hasAnyToolInProgress,
|
||||
} from '../utils/collapseReadSearch.js';
|
||||
@@ -89,7 +89,7 @@ export function hasContentAfterIndex(
|
||||
continue;
|
||||
}
|
||||
if (content?.type === 'tool_use') {
|
||||
if (getToolSearchOrReadInfo(content.name!, content.input, tools).isCollapsible) {
|
||||
if (getSearchExtraToolsOrReadInfo(content.name!, content.input, tools).isCollapsible) {
|
||||
continue;
|
||||
}
|
||||
// Non-collapsible tool uses appear in syntheticStreamingToolUseMessages
|
||||
@@ -115,7 +115,7 @@ export function hasContentAfterIndex(
|
||||
// merged into the current collapsed group on the next render cycle
|
||||
if (msg?.type === 'grouped_tool_use') {
|
||||
const firstInput = firstBlock(msg.messages[0]?.message.content)?.input;
|
||||
if (getToolSearchOrReadInfo(msg.toolName, firstInput, tools).isCollapsible) {
|
||||
if (getSearchExtraToolsOrReadInfo(msg.toolName, firstInput, tools).isCollapsible) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,7 +852,7 @@ const MessagesImpl = ({
|
||||
// renderToolResultMessage shows. Falls back to renderableSearchText
|
||||
// (duck-types toolUseResult) for tools that haven't implemented it,
|
||||
// and for all non-tool-result message types. The drift-catcher test
|
||||
// (toolSearchText.test.tsx) renders + compares to keep these in sync.
|
||||
// (searchExtraToolsText.test.tsx) renders + compares to keep these in sync.
|
||||
//
|
||||
// A second-React-root reconcile approach was tried and ruled out
|
||||
// (measured 3.1ms/msg, growing — flushSyncWork processes all roots;
|
||||
|
||||
@@ -3,21 +3,21 @@ import { Box, Text } from '@anthropic/ink';
|
||||
import { Select } from './CustomSelect/select.js';
|
||||
import { PermissionDialog } from './permissions/PermissionDialog.js';
|
||||
|
||||
type ToolSearchHintItem = {
|
||||
type SearchExtraToolsHintItem = {
|
||||
name: string;
|
||||
description: string;
|
||||
score: number;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
tools: ToolSearchHintItem[];
|
||||
tools: SearchExtraToolsHintItem[];
|
||||
onSelect: (toolName: string) => void;
|
||||
onDismiss: () => void;
|
||||
};
|
||||
|
||||
const AUTO_DISMISS_MS = 30_000;
|
||||
|
||||
export function ToolSearchHint({ tools, onSelect, onDismiss }: Props): React.ReactNode {
|
||||
export function SearchExtraToolsHint({ tools, onSelect, onDismiss }: Props): React.ReactNode {
|
||||
const onSelectRef = React.useRef(onSelect);
|
||||
const onDismissRef = React.useRef(onDismiss);
|
||||
onSelectRef.current = onSelect;
|
||||
@@ -30,35 +30,37 @@ mock.module('src/services/analytics/growthbook.js', () => ({
|
||||
}))
|
||||
|
||||
const {
|
||||
subscribeToToolSearchPrefetch,
|
||||
getToolSearchPrefetchSnapshot,
|
||||
clearToolSearchPrefetchResults,
|
||||
} = await import('src/services/toolSearch/prefetch.js')
|
||||
subscribeToSearchExtraToolsPrefetch,
|
||||
getSearchExtraToolsPrefetchSnapshot,
|
||||
clearSearchExtraToolsPrefetchResults,
|
||||
} = await import('src/services/searchExtraTools/prefetch.js')
|
||||
|
||||
const { useToolSearchHint } = await import('src/hooks/useToolSearchHint.js')
|
||||
const { useSearchExtraToolsHint } = await import(
|
||||
'src/hooks/useSearchExtraToolsHint.js'
|
||||
)
|
||||
|
||||
describe('useToolSearchHint', () => {
|
||||
describe('useSearchExtraToolsHint', () => {
|
||||
// We test the subscription/snapshot API directly since
|
||||
// React hooks require a renderer.
|
||||
test('returns empty tools when no prefetch result', () => {
|
||||
clearToolSearchPrefetchResults()
|
||||
const snapshot = getToolSearchPrefetchSnapshot()
|
||||
clearSearchExtraToolsPrefetchResults()
|
||||
const snapshot = getSearchExtraToolsPrefetchSnapshot()
|
||||
expect(snapshot).toEqual([])
|
||||
})
|
||||
|
||||
test('snapshot updates when listeners are notified', () => {
|
||||
clearToolSearchPrefetchResults()
|
||||
clearSearchExtraToolsPrefetchResults()
|
||||
|
||||
// Simulate what prefetch does: set results and notify
|
||||
const mockSetResults = (results: unknown[]) => {
|
||||
// We can't directly set latestPrefetchResult, but we can test
|
||||
// the clear function and subscription mechanism
|
||||
clearToolSearchPrefetchResults()
|
||||
clearSearchExtraToolsPrefetchResults()
|
||||
}
|
||||
|
||||
// Test subscription
|
||||
let callCount = 0
|
||||
const unsubscribe = subscribeToToolSearchPrefetch(() => {
|
||||
const unsubscribe = subscribeToSearchExtraToolsPrefetch(() => {
|
||||
callCount++
|
||||
})
|
||||
expect(callCount).toBe(0)
|
||||
@@ -69,12 +71,12 @@ describe('useToolSearchHint', () => {
|
||||
|
||||
// Unsubscribe and verify no more calls
|
||||
unsubscribe()
|
||||
clearToolSearchPrefetchResults()
|
||||
clearSearchExtraToolsPrefetchResults()
|
||||
expect(callCount).toBe(1)
|
||||
})
|
||||
|
||||
test('clearToolSearchPrefetchResults resets snapshot', () => {
|
||||
clearToolSearchPrefetchResults()
|
||||
expect(getToolSearchPrefetchSnapshot()).toEqual([])
|
||||
test('clearSearchExtraToolsPrefetchResults resets snapshot', () => {
|
||||
clearSearchExtraToolsPrefetchResults()
|
||||
expect(getSearchExtraToolsPrefetchSnapshot()).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -140,7 +140,7 @@ export function AttachmentMessage({ attachment, addMargin, verbose, isTranscript
|
||||
|
||||
// tool_discovery rendered here (not in the switch) so the 'tool_discovery'
|
||||
// string literal stays inside a feature()-guarded block.
|
||||
if (feature('EXPERIMENTAL_TOOL_SEARCH')) {
|
||||
if (feature('EXPERIMENTAL_SEARCH_EXTRA_TOOLS')) {
|
||||
if (attachment.type === 'tool_discovery') {
|
||||
if (attachment.tools.length === 0) return null;
|
||||
const names = attachment.tools.map(t => t.name).join(', ');
|
||||
|
||||
@@ -57,7 +57,7 @@ function VerboseToolUse({
|
||||
theme: ThemeName;
|
||||
}): React.ReactNode {
|
||||
const bg = useSelectedMessageBg();
|
||||
// Same REPL-primitive fallback as getToolSearchOrReadInfo — REPL mode strips
|
||||
// Same REPL-primitive fallback as getSearchExtraToolsOrReadInfo — REPL mode strips
|
||||
// these from the execution tools list, but virtual messages still need them
|
||||
// to render in verbose mode.
|
||||
const tool = findToolByName(tools, content.name) ?? findToolByName(getReplPrimitiveTools(), content.name);
|
||||
|
||||
Reference in New Issue
Block a user