@anionex/dsh-tool-search 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 - 2026-09-05
4
+
5
+ - Defer the plugin-provided `dsh_im_return_file` tool instead of treating it as a maintained DSH core tool.
6
+
3
7
  ## 0.1.0 - 2026-09-05
4
8
 
5
9
  - Add deterministic local BM25 discovery for deferred DSH tools.
package/README.md CHANGED
@@ -29,13 +29,12 @@ The initial native schema surface contains:
29
29
  - DSH core tools maintained in `DEFAULT_CORE_TOOLS`
30
30
  - exact names added to `alwaysVisible`
31
31
 
32
- Version `0.1.0` treats these names as core:
32
+ The default policy treats these names as core:
33
33
 
34
34
  ```text
35
- apply_patch, ask_user_question, bash, create_goal, dsh_im_return_file,
36
- exit_plan_mode, get_goal, interrupt_agent, job_kill, job_list, job_output,
37
- list_agents, ralph, report, send_message, skill, subagent, subagent_fork,
38
- todo_write, update_goal, workflow
35
+ apply_patch, ask_user_question, bash, create_goal, exit_plan_mode, get_goal,
36
+ interrupt_agent, job_kill, job_list, job_output, list_agents, ralph, report,
37
+ send_message, skill, subagent, subagent_fork, todo_write, update_goal, workflow
39
38
  ```
40
39
 
41
40
  Every other registered tool is searchable but initially omitted. `neverSearch` hides a name from both the initial surface and the search corpus; it wins a conflict with `alwaysVisible`. `tool_search` itself cannot be blacklisted, preventing a configuration dead end.
package/README.zh.md CHANGED
@@ -29,13 +29,12 @@ dsh plugin --profile web add @anionex/dsh-tool-search
29
29
  - `DEFAULT_CORE_TOOLS` 中维护的 DSH 核心工具
30
30
  - `alwaysVisible` 中配置的精确名称
31
31
 
32
- `0.1.0` 把以下名称视为核心工具:
32
+ 默认策略把以下名称视为核心工具:
33
33
 
34
34
  ```text
35
- apply_patch, ask_user_question, bash, create_goal, dsh_im_return_file,
36
- exit_plan_mode, get_goal, interrupt_agent, job_kill, job_list, job_output,
37
- list_agents, ralph, report, send_message, skill, subagent, subagent_fork,
38
- todo_write, update_goal, workflow
35
+ apply_patch, ask_user_question, bash, create_goal, exit_plan_mode, get_goal,
36
+ interrupt_agent, job_kill, job_list, job_output, list_agents, ralph, report,
37
+ send_message, skill, subagent, subagent_fork, todo_write, update_goal, workflow
39
38
  ```
40
39
 
41
40
  其他已注册工具都可搜索,但默认不显示。`neverSearch` 同时从初始集合和搜索语料中隐藏名称,并在与 `alwaysVisible` 冲突时优先。`tool_search` 自身不能被拉黑,避免配置后无工具可发现。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "packageVersion": "0.1.0",
3
+ "packageVersion": "0.1.1",
4
4
  "tokenizer": "gpt-tokenizer 4.0.0 (o200k_base default encode)",
