mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(coding-agent): accept +name/-name entries in --tools
A --tools list of only +name/-name entries changes the default tool selection like the defaultTools setting, e.g. pi -t +codemode. Mixed lists and patterns in modifiers are rejected, and /reload keeps tools removed with -name removed.
This commit is contained in:
parent
23cf2b948d
commit
ddaa0a0341
9 changed files with 183 additions and 34 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added `+name` and `-name` entries to `--tools`, which change the default tool selection instead of replacing it, for example `pi -t +codemode`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed managed installs keeping every old release; `pi update` now keeps only the new release and the one it updated from ([#10392](https://github.com/earendil-works/pi/issues/10392))
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ pi --tools read,grep,find,ls --print "Review this project"
|
|||
See [Settings](settings.md#tools) for configuring the default tool selection.
|
||||
|
||||
- `-t`, `--tools <list>`<br>
|
||||
Replaces the default selection with a comma-separated allowlist of built-in, extension, or custom tools. Entries are tool names or patterns where `*` matches any characters. MCP tools are kept unless an entry starts with `mcp__` (see [MCP tools](#mcp-tools)).
|
||||
Replaces the default selection with a comma-separated allowlist of built-in, extension, or custom tools. Entries are tool names or patterns where `*` matches any characters. MCP tools are kept unless an entry starts with `mcp__` (see [MCP tools](#mcp-tools)). A list of only `+name` and `-name` entries is not an allowlist; it changes the default selection instead.
|
||||
- `-xt`, `--exclude-tools <list>`<br>
|
||||
Disables comma-separated tool names or patterns after all other selection options, MCP tools included.
|
||||
- `-nbt`, `--no-builtin-tools`<br>
|
||||
|
|
@ -125,7 +125,7 @@ See [Settings](settings.md#tools) for configuring the default tool selection.
|
|||
- `-nt`, `--no-tools`<br>
|
||||
Starts with all built-in, extension, custom, and MCP tools disabled.
|
||||
|
||||
Default enabled tools are `read`, `bash`, `edit`, and `write`, unless `defaultTools` changes them. `--tools` replaces the whole selection, so name every tool you want; `defaultTools` also accepts `+name` and `-name` to change the defaults instead.
|
||||
Default enabled tools are `read`, `bash`, `edit`, and `write`, unless `defaultTools` changes them. `--tools` with plain names replaces the whole selection, so name every tool you want. Like `defaultTools`, it also accepts a list of only `+name` and `-name` entries, which adds tools to or removes them from the default selection: `pi --tools +codemode,-write` keeps the other default tools, enables `codemode`, and disables `write`. These entries take exact tool names, not `*` patterns; use `--exclude-tools` to disable tools by pattern. Plain names and `+name`/`-name` entries cannot be mixed. `/reload` enables tools newly added to `defaultTools`, but a tool removed with `-name` stays removed.
|
||||
|
||||
<a id="mcp-tools"></a>
|
||||
|
||||
|
|
@ -165,10 +165,10 @@ To turn on `codemode` for every session, add it to the default tools in `~/.pi/a
|
|||
}
|
||||
```
|
||||
|
||||
This keeps `read`, `bash`, `edit`, and `write` and adds `codemode`. For one invocation, list every tool, since `--tools` replaces the selection:
|
||||
This keeps `read`, `bash`, `edit`, and `write` and adds `codemode`. For one invocation, add it with `--tools`:
|
||||
|
||||
```sh
|
||||
pi --tools read,bash,edit,write,codemode
|
||||
pi --tools +codemode
|
||||
```
|
||||
|
||||
Codemode is useful without MCP: scripts can run several tool calls in parallel, filter large output before it reaches the model, call classifier models such as TypeSafe's Jev through `models.classify()` (see [Classifier models](models.md#use-classifier-models)), and generate images through `models.generateImages()` (see [Image models](models.md#use-image-models)).
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ A list of only `+name` and `-name` entries changes the inherited selection inste
|
|||
|
||||
This replaces `bash` with `powershell` and enables `grep`: `["-bash", "+powershell", "+grep"]`. Project settings apply on top of user settings: a project list with only `+name` and `-name` entries changes the user's selection, and a project list with a plain name replaces it. In one list, plain names form the selection, and `+name` and `-name` then apply in order.
|
||||
|
||||
`/reload` enables tools newly added to `defaultTools`. It does not disable tools removed from it or re-enable unchanged tools you turned off. `--tools`, `--no-tools`, and `--no-builtin-tools` override `defaultTools`, also on reload.
|
||||
`/reload` enables tools newly added to `defaultTools`. It does not disable tools removed from it or re-enable unchanged tools you turned off. `--tools` with plain names, `--no-tools`, and `--no-builtin-tools` override `defaultTools`, also on reload.
|
||||
|
||||
CLI tool options override this setting for one invocation; `--tools` does not accept `+name` or `-name`. See [Command Line](cli.md#tools).
|
||||
CLI tool options override this setting for one invocation. `--tools` with only `+name` and `-name` entries changes the resolved `defaultTools` selection instead, for example `pi --tools +codemode`. On `/reload`, these entries apply to the reloaded setting too, so a tool removed with `-name` stays removed. See [Command Line](cli.md#tools).
|
||||
|
||||
## Sessions and context
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|||
import chalk from "chalk";
|
||||
import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, ENV_SESSION_DIR } from "../config.ts";
|
||||
import type { ExtensionFlag } from "../core/extensions/types.ts";
|
||||
import type { TuiMode } from "../core/settings-manager.ts";
|
||||
import { getToolListError, type TuiMode } from "../core/settings-manager.ts";
|
||||
|
||||
export type Mode = "text" | "json" | "rpc";
|
||||
|
||||
|
|
@ -149,10 +149,16 @@ export function parseArgs(args: string[]): Args {
|
|||
} else if (arg === "--no-builtin-tools" || arg === "-nbt") {
|
||||
result.noBuiltinTools = true;
|
||||
} else if ((arg === "--tools" || arg === "-t") && i + 1 < args.length) {
|
||||
result.tools = args[++i]
|
||||
const tools = args[++i]
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter((name) => name.length > 0);
|
||||
const error = getToolListError(tools);
|
||||
if (error) {
|
||||
result.diagnostics.push({ type: "error", message: `${arg}: ${error}` });
|
||||
} else {
|
||||
result.tools = tools;
|
||||
}
|
||||
} else if ((arg === "--exclude-tools" || arg === "-xt") && i + 1 < args.length) {
|
||||
result.excludeTools = args[++i]
|
||||
.split(",")
|
||||
|
|
@ -313,6 +319,7 @@ ${chalk.bold("Options:")}
|
|||
--no-builtin-tools, -nbt Disable built-in tools by default but keep extension/custom tools enabled
|
||||
--tools, -t <tools> Comma-separated allowlist of tool names or patterns (*) to enable
|
||||
Keeps MCP tools unless an entry starts with mcp__
|
||||
Only +name/-name entries add to or remove from the defaults
|
||||
--exclude-tools, -xt <tools> Comma-separated denylist of tool names or patterns (*) to disable
|
||||
Applies to all tools, MCP tools included
|
||||
--thinking <level> Set thinking level: off, minimal, low, medium, high, xhigh, max
|
||||
|
|
@ -395,6 +402,9 @@ ${chalk.bold("Examples:")}
|
|||
# Read-only mode (no file modifications possible)
|
||||
${APP_NAME} --tools read,grep,find,ls -p "Review the code in src/"
|
||||
|
||||
# Add codemode to the default tools
|
||||
${APP_NAME} --tools +codemode
|
||||
|
||||
# Codemode with only the tools of one MCP server
|
||||
${APP_NAME} --tools read,bash,codemode,'mcp__radius__*'
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,12 @@ import {
|
|||
SessionManager,
|
||||
type SessionProjection,
|
||||
} from "./session-manager.ts";
|
||||
import { type CacheWarmingMode, DEFAULT_TOOL_NAMES, type SettingsManager } from "./settings-manager.ts";
|
||||
import {
|
||||
applyToolModifiers,
|
||||
type CacheWarmingMode,
|
||||
DEFAULT_TOOL_NAMES,
|
||||
type SettingsManager,
|
||||
} from "./settings-manager.ts";
|
||||
import type { SlashCommandInfo } from "./slash-commands.ts";
|
||||
import { BUILTIN_PATH_PREFIX, createSyntheticSourceInfo, isSyntheticPath, type SourceInfo } from "./source-info.ts";
|
||||
import {
|
||||
|
|
@ -266,6 +271,11 @@ export interface AgentSessionConfig {
|
|||
* tools newly added to the setting. Tools removed from it stay active.
|
||||
*/
|
||||
usesDefaultTools?: boolean;
|
||||
/**
|
||||
* `+name`/`-name` entries applied on top of the `defaultTools` setting, from `--tools`. Reload
|
||||
* applies them to the reloaded setting too, so a removed tool stays removed.
|
||||
*/
|
||||
defaultToolModifiers?: string[];
|
||||
/**
|
||||
* Optional allowlist of tool names or patterns (`*` matches any characters). When provided, only
|
||||
* matching tools are exposed. A non-empty list without `mcp__` entries also keeps MCP tools
|
||||
|
|
@ -436,6 +446,7 @@ export class AgentSession {
|
|||
*/
|
||||
private _pendingToolNames = new Set<string>();
|
||||
private _usesDefaultTools: boolean;
|
||||
private _defaultToolModifiers: string[];
|
||||
/** Matches the `--tools` entries: tool names or patterns. */
|
||||
private _allowedTools?: (name: string) => boolean;
|
||||
/**
|
||||
|
|
@ -487,6 +498,7 @@ export class AgentSession {
|
|||
this._extensionRunnerRef = config.extensionRunnerRef;
|
||||
this._initialActiveToolNames = config.initialActiveToolNames;
|
||||
this._usesDefaultTools = config.usesDefaultTools ?? false;
|
||||
this._defaultToolModifiers = config.defaultToolModifiers ?? [];
|
||||
if (config.allowedToolNames) {
|
||||
this._allowedTools = createToolNameMatcher(config.allowedToolNames);
|
||||
this._allowlistFiltersMcp =
|
||||
|
|
@ -3648,20 +3660,21 @@ export class AgentSession {
|
|||
const previousFlagValues = oldRunner.getFlagValues();
|
||||
await emitSessionShutdownEvent(oldRunner, { type: "session_shutdown", reason: "reload" });
|
||||
oldRunner.invalidate();
|
||||
const previousDefaultTools = new Set(
|
||||
this._usesDefaultTools ? (this.settingsManager.getDefaultTools() ?? DEFAULT_TOOL_NAMES) : [],
|
||||
);
|
||||
const getDefaultTools = () =>
|
||||
this._usesDefaultTools
|
||||
? applyToolModifiers(
|
||||
this.settingsManager.getDefaultTools() ?? DEFAULT_TOOL_NAMES,
|
||||
this._defaultToolModifiers,
|
||||
)
|
||||
: [];
|
||||
const previousDefaultTools = new Set(getDefaultTools());
|
||||
await this.settingsManager.reload();
|
||||
this.syncQueueModesFromSettings();
|
||||
resetApiProviders();
|
||||
await this._resourceLoader.reload();
|
||||
// Activate tools newly added to defaultTools. Removed ones stay active, and tools disabled
|
||||
// during the session stay disabled unless the setting newly adds them.
|
||||
const addedDefaultTools = this._usesDefaultTools
|
||||
? (this.settingsManager.getDefaultTools() ?? DEFAULT_TOOL_NAMES).filter(
|
||||
(name) => !previousDefaultTools.has(name),
|
||||
)
|
||||
: [];
|
||||
const addedDefaultTools = getDefaultTools().filter((name) => !previousDefaultTools.has(name));
|
||||
// Tools the new extensions register later, such as MCP tools, are pending until then.
|
||||
for (const name of this.getActiveToolNames()) this._pendingToolNames.add(name);
|
||||
this._buildRuntime({
|
||||
|
|
|
|||
|
|
@ -17,7 +17,13 @@ import { mergeProviderAttributionHeaders } from "./provider-attribution.ts";
|
|||
import type { ResourceLoader } from "./resource-loader.ts";
|
||||
import { DefaultResourceLoader } from "./resource-loader.ts";
|
||||
import { getDefaultSessionDir, SessionManager } from "./session-manager.ts";
|
||||
import { DEFAULT_TOOL_NAMES, SettingsManager } from "./settings-manager.ts";
|
||||
import {
|
||||
applyToolModifiers,
|
||||
DEFAULT_TOOL_NAMES,
|
||||
getToolListError,
|
||||
isToolModifier,
|
||||
SettingsManager,
|
||||
} from "./settings-manager.ts";
|
||||
import { time } from "./timings.ts";
|
||||
import {
|
||||
createBashTool,
|
||||
|
|
@ -73,6 +79,10 @@ export interface CreateAgentSessionOptions {
|
|||
* enabled. MCP tools stay registered for codemode and tool search unless an
|
||||
* entry starts with `mcp__`; then only matching MCP tools are kept. An empty
|
||||
* list, like `noTools: "all"`, disables MCP tools too.
|
||||
*
|
||||
* A list of only `+name` and `-name` entries is not an allowlist: it adds tools
|
||||
* to or removes them from the default selection, like the `defaultTools` setting.
|
||||
* These entries take exact names. Mixing them with plain entries throws.
|
||||
*/
|
||||
tools?: string[];
|
||||
/**
|
||||
|
|
@ -267,13 +277,21 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
thinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;
|
||||
}
|
||||
|
||||
const configuredDefaultToolNames = settingsManager.getDefaultTools();
|
||||
const allowedToolNames = options.tools ?? (options.noTools === "all" ? [] : undefined);
|
||||
const toolListError = options.tools ? getToolListError(options.tools) : undefined;
|
||||
if (toolListError) throw new Error(`Invalid tools option: ${toolListError}`);
|
||||
const defaultToolNames = options.noTools ? [] : (settingsManager.getDefaultTools() ?? DEFAULT_TOOL_NAMES);
|
||||
// A `tools` list of only `+name`/`-name` entries changes the default selection instead of
|
||||
// replacing it, like the `defaultTools` setting.
|
||||
const toolModifiers = options.tools?.some(isToolModifier) ? options.tools : undefined;
|
||||
const selectedToolNames = toolModifiers ? applyToolModifiers(defaultToolNames, toolModifiers) : options.tools;
|
||||
const allowedToolNames = toolModifiers
|
||||
? options.noTools === "all"
|
||||
? selectedToolNames
|
||||
: undefined
|
||||
: (options.tools ?? (options.noTools === "all" ? [] : undefined));
|
||||
const excludedToolNames = options.excludeTools;
|
||||
const isExcludedTool = excludedToolNames ? createToolNameMatcher(excludedToolNames) : undefined;
|
||||
const initialActiveToolNames = (
|
||||
options.tools ?? (options.noTools ? [] : (configuredDefaultToolNames ?? DEFAULT_TOOL_NAMES))
|
||||
).filter((name) => !isExcludedTool?.(name));
|
||||
const initialActiveToolNames = (selectedToolNames ?? defaultToolNames).filter((name) => !isExcludedTool?.(name));
|
||||
|
||||
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
||||
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
|
||||
|
|
@ -451,7 +469,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
modelRuntime,
|
||||
cacheWarmer,
|
||||
initialActiveToolNames,
|
||||
usesDefaultTools: options.tools === undefined && !options.noTools,
|
||||
usesDefaultTools: (options.tools === undefined || toolModifiers !== undefined) && !options.noTools,
|
||||
defaultToolModifiers: toolModifiers,
|
||||
allowedToolNames,
|
||||
excludedToolNames,
|
||||
extensionRunnerRef,
|
||||
|
|
|
|||
|
|
@ -214,10 +214,41 @@ function deepMergeObjects(base: Record<string, unknown>, overrides: Record<strin
|
|||
/** Tools enabled at startup when `defaultTools` does not change them. */
|
||||
export const DEFAULT_TOOL_NAMES: readonly string[] = ["read", "bash", "edit", "write"];
|
||||
|
||||
function isToolModifier(entry: unknown): boolean {
|
||||
/** Whether a tool selection entry is a `+name` or `-name` modifier. */
|
||||
export function isToolModifier(entry: unknown): boolean {
|
||||
return typeof entry === "string" && (entry.startsWith("+") || entry.startsWith("-"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a tool list from `--tools` or the SDK `tools` option. It is either an allowlist of plain
|
||||
* names and patterns or a list of only `+name`/`-name` entries with exact names. Returns the
|
||||
* problem, or undefined when the list is valid.
|
||||
*/
|
||||
export function getToolListError(entries: readonly string[]): string | undefined {
|
||||
const modifiers = entries.filter(isToolModifier);
|
||||
if (modifiers.length === 0) return undefined;
|
||||
if (modifiers.length < entries.length) return "tool names cannot be mixed with +name or -name entries";
|
||||
const pattern = modifiers.find((entry) => entry.includes("*"));
|
||||
if (pattern) return `+name and -name entries take exact tool names, not patterns: ${pattern}`;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the `+name` and `-name` entries of `entries` to `base` in order: `+name` adds a tool and
|
||||
* `-name` removes one. Other entries are ignored.
|
||||
*/
|
||||
export function applyToolModifiers(base: readonly string[], entries: readonly string[]): string[] {
|
||||
const tools = [...base];
|
||||
for (const entry of entries) {
|
||||
if (!isToolModifier(entry)) continue;
|
||||
const name = entry.slice(1);
|
||||
const index = tools.indexOf(name);
|
||||
if (entry.startsWith("+") && index === -1 && name) tools.push(name);
|
||||
else if (entry.startsWith("-") && index !== -1) tools.splice(index, 1);
|
||||
}
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge `defaultTools` of two settings layers. A list with plain tool names replaces the inherited
|
||||
* one; a list of only `+name`/`-name` entries is appended, so it modifies the inherited selection.
|
||||
|
|
@ -235,15 +266,7 @@ function mergeDefaultTools(base: string[] | undefined, overrides: string[] | und
|
|||
*/
|
||||
function resolveDefaultTools(entries: string[]): string[] {
|
||||
const plain = entries.filter((entry) => !isToolModifier(entry));
|
||||
const tools = plain.length > 0 || entries.length === 0 ? plain : [...DEFAULT_TOOL_NAMES];
|
||||
for (const entry of entries) {
|
||||
if (!isToolModifier(entry)) continue;
|
||||
const name = entry.slice(1);
|
||||
const index = tools.indexOf(name);
|
||||
if (entry.startsWith("+") && index === -1 && name) tools.push(name);
|
||||
else if (entry.startsWith("-") && index !== -1) tools.splice(index, 1);
|
||||
}
|
||||
return tools;
|
||||
return applyToolModifiers(plain.length > 0 || entries.length === 0 ? plain : DEFAULT_TOOL_NAMES, entries);
|
||||
}
|
||||
|
||||
/** Deep merge settings: project/overrides take precedence, nested objects merge recursively */
|
||||
|
|
|
|||
|
|
@ -465,6 +465,31 @@ describe("parseArgs", () => {
|
|||
expect(result.tools).toEqual(["read", "bash"]);
|
||||
});
|
||||
|
||||
test("parses +name and -name tool modifiers", () => {
|
||||
const result = parseArgs(["-t", "+codemode,-write"]);
|
||||
expect(result.tools).toEqual(["+codemode", "-write"]);
|
||||
expect(result.diagnostics).toEqual([]);
|
||||
});
|
||||
|
||||
test("rejects tool names mixed with modifiers", () => {
|
||||
const result = parseArgs(["--tools", "read,+codemode"]);
|
||||
expect(result.tools).toBeUndefined();
|
||||
expect(result.diagnostics).toEqual([
|
||||
{ type: "error", message: "--tools: tool names cannot be mixed with +name or -name entries" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("rejects patterns in tool modifiers", () => {
|
||||
const result = parseArgs(["-t", "+mcp__radius__*"]);
|
||||
expect(result.tools).toBeUndefined();
|
||||
expect(result.diagnostics).toEqual([
|
||||
{
|
||||
type: "error",
|
||||
message: "-t: +name and -name entries take exact tool names, not patterns: +mcp__radius__*",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("parses --exclude-tools flag", () => {
|
||||
const result = parseArgs(["--exclude-tools", "read,bash"]);
|
||||
expect(result.excludeTools).toEqual(["read", "bash"]);
|
||||
|
|
|
|||
|
|
@ -156,6 +156,50 @@ describe("defaultTools setting", () => {
|
|||
toolLessSession.dispose();
|
||||
});
|
||||
|
||||
it("applies +name and -name tool options to the default selection", async () => {
|
||||
const inactiveTool: InlineExtension = (pi) => {
|
||||
pi.registerTool({
|
||||
name: "inactive_tool",
|
||||
label: "Inactive Tool",
|
||||
description: "Extension tool registered inactive",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }),
|
||||
defaultActive: false,
|
||||
});
|
||||
pi.registerTool({
|
||||
name: "active_tool",
|
||||
label: "Active Tool",
|
||||
description: "Extension tool registered active",
|
||||
parameters: Type.Object({}),
|
||||
execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }),
|
||||
});
|
||||
};
|
||||
|
||||
const session = await createSession(["+grep"], { tools: ["+inactive_tool", "-write"] }, [inactiveTool]);
|
||||
expect(session.getActiveToolNames().sort()).toEqual([
|
||||
"active_tool",
|
||||
"bash",
|
||||
"edit",
|
||||
"grep",
|
||||
"inactive_tool",
|
||||
"read",
|
||||
]);
|
||||
session.dispose();
|
||||
|
||||
const toolLess = await createSession(["read"], { noTools: "all", tools: ["+inactive_tool"] }, [inactiveTool]);
|
||||
expect(toolLess.getActiveToolNames()).toEqual(["inactive_tool"]);
|
||||
toolLess.dispose();
|
||||
});
|
||||
|
||||
it("rejects invalid tool modifier options", async () => {
|
||||
await expect(createSession([], { tools: ["read", "+grep"] })).rejects.toThrow(
|
||||
"Invalid tools option: tool names cannot be mixed with +name or -name entries",
|
||||
);
|
||||
await expect(createSession([], { tools: ["-gr*"] })).rejects.toThrow(
|
||||
"Invalid tools option: +name and -name entries take exact tool names, not patterns: -gr*",
|
||||
);
|
||||
});
|
||||
|
||||
describe("reload", () => {
|
||||
const inactiveTool: InlineExtension = (pi) => {
|
||||
pi.registerTool({
|
||||
|
|
@ -211,6 +255,17 @@ describe("defaultTools setting", () => {
|
|||
session.dispose();
|
||||
});
|
||||
|
||||
it("keeps tools removed by -name tool options removed on reload", async () => {
|
||||
writeSettings({ defaultTools: ["read"] });
|
||||
const session = await createFileSession({ tools: ["-bash", "+grep"] });
|
||||
expect(session.getActiveToolNames()).toEqual(["read", "grep"]);
|
||||
|
||||
writeSettings({ defaultTools: ["read", "bash", "inactive_tool"] });
|
||||
await session.reload();
|
||||
expect(session.getActiveToolNames().sort()).toEqual(["grep", "inactive_tool", "read"]);
|
||||
session.dispose();
|
||||
});
|
||||
|
||||
it("keeps explicit tool options on reload", async () => {
|
||||
const allowlisted = await createFileSession({ tools: ["read"] });
|
||||
writeSettings({ defaultTools: ["+grep"] });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue