@tintinweb/pi-subagents 0.7.2 → 0.7.3
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 +11 -0
- package/README.md +2 -0
- package/dist/agent-manager.js +1 -0
- package/dist/agent-runner.d.ts +2 -0
- package/dist/agent-runner.js +2 -0
- package/dist/cross-extension-rpc.js +23 -1
- package/dist/prompts.d.ts +4 -0
- package/dist/prompts.js +7 -2
- package/package.json +1 -1
- package/src/agent-manager.ts +1 -0
- package/src/agent-runner.ts +7 -0
- package/src/cross-extension-rpc.ts +28 -1
- package/src/prompts.ts +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.3] - 2026-05-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`<active_agent name="…"/>` tag prepended to every child system prompt** ([#73](https://github.com/tintinweb/pi-subagents/pull/73) — thanks [@chris-lasher](https://github.com/chris-lasher)). `buildAgentPrompt` now emits `<active_agent name="${config.name}"/>` as the first line of the assembled prompt in both `replace` and `append` modes, before the env block. Downstream extensions (e.g. permission/policy systems) can parse it from inside the child session to resolve per-agent policy. The tag uses the agent's `config.name` verbatim — no escaping or normalization — and does not couple this extension to any specific downstream consumer; ignoring it is harmless.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Subagent sessions now get a stable, type-derived name with an id suffix for parallel spawns** ([#51](https://github.com/tintinweb/pi-subagents/pull/51) — thanks [@forcepushdev](https://github.com/forcepushdev)). `runAgent` calls `session.setSessionName(agentConfig?.name ?? type)`, and when the manager assigns an `agentId` (always, in production), the name is suffixed with an 8-char slice — e.g. `Explore#a1b2c3d4` — so concurrent spawns of the same agent type are distinguishable in the overlay instead of all collapsing onto the same bare name. Direct `runAgent` callers without an `agentId` (e.g. tests) get the bare name.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Cross-extension spawn RPC now accepts a string `options.model`** ([#59](https://github.com/tintinweb/pi-subagents/pull/59), fixes [#60](https://github.com/tintinweb/pi-subagents/issues/60)). Cross-extension callers (e.g. `@tintinweb/pi-tasks@>=0.4.3`'s `TaskExecute`) naturally forward `model` as a serializable `"provider/modelId"` string. Previously the spawn handler passed strings straight through to `runAgent()`, which expects a `Model` object — the spawned agent then crashed with `No API key found for undefined`. The handler now resolves strings via the same `resolveModel(ctx.modelRegistry)` path the scheduler uses; `Model` objects pass through unchanged. Unresolved strings surface the human-readable `Model not found: "…"` error instead of the auth-lookup crash. Thanks @any-victor.
|
|
20
|
+
|
|
10
21
|
## [0.7.2] - 2026-05-12
|
|
11
22
|
|
|
12
23
|
> **Heads-up — behavior changes in skill preloading:**
|
package/README.md
CHANGED
|
@@ -406,6 +406,8 @@ pi.events.emit("subagents:rpc:spawn", {
|
|
|
406
406
|
});
|
|
407
407
|
```
|
|
408
408
|
|
|
409
|
+
`options.model` accepts either a `Model` object (e.g. `ctx.model`) or a `"provider/modelId"` string — strings are resolved against `ctx.modelRegistry` at the RPC boundary, so cross-extension callers can forward serializable values without losing auth context.
|
|
410
|
+
|
|
409
411
|
### Stop
|
|
410
412
|
|
|
411
413
|
Stop a running agent by ID:
|
package/dist/agent-manager.js
CHANGED
|
@@ -107,6 +107,7 @@ export class AgentManager {
|
|
|
107
107
|
const detach = () => { detachParentSignal?.(); detachParentSignal = undefined; };
|
|
108
108
|
const promise = runAgent(ctx, type, prompt, {
|
|
109
109
|
pi,
|
|
110
|
+
agentId: id,
|
|
110
111
|
model: options.model,
|
|
111
112
|
maxTurns: options.maxTurns,
|
|
112
113
|
isolated: options.isolated,
|
package/dist/agent-runner.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface ToolActivity {
|
|
|
23
23
|
export interface RunOptions {
|
|
24
24
|
/** ExtensionAPI instance — used for pi.exec() instead of execSync. */
|
|
25
25
|
pi: ExtensionAPI;
|
|
26
|
+
/** Manager-assigned id; suffixes session name to disambiguate parallel spawns (e.g. `Explore#a1b2c3d4`). */
|
|
27
|
+
agentId?: string;
|
|
26
28
|
model?: Model<any>;
|
|
27
29
|
maxTurns?: number;
|
|
28
30
|
signal?: AbortSignal;
|
package/dist/agent-runner.js
CHANGED
|
@@ -187,6 +187,8 @@ export async function runAgent(ctx, type, prompt, options) {
|
|
|
187
187
|
sessionOpts.thinkingLevel = thinkingLevel;
|
|
188
188
|
}
|
|
189
189
|
const { session } = await createAgentSession(sessionOpts);
|
|
190
|
+
const baseSessionName = agentConfig?.name ?? type;
|
|
191
|
+
session.setSessionName(options.agentId ? `${baseSessionName}#${options.agentId.slice(0, 8)}` : baseSessionName);
|
|
190
192
|
// Build disallowed tools set from agent config
|
|
191
193
|
const disallowedSet = agentConfig?.disallowedTools
|
|
192
194
|
? new Set(agentConfig.disallowedTools)
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* success → { success: true, data?: T }
|
|
9
9
|
* error → { success: false, error: string }
|
|
10
10
|
*/
|
|
11
|
+
import { resolveModel } from "./model-resolver.js";
|
|
11
12
|
/** RPC protocol version — bumped when the envelope or method contracts change. */
|
|
12
13
|
export const PROTOCOL_VERSION = 2;
|
|
13
14
|
/**
|
|
@@ -44,7 +45,28 @@ export function registerRpcHandlers(deps) {
|
|
|
44
45
|
const ctx = getCtx();
|
|
45
46
|
if (!ctx)
|
|
46
47
|
throw new Error("No active session");
|
|
47
|
-
|
|
48
|
+
// Cross-extension RPC callers (e.g. pi-tasks TaskExecute) naturally
|
|
49
|
+
// forward serializable values, so options.model can be a string like
|
|
50
|
+
// "openai-codex/gpt-5.5". Resolve it to a real Model instance here
|
|
51
|
+
// — same pattern the scheduler path already uses — so the spawned
|
|
52
|
+
// agent's auth lookup doesn't crash with "No API key found for
|
|
53
|
+
// undefined".
|
|
54
|
+
let normalizedOptions = options ?? {};
|
|
55
|
+
if (typeof normalizedOptions.model === "string") {
|
|
56
|
+
const registry = ctx.modelRegistry;
|
|
57
|
+
if (!registry) {
|
|
58
|
+
throw new Error(`Model override "${normalizedOptions.model}" provided but ctx.modelRegistry is unavailable`);
|
|
59
|
+
}
|
|
60
|
+
const resolved = resolveModel(normalizedOptions.model, registry);
|
|
61
|
+
if (typeof resolved === "string") {
|
|
62
|
+
// resolveModel returns a human-readable error string when the
|
|
63
|
+
// input doesn't match any available model. Surface it instead of
|
|
64
|
+
// silently falling back so the caller sees the auth/typo issue.
|
|
65
|
+
throw new Error(resolved);
|
|
66
|
+
}
|
|
67
|
+
normalizedOptions = { ...normalizedOptions, model: resolved };
|
|
68
|
+
}
|
|
69
|
+
return { id: manager.spawn(pi, ctx, type, prompt, normalizedOptions) };
|
|
48
70
|
});
|
|
49
71
|
const unsubStop = handleRpc(events, "subagents:rpc:stop", ({ agentId }) => {
|
|
50
72
|
if (!manager.abort(agentId))
|
package/dist/prompts.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export interface PromptExtras {
|
|
|
19
19
|
* - "append" mode: env header + parent system prompt + sub-agent context + config.systemPrompt
|
|
20
20
|
* - "append" with empty systemPrompt: pure parent clone
|
|
21
21
|
*
|
|
22
|
+
* Both modes prepend an `<active_agent name="${config.name}"/>` tag so downstream
|
|
23
|
+
* extensions (e.g. permission/policy systems) can resolve per-agent policy
|
|
24
|
+
* inside the child session by parsing the system prompt.
|
|
25
|
+
*
|
|
22
26
|
* @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
|
|
23
27
|
* @param extras Optional extra sections to inject (memory, preloaded skills).
|
|
24
28
|
*/
|
package/dist/prompts.js
CHANGED
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
* - "append" mode: env header + parent system prompt + sub-agent context + config.systemPrompt
|
|
9
9
|
* - "append" with empty systemPrompt: pure parent clone
|
|
10
10
|
*
|
|
11
|
+
* Both modes prepend an `<active_agent name="${config.name}"/>` tag so downstream
|
|
12
|
+
* extensions (e.g. permission/policy systems) can resolve per-agent policy
|
|
13
|
+
* inside the child session by parsing the system prompt.
|
|
14
|
+
*
|
|
11
15
|
* @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
|
|
12
16
|
* @param extras Optional extra sections to inject (memory, preloaded skills).
|
|
13
17
|
*/
|
|
14
18
|
export function buildAgentPrompt(config, cwd, env, parentSystemPrompt, extras) {
|
|
19
|
+
const activeAgentTag = `<active_agent name="${config.name}"/>\n\n`;
|
|
15
20
|
const envBlock = `# Environment
|
|
16
21
|
Working directory: ${cwd}
|
|
17
22
|
${env.isGitRepo ? `Git repository: yes\nBranch: ${env.branch}` : "Not a git repository"}
|
|
@@ -44,14 +49,14 @@ You are operating as a sub-agent invoked to handle a specific task.
|
|
|
44
49
|
const customSection = config.systemPrompt?.trim()
|
|
45
50
|
? `\n\n<agent_instructions>\n${config.systemPrompt}\n</agent_instructions>`
|
|
46
51
|
: "";
|
|
47
|
-
return envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection + extrasSuffix;
|
|
52
|
+
return activeAgentTag + envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection + extrasSuffix;
|
|
48
53
|
}
|
|
49
54
|
// "replace" mode — env header + the config's full system prompt
|
|
50
55
|
const replaceHeader = `You are a pi coding agent sub-agent.
|
|
51
56
|
You have been invoked to handle a specific task autonomously.
|
|
52
57
|
|
|
53
58
|
${envBlock}`;
|
|
54
|
-
return replaceHeader + "\n\n" + config.systemPrompt + extrasSuffix;
|
|
59
|
+
return activeAgentTag + replaceHeader + "\n\n" + config.systemPrompt + extrasSuffix;
|
|
55
60
|
}
|
|
56
61
|
/** Fallback base prompt when parent system prompt is unavailable in append mode. */
|
|
57
62
|
const genericBase = `# Role
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
package/src/agent-runner.ts
CHANGED
|
@@ -88,6 +88,8 @@ export interface ToolActivity {
|
|
|
88
88
|
export interface RunOptions {
|
|
89
89
|
/** ExtensionAPI instance — used for pi.exec() instead of execSync. */
|
|
90
90
|
pi: ExtensionAPI;
|
|
91
|
+
/** Manager-assigned id; suffixes session name to disambiguate parallel spawns (e.g. `Explore#a1b2c3d4`). */
|
|
92
|
+
agentId?: string;
|
|
91
93
|
model?: Model<any>;
|
|
92
94
|
maxTurns?: number;
|
|
93
95
|
signal?: AbortSignal;
|
|
@@ -280,6 +282,11 @@ export async function runAgent(
|
|
|
280
282
|
|
|
281
283
|
const { session } = await createAgentSession(sessionOpts);
|
|
282
284
|
|
|
285
|
+
const baseSessionName = agentConfig?.name ?? type;
|
|
286
|
+
session.setSessionName(
|
|
287
|
+
options.agentId ? `${baseSessionName}#${options.agentId.slice(0, 8)}` : baseSessionName,
|
|
288
|
+
);
|
|
289
|
+
|
|
283
290
|
// Build disallowed tools set from agent config
|
|
284
291
|
const disallowedSet = agentConfig?.disallowedTools
|
|
285
292
|
? new Set(agentConfig.disallowedTools)
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* error → { success: false, error: string }
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { type ModelRegistry, resolveModel } from "./model-resolver.js";
|
|
13
|
+
|
|
12
14
|
/** Minimal event bus interface needed by the RPC handlers. */
|
|
13
15
|
export interface EventBus {
|
|
14
16
|
on(event: string, handler: (data: unknown) => void): () => void;
|
|
@@ -81,7 +83,32 @@ export function registerRpcHandlers(deps: RpcDeps): RpcHandle {
|
|
|
81
83
|
events, "subagents:rpc:spawn", ({ type, prompt, options }) => {
|
|
82
84
|
const ctx = getCtx();
|
|
83
85
|
if (!ctx) throw new Error("No active session");
|
|
84
|
-
|
|
86
|
+
|
|
87
|
+
// Cross-extension RPC callers (e.g. pi-tasks TaskExecute) naturally
|
|
88
|
+
// forward serializable values, so options.model can be a string like
|
|
89
|
+
// "openai-codex/gpt-5.5". Resolve it to a real Model instance here
|
|
90
|
+
// — same pattern the scheduler path already uses — so the spawned
|
|
91
|
+
// agent's auth lookup doesn't crash with "No API key found for
|
|
92
|
+
// undefined".
|
|
93
|
+
let normalizedOptions = options ?? {};
|
|
94
|
+
if (typeof normalizedOptions.model === "string") {
|
|
95
|
+
const registry = (ctx as { modelRegistry?: ModelRegistry }).modelRegistry;
|
|
96
|
+
if (!registry) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Model override "${normalizedOptions.model}" provided but ctx.modelRegistry is unavailable`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const resolved = resolveModel(normalizedOptions.model, registry);
|
|
102
|
+
if (typeof resolved === "string") {
|
|
103
|
+
// resolveModel returns a human-readable error string when the
|
|
104
|
+
// input doesn't match any available model. Surface it instead of
|
|
105
|
+
// silently falling back so the caller sees the auth/typo issue.
|
|
106
|
+
throw new Error(resolved);
|
|
107
|
+
}
|
|
108
|
+
normalizedOptions = { ...normalizedOptions, model: resolved };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { id: manager.spawn(pi, ctx, type, prompt, normalizedOptions) };
|
|
85
112
|
},
|
|
86
113
|
);
|
|
87
114
|
|
package/src/prompts.ts
CHANGED
|
@@ -19,6 +19,10 @@ export interface PromptExtras {
|
|
|
19
19
|
* - "append" mode: env header + parent system prompt + sub-agent context + config.systemPrompt
|
|
20
20
|
* - "append" with empty systemPrompt: pure parent clone
|
|
21
21
|
*
|
|
22
|
+
* Both modes prepend an `<active_agent name="${config.name}"/>` tag so downstream
|
|
23
|
+
* extensions (e.g. permission/policy systems) can resolve per-agent policy
|
|
24
|
+
* inside the child session by parsing the system prompt.
|
|
25
|
+
*
|
|
22
26
|
* @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
|
|
23
27
|
* @param extras Optional extra sections to inject (memory, preloaded skills).
|
|
24
28
|
*/
|
|
@@ -29,6 +33,8 @@ export function buildAgentPrompt(
|
|
|
29
33
|
parentSystemPrompt?: string,
|
|
30
34
|
extras?: PromptExtras,
|
|
31
35
|
): string {
|
|
36
|
+
const activeAgentTag = `<active_agent name="${config.name}"/>\n\n`;
|
|
37
|
+
|
|
32
38
|
const envBlock = `# Environment
|
|
33
39
|
Working directory: ${cwd}
|
|
34
40
|
${env.isGitRepo ? `Git repository: yes\nBranch: ${env.branch}` : "Not a git repository"}
|
|
@@ -66,7 +72,7 @@ You are operating as a sub-agent invoked to handle a specific task.
|
|
|
66
72
|
? `\n\n<agent_instructions>\n${config.systemPrompt}\n</agent_instructions>`
|
|
67
73
|
: "";
|
|
68
74
|
|
|
69
|
-
return envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection + extrasSuffix;
|
|
75
|
+
return activeAgentTag + envBlock + "\n\n<inherited_system_prompt>\n" + identity + "\n</inherited_system_prompt>\n\n" + bridge + customSection + extrasSuffix;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
// "replace" mode — env header + the config's full system prompt
|
|
@@ -75,7 +81,7 @@ You have been invoked to handle a specific task autonomously.
|
|
|
75
81
|
|
|
76
82
|
${envBlock}`;
|
|
77
83
|
|
|
78
|
-
return replaceHeader + "\n\n" + config.systemPrompt + extrasSuffix;
|
|
84
|
+
return activeAgentTag + replaceHeader + "\n\n" + config.systemPrompt + extrasSuffix;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
/** Fallback base prompt when parent system prompt is unavailable in append mode. */
|