@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 +14 -0
- package/README.md +1 -1
- package/dist/agent-runner.js +19 -19
- package/dist/agent-types.d.ts +7 -10
- package/dist/agent-types.js +12 -28
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -7
- package/package.json +4 -4
- package/src/agent-runner.ts +20 -18
- package/src/agent-types.ts +12 -40
- package/src/index.ts +8 -8
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
|
|
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
|
package/dist/agent-runner.js
CHANGED
|
@@ -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,
|
|
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
|
|
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(
|
|
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
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
125
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
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 (
|
|
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));
|
package/dist/agent-types.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
33
|
+
export declare function getMemoryToolNames(existingToolNames: Set<string>): string[];
|
|
36
34
|
/**
|
|
37
|
-
* Get only
|
|
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
|
|
41
|
-
/** Get built-in
|
|
42
|
-
export declare function
|
|
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;
|
package/dist/agent-types.js
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
110
|
-
export function
|
|
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
|
|
115
|
-
return
|
|
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
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("
|
|
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.
|
|
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.
|
|
25
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
26
|
-
"@mariozechner/pi-tui": "^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": {
|
package/src/agent-runner.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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(
|
|
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
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
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
|
-
|
|
204
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
}
|
package/src/agent-types.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
139
|
-
export function
|
|
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
|
|
144
|
-
return
|
|
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
|
|
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("
|
|
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
|
|
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
|
|