mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
fix(tool-search): avoid blocking known group activation
This commit is contained in:
@@ -79,7 +79,7 @@
|
|||||||
- Hashline 默认以 2 字符行哈希覆盖内置 `read` / `edit`,`grep` 和 `replaceText` 均关闭;它保证锚定编辑而不负责压缩,大文件上下文节省必须来自先定位/隔离分析、再做最小范围 `offset/limit` 读取。
|
- Hashline 默认以 2 字符行哈希覆盖内置 `read` / `edit`,`grep` 和 `replaceText` 均关闭;它保证锚定编辑而不负责压缩,大文件上下文节省必须来自先定位/隔离分析、再做最小范围 `offset/limit` 读取。
|
||||||
- 工具路由规则不得替换 Pi 默认系统提示词:代码结构、调用关系和待修改 symbol 优先 CodeGraph,定义/引用/类型/诊断优先 LSP;工作区内大文件探索、分析和总结优先 `ctx_execute_file`,日志、构建与不可预测命令输出优先 Context Mode;字面搜索先用 FFF `find` 收敛文件位置,再在收敛路径内用 `grep`/`multi_grep` 获取行号;真正修改前才用小范围 Hashline `read` 获取新鲜锚点并用锚定 `edit`。在线搜索按场景只先选一个服务:广泛发现、新闻和候选来源用 Tavily,官方/技术/论文和高确定性来源用 Exa,中文、站点限定和日期筛选用 Keenable;只有必要时才跨服务复核。遇到大量结果、截断或上限时继续缩小范围,不得靠提高 limit 或倾倒全部结果解决。
|
- 工具路由规则不得替换 Pi 默认系统提示词:代码结构、调用关系和待修改 symbol 优先 CodeGraph,定义/引用/类型/诊断优先 LSP;工作区内大文件探索、分析和总结优先 `ctx_execute_file`,日志、构建与不可预测命令输出优先 Context Mode;字面搜索先用 FFF `find` 收敛文件位置,再在收敛路径内用 `grep`/`multi_grep` 获取行号;真正修改前才用小范围 Hashline `read` 获取新鲜锚点并用锚定 `edit`。在线搜索按场景只先选一个服务:广泛发现、新闻和候选来源用 Tavily,官方/技术/论文和高确定性来源用 Exa,中文、站点限定和日期筛选用 Keenable;只有必要时才跨服务复核。遇到大量结果、截断或上限时继续缩小范围,不得靠提高 limit 或倾倒全部结果解决。
|
||||||
- `ask_user_question` 只用于模型继续任务所必需的用户决定、偏好、确认或澄清;不得重复询问直接用户消息中已有的信息。工具仅支持交互式 TUI、顺序执行和最多 8 题;取消或 abort 必须返回 `cancelled: true`,不得伪造答案,且 `/qna` 不属于该扩展。其成功 `details.answers` 必须保留 `question` / `answer` 字段,以匹配 AutoReview 已有的可信结构化用户交互边界。
|
- `ask_user_question` 只用于模型继续任务所必需的用户决定、偏好、确认或澄清;不得重复询问直接用户消息中已有的信息。工具仅支持交互式 TUI、顺序执行和最多 8 题;取消或 abort 必须返回 `cancelled: true`,不得伪造答案,且 `/qna` 不属于该扩展。其成功 `details.answers` 必须保留 `question` / `answer` 字段,以匹配 AutoReview 已有的可信结构化用户交互边界。
|
||||||
- Tool Search 默认常驻核心 `read` / `write` / `edit` / `bash` / `grep` / `find`、`codegraph_explore`、`lsp_diagnostics` 与 `tool_search`;其余组合包工具必须优先使用 `pi-tool-search/extensions/bundle-groups.ts` 中提交的权威工作流分组。默认最多 3 个动态组、模型组最多 8 个工具、动态工具总数最多 20,加载或组内调用更新 LRU,超限先移除最久未用组。
|
- Tool Search 默认常驻核心 `read` / `write` / `edit` / `bash` / `grep` / `find`、`codegraph_explore`、`lsp_diagnostics` 与 `tool_search`;Windows 上内置 `powershell` 必须与 `bash` 同属固定本地 Shell 核心工具,非 Windows 平台必须从目录中过滤。其余组合包工具必须优先使用 `pi-tool-search/extensions/bundle-groups.ts` 中提交的权威工作流分组;精确已有分组必须直接激活,不得等待无关未知工具的模型增强。默认最多 3 个动态组、模型组最多 8 个工具、动态工具总数最多 20,加载或组内调用更新 LRU,超限先移除最久未用组。
|
||||||
- `pi-chrome` 在未授权时不注册 Chrome 工具;显式授权后出现的 21 个 `chrome_*` 工具必须由 Tool Search 分入 `chrome-navigation`、`chrome-interaction`、`chrome-debugging`,不得落入 unknown-tools 模型增强路径。Tool Search 重新激活 schema 不能替代上游每次执行时的授权校验。
|
- `pi-chrome` 在未授权时不注册 Chrome 工具;显式授权后出现的 21 个 `chrome_*` 工具必须由 Tool Search 分入 `chrome-navigation`、`chrome-interaction`、`chrome-debugging`,不得落入 unknown-tools 模型增强路径。Tool Search 重新激活 schema 不能替代上游每次执行时的授权校验。
|
||||||
- 标准组合包工具全部命中预置目录时 Tool Search 不得调用模型、创建用户缓存或发送隐藏 schema。只有存在未识别第三方工具时才允许惰性模型增强;生成结果必须保留全部预置 assignment,模型元数据不得替代真实名称、参数或 schema。缓存必须位于 agent 目录且权限 `600`;模型失败必须继续使用预置目录 + 确定性未知工具分组。`groupOverrides` 优先级高于预置分组。
|
- 标准组合包工具全部命中预置目录时 Tool Search 不得调用模型、创建用户缓存或发送隐藏 schema。只有存在未识别第三方工具时才允许惰性模型增强;生成结果必须保留全部预置 assignment,模型元数据不得替代真实名称、参数或 schema。缓存必须位于 agent 目录且权限 `600`;模型失败必须继续使用预置目录 + 确定性未知工具分组。`groupOverrides` 优先级高于预置分组。
|
||||||
- Tool Search 必须直接使用当前 `@earendil-works/pi-coding-agent`、`typebox`、`ModelRegistry.complete()` 和 `setActiveTools()`;首次纯增加载使用当前 Pi 的增量结果传播,LRU 替换允许走宿主安全 fallback。不得安装或映射旧 `@mariozechner` runtime,不得恢复 provider payload 改写、代理分发或隐藏 `sendMessage` steer/retry 兼容循环。
|
- Tool Search 必须直接使用当前 `@earendil-works/pi-coding-agent`、`typebox`、`ModelRegistry.complete()` 和 `setActiveTools()`;首次纯增加载使用当前 Pi 的增量结果传播,LRU 替换允许走宿主安全 fallback。不得安装或映射旧 `@mariozechner` runtime,不得恢复 provider payload 改写、代理分发或隐藏 `sendMessage` steer/retry 兼容循环。
|
||||||
|
|||||||
@@ -152,9 +152,9 @@ CodeGraph 本体不由组合包安装。需要使用 CodeGraph 的机器应自
|
|||||||
|
|
||||||
本地源码直接使用 `@earendil-works/pi-coding-agent`、当前 `typebox`、`ModelRegistry.complete()` 与 `setActiveTools()`,不安装、别名映射或加载旧 `@mariozechner` runtime,也不使用 provider payload 改写、代理执行或隐藏 `sendMessage` 循环。
|
本地源码直接使用 `@earendil-works/pi-coding-agent`、当前 `typebox`、`ModelRegistry.complete()` 与 `setActiveTools()`,不安装、别名映射或加载旧 `@mariozechner` runtime,也不使用 provider payload 改写、代理执行或隐藏 `sendMessage` 循环。
|
||||||
|
|
||||||
新 session 默认常驻 Pi 核心 `read`、`write`、`edit`、`bash`、`grep`、`find`,以及 `codegraph_explore`、`lsp_diagnostics` 和 `tool_search`。组合包在 `pi-tool-search/extensions/bundle-groups.ts` 中为自身暴露的工具预置权威分组,包括本地文件导航、SSH 远端文件/命令、Chrome 导航/交互/调试、用户交互、CodeGraph/LSP、Tavily、Exa、Keenable、Context Mode 执行/知识库/观测/管理、Memory 查询/维护、Skill 和 MCP 管理。不可用或尚未授权而未注册的可选工具会自动从组中滤除,固定工具也不占动态组额度。
|
新 session 默认常驻 Pi 核心 `read`、`write`、`edit`、`bash`、`grep`、`find`,以及 `codegraph_explore`、`lsp_diagnostics` 和 `tool_search`;Windows 上额外把内置 `powershell` 与 `bash` 作为同类本地 Shell 核心工具固定启用,非 Windows 平台则从目录中过滤它。组合包在 `pi-tool-search/extensions/bundle-groups.ts` 中为自身暴露的工具预置权威分组,包括本地文件导航、SSH 远端文件/命令、Chrome 导航/交互/调试、用户交互、CodeGraph/LSP、Tavily、Exa、Keenable、Context Mode 执行/知识库/观测/管理、Memory 查询/维护、Skill 和 MCP 管理。不可用或尚未授权而未注册的可选工具会自动从组中滤除,固定工具也不占动态组额度。
|
||||||
|
|
||||||
标准组合包的全部隐藏工具都能命中预置目录,因此首次使用不调用模型、不生成用户缓存,也不把完整隐藏 schema 发送给 provider。只有用户另外安装了未识别工具时,才先为新增工具提供确定性分组,并可在第一次 `tool_search` 时使用当前已认证模型补充目录;模型结果必须保留组合包预置分组,否则直接拒绝。有效增强缓存以 `0600` 写到 agent 目录的 `tool-search/catalog-v1.json`,调用用量计入工具结果。用户 `groupOverrides` 的优先级高于预置目录;`/tool-search-rebuild` 会立即恢复预置目录,只有仍存在额外工具时才可能在下次搜索惰性增强。
|
标准组合包的全部隐藏工具都能命中预置目录,因此首次使用不调用模型、不生成用户缓存,也不把完整隐藏 schema 发送给 provider。只有用户另外安装了未识别工具,并且当前调用不是精确加载已有分组时,才可使用当前已认证模型补充目录;精确已有分组始终立即按当前确定性目录激活,不等待无关未知工具的模型增强。模型结果必须保留组合包预置分组,否则直接拒绝。有效增强缓存以 `0600` 写到 agent 目录的 `tool-search/catalog-v1.json`,调用用量计入工具结果。用户 `groupOverrides` 的优先级高于预置目录;`/tool-search-rebuild` 会立即恢复预置目录,只有仍存在额外工具时才可能在后续未知分组或 query 搜索中惰性增强。
|
||||||
|
|
||||||
动态组默认最多同时加载 3 个、每个模型生成组最多 8 个工具、全部动态组最多 20 个工具。加载或调用组内工具会更新 LRU;加载第 4 组或超出工具总量时先卸载最久未使用的非固定组。首次纯增加载继续使用 Pi 原生增量传播;发生卸载和加载的替换不是纯增量,Pi 会自动走安全 fallback。
|
动态组默认最多同时加载 3 个、每个模型生成组最多 8 个工具、全部动态组最多 20 个工具。加载或调用组内工具会更新 LRU;加载第 4 组或超出工具总量时先卸载最久未使用的非固定组。首次纯增加载继续使用 Pi 原生增量传播;发生卸载和加载的替换不是纯增量,Pi 会自动走安全 fallback。
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
- Added sequential `ssh_cd` to the authoritative remote-shell workflow and direct the model to wait for a successful workspace change before dependent remote calls, avoiding both the unknown-tools fallback and cwd races.
|
- Added sequential `ssh_cd` to the authoritative remote-shell workflow and direct the model to wait for a successful workspace change before dependent remote calls, avoiding both the unknown-tools fallback and cwd races.
|
||||||
- Added the authoritative `user-interaction` group for the maintained `ask_user_question` TUI tool.
|
- Added the authoritative `user-interaction` group for the maintained `ask_user_question` TUI tool.
|
||||||
- Added authoritative `chrome-navigation`, `chrome-interaction`, and `chrome-debugging` groups for all 21 tools registered by the pinned `pi-chrome` bridge after explicit session authorization.
|
- Added authoritative `chrome-navigation`, `chrome-interaction`, and `chrome-debugging` groups for all 21 tools registered by the pinned `pi-chrome` bridge after explicit session authorization.
|
||||||
|
### Fixed
|
||||||
|
- Treat Pi's optional `powershell` definition as the same pinned local-shell/core class as `bash` on Windows, while excluding it from non-Windows catalogs so it cannot become an unknown-tool model group.
|
||||||
|
- Activate exact existing groups from the current catalog before optional unknown-tool model enrichment, preventing unrelated catalog generation from delaying known group loads.
|
||||||
|
|
||||||
|
|
||||||
## [0.3.6] - 2026-04-24
|
## [0.3.6] - 2026-04-24
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ Generated metadata never replaces executable schemas. Exact names, parameter typ
|
|||||||
|
|
||||||
## Catalog lifecycle
|
## Catalog lifecycle
|
||||||
|
|
||||||
1. `session_start` keeps `tool_search`, Pi core tools, and configured `alwaysEnabled` names active.
|
1. `session_start` keeps `tool_search`, Pi core tools, and configured `alwaysEnabled` names active. PowerShell is filtered out on non-Windows hosts; on Windows it is pinned beside Bash as the same local-shell capability class.
|
||||||
2. Hidden tool definitions are hashed with the grouping constraints.
|
2. Hidden tool definitions are hashed with the grouping constraints.
|
||||||
3. Every tool exposed by the standard `my-pi` bundle is matched against the checked-in catalog in `extensions/bundle-groups.ts`. If all tools are recognized, no model call and no user cache are needed.
|
3. Every tool exposed by the standard `my-pi` bundle is matched against the checked-in catalog in `extensions/bundle-groups.ts`. If all tools are recognized, no model call and no user cache are needed.
|
||||||
4. User `groupOverrides` take priority over checked-in assignments. Optional or unavailable bundle tools are simply filtered out of their predefined groups.
|
4. User `groupOverrides` take priority over checked-in assignments. Optional or unavailable bundle tools are simply filtered out of their predefined groups.
|
||||||
5. Additional user or third-party tools receive deterministic prefix/source groups immediately. A matching private model-enriched cache at `<agent-dir>/tool-search/catalog-v1.json` is reused when present.
|
5. Additional user or third-party tools receive deterministic prefix/source groups immediately. A matching private model-enriched cache at `<agent-dir>/tool-search/catalog-v1.json` is reused when present.
|
||||||
6. Only when unrecognized tools exist does the first `tool_search` call ask the current authenticated model to enrich the complete catalog. Validation requires all checked-in bundle tools to remain in their predefined groups before a `0600` cache is accepted.
|
6. Only when unrecognized tools exist and the call does not name an exact existing group may `tool_search` ask the current authenticated model to enrich the complete catalog. Exact group activation always uses the current deterministic catalog immediately. Validation requires all checked-in bundle tools to remain in their predefined groups before a `0600` cache is accepted.
|
||||||
7. Missing authentication, invalid JSON, timeout, cancellation, changed bundle assignments, or cache errors leave the checked-in plus deterministic hybrid catalog usable.
|
7. Missing authentication, invalid JSON, timeout, cancellation, changed bundle assignments, or cache errors leave the checked-in plus deterministic hybrid catalog usable.
|
||||||
|
|
||||||
A standard bundle installation therefore sends no tool definitions to a model during catalog setup. Complete definitions are sent to the selected provider only when extra unrecognized tools require enrichment; that nested call's usage is attached to the `tool_search` result.
|
A standard bundle installation therefore sends no tool definitions to a model during catalog setup. Complete definitions are sent to the selected provider only when extra unrecognized tools require enrichment during an unknown-group or query lookup; exact existing group loads never wait for that nested call. Any nested-call usage is attached to the `tool_search` result.
|
||||||
|
|
||||||
Run `/tool-search-rebuild` to remove model enrichment. Standard bundle tools immediately return to the checked-in catalog; extra tools can be enriched lazily on the next search.
|
Run `/tool-search-rebuild` to remove model enrichment. Standard bundle tools immediately return to the checked-in catalog; extra tools can be enriched lazily on the next search.
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
`pi-tool-search` keeps full low-frequency schemas hidden behind a compact, validated group catalog.
|
`pi-tool-search` keeps full low-frequency schemas hidden behind a compact, validated group catalog.
|
||||||
|
|
||||||
1. `session_start` activates `tool_search`, the six Pi core tools, and configured `alwaysEnabled` names. These pinned tools do not consume the dynamic-group budget.
|
1. `session_start` activates `tool_search`, the six cross-platform Pi core tools, and configured `alwaysEnabled` names. On Windows, `powershell` is pinned beside `bash` as a seventh local-shell/core tool; on non-Windows hosts it is excluded from the catalog. These pinned tools do not consume the dynamic-group budget.
|
||||||
2. Hidden definitions are hashed with grouping constraints and matched against `extensions/bundle-groups.ts`. Available standard tools receive checked-in groups; unavailable optional tools are omitted.
|
2. Hidden definitions are hashed with grouping constraints and matched against `extensions/bundle-groups.ts`. Available standard tools receive checked-in groups; unavailable optional tools are omitted.
|
||||||
3. If every hidden tool is recognized, the bundle catalog is used directly. No model call or per-user cache is required.
|
3. If every hidden tool is recognized, the bundle catalog is used directly. No model call or per-user cache is required.
|
||||||
4. Additional tools receive immediate deterministic prefix/source groups. A matching model-enriched cache is reused only if it preserves all checked-in bundle assignments.
|
4. Additional tools receive immediate deterministic prefix/source groups. A matching model-enriched cache is reused only if it preserves all checked-in bundle assignments.
|
||||||
5. Only a hybrid catalog with unrecognized tools can call `ModelRegistry.complete()`. The prompt marks bundle assignments as fixed; validation rejects any response that moves them.
|
5. Only a hybrid catalog with unrecognized tools can call `ModelRegistry.complete()`, and an exact existing group skips that enrichment so activation cannot wait on an unrelated unknown tool. Unknown-group and query lookups may still enrich the catalog. The prompt marks bundle assignments as fixed; validation rejects any response that moves them.
|
||||||
6. Code validation still requires every exact tool name exactly once and rejects unknown names, duplicates, oversized generated groups, omissions, and tool-card/group mismatches. Generated text never changes executable schemas.
|
6. Code validation still requires every exact tool name exactly once and rejects unknown names, duplicates, oversized generated groups, omissions, and tool-card/group mismatches. Generated text never changes executable schemas.
|
||||||
7. Loading a group calls `setActiveTools()` with its original full tool definitions. Pi supplies newly added schemas on the next model request.
|
7. Loading a group calls `setActiveTools()` with its original full tool definitions. Pi supplies newly added schemas on the next model request.
|
||||||
8. Group load and member execution update an in-session LRU counter. Loading past `maxActiveGroups` or `maxDynamicTools` removes the least-recently-used dynamic groups.
|
8. Group load and member execution update an in-session LRU counter. Loading past `maxActiveGroups` or `maxDynamicTools` removes the least-recently-used dynamic groups.
|
||||||
|
|||||||
@@ -31,10 +31,25 @@ import {
|
|||||||
import { ensureToolSearchDefaults, readToolSearchConfig } from "./config.ts";
|
import { ensureToolSearchDefaults, readToolSearchConfig } from "./config.ts";
|
||||||
|
|
||||||
const TOOL_SEARCH_NAME = "tool_search";
|
const TOOL_SEARCH_NAME = "tool_search";
|
||||||
|
const POWERSHELL_TOOL_NAME = "powershell";
|
||||||
const CORE_TOOLS = ["read", "write", "edit", "bash", "grep", "find"];
|
const CORE_TOOLS = ["read", "write", "edit", "bash", "grep", "find"];
|
||||||
|
|
||||||
type CatalogSource = "bundle" | "cache" | "fallback" | "hybrid" | "model";
|
type CatalogSource = "bundle" | "cache" | "fallback" | "hybrid" | "model";
|
||||||
type ModelUsage = Awaited<ReturnType<ExtensionContext["modelRegistry"]["complete"]>>["usage"];
|
type ModelUsage = Awaited<ReturnType<ExtensionContext["modelRegistry"]["complete"]>>["usage"];
|
||||||
|
type CatalogGenerationResult = { notice?: string; usage?: ModelUsage };
|
||||||
|
|
||||||
|
export function platformToolPolicy<T extends { name: string }>(
|
||||||
|
definitions: readonly T[],
|
||||||
|
platform: NodeJS.Platform,
|
||||||
|
): { tools: T[]; coreTools: string[] } {
|
||||||
|
const tools = platform === "win32"
|
||||||
|
? [...definitions]
|
||||||
|
: definitions.filter((tool) => tool.name !== POWERSHELL_TOOL_NAME);
|
||||||
|
const coreTools = platform === "win32"
|
||||||
|
? [...CORE_TOOLS, POWERSHELL_TOOL_NAME]
|
||||||
|
: [...CORE_TOOLS];
|
||||||
|
return { tools, coreTools };
|
||||||
|
}
|
||||||
|
|
||||||
function unique(values: Iterable<string>): string[] {
|
function unique(values: Iterable<string>): string[] {
|
||||||
return [...new Set(values)];
|
return [...new Set(values)];
|
||||||
@@ -191,20 +206,14 @@ export default function toolSearchExtension(pi: ExtensionAPI): void {
|
|||||||
}),
|
}),
|
||||||
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
||||||
const requestedId = params.group?.trim().toLowerCase();
|
const requestedId = params.group?.trim().toLowerCase();
|
||||||
const preGenerationGroup = requestedId ? groupById(requestedId) : undefined;
|
let selected = requestedId ? groupById(requestedId) : undefined;
|
||||||
const generation = await ensureModelCatalog(ctx, signal);
|
const generation: CatalogGenerationResult = selected
|
||||||
|
? {}
|
||||||
|
: await ensureModelCatalog(ctx, signal);
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
if (generation.notice) lines.push(generation.notice);
|
if (generation.notice) lines.push(generation.notice);
|
||||||
|
|
||||||
let selected = requestedId ? groupById(requestedId) : undefined;
|
if (!selected && requestedId) selected = groupById(requestedId);
|
||||||
if (!selected && preGenerationGroup) {
|
|
||||||
const previousNames = new Set(preGenerationGroup.tools);
|
|
||||||
selected = [...catalog.groups]
|
|
||||||
.map((group) => ({ group, overlap: group.tools.filter((name) => previousNames.has(name)).length }))
|
|
||||||
.sort((left, right) => right.overlap - left.overlap || left.group.id.localeCompare(right.group.id))
|
|
||||||
.find((candidate) => candidate.overlap > 0)?.group;
|
|
||||||
if (selected) lines.push(`Mapped initial fallback group ${preGenerationGroup.id} to generated group ${selected.id}.`);
|
|
||||||
}
|
|
||||||
const ranked = params.query ? rankGroups(catalog, params.query).slice(0, 3) : [];
|
const ranked = params.query ? rankGroups(catalog, params.query).slice(0, 3) : [];
|
||||||
if (!selected && params.query && (ranked[0]?.score ?? 0) > 0) selected = ranked[0]?.group;
|
if (!selected && params.query && (ranked[0]?.score ?? 0) > 0) selected = ranked[0]?.group;
|
||||||
|
|
||||||
@@ -338,9 +347,13 @@ export default function toolSearchExtension(pi: ExtensionAPI): void {
|
|||||||
|
|
||||||
function refreshState(ctx: Pick<ExtensionContext, "ui">, forceReset: boolean): void {
|
function refreshState(ctx: Pick<ExtensionContext, "ui">, forceReset: boolean): void {
|
||||||
const nextConfig = readToolSearchConfig(agentDir);
|
const nextConfig = readToolSearchConfig(agentDir);
|
||||||
const allTools = pi.getAllTools().filter((tool) => tool.name !== TOOL_SEARCH_NAME);
|
const registeredTools = pi.getAllTools().filter((tool) => tool.name !== TOOL_SEARCH_NAME);
|
||||||
|
const platformPolicy = platformToolPolicy(registeredTools, process.platform);
|
||||||
|
const allTools = platformPolicy.tools;
|
||||||
const availableNames = new Set(allTools.map((tool) => tool.name));
|
const availableNames = new Set(allTools.map((tool) => tool.name));
|
||||||
const nextPinned = new Set([...CORE_TOOLS, ...nextConfig.alwaysEnabled].filter((name) => availableNames.has(name)));
|
const nextPinned = new Set(
|
||||||
|
[...platformPolicy.coreTools, ...nextConfig.alwaysEnabled].filter((name) => availableNames.has(name)),
|
||||||
|
);
|
||||||
const hiddenTools = allTools.filter((tool) => !nextPinned.has(tool.name));
|
const hiddenTools = allTools.filter((tool) => !nextPinned.has(tool.name));
|
||||||
const nextConstraints = {
|
const nextConstraints = {
|
||||||
maxToolsPerGroup: Math.min(nextConfig.maxToolsPerGroup, nextConfig.maxDynamicTools),
|
maxToolsPerGroup: Math.min(nextConfig.maxToolsPerGroup, nextConfig.maxDynamicTools),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
|
|
||||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||||
import toolSearchExtension from "../extensions/index.ts";
|
import toolSearchExtension, { platformToolPolicy } from "../extensions/index.ts";
|
||||||
|
|
||||||
interface ToolSearchResult {
|
interface ToolSearchResult {
|
||||||
details: {
|
details: {
|
||||||
@@ -249,3 +249,76 @@ test("uses the precomputed bundle groups without calling a model", async () => {
|
|||||||
await rm(agentDir, { recursive: true, force: true });
|
await rm(agentDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps PowerShell with Bash only on Windows", () => {
|
||||||
|
const definitions = [
|
||||||
|
sourceTool("bash", "Run Bash commands"),
|
||||||
|
sourceTool("powershell", "Run PowerShell commands"),
|
||||||
|
sourceTool("read", "Read a file"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const nonWindows = platformToolPolicy(definitions, "darwin");
|
||||||
|
assert.deepEqual(nonWindows.tools.map((tool) => tool.name), ["bash", "read"]);
|
||||||
|
assert.ok(nonWindows.coreTools.includes("bash"));
|
||||||
|
assert.ok(!nonWindows.coreTools.includes("powershell"));
|
||||||
|
|
||||||
|
const windows = platformToolPolicy(definitions, "win32");
|
||||||
|
assert.deepEqual(windows.tools.map((tool) => tool.name), ["bash", "powershell", "read"]);
|
||||||
|
assert.ok(windows.coreTools.includes("bash"));
|
||||||
|
assert.ok(windows.coreTools.includes("powershell"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("loads an exact known group without waiting for unknown-tool model enrichment", async () => {
|
||||||
|
const agentDir = await mkdtemp(join(tmpdir(), "my-pi-tool-search-fast-path-"));
|
||||||
|
const previousAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||||
|
process.env.PI_CODING_AGENT_DIR = agentDir;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const handlers = new Map<string, (...args: any[]) => unknown>();
|
||||||
|
const registered = new Map<string, RegisteredTool>();
|
||||||
|
let modelCalls = 0;
|
||||||
|
const sourceTools = [
|
||||||
|
sourceTool("read", "Read a file"),
|
||||||
|
sourceTool("tavily_web_search", "Search broadly for current web sources"),
|
||||||
|
sourceTool("tavily_web_fetch", "Fetch a selected Tavily source"),
|
||||||
|
sourceTool("unrecognized_extra", "An unrecognized third-party capability"),
|
||||||
|
];
|
||||||
|
const api = {
|
||||||
|
getAllTools: () => sourceTools,
|
||||||
|
registerTool: (definition: unknown) => {
|
||||||
|
const tool = definition as RegisteredTool & { name: string };
|
||||||
|
registered.set(tool.name, tool);
|
||||||
|
},
|
||||||
|
registerCommand: () => {},
|
||||||
|
setActiveTools: () => {},
|
||||||
|
on: (event: string, handler: (...args: any[]) => unknown) => handlers.set(event, handler),
|
||||||
|
} as unknown as ExtensionAPI;
|
||||||
|
toolSearchExtension(api);
|
||||||
|
const ctx = testContext({
|
||||||
|
model: { provider: "test", id: "catalog-model" } as ExtensionContext["model"],
|
||||||
|
modelRegistry: {
|
||||||
|
hasConfiguredAuth: () => true,
|
||||||
|
complete: async () => {
|
||||||
|
modelCalls += 1;
|
||||||
|
throw new Error("an exact known group must not wait for model enrichment");
|
||||||
|
},
|
||||||
|
} as unknown as ExtensionContext["modelRegistry"],
|
||||||
|
});
|
||||||
|
handlers.get("session_start")?.({}, ctx);
|
||||||
|
|
||||||
|
const result = await registered.get("tool_search")?.execute(
|
||||||
|
"call-1",
|
||||||
|
{ group: "web-tavily" },
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
assert.equal(result?.details.loadedGroup, "web-tavily");
|
||||||
|
assert.equal(result?.details.catalogSource, "hybrid");
|
||||||
|
assert.equal(modelCalls, 0);
|
||||||
|
} finally {
|
||||||
|
if (previousAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
||||||
|
else process.env.PI_CODING_AGENT_DIR = previousAgentDir;
|
||||||
|
await rm(agentDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user