feat(coding-agent): made write and find tools essential

- Promoted `write` and `find` tools to `essential` status to ensure they are always available regardless of discovery mode.
- Updated `DEFAULT_ESSENTIAL_TOOL_NAMES` to include these tools by default.
- Updated documentation and tests to reflect the change in default essential tool availability.

Fixes #3165
This commit is contained in:
can1357
2026-06-21 07:02:32 +02:00
parent 151561cb7c
commit 2eef88978b
6 changed files with 16 additions and 14 deletions
+1 -1
View File
@@ -113,6 +113,6 @@
- Built-in entries appear only in `"all"` mode and only for registry tools whose `loadMode === "discoverable"` and are not currently active.
- Hidden/internal built-ins are intentionally excluded from the built-in corpus: `resolve`, `yield`, `report_finding`, `report_tool_issue` are called out in the `#collectDiscoverableBuiltinTools()` comment.
- `DiscoverableToolSource` includes `"extension"` and `"custom"`, but `AgentSession.getDiscoverableTools()` currently assembles only built-in and MCP sources.
- On startup, `packages/coding-agent/src/sdk.ts` resolves `"auto"` after the full registry exists and injects `search_tool_bm25` when the count exceeds 40. It hides non-essential discoverable built-ins only in `tools.discoveryMode = "all"`; defaults are `read`, `bash`, and `edit` unless `tools.essentialOverride` changes them.
- On startup, `packages/coding-agent/src/sdk.ts` resolves `"auto"` after the full registry exists and injects `search_tool_bm25` when the count exceeds 40. It hides non-essential discoverable built-ins only in `tools.discoveryMode = "all"`. Tools whose class is marked as `loadMode === "essential"` (defaults are `read`, `bash`, `edit`, `write`, and `find`) are always active; they survive hiding regardless of configuration. `tools.essentialOverride` can be used to treat additional discoverable tools as essential (active on startup) or to explicitly specify the active essential list.
- Query tokenization is simple and deterministic: Unicode is NFKD-normalized, combining marks are dropped, acronym/camelCase and digit-to-capital boundaries are split, non-letter/non-number characters become spaces, tokens are lowercased, and only non-empty tokens survive.
- Scores are rounded differently by surface: `details.tools[].score` keeps 6 decimals; the TUI line renders 3.
+4
View File
@@ -6,6 +6,10 @@
- Added `tab.ariaSnapshot(selector?)` to the browser tool for Playwright-format ARIA-tree YAML
- Added `tab.ref("e5")` and support for `aria-ref=e5` selectors in all `tab` action methods
### Changed
- Made `write` and `find` tools essential so they are always available initially (surviving `tools.discoveryMode === "all"` hiding) to ensure instructions to write/find files are immediately executable ([#3165](https://github.com/can1357/oh-my-pi/issues/3165))
### Fixed
- Fixed streamed tool-call previews freezing on their placeholder body (`$ …`, `Write: …`, an empty args tree) even after the tool finished: the pending card was created while arguments streamed, but when the closing full-arguments `message_update` never arrived (smooth-streaming disabled leaving the throttled arguments stale, an owned-dialect projector, or a superseded/aborted turn that still ran the call) nothing re-applied the final args. `tool_execution_start` — the one event every execution path emits with validated full arguments right before the result — now reconciles them onto the existing pending card and cancels any in-flight reveal so a late tick can't re-truncate the body.
+1 -2
View File
@@ -101,8 +101,7 @@ interface FindTarget {
export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
readonly name = "find";
readonly approval = "read" as const;
readonly summary = "Find files and directories matching a glob pattern";
readonly loadMode = "discoverable";
readonly loadMode = "essential";
readonly label = "Find";
readonly description: string;
readonly parameters = findSchema;
+1 -1
View File
@@ -375,7 +375,7 @@ export type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool |
export type BuiltinToolLoadMode = "essential" | "discoverable";
/** Default essential tool names when tools.essentialOverride is empty. */
export const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[] = ["read", "bash", "edit"] as const;
export const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[] = ["read", "bash", "edit", "write", "find"] as const;
/**
* Resolve the active essential built-in tool names from settings.
+1 -2
View File
@@ -293,8 +293,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
readonly parameters = writeSchema;
readonly strict = true;
readonly concurrency = "exclusive";
readonly loadMode = "discoverable";
readonly summary = "Write content to a file (creates or overwrites)";
readonly loadMode = "essential";
/** Stream matchers should see the real file content, not its JSON-escaped argument encoding. */
matcherDigest(args: unknown): string | undefined {
@@ -128,22 +128,22 @@ describe("filterInitialToolsForDiscoveryAll", () => {
read: "essential",
edit: "essential",
todo: "discoverable",
find: "discoverable",
search: "discoverable",
};
const base = {
loadModeOf: (name: string): BuiltinToolLoadMode | undefined => loadModes[name],
essentialNames: new Set(["read", "bash", "edit"]),
essentialNames: new Set(["read", "bash", "edit", "write", "find"]),
explicitlyRequested: new Set<string>(),
restored: new Set<string>(),
forceActive: new Set<string>(),
};
it("hides non-essential discoverable built-ins", () => {
expect(filterInitialToolsForDiscoveryAll(["read", "edit", "todo", "find"], base)).toEqual(["read", "edit"]);
expect(filterInitialToolsForDiscoveryAll(["read", "edit", "todo", "search"], base)).toEqual(["read", "edit"]);
});
it("keeps discoverable tools required by a forced tool_choice (eager todo)", () => {
const result = filterInitialToolsForDiscoveryAll(["read", "todo", "find"], {
const result = filterInitialToolsForDiscoveryAll(["read", "todo", "search"], {
...base,
forceActive: new Set(["todo"]),
});
@@ -151,15 +151,15 @@ describe("filterInitialToolsForDiscoveryAll", () => {
});
it("keeps explicitly requested and restored discoverable tools", () => {
const result = filterInitialToolsForDiscoveryAll(["todo", "find"], {
const result = filterInitialToolsForDiscoveryAll(["todo", "search"], {
...base,
explicitlyRequested: new Set(["find"]),
explicitlyRequested: new Set(["search"]),
restored: new Set(["todo"]),
});
expect([...result].sort()).toEqual(["find", "todo"]);
expect([...result].sort()).toEqual(["search", "todo"]);
});
it("never hides tools without a built-in loadMode (MCP/custom/extension)", () => {
expect(filterInitialToolsForDiscoveryAll(["mcp__server__tool", "find"], base)).toEqual(["mcp__server__tool"]);
expect(filterInitialToolsForDiscoveryAll(["mcp__server__tool", "search"], base)).toEqual(["mcp__server__tool"]);
});
});