Files
oh-my-pi/packages/coding-agent/src/tools/essential-tools.ts
T
roboomp 5340a9517a fix(tools): keep essential built-ins top-level when re-registered without loadMode
Extension/SDK/RPC registerTool defaulted an omitted loadMode to
"discoverable". A UI-only re-register of an essential built-in
(read/write/bash/edit/glob) then became discoverable and, with tools.xdev
on, was unmounted from the top-level schema. read/write dropping also
broke the xd:// transport (read xd://, write xd://<tool>), leaving the
model with no callable coding essentials.

- Add defaultLoadModeForToolName: omitted loadMode resolves to "essential"
  for known essential built-in names, "discoverable" otherwise.
- Apply it at all four adapter boundaries (extension wrapper, custom-tools
  wrapper, sdk customToolToDefinition, rpc normalizeHostToolDefinitions).
- Transport invariant: read/write never mount under xdev regardless of
  loadMode (they carry the transport).
- Regression test covering the demotion, transport invariant, and a drift
  guard tying the essential-name set to the tool classes.

Fixes #5764
2026-07-16 23:08:02 +00:00

46 lines
1.8 KiB
TypeScript

/**
* Canonical set of built-in tools that must stay top-level.
*
* These are the coding essentials the model always needs directly in its
* callable schema. `read`/`write` are additionally the `xd://` transport
* (`read xd://` lists devices, `write xd://<tool>` executes them), so demoting
* them under xdev makes every mounted device unreachable.
*
* Adapter boundaries (extension `registerTool`, SDK custom tools, RPC host
* tools) default an omitted `loadMode` to `"discoverable"`. A UI-only
* re-register of a built-in — e.g. wrapping `read`/`write`/`bash`/`edit`/`glob`
* to customize rendering — would then silently demote it to `discoverable` and,
* with `tools.xdev` on, unmount it from the top-level schema (issue #5764).
* {@link defaultLoadModeForToolName} pins these names to `"essential"` when the
* definition omits `loadMode`, so re-registering a built-in never demotes it.
*/
import type { ToolLoadMode } from "@oh-my-pi/pi-agent-core";
/**
* Built-in tool names whose classes declare `loadMode = "essential"`. Kept in
* sync with the tool classes by `essential-tools.test.ts` (drift guard).
*/
export const ESSENTIAL_BUILTIN_TOOL_NAMES: Record<string, true> = {
read: true,
write: true,
bash: true,
edit: true,
glob: true,
eval: true,
task: true,
hub: true,
learn: true,
manage_skill: true,
};
/**
* Resolve a tool's presentation mode at an adapter boundary. An explicit
* `declared` mode always wins. When omitted, known essential built-in names
* default to `"essential"` (so a re-register never demotes them); everything
* else defaults to `"discoverable"`.
*/
export function defaultLoadModeForToolName(name: string, declared?: ToolLoadMode): ToolLoadMode {
if (declared) return declared;
return name in ESSENTIAL_BUILTIN_TOOL_NAMES ? "essential" : "discoverable";
}