@tintinweb/pi-subagents 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2026-04-24
11
+
12
+ > **⚠️ Breaking: drops support for `pi` < 0.68.** The upstream `pi-coding-agent` package shipped breaking API changes in v0.68 (and further ones in v0.70). This release migrates to `^0.70.2` and is **not** backward-compatible with hosts on `pi` 0.62–0.67. Users on those versions must upgrade their `pi` installation (`npm install -g @mariozechner/pi-coding-agent@latest`) before updating this extension.
13
+
14
+ ### Changed
15
+ - **Bumped peer `@mariozechner/pi-coding-agent` to `^0.70.2`** ([#28](https://github.com/tintinweb/pi-subagents/pull/28)) — crosses the v0.68 breaking-change line upstream. Specifically: tools are now passed as `string[]` (was `Tool[]`); `cwd`/`agentDir` are mandatory on `SettingsManager.create()` and `DefaultResourceLoader`; `session_switch` event renamed to `session_before_switch`; `ToolDefinition.params` widens to `unknown` under contextual typing, requiring `defineTool(...)`.
16
+ - **Tool registrations wrapped with `defineTool(...)`** — preserves `TParams` inference so `execute` handlers get properly-typed `params` instead of `unknown`. Applies to the `Agent`, `get_subagent_result`, and `steer_subagent` tools.
17
+
18
+ ### Removed
19
+ - **Cwd-bound tool factory registry** — the internal `TOOL_FACTORIES` closure table and `create{Bash,Edit,Read,Write,Grep,Find,Ls}Tool` imports are gone. Exported helpers renamed: `getToolsForType(type, cwd)` → `getToolNamesForType(type)`, `getMemoryTools(cwd, set)` → `getMemoryToolNames(set)`, `getReadOnlyMemoryTools(cwd, set)` → `getReadOnlyMemoryToolNames(set)` — all returning `string[]` instead of `Tool[]`. The host binds cwd when resolving tool names, so the extension no longer instantiates tools directly.
20
+
21
+ ### Fixed
22
+ - **Subagent `SettingsManager` read wrong project settings in worktree mode** ([#30](https://github.com/tintinweb/pi-subagents/pull/30)) — `SettingsManager.create()` was called without arguments, defaulting `cwd` to `process.cwd()`. When the subagent's effective cwd differed (worktree isolation or explicit `cwd` override), its settings manager read `.pi/settings.json` from the parent's cwd rather than its own, diverging from the loader and session manager. Now passes `effectiveCwd` and `agentDir` explicitly, keeping all three managers consistent.
23
+
10
24
  ## [0.5.2] - 2026-03-26
11
25
 
12
26
  ### Fixed
package/README.md CHANGED
@@ -417,7 +417,7 @@ src/
417
417
  index.ts # Extension entry: tool/command registration, rendering
418
418
  types.ts # Type definitions (AgentConfig, AgentRecord, etc.)
419
419
  default-agents.ts # Embedded default agent configs (general-purpose, Explore, Plan)
420
- agent-types.ts # Unified agent registry (defaults + user), tool factories
420
+ agent-types.ts # Unified agent registry (defaults + user), tool name resolution
421
421
  agent-runner.ts # Session creation, execution, graceful max_turns, steer/resume
422
422
  agent-manager.ts # Agent lifecycle, concurrency queue, completion notifications
423
423
  cross-extension-rpc.ts # RPC handlers for cross-extension spawn/ping via pi.events
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * agent-runner.ts — Core execution engine: creates sessions, runs agents, collects results.
3
3
  */
4
- import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
5
- import { getAgentConfig, getConfig, getMemoryTools, getReadOnlyMemoryTools, getToolsForType } from "./agent-types.js";
4
+ import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
5
+ import { getAgentConfig, getConfig, getMemoryToolNames, getReadOnlyMemoryToolNames, getToolNamesForType } from "./agent-types.js";
6
6
  import { buildParentContext, extractText } from "./context.js";
7
7
  import { detectEnv } from "./env.js";
8
8
  import { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
@@ -110,28 +110,26 @@ export async function runAgent(ctx, type, prompt, options) {
110
110
  extras.skillBlocks = loaded;
111
111
  }
112
112
  }
113
- let tools = getToolsForType(type, effectiveCwd);
113
+ let toolNames = getToolNamesForType(type);
114
114
  // Persistent memory: detect write capability and branch accordingly.
115
115
  // Account for disallowedTools — a tool in the base set but on the denylist is not truly available.
116
116
  if (agentConfig?.memory) {
117
- const existingNames = new Set(tools.map(t => t.name));
117
+ const existingNames = new Set(toolNames);
118
118
  const denied = agentConfig.disallowedTools ? new Set(agentConfig.disallowedTools) : undefined;
119
119
  const effectivelyHas = (name) => existingNames.has(name) && !denied?.has(name);
120
120
  const hasWriteTools = effectivelyHas("write") || effectivelyHas("edit");
121
121
  if (hasWriteTools) {
122
- // Read-write memory: add any missing memory tools (read/write/edit)
123
- const memTools = getMemoryTools(effectiveCwd, existingNames);
124
- if (memTools.length > 0)
125
- tools = [...tools, ...memTools];
122
+ // Read-write memory: add any missing memory tool names (read/write/edit)
123
+ const extraNames = getMemoryToolNames(existingNames);
124
+ if (extraNames.length > 0)
125
+ toolNames = [...toolNames, ...extraNames];
126
126
  extras.memoryBlock = buildMemoryBlock(agentConfig.name, agentConfig.memory, effectiveCwd);
127
127
  }
128
128
  else {
129
- // Read-only memory: only add read tool, use read-only prompt
130
- if (!existingNames.has("read")) {
131
- const readTools = getReadOnlyMemoryTools(effectiveCwd, existingNames);
132
- if (readTools.length > 0)
133
- tools = [...tools, ...readTools];
134
- }
129
+ // Read-only memory: only add read tool name, use read-only prompt
130
+ const extraNames = getReadOnlyMemoryToolNames(existingNames);
131
+ if (extraNames.length > 0)
132
+ toolNames = [...toolNames, ...extraNames];
135
133
  extras.memoryBlock = buildReadOnlyMemoryBlock(agentConfig.name, agentConfig.memory, effectiveCwd);
136
134
  }
137
135
  }
@@ -158,9 +156,11 @@ export async function runAgent(ctx, type, prompt, options) {
158
156
  // When skills is string[], we've already preloaded them into the prompt.
159
157
  // Still pass noSkills: true since we don't need the skill loader to load them again.
160
158
  const noSkills = skills === false || Array.isArray(skills);
159
+ const agentDir = getAgentDir();
161
160
  // Load extensions/skills: true or string[] → load; false → don't
162
161
  const loader = new DefaultResourceLoader({
163
162
  cwd: effectiveCwd,
163
+ agentDir,
164
164
  noExtensions: extensions === false,
165
165
  noSkills,
166
166
  noPromptTemplates: true,
@@ -174,17 +174,17 @@ export async function runAgent(ctx, type, prompt, options) {
174
174
  const thinkingLevel = options.thinkingLevel ?? agentConfig?.thinking;
175
175
  const sessionOpts = {
176
176
  cwd: effectiveCwd,
177
+ agentDir,
177
178
  sessionManager: SessionManager.inMemory(effectiveCwd),
178
- settingsManager: SettingsManager.create(),
179
+ settingsManager: SettingsManager.create(effectiveCwd, agentDir),
179
180
  modelRegistry: ctx.modelRegistry,
180
181
  model,
181
- tools,
182
+ tools: toolNames,
182
183
  resourceLoader: loader,
183
184
  };
184
185
  if (thinkingLevel) {
185
186
  sessionOpts.thinkingLevel = thinkingLevel;
186
187
  }
187
- // createAgentSession's type signature may not include thinkingLevel yet
188
188
  const { session } = await createAgentSession(sessionOpts);
189
189
  // Build disallowed tools set from agent config
190
190
  const disallowedSet = agentConfig?.disallowedTools
@@ -193,13 +193,13 @@ export async function runAgent(ctx, type, prompt, options) {
193
193
  // Filter active tools: remove our own tools to prevent nesting,
194
194
  // apply extension allowlist if specified, and apply disallowedTools denylist
195
195
  if (extensions !== false) {
196
- const builtinToolNames = new Set(tools.map(t => t.name));
196
+ const builtinToolNameSet = new Set(toolNames);
197
197
  const activeTools = session.getActiveToolNames().filter((t) => {
198
198
  if (EXCLUDED_TOOL_NAMES.includes(t))
199
199
  return false;
200
200
  if (disallowedSet?.has(t))
201
201
  return false;
202
- if (builtinToolNames.has(t))
202
+ if (builtinToolNameSet.has(t))
203
203
  return true;
204
204
  if (Array.isArray(extensions)) {
205
205
  return extensions.some(ext => t.startsWith(ext) || t.includes(ext));
@@ -4,9 +4,8 @@
4
4
  * Merges embedded default agents with user-defined agents from .pi/agents/*.md.
5
5
  * User agents override defaults with the same name. Disabled agents are kept but excluded from spawning.
6
6
  */
7
- import type { AgentTool } from "@mariozechner/pi-agent-core";
8
7
  import type { AgentConfig } from "./types.js";
9
- /** All known built-in tool names, derived from the factory registry. */
8
+ /** All known built-in tool names. */
10
9
  export declare const BUILTIN_TOOL_NAMES: string[];
11
10
  /**
12
11
  * Register agents into the unified registry.
@@ -29,17 +28,15 @@ export declare function getUserAgentNames(): string[];
29
28
  /** Check if a type is valid and enabled (case-insensitive). */
30
29
  export declare function isValidType(type: string): boolean;
31
30
  /**
32
- * Get the tools needed for memory management (read, write, edit).
33
- * Only returns tools that are NOT already in the provided set.
31
+ * Get memory tool names (read/write/edit) not already in the provided set.
34
32
  */
35
- export declare function getMemoryTools(cwd: string, existingToolNames: Set<string>): AgentTool<any>[];
33
+ export declare function getMemoryToolNames(existingToolNames: Set<string>): string[];
36
34
  /**
37
- * Get only the read tool for read-only memory access.
38
- * Only returns tools that are NOT already in the provided set.
35
+ * Get read-only memory tool names not already in the provided set.
39
36
  */
40
- export declare function getReadOnlyMemoryTools(cwd: string, existingToolNames: Set<string>): AgentTool<any>[];
41
- /** Get built-in tools for a type (case-insensitive). */
42
- export declare function getToolsForType(type: string, cwd: string): AgentTool<any>[];
37
+ export declare function getReadOnlyMemoryToolNames(existingToolNames: Set<string>): string[];
38
+ /** Get built-in tool names for a type (case-insensitive). */
39
+ export declare function getToolNamesForType(type: string): string[];
43
40
  /** Get config for a type (case-insensitive, returns a SubagentTypeConfig-compatible object). Falls back to general-purpose. */
44
41
  export declare function getConfig(type: string): {
45
42
  displayName: string;
@@ -4,19 +4,9 @@
4
4
  * Merges embedded default agents with user-defined agents from .pi/agents/*.md.
5
5
  * User agents override defaults with the same name. Disabled agents are kept but excluded from spawning.
6
6
  */
7
- import { createBashTool, createEditTool, createFindTool, createGrepTool, createLsTool, createReadTool, createWriteTool, } from "@mariozechner/pi-coding-agent";
8
7
  import { DEFAULT_AGENTS } from "./default-agents.js";
9
- const TOOL_FACTORIES = {
10
- read: (cwd) => createReadTool(cwd),
11
- bash: (cwd) => createBashTool(cwd),
12
- edit: (cwd) => createEditTool(cwd),
13
- write: (cwd) => createWriteTool(cwd),
14
- grep: (cwd) => createGrepTool(cwd),
15
- find: (cwd) => createFindTool(cwd),
16
- ls: (cwd) => createLsTool(cwd),
17
- };
18
- /** All known built-in tool names, derived from the factory registry. */
19
- export const BUILTIN_TOOL_NAMES = Object.keys(TOOL_FACTORIES);
8
+ /** All known built-in tool names. */
9
+ export const BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
20
10
  /** Unified runtime registry of all agents (defaults + user-defined). */
21
11
  const agents = new Map();
22
12
  /**
@@ -87,32 +77,26 @@ export function isValidType(type) {
87
77
  /** Tool names required for memory management. */
88
78
  const MEMORY_TOOL_NAMES = ["read", "write", "edit"];
89
79
  /**
90
- * Get the tools needed for memory management (read, write, edit).
91
- * Only returns tools that are NOT already in the provided set.
80
+ * Get memory tool names (read/write/edit) not already in the provided set.
92
81
  */
93
- export function getMemoryTools(cwd, existingToolNames) {
94
- return MEMORY_TOOL_NAMES
95
- .filter(n => !existingToolNames.has(n) && n in TOOL_FACTORIES)
96
- .map(n => TOOL_FACTORIES[n](cwd));
82
+ export function getMemoryToolNames(existingToolNames) {
83
+ return MEMORY_TOOL_NAMES.filter(n => !existingToolNames.has(n));
97
84
  }
98
85
  /** Tool names needed for read-only memory access. */
99
86
  const READONLY_MEMORY_TOOL_NAMES = ["read"];
100
87
  /**
101
- * Get only the read tool for read-only memory access.
102
- * Only returns tools that are NOT already in the provided set.
88
+ * Get read-only memory tool names not already in the provided set.
103
89
  */
104
- export function getReadOnlyMemoryTools(cwd, existingToolNames) {
105
- return READONLY_MEMORY_TOOL_NAMES
106
- .filter(n => !existingToolNames.has(n) && n in TOOL_FACTORIES)
107
- .map(n => TOOL_FACTORIES[n](cwd));
90
+ export function getReadOnlyMemoryToolNames(existingToolNames) {
91
+ return READONLY_MEMORY_TOOL_NAMES.filter(n => !existingToolNames.has(n));
108
92
  }
109
- /** Get built-in tools for a type (case-insensitive). */
110
- export function getToolsForType(type, cwd) {
93
+ /** Get built-in tool names for a type (case-insensitive). */
94
+ export function getToolNamesForType(type) {
111
95
  const key = resolveKey(type);
112
96
  const raw = key ? agents.get(key) : undefined;
113
97
  const config = raw?.enabled !== false ? raw : undefined;
114
- const toolNames = config?.builtinToolNames?.length ? config.builtinToolNames : BUILTIN_TOOL_NAMES;
115
- return toolNames.filter((n) => n in TOOL_FACTORIES).map((n) => TOOL_FACTORIES[n](cwd));
98
+ const names = config?.builtinToolNames?.length ? config.builtinToolNames : [...BUILTIN_TOOL_NAMES];
99
+ return names;
116
100
  }
117
101
  /** Get config for a type (case-insensitive, returns a SubagentTypeConfig-compatible object). Falls back to general-purpose. */
118
102
  export function getConfig(type) {
package/dist/index.d.ts CHANGED
@@ -9,5 +9,5 @@
9
9
  * Commands:
10
10
  * /agents — Interactive agent management menu
11
11
  */
12
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
12
+ import { type ExtensionAPI } from "@mariozechner/pi-coding-agent";
13
13
  export default function (pi: ExtensionAPI): void;
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@
12
12
  import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
13
13
  import { homedir } from "node:os";
14
14
  import { join } from "node:path";
15
+ import { defineTool } from "@mariozechner/pi-coding-agent";
15
16
  import { Text } from "@mariozechner/pi-tui";
16
17
  import { Type } from "@sinclair/typebox";
17
18
  import { AgentManager } from "./agent-manager.js";
@@ -383,7 +384,7 @@ export default function (pi) {
383
384
  currentCtx = ctx;
384
385
  manager.clearCompleted(); // preserve existing behavior
385
386
  });
386
- pi.on("session_switch", () => { manager.clearCompleted(); });
387
+ pi.on("session_before_switch", () => { manager.clearCompleted(); });
387
388
  const { unsubPing: unsubPingRpc, unsubSpawn: unsubSpawnRpc, unsubStop: unsubStopRpc } = registerRpcHandlers({
388
389
  events: pi.events,
389
390
  pi,
@@ -489,7 +490,7 @@ export default function (pi) {
489
490
  }
490
491
  const typeListText = buildTypeListText();
491
492
  // ---- Agent tool ----
492
- pi.registerTool({
493
+ pi.registerTool(defineTool({
493
494
  name: "Agent",
494
495
  label: "Agent",
495
496
  description: `Launch a new agent to handle complex, multi-step tasks autonomously.
@@ -847,9 +848,9 @@ Guidelines:
847
848
  return textResult(`${fallbackNote}Agent completed in ${formatMs(durationMs)} (${statsParts.join(", ")})${getStatusNote(record.status)}.\n\n` +
848
849
  (record.result?.trim() || "No output."), details);
849
850
  },
850
- });
851
+ }));
851
852
  // ---- get_subagent_result tool ----
852
- pi.registerTool({
853
+ pi.registerTool(defineTool({
853
854
  name: "get_subagent_result",
854
855
  label: "Get Agent Result",
855
856
  description: "Check status and retrieve results from a background agent. Use the agent ID returned by Agent with run_in_background.",
@@ -908,9 +909,9 @@ Guidelines:
908
909
  }
909
910
  return textResult(output);
910
911
  },
911
- });
912
+ }));
912
913
  // ---- steer_subagent tool ----
913
- pi.registerTool({
914
+ pi.registerTool(defineTool({
914
915
  name: "steer_subagent",
915
916
  label: "Steer Agent",
916
917
  description: "Send a steering message to a running agent. The message will interrupt the agent after its current tool execution " +
@@ -948,7 +949,7 @@ Guidelines:
948
949
  return textResult(`Failed to steer agent: ${err instanceof Error ? err.message : String(err)}`);
949
950
  }
950
951
  },
951
- });
952
+ }));
952
953
  // ---- /agents interactive menu ----
953
954
  const projectAgentsDir = () => join(process.cwd(), ".pi", "agents");
954
955
  const personalAgentsDir = () => join(homedir(), ".pi", "agent", "agents");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tintinweb/pi-subagents",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "A pi extension extension that brings smart Claude Code-style autonomous sub-agents to pi.",
5
5
  "author": "tintinweb",
6
6
  "license": "MIT",
@@ -21,9 +21,9 @@
21
21
  "autonomous"
22
22
  ],
23
23
  "dependencies": {
24
- "@mariozechner/pi-ai": "^0.62.0",
25
- "@mariozechner/pi-coding-agent": "^0.62.0",
26
- "@mariozechner/pi-tui": "^0.62.0",
24
+ "@mariozechner/pi-ai": "^0.70.2",
25
+ "@mariozechner/pi-coding-agent": "^0.70.2",
26
+ "@mariozechner/pi-tui": "^0.70.2",
27
27
  "@sinclair/typebox": "latest"
28
28
  },
29
29
  "scripts": {
@@ -10,10 +10,11 @@ import {
10
10
  createAgentSession,
11
11
  DefaultResourceLoader,
12
12
  type ExtensionAPI,
13
+ getAgentDir,
13
14
  SessionManager,
14
15
  SettingsManager,
15
16
  } from "@mariozechner/pi-coding-agent";
16
- import { getAgentConfig, getConfig, getMemoryTools, getReadOnlyMemoryTools, getToolsForType } from "./agent-types.js";
17
+ import { getAgentConfig, getConfig, getMemoryToolNames, getReadOnlyMemoryToolNames, getToolNamesForType } from "./agent-types.js";
17
18
  import { buildParentContext, extractText } from "./context.js";
18
19
  import { detectEnv } from "./env.js";
19
20
  import { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
@@ -183,27 +184,25 @@ export async function runAgent(
183
184
  }
184
185
  }
185
186
 
186
- let tools = getToolsForType(type, effectiveCwd);
187
+ let toolNames = getToolNamesForType(type);
187
188
 
188
189
  // Persistent memory: detect write capability and branch accordingly.
189
190
  // Account for disallowedTools — a tool in the base set but on the denylist is not truly available.
190
191
  if (agentConfig?.memory) {
191
- const existingNames = new Set(tools.map(t => t.name));
192
+ const existingNames = new Set(toolNames);
192
193
  const denied = agentConfig.disallowedTools ? new Set(agentConfig.disallowedTools) : undefined;
193
194
  const effectivelyHas = (name: string) => existingNames.has(name) && !denied?.has(name);
194
195
  const hasWriteTools = effectivelyHas("write") || effectivelyHas("edit");
195
196
 
196
197
  if (hasWriteTools) {
197
- // Read-write memory: add any missing memory tools (read/write/edit)
198
- const memTools = getMemoryTools(effectiveCwd, existingNames);
199
- if (memTools.length > 0) tools = [...tools, ...memTools];
198
+ // Read-write memory: add any missing memory tool names (read/write/edit)
199
+ const extraNames = getMemoryToolNames(existingNames);
200
+ if (extraNames.length > 0) toolNames = [...toolNames, ...extraNames];
200
201
  extras.memoryBlock = buildMemoryBlock(agentConfig.name, agentConfig.memory, effectiveCwd);
201
202
  } else {
202
- // Read-only memory: only add read tool, use read-only prompt
203
- if (!existingNames.has("read")) {
204
- const readTools = getReadOnlyMemoryTools(effectiveCwd, existingNames);
205
- if (readTools.length > 0) tools = [...tools, ...readTools];
206
- }
203
+ // Read-only memory: only add read tool name, use read-only prompt
204
+ const extraNames = getReadOnlyMemoryToolNames(existingNames);
205
+ if (extraNames.length > 0) toolNames = [...toolNames, ...extraNames];
207
206
  extras.memoryBlock = buildReadOnlyMemoryBlock(agentConfig.name, agentConfig.memory, effectiveCwd);
208
207
  }
209
208
  }
@@ -232,9 +231,12 @@ export async function runAgent(
232
231
  // Still pass noSkills: true since we don't need the skill loader to load them again.
233
232
  const noSkills = skills === false || Array.isArray(skills);
234
233
 
234
+ const agentDir = getAgentDir();
235
+
235
236
  // Load extensions/skills: true or string[] → load; false → don't
236
237
  const loader = new DefaultResourceLoader({
237
238
  cwd: effectiveCwd,
239
+ agentDir,
238
240
  noExtensions: extensions === false,
239
241
  noSkills,
240
242
  noPromptTemplates: true,
@@ -251,21 +253,21 @@ export async function runAgent(
251
253
  // Resolve thinking level: explicit option > agent config > undefined (inherit)
252
254
  const thinkingLevel = options.thinkingLevel ?? agentConfig?.thinking;
253
255
 
254
- const sessionOpts: Record<string, unknown> = {
256
+ const sessionOpts: Parameters<typeof createAgentSession>[0] = {
255
257
  cwd: effectiveCwd,
258
+ agentDir,
256
259
  sessionManager: SessionManager.inMemory(effectiveCwd),
257
- settingsManager: SettingsManager.create(),
260
+ settingsManager: SettingsManager.create(effectiveCwd, agentDir),
258
261
  modelRegistry: ctx.modelRegistry,
259
262
  model,
260
- tools,
263
+ tools: toolNames,
261
264
  resourceLoader: loader,
262
265
  };
263
266
  if (thinkingLevel) {
264
267
  sessionOpts.thinkingLevel = thinkingLevel;
265
268
  }
266
269
 
267
- // createAgentSession's type signature may not include thinkingLevel yet
268
- const { session } = await createAgentSession(sessionOpts as Parameters<typeof createAgentSession>[0]);
270
+ const { session } = await createAgentSession(sessionOpts);
269
271
 
270
272
  // Build disallowed tools set from agent config
271
273
  const disallowedSet = agentConfig?.disallowedTools
@@ -275,11 +277,11 @@ export async function runAgent(
275
277
  // Filter active tools: remove our own tools to prevent nesting,
276
278
  // apply extension allowlist if specified, and apply disallowedTools denylist
277
279
  if (extensions !== false) {
278
- const builtinToolNames = new Set(tools.map(t => t.name));
280
+ const builtinToolNameSet = new Set(toolNames);
279
281
  const activeTools = session.getActiveToolNames().filter((t) => {
280
282
  if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
281
283
  if (disallowedSet?.has(t)) return false;
282
- if (builtinToolNames.has(t)) return true;
284
+ if (builtinToolNameSet.has(t)) return true;
283
285
  if (Array.isArray(extensions)) {
284
286
  return extensions.some(ext => t.startsWith(ext) || t.includes(ext));
285
287
  }
@@ -5,33 +5,11 @@
5
5
  * User agents override defaults with the same name. Disabled agents are kept but excluded from spawning.
6
6
  */
7
7
 
8
- import type { AgentTool } from "@mariozechner/pi-agent-core";
9
- import {
10
- createBashTool,
11
- createEditTool,
12
- createFindTool,
13
- createGrepTool,
14
- createLsTool,
15
- createReadTool,
16
- createWriteTool,
17
- } from "@mariozechner/pi-coding-agent";
18
8
  import { DEFAULT_AGENTS } from "./default-agents.js";
19
9
  import type { AgentConfig } from "./types.js";
20
10
 
21
- type ToolFactory = (cwd: string) => AgentTool<any>;
22
-
23
- const TOOL_FACTORIES: Record<string, ToolFactory> = {
24
- read: (cwd) => createReadTool(cwd),
25
- bash: (cwd) => createBashTool(cwd),
26
- edit: (cwd) => createEditTool(cwd),
27
- write: (cwd) => createWriteTool(cwd),
28
- grep: (cwd) => createGrepTool(cwd),
29
- find: (cwd) => createFindTool(cwd),
30
- ls: (cwd) => createLsTool(cwd),
31
- };
32
-
33
- /** All known built-in tool names, derived from the factory registry. */
34
- export const BUILTIN_TOOL_NAMES = Object.keys(TOOL_FACTORIES);
11
+ /** All known built-in tool names. */
12
+ export const BUILTIN_TOOL_NAMES: string[] = ["read", "bash", "edit", "write", "grep", "find", "ls"];
35
13
 
36
14
  /** Unified runtime registry of all agents (defaults + user-defined). */
37
15
  const agents = new Map<string, AgentConfig>();
@@ -113,35 +91,29 @@ export function isValidType(type: string): boolean {
113
91
  const MEMORY_TOOL_NAMES = ["read", "write", "edit"];
114
92
 
115
93
  /**
116
- * Get the tools needed for memory management (read, write, edit).
117
- * Only returns tools that are NOT already in the provided set.
94
+ * Get memory tool names (read/write/edit) not already in the provided set.
118
95
  */
119
- export function getMemoryTools(cwd: string, existingToolNames: Set<string>): AgentTool<any>[] {
120
- return MEMORY_TOOL_NAMES
121
- .filter(n => !existingToolNames.has(n) && n in TOOL_FACTORIES)
122
- .map(n => TOOL_FACTORIES[n](cwd));
96
+ export function getMemoryToolNames(existingToolNames: Set<string>): string[] {
97
+ return MEMORY_TOOL_NAMES.filter(n => !existingToolNames.has(n));
123
98
  }
124
99
 
125
100
  /** Tool names needed for read-only memory access. */
126
101
  const READONLY_MEMORY_TOOL_NAMES = ["read"];
127
102
 
128
103
  /**
129
- * Get only the read tool for read-only memory access.
130
- * Only returns tools that are NOT already in the provided set.
104
+ * Get read-only memory tool names not already in the provided set.
131
105
  */
132
- export function getReadOnlyMemoryTools(cwd: string, existingToolNames: Set<string>): AgentTool<any>[] {
133
- return READONLY_MEMORY_TOOL_NAMES
134
- .filter(n => !existingToolNames.has(n) && n in TOOL_FACTORIES)
135
- .map(n => TOOL_FACTORIES[n](cwd));
106
+ export function getReadOnlyMemoryToolNames(existingToolNames: Set<string>): string[] {
107
+ return READONLY_MEMORY_TOOL_NAMES.filter(n => !existingToolNames.has(n));
136
108
  }
137
109
 
138
- /** Get built-in tools for a type (case-insensitive). */
139
- export function getToolsForType(type: string, cwd: string): AgentTool<any>[] {
110
+ /** Get built-in tool names for a type (case-insensitive). */
111
+ export function getToolNamesForType(type: string): string[] {
140
112
  const key = resolveKey(type);
141
113
  const raw = key ? agents.get(key) : undefined;
142
114
  const config = raw?.enabled !== false ? raw : undefined;
143
- const toolNames = config?.builtinToolNames?.length ? config.builtinToolNames : BUILTIN_TOOL_NAMES;
144
- return toolNames.filter((n) => n in TOOL_FACTORIES).map((n) => TOOL_FACTORIES[n](cwd));
115
+ const names = config?.builtinToolNames?.length ? config.builtinToolNames : [...BUILTIN_TOOL_NAMES];
116
+ return names;
145
117
  }
146
118
 
147
119
  /** Get config for a type (case-insensitive, returns a SubagentTypeConfig-compatible object). Falls back to general-purpose. */
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
14
14
  import { homedir } from "node:os";
15
15
  import { join } from "node:path";
16
- import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
16
+ import { defineTool, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@mariozechner/pi-coding-agent";
17
17
  import { Text } from "@mariozechner/pi-tui";
18
18
  import { Type } from "@sinclair/typebox";
19
19
  import { AgentManager } from "./agent-manager.js";
@@ -430,7 +430,7 @@ export default function (pi: ExtensionAPI) {
430
430
  manager.clearCompleted(); // preserve existing behavior
431
431
  });
432
432
 
433
- pi.on("session_switch", () => { manager.clearCompleted(); });
433
+ pi.on("session_before_switch", () => { manager.clearCompleted(); });
434
434
 
435
435
  const { unsubPing: unsubPingRpc, unsubSpawn: unsubSpawnRpc, unsubStop: unsubStopRpc } = registerRpcHandlers({
436
436
  events: pi.events,
@@ -550,7 +550,7 @@ export default function (pi: ExtensionAPI) {
550
550
 
551
551
  // ---- Agent tool ----
552
552
 
553
- pi.registerTool<any, AgentDetails>({
553
+ pi.registerTool(defineTool({
554
554
  name: "Agent",
555
555
  label: "Agent",
556
556
  description: `Launch a new agent to handle complex, multi-step tasks autonomously.
@@ -964,11 +964,11 @@ Guidelines:
964
964
  details,
965
965
  );
966
966
  },
967
- });
967
+ }));
968
968
 
969
969
  // ---- get_subagent_result tool ----
970
970
 
971
- pi.registerTool({
971
+ pi.registerTool(defineTool({
972
972
  name: "get_subagent_result",
973
973
  label: "Get Agent Result",
974
974
  description:
@@ -1038,11 +1038,11 @@ Guidelines:
1038
1038
 
1039
1039
  return textResult(output);
1040
1040
  },
1041
- });
1041
+ }));
1042
1042
 
1043
1043
  // ---- steer_subagent tool ----
1044
1044
 
1045
- pi.registerTool({
1045
+ pi.registerTool(defineTool({
1046
1046
  name: "steer_subagent",
1047
1047
  label: "Steer Agent",
1048
1048
  description:
@@ -1080,7 +1080,7 @@ Guidelines:
1080
1080
  return textResult(`Failed to steer agent: ${err instanceof Error ? err.message : String(err)}`);
1081
1081
  }
1082
1082
  },
1083
- });
1083
+ }));
1084
1084
 
1085
1085
  // ---- /agents interactive menu ----
1086
1086