5
5
  "fixture": {
6
6
  "description": "Deterministic nested function schemas; full surface versus tool_search-only initial surface.",
package/lib/client.js CHANGED
@@ -20,7 +20,6 @@ const DEFAULT_CORE_TOOLS = Object.freeze([
20
20
  "ask_user_question",
21
21
  "bash",
22
22
  "create_goal",
23
- "dsh_im_return_file",
24
23
  "exit_plan_mode",
25
24
  "get_goal",
26
25
  "interrupt_agent",
@@ -60,6 +59,7 @@ const en = {
60
59
  selected: "Live agents",
61
60
  defaultPolicy: "Default",
62
61
  alwaysPolicy: "Always",
62
+ deferredPolicy: "Deferred",
63
63
  blockedPolicy: "Blocked",
64
64
  loading: "Loading...",
65
65
  empty: "No matching tools",
@@ -79,6 +79,7 @@ const zh = {
79
79
  selected: "活跃 Agent",
80
80
  defaultPolicy: "默认",
81
81
  alwaysPolicy: "常驻",
82
+ deferredPolicy: "延迟",
82
83
  blockedPolicy: "禁用",
83
84
  loading: "加载中...",
84
85
  empty: "没有匹配工具",
@@ -313,7 +314,7 @@ function ToolSearchSettingsSection({ scope, t }) {
313
314
  children: [
314
315
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
315
316
  value: "default",
316
- children: core.has(tool.name) ? `${translate("defaultPolicy")} (${translate("alwaysPolicy")})` : translate("defaultPolicy")
317
+ children: `${translate("defaultPolicy")} (${translate(core.has(tool.name) ? "alwaysPolicy" : "deferredPolicy")})`
317
318
  }),
318
319
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
319
320
  value: "always",
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useSyncExternalStore","useCallback","useState","useMemo"],"sources":["../src/shared.ts","../src/client/index.tsx"],"sourcesContent":["/** Values shared by the Host runtime and Web settings client. */\n\nexport const TOOL_SEARCH_NAME = 'tool_search'\nexport const TOOL_SEARCH_SETTINGS_NAMESPACE = 'tool-search'\nexport const TOOL_SEARCH_CATALOG_ROUTE = '/_dsh/tool-search/catalog'\nexport const DEFAULT_RESULT_LIMIT = 5\nexport const MAX_RESULT_LIMIT = 20\n\n/**\n * Stable bootstrap surface for current DSH releases. Unknown future first-party\n * names are deferred until this list or the user allowlist is updated because\n * ToolDefinition does not expose package provenance.\n */\nexport const DEFAULT_CORE_TOOLS = Object.freeze([\n 'apply_patch',\n 'ask_user_question',\n 'bash',\n 'create_goal',\n 'dsh_im_return_file',\n 'exit_plan_mode',\n 'get_goal',\n 'interrupt_agent',\n 'job_kill',\n 'job_list',\n 'job_output',\n 'list_agents',\n 'ralph',\n 'report',\n 'send_message',\n 'skill',\n 'subagent',\n 'subagent_fork',\n 'todo_write',\n 'update_goal',\n 'workflow',\n] as const)\n\nexport interface ToolSearchSettings {\n defaultLimit: number\n alwaysVisible: string[]\n neverSearch: string[]\n}\n\nexport const DEFAULT_SETTINGS: Readonly<ToolSearchSettings> = Object.freeze({\n defaultLimit: DEFAULT_RESULT_LIMIT,\n alwaysVisible: [],\n neverSearch: [],\n})\n\nexport type ToolPolicy = 'always' | 'deferred' | 'blocked'\n\nexport interface CatalogToolSummary {\n name: string\n description: string\n policy: ToolPolicy\n selectedBy: string[]\n}\n\nexport interface CatalogSnapshot {\n schemaVersion: 1\n generation: number\n tools: CatalogToolSummary[]\n}\n","/** DSH Web settings surface for deferred tool policy. */\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react'\nimport {\n DEFAULT_CORE_TOOLS,\n DEFAULT_RESULT_LIMIT,\n MAX_RESULT_LIMIT,\n TOOL_SEARCH_CATALOG_ROUTE,\n TOOL_SEARCH_NAME,\n TOOL_SEARCH_SETTINGS_NAMESPACE,\n type CatalogSnapshot,\n type ToolPolicy,\n type ToolSearchSettings,\n} from '../shared.ts'\n\ninterface SettingsScopeSnapshot<T> {\n status: 'loading' | 'ready' | 'unavailable'\n value: T | undefined\n writable: boolean\n}\n\ninterface SettingsScope<T> {\n getSnapshot(): SettingsScopeSnapshot<T>\n subscribe(listener: () => void): () => void\n set(field: string, value: unknown): Promise<void>\n}\n\ntype Disposer = () => void\n\ninterface ClientContext {\n effect(setup: () => Disposer | void, label: string): void\n locale: {\n register(namespace: string, catalogs: Record<string, Record<string, string>>): Disposer\n bind(namespace: string): (key: string) => string\n }\n settingsScope: {\n bind<T>(options: { namespace: string }): SettingsScope<T>\n }\n slots: {\n inject(name: string, callback: () => Disposer): Disposer\n register<P>(\n options: {\n name: string\n id: string\n order: number\n label: () => string\n inject: () => P\n },\n component: (props: P) => ReactNode,\n ): Disposer\n }\n}\n\ntype Translate = (key: keyof typeof en) => string\n\nconst en = {\n nav: 'Tool Search',\n topK: 'Default results',\n alwaysVisible: 'Always visible',\n neverSearch: 'Never searchable',\n catalog: 'Tool catalog',\n filter: 'Filter tools',\n refresh: 'Refresh',\n tool: 'Tool',\n policy: 'Policy',\n selected: 'Live agents',\n defaultPolicy: 'Default',\n alwaysPolicy: 'Always',\n blockedPolicy: 'Blocked',\n loading: 'Loading...',\n empty: 'No matching tools',\n saved: 'Saved',\n onePerLine: 'One exact tool name per line',\n}\n\nconst zh: typeof en = {\n nav: '工具搜索',\n topK: '默认结果数',\n alwaysVisible: '常驻白名单',\n neverSearch: '搜索黑名单',\n catalog: '工具目录',\n filter: '筛选工具',\n refresh: '刷新',\n tool: '工具',\n policy: '策略',\n selected: '活跃 Agent',\n defaultPolicy: '默认',\n alwaysPolicy: '常驻',\n blockedPolicy: '禁用',\n loading: '加载中...',\n empty: '没有匹配工具',\n saved: '已保存',\n onePerLine: '每行一个精确工具名',\n}\n\nconst EMPTY_SETTINGS: SettingsScopeSnapshot<ToolSearchSettings> = {\n status: 'unavailable',\n value: undefined,\n writable: false,\n}\n\nconst styles = `\n.dts-settings{container-type:inline-size;display:flex;flex-direction:column;gap:20px;width:100%;min-width:0;color:var(--dsw-alias-label-primary)}\n.dts-toolbar{display:grid;grid-template-columns:minmax(160px,220px) minmax(220px,1fr) auto;align-items:end;gap:12px}\n.dts-field{display:flex;flex-direction:column;gap:6px;min-width:0}\n.dts-field>span,.dts-section-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}\n.dts-input,.dts-textarea,.dts-select{box-sizing:border-box;width:100%;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit}\n.dts-input,.dts-select{height:34px;padding:0 9px}.dts-textarea{min-height:104px;padding:8px 9px;resize:vertical;line-height:20px}\n.dts-input:focus,.dts-textarea:focus,.dts-select:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}\n.dts-lists{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}\n.dts-hint{font-size:11px;color:var(--dsw-alias-label-tertiary)}\n.dts-button{height:34px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer}\n.dts-button:hover{background:var(--dsw-alias-interactive-bg-hover)}.dts-button:disabled{opacity:.5;cursor:not-allowed}\n.dts-status{min-height:18px;font-size:12px;color:var(--dsw-alias-label-secondary)}.dts-status[data-error=true]{color:var(--dsw-alias-state-error-primary)}\n.dts-table-wrap{min-width:0;overflow:auto;border-top:1px solid var(--dsw-alias-border-l2);border-bottom:1px solid var(--dsw-alias-border-l2)}\n.dts-table{width:100%;min-width:620px;border-collapse:collapse;table-layout:fixed}\n.dts-table th,.dts-table td{box-sizing:border-box;padding:9px 10px;border-bottom:1px solid var(--dsw-alias-border-l1);text-align:left;vertical-align:middle;font-size:12px}\n.dts-table th{position:sticky;top:0;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-weight:600}\n.dts-table tr:last-child td{border-bottom:0}.dts-tool{width:52%}.dts-tool code{display:block;overflow:hidden;text-overflow:ellipsis;overflow-wrap:anywhere;white-space:nowrap;color:var(--dsw-alias-label-primary)}\n.dts-description{display:block;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary)}\n.dts-policy{width:26%}.dts-selected{width:22%;color:var(--dsw-alias-label-secondary)}\n@container(max-width:620px){.dts-toolbar{grid-template-columns:1fr auto}.dts-toolbar .dts-limit{grid-column:1/-1}.dts-lists{grid-template-columns:1fr}.dts-table{min-width:0}.dts-table thead{display:none}.dts-table tr{display:grid;grid-template-columns:minmax(0,1fr) 132px;padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dts-table td{padding:4px 0;border:0}.dts-table .dts-tool,.dts-table .dts-policy{width:auto}.dts-table .dts-selected{grid-column:1/-1;width:auto;font-size:11px}.dts-table .dts-selected::before{content:attr(data-label) \": \";color:var(--dsw-alias-label-tertiary);font-weight:600}}\n`\n\nfunction namesFromText(value: string): string[] {\n return [...new Set(value.split(/\\r?\\n/u).map(name => name.trim()).filter(Boolean))].sort()\n}\n\nfunction overrideFor(name: string, settings: ToolSearchSettings): ToolPolicy | 'default' {\n if (settings.neverSearch.includes(name)) return 'blocked'\n if (settings.alwaysVisible.includes(name)) return 'always'\n return 'default'\n}\n\nfunction decodeCatalog(value: unknown): CatalogSnapshot {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new Error('Invalid catalog response')\n const record = value as Record<string, unknown>\n if (record['schemaVersion'] !== 1 || !Array.isArray(record['tools']) || typeof record['generation'] !== 'number') {\n throw new Error('Invalid catalog response')\n }\n return value as CatalogSnapshot\n}\n\ntype SettingsProps = {\n scope?: SettingsScope<ToolSearchSettings>\n t?: Translate\n}\n\nexport function ToolSearchSettingsSection({ scope, t }: SettingsProps): ReactNode {\n const snapshot = useSyncExternalStore(\n useCallback((notify: () => void) => scope?.subscribe(notify) ?? (() => {}), [scope]),\n useCallback(() => scope?.getSnapshot() ?? EMPTY_SETTINGS, [scope]),\n )\n const [catalog, setCatalog] = useState<CatalogSnapshot>()\n const [filter, setFilter] = useState('')\n const [alwaysText, setAlwaysText] = useState('')\n const [blockedText, setBlockedText] = useState('')\n const [limitText, setLimitText] = useState(String(DEFAULT_RESULT_LIMIT))\n const [status, setStatus] = useState<{ text: string; error: boolean }>()\n const translate = t ?? ((key: keyof typeof en) => en[key])\n const settings = snapshot.value\n\n const refresh = useCallback(async (): Promise<void> => {\n try {\n const response = await fetch(TOOL_SEARCH_CATALOG_ROUTE, { cache: 'no-store' })\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n setCatalog(decodeCatalog(await response.json()))\n setStatus(undefined)\n } catch (error) {\n setStatus({ text: error instanceof Error ? error.message : String(error), error: true })\n }\n }, [])\n\n useEffect(() => { void refresh() }, [refresh])\n useEffect(() => {\n if (settings === undefined) return\n setAlwaysText(settings.alwaysVisible.join('\\n'))\n setBlockedText(settings.neverSearch.join('\\n'))\n setLimitText(String(settings.defaultLimit))\n }, [settings])\n\n const commit = useCallback((field: keyof ToolSearchSettings, value: unknown): void => {\n if (scope === undefined || !snapshot.writable) return\n setStatus(undefined)\n void scope.set(field, value).then(() => {\n setStatus({ text: translate('saved'), error: false })\n void refresh()\n }, error => {\n setStatus({ text: error instanceof Error ? error.message : String(error), error: true })\n })\n }, [refresh, scope, snapshot.writable, translate])\n\n const visibleTools = useMemo(() => {\n const query = filter.trim().toLowerCase()\n if (query.length === 0) return catalog?.tools ?? []\n return (catalog?.tools ?? []).filter(tool =>\n tool.name.toLowerCase().includes(query) || tool.description.toLowerCase().includes(query),\n )\n }, [catalog, filter])\n\n const changePolicy = (name: string, next: ToolPolicy | 'default'): void => {\n if (settings === undefined || name === TOOL_SEARCH_NAME) return\n const always = new Set(settings.alwaysVisible)\n const blocked = new Set(settings.neverSearch)\n always.delete(name)\n blocked.delete(name)\n if (next === 'always') always.add(name)\n if (next === 'blocked') blocked.add(name)\n commit('alwaysVisible', [...always].sort())\n commit('neverSearch', [...blocked].sort())\n }\n\n const core = new Set<string>(DEFAULT_CORE_TOOLS)\n return <div className=\"dts-settings\">\n <div className=\"dts-toolbar\">\n <label className=\"dts-field dts-limit\">\n <span>{translate('topK')}</span>\n <input\n className=\"dts-input\"\n type=\"number\"\n min={1}\n max={MAX_RESULT_LIMIT}\n value={limitText}\n disabled={!snapshot.writable}\n onChange={event => setLimitText(event.target.value)}\n onBlur={() => {\n const value = Number.parseInt(limitText, 10)\n if (Number.isSafeInteger(value) && value >= 1 && value <= MAX_RESULT_LIMIT) commit('defaultLimit', value)\n else setLimitText(String(settings?.defaultLimit ?? DEFAULT_RESULT_LIMIT))\n }}\n />\n </label>\n <label className=\"dts-field\">\n <span>{translate('filter')}</span>\n <input className=\"dts-input\" type=\"search\" value={filter} onChange={event => setFilter(event.target.value)} />\n </label>\n <button className=\"dts-button\" type=\"button\" onClick={() => { void refresh() }}>{translate('refresh')}</button>\n </div>\n\n <div className=\"dts-lists\">\n <label className=\"dts-field\">\n <span>{translate('alwaysVisible')}</span>\n <textarea\n className=\"dts-textarea\"\n value={alwaysText}\n disabled={!snapshot.writable}\n placeholder={translate('onePerLine')}\n onChange={event => setAlwaysText(event.target.value)}\n onBlur={() => commit('alwaysVisible', namesFromText(alwaysText))}\n />\n </label>\n <label className=\"dts-field\">\n <span>{translate('neverSearch')}</span>\n <textarea\n className=\"dts-textarea\"\n value={blockedText}\n disabled={!snapshot.writable}\n placeholder={translate('onePerLine')}\n onChange={event => setBlockedText(event.target.value)}\n onBlur={() => commit('neverSearch', namesFromText(blockedText).filter(name => name !== TOOL_SEARCH_NAME))}\n />\n </label>\n </div>\n\n <section className=\"dts-field\">\n <div className=\"dts-section-title\">{translate('catalog')}</div>\n <div className=\"dts-table-wrap\">\n <table className=\"dts-table\">\n <thead><tr>\n <th className=\"dts-tool\">{translate('tool')}</th>\n <th className=\"dts-policy\">{translate('policy')}</th>\n <th className=\"dts-selected\">{translate('selected')}</th>\n </tr></thead>\n <tbody>\n {visibleTools.map(tool => <tr key={tool.name}>\n <td className=\"dts-tool\">\n <code title={tool.name}>{tool.name}</code>\n {tool.description.length > 0 ? <span className=\"dts-description\" title={tool.description}>{tool.description}</span> : null}\n </td>\n <td className=\"dts-policy\">\n <select\n className=\"dts-select\"\n aria-label={`${translate('policy')}: ${tool.name}`}\n value={tool.name === TOOL_SEARCH_NAME ? 'always' : overrideFor(tool.name, settings ?? {\n defaultLimit: DEFAULT_RESULT_LIMIT,\n alwaysVisible: [],\n neverSearch: [],\n })}\n disabled={!snapshot.writable || tool.name === TOOL_SEARCH_NAME}\n onChange={event => changePolicy(tool.name, event.target.value as ToolPolicy | 'default')}\n >\n <option value=\"default\">{core.has(tool.name) ? `${translate('defaultPolicy')} (${translate('alwaysPolicy')})` : translate('defaultPolicy')}</option>\n <option value=\"always\">{translate('alwaysPolicy')}</option>\n <option value=\"blocked\">{translate('blockedPolicy')}</option>\n </select>\n </td>\n <td\n className=\"dts-selected\"\n data-label={translate('selected')}\n aria-label={`${translate('selected')}: ${tool.selectedBy.length}`}\n >{tool.selectedBy.length}</td>\n </tr>)}\n {catalog !== undefined && visibleTools.length === 0\n ? <tr><td colSpan={3}>{translate('empty')}</td></tr>\n : null}\n </tbody>\n </table>\n </div>\n <div className=\"dts-status\" data-error={status?.error || undefined}>\n {status?.text ?? (catalog === undefined ? translate('loading') : '')}\n </div>\n </section>\n </div>\n}\n\nexport const inject = ['slots', 'locale', 'settingsScope']\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => {\n if (document.querySelector('style[data-plugin-css=\"@anionex/dsh-tool-search\"]') !== null) return () => {}\n const tag = document.createElement('style')\n tag.dataset.pluginCss = '@anionex/dsh-tool-search'\n tag.textContent = styles\n document.head.append(tag)\n return () => tag.remove()\n }, 'dsh-tool-search: styles')\n ctx.effect(() => ctx.locale.register('settings.toolSearch', { en, zh }), 'dsh-tool-search: locale')\n const t = ctx.locale.bind('settings.toolSearch') as Translate\n const scope = ctx.settingsScope.bind<ToolSearchSettings>({ namespace: TOOL_SEARCH_SETTINGS_NAMESPACE })\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'tool-search',\n order: 34,\n label: () => t('nav'),\n inject: () => ({ scope, t }),\n }, ToolSearchSettingsSection))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useSyncExternalStore","useCallback","useState","useMemo"],"sources":["../src/shared.ts","../src/client/index.tsx"],"sourcesContent":["/** Values shared by the Host runtime and Web settings client. */\n\nexport const TOOL_SEARCH_NAME = 'tool_search'\nexport const TOOL_SEARCH_SETTINGS_NAMESPACE = 'tool-search'\nexport const TOOL_SEARCH_CATALOG_ROUTE = '/_dsh/tool-search/catalog'\nexport const DEFAULT_RESULT_LIMIT = 5\nexport const MAX_RESULT_LIMIT = 20\n\n/**\n * Stable bootstrap surface for current DSH releases. Unknown future first-party\n * names are deferred until this list or the user allowlist is updated because\n * ToolDefinition does not expose package provenance.\n */\nexport const DEFAULT_CORE_TOOLS = Object.freeze([\n 'apply_patch',\n 'ask_user_question',\n 'bash',\n 'create_goal',\n 'exit_plan_mode',\n 'get_goal',\n 'interrupt_agent',\n 'job_kill',\n 'job_list',\n 'job_output',\n 'list_agents',\n 'ralph',\n 'report',\n 'send_message',\n 'skill',\n 'subagent',\n 'subagent_fork',\n 'todo_write',\n 'update_goal',\n 'workflow',\n] as const)\n\nexport interface ToolSearchSettings {\n defaultLimit: number\n alwaysVisible: string[]\n neverSearch: string[]\n}\n\nexport const DEFAULT_SETTINGS: Readonly<ToolSearchSettings> = Object.freeze({\n defaultLimit: DEFAULT_RESULT_LIMIT,\n alwaysVisible: [],\n neverSearch: [],\n})\n\nexport type ToolPolicy = 'always' | 'deferred' | 'blocked'\n\nexport interface CatalogToolSummary {\n name: string\n description: string\n policy: ToolPolicy\n selectedBy: string[]\n}\n\nexport interface CatalogSnapshot {\n schemaVersion: 1\n generation: number\n tools: CatalogToolSummary[]\n}\n","/** DSH Web settings surface for deferred tool policy. */\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react'\nimport {\n DEFAULT_CORE_TOOLS,\n DEFAULT_RESULT_LIMIT,\n MAX_RESULT_LIMIT,\n TOOL_SEARCH_CATALOG_ROUTE,\n TOOL_SEARCH_NAME,\n TOOL_SEARCH_SETTINGS_NAMESPACE,\n type CatalogSnapshot,\n type ToolPolicy,\n type ToolSearchSettings,\n} from '../shared.ts'\n\ninterface SettingsScopeSnapshot<T> {\n status: 'loading' | 'ready' | 'unavailable'\n value: T | undefined\n writable: boolean\n}\n\ninterface SettingsScope<T> {\n getSnapshot(): SettingsScopeSnapshot<T>\n subscribe(listener: () => void): () => void\n set(field: string, value: unknown): Promise<void>\n}\n\ntype Disposer = () => void\n\ninterface ClientContext {\n effect(setup: () => Disposer | void, label: string): void\n locale: {\n register(namespace: string, catalogs: Record<string, Record<string, string>>): Disposer\n bind(namespace: string): (key: string) => string\n }\n settingsScope: {\n bind<T>(options: { namespace: string }): SettingsScope<T>\n }\n slots: {\n inject(name: string, callback: () => Disposer): Disposer\n register<P>(\n options: {\n name: string\n id: string\n order: number\n label: () => string\n inject: () => P\n },\n component: (props: P) => ReactNode,\n ): Disposer\n }\n}\n\ntype Translate = (key: keyof typeof en) => string\n\nconst en = {\n nav: 'Tool Search',\n topK: 'Default results',\n alwaysVisible: 'Always visible',\n neverSearch: 'Never searchable',\n catalog: 'Tool catalog',\n filter: 'Filter tools',\n refresh: 'Refresh',\n tool: 'Tool',\n policy: 'Policy',\n selected: 'Live agents',\n defaultPolicy: 'Default',\n alwaysPolicy: 'Always',\n deferredPolicy: 'Deferred',\n blockedPolicy: 'Blocked',\n loading: 'Loading...',\n empty: 'No matching tools',\n saved: 'Saved',\n onePerLine: 'One exact tool name per line',\n}\n\nconst zh: typeof en = {\n nav: '工具搜索',\n topK: '默认结果数',\n alwaysVisible: '常驻白名单',\n neverSearch: '搜索黑名单',\n catalog: '工具目录',\n filter: '筛选工具',\n refresh: '刷新',\n tool: '工具',\n policy: '策略',\n selected: '活跃 Agent',\n defaultPolicy: '默认',\n alwaysPolicy: '常驻',\n deferredPolicy: '延迟',\n blockedPolicy: '禁用',\n loading: '加载中...',\n empty: '没有匹配工具',\n saved: '已保存',\n onePerLine: '每行一个精确工具名',\n}\n\nconst EMPTY_SETTINGS: SettingsScopeSnapshot<ToolSearchSettings> = {\n status: 'unavailable',\n value: undefined,\n writable: false,\n}\n\nconst styles = `\n.dts-settings{container-type:inline-size;display:flex;flex-direction:column;gap:20px;width:100%;min-width:0;color:var(--dsw-alias-label-primary)}\n.dts-toolbar{display:grid;grid-template-columns:minmax(160px,220px) minmax(220px,1fr) auto;align-items:end;gap:12px}\n.dts-field{display:flex;flex-direction:column;gap:6px;min-width:0}\n.dts-field>span,.dts-section-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}\n.dts-input,.dts-textarea,.dts-select{box-sizing:border-box;width:100%;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit}\n.dts-input,.dts-select{height:34px;padding:0 9px}.dts-textarea{min-height:104px;padding:8px 9px;resize:vertical;line-height:20px}\n.dts-input:focus,.dts-textarea:focus,.dts-select:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}\n.dts-lists{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}\n.dts-hint{font-size:11px;color:var(--dsw-alias-label-tertiary)}\n.dts-button{height:34px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer}\n.dts-button:hover{background:var(--dsw-alias-interactive-bg-hover)}.dts-button:disabled{opacity:.5;cursor:not-allowed}\n.dts-status{min-height:18px;font-size:12px;color:var(--dsw-alias-label-secondary)}.dts-status[data-error=true]{color:var(--dsw-alias-state-error-primary)}\n.dts-table-wrap{min-width:0;overflow:auto;border-top:1px solid var(--dsw-alias-border-l2);border-bottom:1px solid var(--dsw-alias-border-l2)}\n.dts-table{width:100%;min-width:620px;border-collapse:collapse;table-layout:fixed}\n.dts-table th,.dts-table td{box-sizing:border-box;padding:9px 10px;border-bottom:1px solid var(--dsw-alias-border-l1);text-align:left;vertical-align:middle;font-size:12px}\n.dts-table th{position:sticky;top:0;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-weight:600}\n.dts-table tr:last-child td{border-bottom:0}.dts-tool{width:52%}.dts-tool code{display:block;overflow:hidden;text-overflow:ellipsis;overflow-wrap:anywhere;white-space:nowrap;color:var(--dsw-alias-label-primary)}\n.dts-description{display:block;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary)}\n.dts-policy{width:26%}.dts-selected{width:22%;color:var(--dsw-alias-label-secondary)}\n@container(max-width:620px){.dts-toolbar{grid-template-columns:1fr auto}.dts-toolbar .dts-limit{grid-column:1/-1}.dts-lists{grid-template-columns:1fr}.dts-table{min-width:0}.dts-table thead{display:none}.dts-table tr{display:grid;grid-template-columns:minmax(0,1fr) 132px;padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dts-table td{padding:4px 0;border:0}.dts-table .dts-tool,.dts-table .dts-policy{width:auto}.dts-table .dts-selected{grid-column:1/-1;width:auto;font-size:11px}.dts-table .dts-selected::before{content:attr(data-label) \": \";color:var(--dsw-alias-label-tertiary);font-weight:600}}\n`\n\nfunction namesFromText(value: string): string[] {\n return [...new Set(value.split(/\\r?\\n/u).map(name => name.trim()).filter(Boolean))].sort()\n}\n\nfunction overrideFor(name: string, settings: ToolSearchSettings): ToolPolicy | 'default' {\n if (settings.neverSearch.includes(name)) return 'blocked'\n if (settings.alwaysVisible.includes(name)) return 'always'\n return 'default'\n}\n\nfunction decodeCatalog(value: unknown): CatalogSnapshot {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new Error('Invalid catalog response')\n const record = value as Record<string, unknown>\n if (record['schemaVersion'] !== 1 || !Array.isArray(record['tools']) || typeof record['generation'] !== 'number') {\n throw new Error('Invalid catalog response')\n }\n return value as CatalogSnapshot\n}\n\ntype SettingsProps = {\n scope?: SettingsScope<ToolSearchSettings>\n t?: Translate\n}\n\nexport function ToolSearchSettingsSection({ scope, t }: SettingsProps): ReactNode {\n const snapshot = useSyncExternalStore(\n useCallback((notify: () => void) => scope?.subscribe(notify) ?? (() => {}), [scope]),\n useCallback(() => scope?.getSnapshot() ?? EMPTY_SETTINGS, [scope]),\n )\n const [catalog, setCatalog] = useState<CatalogSnapshot>()\n const [filter, setFilter] = useState('')\n const [alwaysText, setAlwaysText] = useState('')\n const [blockedText, setBlockedText] = useState('')\n const [limitText, setLimitText] = useState(String(DEFAULT_RESULT_LIMIT))\n const [status, setStatus] = useState<{ text: string; error: boolean }>()\n const translate = t ?? ((key: keyof typeof en) => en[key])\n const settings = snapshot.value\n\n const refresh = useCallback(async (): Promise<void> => {\n try {\n const response = await fetch(TOOL_SEARCH_CATALOG_ROUTE, { cache: 'no-store' })\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n setCatalog(decodeCatalog(await response.json()))\n setStatus(undefined)\n } catch (error) {\n setStatus({ text: error instanceof Error ? error.message : String(error), error: true })\n }\n }, [])\n\n useEffect(() => { void refresh() }, [refresh])\n useEffect(() => {\n if (settings === undefined) return\n setAlwaysText(settings.alwaysVisible.join('\\n'))\n setBlockedText(settings.neverSearch.join('\\n'))\n setLimitText(String(settings.defaultLimit))\n }, [settings])\n\n const commit = useCallback((field: keyof ToolSearchSettings, value: unknown): void => {\n if (scope === undefined || !snapshot.writable) return\n setStatus(undefined)\n void scope.set(field, value).then(() => {\n setStatus({ text: translate('saved'), error: false })\n void refresh()\n }, error => {\n setStatus({ text: error instanceof Error ? error.message : String(error), error: true })\n })\n }, [refresh, scope, snapshot.writable, translate])\n\n const visibleTools = useMemo(() => {\n const query = filter.trim().toLowerCase()\n if (query.length === 0) return catalog?.tools ?? []\n return (catalog?.tools ?? []).filter(tool =>\n tool.name.toLowerCase().includes(query) || tool.description.toLowerCase().includes(query),\n )\n }, [catalog, filter])\n\n const changePolicy = (name: string, next: ToolPolicy | 'default'): void => {\n if (settings === undefined || name === TOOL_SEARCH_NAME) return\n const always = new Set(settings.alwaysVisible)\n const blocked = new Set(settings.neverSearch)\n always.delete(name)\n blocked.delete(name)\n if (next === 'always') always.add(name)\n if (next === 'blocked') blocked.add(name)\n commit('alwaysVisible', [...always].sort())\n commit('neverSearch', [...blocked].sort())\n }\n\n const core = new Set<string>(DEFAULT_CORE_TOOLS)\n return <div className=\"dts-settings\">\n <div className=\"dts-toolbar\">\n <label className=\"dts-field dts-limit\">\n <span>{translate('topK')}</span>\n <input\n className=\"dts-input\"\n type=\"number\"\n min={1}\n max={MAX_RESULT_LIMIT}\n value={limitText}\n disabled={!snapshot.writable}\n onChange={event => setLimitText(event.target.value)}\n onBlur={() => {\n const value = Number.parseInt(limitText, 10)\n if (Number.isSafeInteger(value) && value >= 1 && value <= MAX_RESULT_LIMIT) commit('defaultLimit', value)\n else setLimitText(String(settings?.defaultLimit ?? DEFAULT_RESULT_LIMIT))\n }}\n />\n </label>\n <label className=\"dts-field\">\n <span>{translate('filter')}</span>\n <input className=\"dts-input\" type=\"search\" value={filter} onChange={event => setFilter(event.target.value)} />\n </label>\n <button className=\"dts-button\" type=\"button\" onClick={() => { void refresh() }}>{translate('refresh')}</button>\n </div>\n\n <div className=\"dts-lists\">\n <label className=\"dts-field\">\n <span>{translate('alwaysVisible')}</span>\n <textarea\n className=\"dts-textarea\"\n value={alwaysText}\n disabled={!snapshot.writable}\n placeholder={translate('onePerLine')}\n onChange={event => setAlwaysText(event.target.value)}\n onBlur={() => commit('alwaysVisible', namesFromText(alwaysText))}\n />\n </label>\n <label className=\"dts-field\">\n <span>{translate('neverSearch')}</span>\n <textarea\n className=\"dts-textarea\"\n value={blockedText}\n disabled={!snapshot.writable}\n placeholder={translate('onePerLine')}\n onChange={event => setBlockedText(event.target.value)}\n onBlur={() => commit('neverSearch', namesFromText(blockedText).filter(name => name !== TOOL_SEARCH_NAME))}\n />\n </label>\n </div>\n\n <section className=\"dts-field\">\n <div className=\"dts-section-title\">{translate('catalog')}</div>\n <div className=\"dts-table-wrap\">\n <table className=\"dts-table\">\n <thead><tr>\n <th className=\"dts-tool\">{translate('tool')}</th>\n <th className=\"dts-policy\">{translate('policy')}</th>\n <th className=\"dts-selected\">{translate('selected')}</th>\n </tr></thead>\n <tbody>\n {visibleTools.map(tool => <tr key={tool.name}>\n <td className=\"dts-tool\">\n <code title={tool.name}>{tool.name}</code>\n {tool.description.length > 0 ? <span className=\"dts-description\" title={tool.description}>{tool.description}</span> : null}\n </td>\n <td className=\"dts-policy\">\n <select\n className=\"dts-select\"\n aria-label={`${translate('policy')}: ${tool.name}`}\n value={tool.name === TOOL_SEARCH_NAME ? 'always' : overrideFor(tool.name, settings ?? {\n defaultLimit: DEFAULT_RESULT_LIMIT,\n alwaysVisible: [],\n neverSearch: [],\n })}\n disabled={!snapshot.writable || tool.name === TOOL_SEARCH_NAME}\n onChange={event => changePolicy(tool.name, event.target.value as ToolPolicy | 'default')}\n >\n <option value=\"default\">\n {`${translate('defaultPolicy')} (${translate(core.has(tool.name) ? 'alwaysPolicy' : 'deferredPolicy')})`}\n </option>\n <option value=\"always\">{translate('alwaysPolicy')}</option>\n <option value=\"blocked\">{translate('blockedPolicy')}</option>\n </select>\n </td>\n <td\n className=\"dts-selected\"\n data-label={translate('selected')}\n aria-label={`${translate('selected')}: ${tool.selectedBy.length}`}\n >{tool.selectedBy.length}</td>\n </tr>)}\n {catalog !== undefined && visibleTools.length === 0\n ? <tr><td colSpan={3}>{translate('empty')}</td></tr>\n : null}\n </tbody>\n </table>\n </div>\n <div className=\"dts-status\" data-error={status?.error || undefined}>\n {status?.text ?? (catalog === undefined ? translate('loading') : '')}\n </div>\n </section>\n </div>\n}\n\nexport const inject = ['slots', 'locale', 'settingsScope']\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => {\n if (document.querySelector('style[data-plugin-css=\"@anionex/dsh-tool-search\"]') !== null) return () => {}\n const tag = document.createElement('style')\n tag.dataset.pluginCss = '@anionex/dsh-tool-search'\n tag.textContent = styles\n document.head.append(tag)\n return () => tag.remove()\n }, 'dsh-tool-search: styles')\n ctx.effect(() => ctx.locale.register('settings.toolSearch', { en, zh }), 'dsh-tool-search: locale')\n const t = ctx.locale.bind('settings.toolSearch') as Translate\n const scope = ctx.settingsScope.bind<ToolSearchSettings>({ namespace: TOOL_SEARCH_SETTINGS_NAMESPACE })\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'tool-search',\n order: 34,\n label: () => t('nav'),\n inject: () => ({ scope, t }),\n }, ToolSearchSettingsSection))\n}\n"],"mappings":"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"}
package/lib/shared.js CHANGED
@@ -14,7 +14,6 @@ export const DEFAULT_CORE_TOOLS = Object.freeze([
14
14
  'ask_user_question',
15
15
  'bash',
16
16
  'create_goal',
17
- 'dsh_im_return_file',
18
17
  'exit_plan_mode',
19
18
  'get_goal',
20
19
  'interrupt_agent',
package/lib/shared.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAA;AAC7C,MAAM,CAAC,MAAM,8BAA8B,GAAG,aAAa,CAAA;AAC3D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAA;AACrC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,aAAa;IACb,mBAAmB;IACnB,MAAM;IACN,aAAa;IACb,oBAAoB;IACpB,gBAAgB;IAChB,UAAU;IACV,iBAAiB;IACjB,UAAU;IACV,UAAU;IACV,YAAY;IACZ,aAAa;IACb,OAAO;IACP,QAAQ;IACR,cAAc;IACd,OAAO;IACP,UAAU;IACV,eAAe;IACf,YAAY;IACZ,aAAa;IACb,UAAU;CACF,CAAC,CAAA;AAQX,MAAM,CAAC,MAAM,gBAAgB,GAAiC,MAAM,CAAC,MAAM,CAAC;IAC1E,YAAY,EAAE,oBAAoB;IAClC,aAAa,EAAE,EAAE;IACjB,WAAW,EAAE,EAAE;CAChB,CAAC,CAAA"}
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAA;AAC7C,MAAM,CAAC,MAAM,8BAA8B,GAAG,aAAa,CAAA;AAC3D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAA;AACrC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC;IAC9C,aAAa;IACb,mBAAmB;IACnB,MAAM;IACN,aAAa;IACb,gBAAgB;IAChB,UAAU;IACV,iBAAiB;IACjB,UAAU;IACV,UAAU;IACV,YAAY;IACZ,aAAa;IACb,OAAO;IACP,QAAQ;IACR,cAAc;IACd,OAAO;IACP,UAAU;IACV,eAAe;IACf,YAAY;IACZ,aAAa;IACb,UAAU;CACF,CAAC,CAAA;AAQX,MAAM,CAAC,MAAM,gBAAgB,GAAiC,MAAM,CAAC,MAAM,CAAC;IAC1E,YAAY,EAAE,oBAAoB;IAClC,aAAa,EAAE,EAAE;IACjB,WAAW,EAAE,EAAE;CAChB,CAAC,CAAA"}
@@ -48,6 +48,7 @@ declare const en: {
48
48
  selected: string;
49
49
  defaultPolicy: string;
50
50
  alwaysPolicy: string;
51
+ deferredPolicy: string;
51
52
  blockedPolicy: string;
52
53
  loading: string;
53
54
  empty: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAAA,yDAAyD;AAEzD,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AACd,OAAO,EASL,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAA;AAErB,UAAU,qBAAqB,CAAC,CAAC;IAC/B,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,aAAa,CAAA;IAC3C,KAAK,EAAE,CAAC,GAAG,SAAS,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,UAAU,aAAa,CAAC,CAAC;IACvB,WAAW,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAA;IACvC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAC3C,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClD;AAED,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAA;AAE1B,UAAU,aAAa;IACrB,MAAM,CAAC,KAAK,EAAE,MAAM,QAAQ,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACzD,MAAM,EAAE;QACN,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAA;QACvF,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;KACjD,CAAA;IACD,aAAa,EAAE;QACb,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;KAC1D,CAAA;IACD,KAAK,EAAE;QACL,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,GAAG,QAAQ,CAAA;QACxD,QAAQ,CAAC,CAAC,EACR,OAAO,EAAE;YACP,IAAI,EAAE,MAAM,CAAA;YACZ,EAAE,EAAE,MAAM,CAAA;YACV,KAAK,EAAE,MAAM,CAAA;YACb,KAAK,EAAE,MAAM,MAAM,CAAA;YACnB,MAAM,EAAE,MAAM,CAAC,CAAA;SAChB,EACD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,SAAS,GACjC,QAAQ,CAAA;KACZ,CAAA;CACF;AAED,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,OAAO,EAAE,KAAK,MAAM,CAAA;AAEjD,QAAA,MAAM,EAAE;;;;;;;;;;;;;;;;;;CAkBP,CAAA;AAsED,KAAK,aAAa,GAAG;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACzC,CAAC,CAAC,EAAE,SAAS,CAAA;CACd,CAAA;AAED,wBAAgB,yBAAyB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,aAAa,GAAG,SAAS,CAqKhF;AAED,eAAO,MAAM,MAAM,UAAuC,CAAA;AAE1D,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAmB9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAAA,yDAAyD;AAEzD,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAA;AACd,OAAO,EASL,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAA;AAErB,UAAU,qBAAqB,CAAC,CAAC;IAC/B,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,aAAa,CAAA;IAC3C,KAAK,EAAE,CAAC,GAAG,SAAS,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,UAAU,aAAa,CAAC,CAAC;IACvB,WAAW,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAA;IACvC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAC3C,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClD;AAED,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAA;AAE1B,UAAU,aAAa;IACrB,MAAM,CAAC,KAAK,EAAE,MAAM,QAAQ,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACzD,MAAM,EAAE;QACN,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAA;QACvF,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;KACjD,CAAA;IACD,aAAa,EAAE;QACb,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;KAC1D,CAAA;IACD,KAAK,EAAE;QACL,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,GAAG,QAAQ,CAAA;QACxD,QAAQ,CAAC,CAAC,EACR,OAAO,EAAE;YACP,IAAI,EAAE,MAAM,CAAA;YACZ,EAAE,EAAE,MAAM,CAAA;YACV,KAAK,EAAE,MAAM,CAAA;YACb,KAAK,EAAE,MAAM,MAAM,CAAA;YACnB,MAAM,EAAE,MAAM,CAAC,CAAA;SAChB,EACD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,SAAS,GACjC,QAAQ,CAAA;KACZ,CAAA;CACF;AAED,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,OAAO,EAAE,KAAK,MAAM,CAAA;AAEjD,QAAA,MAAM,EAAE;;;;;;;;;;;;;;;;;;;CAmBP,CAAA;AAuED,KAAK,aAAa,GAAG;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACzC,CAAC,CAAC,EAAE,SAAS,CAAA;CACd,CAAA;AAED,wBAAgB,yBAAyB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,aAAa,GAAG,SAAS,CAuKhF;AAED,eAAO,MAAM,MAAM,UAAuC,CAAA;AAE1D,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAmB9C"}
@@ -9,7 +9,7 @@ export declare const MAX_RESULT_LIMIT = 20;
9
9
  * names are deferred until this list or the user allowlist is updated because
10
10
  * ToolDefinition does not expose package provenance.
11
11
  */
12
- export declare const DEFAULT_CORE_TOOLS: readonly ["apply_patch", "ask_user_question", "bash", "create_goal", "dsh_im_return_file", "exit_plan_mode", "get_goal", "interrupt_agent", "job_kill", "job_list", "job_output", "list_agents", "ralph", "report", "send_message", "skill", "subagent", "subagent_fork", "todo_write", "update_goal", "workflow"];
12
+ export declare const DEFAULT_CORE_TOOLS: readonly ["apply_patch", "ask_user_question", "bash", "create_goal", "exit_plan_mode", "get_goal", "interrupt_agent", "job_kill", "job_list", "job_output", "list_agents", "ralph", "report", "send_message", "skill", "subagent", "subagent_fork", "todo_write", "update_goal", "workflow"];
13
13
  export interface ToolSearchSettings {
14
14
  defaultLimit: number;
15
15
  alwaysVisible: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/shared.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,eAAO,MAAM,gBAAgB,gBAAgB,CAAA;AAC7C,eAAO,MAAM,8BAA8B,gBAAgB,CAAA;AAC3D,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,oBAAoB,IAAI,CAAA;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,oTAsBpB,CAAA;AAEX,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAIxD,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAA;AAE1D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,CAAA;IAClB,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,CAAC,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,kBAAkB,EAAE,CAAA;CAC5B"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/shared.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,eAAO,MAAM,gBAAgB,gBAAgB,CAAA;AAC7C,eAAO,MAAM,8BAA8B,gBAAgB,CAAA;AAC3D,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,oBAAoB,IAAI,CAAA;AACrC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,8RAqBpB,CAAA;AAEX,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAIxD,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAA;AAE1D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,CAAA;IAClB,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,CAAC,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,kBAAkB,EAAE,CAAA;CAC5B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anionex/dsh-tool-search",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Codex-style deferred tool loading for DSH with deterministic local BM25 search",
5
5
  "keywords": [
6
6
  "deepseek",
@@ -1,11 +1,12 @@
1
1
  import { spawn } from 'node:child_process'
2
- import { access, mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
2
+ import { access, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from 'node:fs/promises'
3
3
  import { createServer } from 'node:http'
4
4
  import { tmpdir } from 'node:os'
5
5
  import { dirname, join } from 'node:path'
6
6
  import { fileURLToPath } from 'node:url'
7
7
 
8
8
  const root = dirname(dirname(fileURLToPath(import.meta.url)))
9
+ const packageVersion = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')).version
9
10
  const dsh = process.env.DSH_BIN ?? 'dsh'
10
11
  const keepTemp = process.argv.includes('--keep-temp')
11
12
  const timeoutMs = Number(process.env.DSH_TOOL_SEARCH_E2E_TIMEOUT_MS ?? 180_000)
@@ -88,14 +89,24 @@ async function fixturePackage(directory) {
88
89
  ' },',
89
90
  ' execute: args => Promise.resolve({ echo: args.value }),',
90
91
  ' }))',
92
+ ' ctx.tools.register(defineTool({',
93
+ " name: 'dsh_im_return_file',",
94
+ " description: 'Send a readable file to the user through the conversation.',",
95
+ " parameters: { path: { type: 'string', required: true, description: 'Readable file path.' } },",
96
+ ' output: {',
97
+ " schema: { type: 'object', additionalProperties: false, properties: { sent: { type: 'boolean', required: true } } },",
98
+ " render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],",
99
+ ' },',
100
+ ' execute: () => Promise.resolve({ sent: true }),',
101
+ ' }))',
91
102
  '}',
92
103
  '',
93
104
  ].join('\n'))
94
105
  }
95
106
 
96
107
  async function startLlm(script = [
97
- { kind: 'tool', name: 'tool_search', arguments: '{"query":"fixture_echo","limit":1}' },
98
- { kind: 'tool', name: 'fixture_echo', arguments: '{"value":"real-agent-ok"}' },
108
+ { kind: 'tool', name: 'tool_search', arguments: '{"query":"dsh_im_return_file","limit":1}' },
109
+ { kind: 'tool', name: 'dsh_im_return_file', arguments: '{"path":"/tmp/tool-search-e2e.txt"}' },
99
110
  { kind: 'text', text: 'tool search e2e done' },
100
111
  ]) {
101
112
  const requests = []
@@ -323,7 +334,7 @@ try {
323
334
  const runResult = await runDsh([
324
335
  '--profile', 'headless',
325
336
  '--patch', patch,
326
- 'Find and call the fixture echo tool.',
337
+ 'Find and call the tool that returns a file through the conversation.',
327
338
  ], {
328
339
  cwd: workspaceDirectory,
329
340
  env: {
@@ -341,12 +352,14 @@ try {
341
352
  const searchedNames = requestToolNames(llm.requests[1])
342
353
  assert(initialNames.includes('tool_search'), 'initial model call omits tool_search')
343
354
  assert(!initialNames.includes('fixture_echo'), 'initial model call exposes deferred fixture_echo')
344
- assert(searchedNames.includes('fixture_echo'), 'next model call omits selected fixture_echo')
355
+ assert(!initialNames.includes('dsh_im_return_file'), 'initial model call exposes plugin-provided dsh_im_return_file')
356
+ assert(!searchedNames.includes('fixture_echo'), 'searching dsh_im_return_file exposes unrelated fixture_echo')
357
+ assert(searchedNames.includes('dsh_im_return_file'), 'next model call omits selected dsh_im_return_file')
345
358
  const searchResult = toolResultTexts(llm.requests[1]).at(-1) ?? ''
346
- assert(searchResult.includes('fixture_echo'), 'tool_search result omits fixture_echo summary')
359
+ assert(searchResult.includes('dsh_im_return_file'), 'tool_search result omits dsh_im_return_file summary')
347
360
  assert(!searchResult.includes('"parameters"'), 'tool_search result duplicates a full schema')
348
- const echoResult = toolResultTexts(llm.requests[2]).at(-1) ?? ''
349
- assert(echoResult.includes('real-agent-ok'), 'fixture_echo did not execute through the real Agent loop')
361
+ const returnFileResult = toolResultTexts(llm.requests[2]).at(-1) ?? ''
362
+ assert(returnFileResult.includes('"sent":true'), 'dsh_im_return_file did not execute through the real Agent loop')
350
363
 
351
364
  process.stdout.write(`${JSON.stringify({
352
365
  ok: true,
@@ -356,8 +369,8 @@ try {
356
369
  modelCalls: llm.requests.length,
357
370
  initialToolCount: initialNames.length,
358
371
  selectedToolCount: searchedNames.length,
359
- selectedOnNextCall: searchedNames.includes('fixture_echo'),
360
- package: '@anionex/dsh-tool-search@0.1.0',
372
+ selectedOnNextCall: searchedNames.includes('dsh_im_return_file'),
373
+ package: `@anionex/dsh-tool-search@${packageVersion}`,
361
374
  temporaryHome: keepTemp ? home : undefined,
362
375
  }, null, 2)}\n`)
363
376
  } finally {
@@ -24,7 +24,7 @@ async function exists(path) {
24
24
 
25
25
  const pkg = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'))
26
26
  check(pkg.name === '@anionex/dsh-tool-search', 'package name is incorrect')
27
- check(pkg.version === '0.1.0', 'release version is incorrect')
27
+ check(pkg.version === '0.1.1', 'release version is incorrect')
28
28
  check(pkg.main === './lib/index.js', 'main entrypoint is incorrect')
29
29
  check(pkg.types === './lib/types/index.d.ts', 'types entrypoint is incorrect')
30
30
  check(pkg.exports?.['./client']?.default === './lib/client.js', 'client export is incorrect')
@@ -73,6 +73,7 @@ const en = {
73
73
  selected: 'Live agents',
74
74
  defaultPolicy: 'Default',
75
75
  alwaysPolicy: 'Always',
76
+ deferredPolicy: 'Deferred',
76
77
  blockedPolicy: 'Blocked',
77
78
  loading: 'Loading...',
78
79
  empty: 'No matching tools',
@@ -93,6 +94,7 @@ const zh: typeof en = {
93
94
  selected: '活跃 Agent',
94
95
  defaultPolicy: '默认',
95
96
  alwaysPolicy: '常驻',
97
+ deferredPolicy: '延迟',
96
98
  blockedPolicy: '禁用',
97
99
  loading: '加载中...',
98
100
  empty: '没有匹配工具',
@@ -296,7 +298,9 @@ export function ToolSearchSettingsSection({ scope, t }: SettingsProps): ReactNod
296
298
  disabled={!snapshot.writable || tool.name === TOOL_SEARCH_NAME}
297
299
  onChange={event => changePolicy(tool.name, event.target.value as ToolPolicy | 'default')}
298
300
  >
299
- <option value="default">{core.has(tool.name) ? `${translate('defaultPolicy')} (${translate('alwaysPolicy')})` : translate('defaultPolicy')}</option>
301
+ <option value="default">
302
+ {`${translate('defaultPolicy')} (${translate(core.has(tool.name) ? 'alwaysPolicy' : 'deferredPolicy')})`}
303
+ </option>
300
304
  <option value="always">{translate('alwaysPolicy')}</option>
301
305
  <option value="blocked">{translate('blockedPolicy')}</option>
302
306
  </select>
package/src/shared.ts CHANGED
@@ -16,7 +16,6 @@ export const DEFAULT_CORE_TOOLS = Object.freeze([
16
16
  'ask_user_question',
17
17
  'bash',
18
18
  'create_goal',
19
- 'dsh_im_return_file',
20
19
  'exit_plan_mode',
21
20
  'get_goal',
22
21
  'interrupt_agent',
@@ -23,12 +23,21 @@ describe('Tool Search settings section', () => {
23
23
  description: 'Capture a browser screenshot.',
24
24
  policy: 'deferred',
25
25
  selectedBy: [],
26
+ }, {
27
+ name: 'dsh_im_return_file',
28
+ description: 'Send a readable file to the user.',
29
+ policy: 'deferred',
30
+ selectedBy: [],
26
31
  }],
27
32
  }), { status: 200 }))))
28
33
 
29
34
  render(<ToolSearchSettingsSection scope={scope} />)
30
35
  expect(await screen.findByText('browser_take_screenshot')).toBeDefined()
31
- expect(screen.getByLabelText('Live agents: 0').getAttribute('data-label')).toBe('Live agents')
36
+ const imPolicy = screen.getByLabelText('Policy: dsh_im_return_file') as HTMLSelectElement
37
+ expect(imPolicy.value).toBe('default')
38
+ expect(imPolicy.options[0]?.text).toBe('Default (Deferred)')
39
+ expect(screen.getAllByLabelText('Live agents: 0').map(element => element.getAttribute('data-label')))
40
+ .toEqual(['Live agents', 'Live agents'])
32
41
  fireEvent.change(screen.getByLabelText('Policy: browser_take_screenshot'), { target: { value: 'always' } })
33
42
  await waitFor(() => {
34
43
  expect(set).toHaveBeenCalledWith('alwaysVisible', ['browser_take_screenshot'])
@@ -24,6 +24,7 @@ describe('tool exposure policy', () => {
24
24
  expect(policy.classify(TOOL_SEARCH_NAME)).toBe('always')
25
25
  expect(policy.classify('apply_patch')).toBe('always')
26
26
  expect(policy.classify('report')).toBe('always')
27
+ expect(policy.classify('dsh_im_return_file')).toBe('deferred')
27
28
  expect(policy.classify('bash')).toBe('blocked')
28
29
  expect(policy.classify('custom_tool')).toBe('always')
29
30
  expect(policy.classify('conflict_tool')).toBe('blocked')