mirror of
https://github.com/earendil-works/pi.git
synced 2026-10-02 00:35:27 +08:00
feat(coding-agent): enable tools newly added to defaultTools on reload
When the session's initial tools come from defaultTools, /reload activates names added to the resolved selection. Removals do not deactivate tools, and explicit --tools/--no-tools/--no-builtin-tools keep overriding the setting. closes #10245
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
- Added a `description` field for MCP servers (`pi mcp add --description`), shown with the server in the system prompt and used to rank its tools in tool search, and a `describeNamespace(name)` codemode helper that returns a namespace's instructions and tool names. `describeNamespace()` and `searchTools()` accept a namespace as `mcp__dev-radius`, `mcp__dev_radius`, `dev-radius`, or `dev_radius`.
|
||||
- Added an `oauth.clientName` setting for MCP servers (`pi mcp add --oauth-client-name`) to change the client name sent during OAuth client registration, for servers such as Figma that only accept known clients ([#10226](https://github.com/earendil-works/pi/issues/10226)).
|
||||
- `/reload` now enables tools newly added to the `defaultTools` setting. Tools removed from it stay enabled, tools turned off during the session stay off unless newly added, and `--tools`, `--no-tools`, and `--no-builtin-tools` still override the setting ([#10245](https://github.com/earendil-works/pi/issues/10245)).
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -53,6 +53,8 @@ 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.
|
||||
|
||||
CLI tool options override this setting for one invocation; `--tools` does not accept `+name` or `-name`. See [Command Line](cli.md#tools).
|
||||
|
||||
## Sessions and context
|
||||
|
||||
@@ -129,7 +129,7 @@ import {
|
||||
SessionManager,
|
||||
type SessionProjection,
|
||||
} from "./session-manager.ts";
|
||||
import type { CacheWarmingMode, SettingsManager } from "./settings-manager.ts";
|
||||
import { 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 {
|
||||
@@ -260,6 +260,11 @@ export interface AgentSessionConfig {
|
||||
cacheWarmer?: Pick<CacheWarmer, "cancel" | "status" | "onAgentSettled" | "onModeChanged" | "onWarmed">;
|
||||
/** Initial active built-in tool names. Default: [read, bash, edit, write] */
|
||||
initialActiveToolNames?: string[];
|
||||
/**
|
||||
* Whether the initial tools come from the `defaultTools` setting. When true, reload activates
|
||||
* tools newly added to the setting. Tools removed from it stay active.
|
||||
*/
|
||||
usesDefaultTools?: boolean;
|
||||
/** Optional allowlist of tool names. When provided, only these tool names are exposed. */
|
||||
allowedToolNames?: string[];
|
||||
/** Optional denylist of tool names. When provided, these tool names are not exposed. */
|
||||
@@ -418,6 +423,7 @@ export class AgentSession {
|
||||
private _cwd: string;
|
||||
private _extensionRunnerRef?: { current?: ExtensionRunner };
|
||||
private _initialActiveToolNames?: string[];
|
||||
private _usesDefaultTools: boolean;
|
||||
private _allowedToolNames?: Set<string>;
|
||||
private _excludedToolNames?: Set<string>;
|
||||
private _baseToolsOverride?: Record<string, AgentTool>;
|
||||
@@ -462,6 +468,7 @@ export class AgentSession {
|
||||
}
|
||||
this._extensionRunnerRef = config.extensionRunnerRef;
|
||||
this._initialActiveToolNames = config.initialActiveToolNames;
|
||||
this._usesDefaultTools = config.usesDefaultTools ?? false;
|
||||
this._allowedToolNames = config.allowedToolNames ? new Set(config.allowedToolNames) : undefined;
|
||||
this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;
|
||||
this._baseToolsOverride = config.baseToolsOverride;
|
||||
@@ -3582,12 +3589,22 @@ 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) : [],
|
||||
);
|
||||
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),
|
||||
)
|
||||
: [];
|
||||
this._buildRuntime({
|
||||
activeToolNames: this.getActiveToolNames(),
|
||||
activeToolNames: [...this.getActiveToolNames(), ...addedDefaultTools],
|
||||
flagValues: previousFlagValues,
|
||||
includeAllExtensionTools: true,
|
||||
});
|
||||
|
||||
@@ -445,6 +445,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
modelRuntime,
|
||||
cacheWarmer,
|
||||
initialActiveToolNames,
|
||||
usesDefaultTools: options.tools === undefined && !options.noTools,
|
||||
allowedToolNames,
|
||||
excludedToolNames,
|
||||
extensionRunnerRef,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { getModel } from "@earendil-works/pi-ai/compat";
|
||||
@@ -156,6 +156,84 @@ describe("defaultTools setting", () => {
|
||||
toolLessSession.dispose();
|
||||
});
|
||||
|
||||
describe("reload", () => {
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
const writeSettings = (settings: object) =>
|
||||
writeFileSync(join(agentDir, "settings.json"), JSON.stringify(settings));
|
||||
|
||||
async function createFileSession(options: ToolOptions = {}) {
|
||||
const settingsManager = SettingsManager.create(tempDir, agentDir);
|
||||
const resourceLoader = new DefaultResourceLoader({
|
||||
cwd: tempDir,
|
||||
agentDir,
|
||||
settingsManager,
|
||||
extensionFactories: [inactiveTool],
|
||||
});
|
||||
await resourceLoader.reload();
|
||||
return (
|
||||
await createAgentSession({
|
||||
cwd: tempDir,
|
||||
agentDir,
|
||||
model: getModel("anthropic", "claude-sonnet-4-5")!,
|
||||
settingsManager,
|
||||
sessionManager: SessionManager.inMemory(tempDir),
|
||||
resourceLoader,
|
||||
...options,
|
||||
})
|
||||
).session;
|
||||
}
|
||||
|
||||
// #10245
|
||||
it("activates only tools newly added to defaultTools", async () => {
|
||||
const session = await createFileSession();
|
||||
expect(session.getActiveToolNames()).toEqual(["read", "bash", "edit", "write"]);
|
||||
session.setActiveToolsByName(["read", "edit", "write"]);
|
||||
|
||||
writeSettings({ defaultTools: ["+inactive_tool", "+grep"] });
|
||||
await session.reload();
|
||||
// bash was disabled during the session and is not newly added, so it stays off.
|
||||
expect(session.getActiveToolNames().sort()).toEqual(["edit", "grep", "inactive_tool", "read", "write"]);
|
||||
|
||||
// Removing tools from the setting does not disable them.
|
||||
writeSettings({ defaultTools: ["-read"] });
|
||||
await session.reload();
|
||||
expect(session.getActiveToolNames().sort()).toEqual(["edit", "grep", "inactive_tool", "read", "write"]);
|
||||
session.dispose();
|
||||
});
|
||||
|
||||
it("keeps explicit tool options on reload", async () => {
|
||||
const allowlisted = await createFileSession({ tools: ["read"] });
|
||||
writeSettings({ defaultTools: ["+grep"] });
|
||||
await allowlisted.reload();
|
||||
expect(allowlisted.getActiveToolNames()).toEqual(["read"]);
|
||||
allowlisted.dispose();
|
||||
|
||||
writeSettings({});
|
||||
const builtinless = await createFileSession({ noTools: "builtin" });
|
||||
writeSettings({ defaultTools: ["+grep"] });
|
||||
await builtinless.reload();
|
||||
expect(builtinless.getActiveToolNames()).toEqual([]);
|
||||
builtinless.dispose();
|
||||
|
||||
writeSettings({});
|
||||
const excluded = await createFileSession({ excludeTools: ["grep"] });
|
||||
writeSettings({ defaultTools: ["+grep", "+inactive_tool"] });
|
||||
await excluded.reload();
|
||||
expect(excluded.getActiveToolNames().sort()).toEqual(["bash", "edit", "inactive_tool", "read", "write"]);
|
||||
excluded.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it("applies through service-based session creation", async () => {
|
||||
const settingsManager = SettingsManager.inMemory({ defaultTools: ["ls"] });
|
||||
const services = await createAgentSessionServices({ cwd: tempDir, agentDir, settingsManager });
|
||||
|
||||
Reference in New Issue
Block a user