@johnnywu/pi-subagents 1.5.0 → 2.1.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 +18 -0
- package/README.md +39 -8
- package/extensions/agent-loader.ts +2 -2
- package/extensions/env-utils.ts +4 -3
- package/extensions/subagent-executor.ts +17 -1
- package/extensions/subagent-prompt.ts +1 -1
- package/extensions/subagent-render.ts +6 -1
- package/extensions/subagent-tool.ts +93 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
# [2.1.0](https://github.com/jwu/pi-subagents/compare/v2.0.0...v2.1.0) (2026-06-17)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* support forked subagent sessions ([50f27f5](https://github.com/jwu/pi-subagents/commit/50f27f5ea45f559f0125944f4f42c8c955cddd49))
|
|
7
|
+
|
|
8
|
+
# [2.0.0](https://github.com/jwu/pi-subagents/compare/v1.5.0...v2.0.0) (2026-06-16)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
* feat!: clarify systemPrompt replace semantics ([5cdb603](https://github.com/jwu/pi-subagents/commit/5cdb603e814b966f43fa0537dae5ac90799233b7))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
* systemPrompt: replace now preserves pi context files.
|
|
17
|
+
Use systemPrompt: replace-all for the previous isolated behavior.
|
|
18
|
+
|
|
1
19
|
# [1.5.0](https://github.com/jwu/pi-subagents/compare/v1.4.0...v1.5.0) (2026-06-03)
|
|
2
20
|
|
|
3
21
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Sub-agents extension for [pi](https://github.com/badlogic/pi-mono) coding agent.
|
|
4
4
|
|
|
5
|
-
Delegates tasks to isolated pi child processes — each running with its own model, system prompt, and tool set. Sub-agents
|
|
5
|
+
Delegates tasks to isolated pi child processes — each running with its own model, system prompt, and tool set. Sub-agents start with a fresh session by default; pass `session: "fork"` to branch from the current parent pi session.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -48,6 +48,23 @@ Or instruct pi to delegate:
|
|
|
48
48
|
Run the code-reviewer agent on the last three commits
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
By default, sub-agents do not inherit parent conversation history. For tasks that need the current conversation, request a forked session:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
subagent({ agent: "code-reviewer", task: "Review the approach we just discussed", session: "fork" })
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`session` is optional and accepts:
|
|
58
|
+
|
|
59
|
+
| Value | Behavior |
|
|
60
|
+
|-------|----------|
|
|
61
|
+
| `none` | Default. Start a new sub-agent session in the subagents session directory. |
|
|
62
|
+
| `fork` | Fork the current parent session at its active leaf and run the sub-agent with that branched session. |
|
|
63
|
+
|
|
64
|
+
If `fork` is requested but unavailable, pi-subagents falls back to `none` and shows a warning in the tool details/rendering. Fallback happens when the parent session is not persisted, has no current leaf, the forked session file is not materialized, or the call uses a `cwd` different from the parent session cwd.
|
|
65
|
+
|
|
66
|
+
The tool prompt also guides the model to choose `session: "fork"` when a delegated task depends on the current conversation, prior discussion, or parent session history, and to keep `session: "none"` for self-contained tasks.
|
|
67
|
+
|
|
51
68
|
### Available subagents in the prompt
|
|
52
69
|
|
|
53
70
|
The extension exposes discovered sub-agents to the model when the active tool set includes `subagent`:
|
|
@@ -63,10 +80,24 @@ Available subagents:
|
|
|
63
80
|
- test-writer
|
|
64
81
|
```
|
|
65
82
|
|
|
66
|
-
The prompt file passed to the child process contains only the agent prompt plus skills. Runtime prompt assembly then depends on `systemPrompt` mode
|
|
83
|
+
The prompt file passed to the child process contains only the agent prompt plus skills. Runtime prompt assembly then depends on `systemPrompt` mode.
|
|
84
|
+
|
|
85
|
+
### What goes into the sub-agent system prompt
|
|
86
|
+
|
|
87
|
+
| Component | `append` | `replace` | `replace-all` |
|
|
88
|
+
|------|----------|-----------|---------------|
|
|
89
|
+
| pi default system prompt | ✅ kept | ❌ replaced | ❌ replaced |
|
|
90
|
+
| Project context files (AGENTS.md/CLAUDE.md, etc.) | ✅ included | ✅ included | ❌ skipped |
|
|
91
|
+
| Agent body (.md file body) | ✅ appended | ✅ becomes the prompt | ✅ becomes the prompt |
|
|
92
|
+
| Skills XML block | ✅ appended | ✅ appended | ✅ appended |
|
|
93
|
+
| Available tools / Guidelines block | from default prompt | re-injected by `before_agent_start` hook | re-injected by `before_agent_start` hook |
|
|
94
|
+
| Available subagents block | injected at agent start | injected at agent start | injected at agent start |
|
|
95
|
+
|
|
96
|
+
`append` keeps pi's full default prompt (with project context) and adds the agent body at the end.
|
|
97
|
+
`replace` swaps out pi's default prompt for the agent body while keeping pi context files.
|
|
98
|
+
`replace-all` is the fully isolated mode: it swaps out pi's default prompt and skips pi context files, then the runtime hook re-injects tool and guideline blocks to preserve tool visibility.
|
|
67
99
|
|
|
68
|
-
|
|
69
|
-
- `replace`: the child process replaces pi's default prompt with the agent prompt and skips project context files; pi-subagents reinjects the active `Available tools` and `Guidelines` blocks at agent-start time so custom replace prompts still expose the selected tool affordances.
|
|
100
|
+
Breaking change: the old `replace` behavior is now `replace-all`. Existing agents that need to keep skipping AGENTS.md/CLAUDE.md should change `systemPrompt: replace` to `systemPrompt: replace-all`.
|
|
70
101
|
|
|
71
102
|
The `Available subagents` block is injected by the child process at agent-start time, after `PI_SUBAGENT_ALLOWED` and recursion depth filtering are applied.
|
|
72
103
|
|
|
@@ -81,7 +112,7 @@ debug: true
|
|
|
81
112
|
---
|
|
82
113
|
```
|
|
83
114
|
|
|
84
|
-
The child process writes `debug-system-prompt.md` in the project cwd. The file contains the prompt visible during `before_agent_start`, including `systemPrompt` append/replace behavior, tools/guidelines, skills, project context files in append
|
|
115
|
+
The child process writes `debug-system-prompt.md` in the project cwd. The file contains the prompt visible during `before_agent_start`, including `systemPrompt` append/replace/replace-all behavior, tools/guidelines, skills, project context files in append and replace modes, and pi-subagents' runtime `Available subagents` block when applicable.
|
|
85
116
|
|
|
86
117
|
## Agent configuration
|
|
87
118
|
|
|
@@ -94,7 +125,7 @@ Agents are Markdown files with YAML frontmatter.
|
|
|
94
125
|
| `tools` | no | _none_ | Comma-separated tool whitelist (`read, write, bash, grep`, etc.) |
|
|
95
126
|
| `model` | no | parent's model | Provider/model-id (`anthropic/claude-sonnet-4-6`) |
|
|
96
127
|
| `thinking` | no | `off` | Reasoning level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
|
|
97
|
-
| `systemPrompt` | no | `append` | How the body is applied: `append` (append to pi default system prompt and project context) or `replace` (replace default prompt and skip project context) |
|
|
128
|
+
| `systemPrompt` | no | `append` | How the body is applied: `append` (append to pi default system prompt and project context), `replace` (replace pi default prompt while keeping project context), or `replace-all` (replace pi default prompt and skip project context) |
|
|
98
129
|
| `skills` | no | _none_ | Comma-separated skill names or simple wildcard patterns (`*`, `obsidian-*`) to load (resolved from project `.agents/skills/`, `.pi/skills/`, global `~/.pi/agent/skills/`, or npm packages) |
|
|
99
130
|
| `allowedAgents` | no | _all_ | Comma-separated list of sub-agents this agent may spawn |
|
|
100
131
|
| `maxDepth` | no | `10` | Maximum recursion depth (`0` = no sub-agents, `1` = one level, etc.) |
|
|
@@ -145,7 +176,7 @@ Sub-agents can spawn their own sub-agents (if the `subagent` tool is in their wh
|
|
|
145
176
|
|
|
146
177
|
The available-subagents prompt entries respect the same filtering: parent sessions use the currently visible agents, and child sessions only list agents allowed by their parent.
|
|
147
178
|
|
|
148
|
-
These are passed via environment variables (`PI_SUBAGENT_DEPTH`, `PI_SUBAGENT_MAX_DEPTH`, `PI_SUBAGENT_ALLOWED`). Child processes also receive `PI_SUBAGENT_NAME` and `
|
|
179
|
+
These are passed via environment variables (`PI_SUBAGENT_DEPTH`, `PI_SUBAGENT_MAX_DEPTH`, `PI_SUBAGENT_ALLOWED`). Child processes also receive `PI_SUBAGENT_NAME`, `PI_SUBAGENT_SYSTEM_PROMPT_MODE`, and `PI_SUBAGENT_SESSION` so runtime hooks can distinguish prompt mode and effective session mode.
|
|
149
180
|
|
|
150
181
|
## Session storage
|
|
151
182
|
|
|
@@ -157,7 +188,7 @@ Sub-agent sessions are saved as `.jsonl` files for post-hoc debugging:
|
|
|
157
188
|
└── ...
|
|
158
189
|
```
|
|
159
190
|
|
|
160
|
-
Each file contains one JSON object per line — session headers, messages, tool calls, and usage data. Parent pi sessions live in the same project directory (no `subagents/` subdirectory).
|
|
191
|
+
Each file contains one JSON object per line — session headers, messages, tool calls, and usage data. Parent pi sessions live in the same project directory (no `subagents/` subdirectory). When `session: "fork"` succeeds, the branched child session is still written under `subagents/` and points back to the parent session in its header.
|
|
161
192
|
|
|
162
193
|
## Development
|
|
163
194
|
|
|
@@ -3,7 +3,7 @@ import * as os from 'node:os';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
|
|
5
5
|
export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
6
|
-
export type SystemPromptMode = 'replace' | 'append';
|
|
6
|
+
export type SystemPromptMode = 'replace' | 'replace-all' | 'append';
|
|
7
7
|
export type AgentSource = 'global' | 'project';
|
|
8
8
|
|
|
9
9
|
export interface AgentConfig {
|
|
@@ -109,7 +109,7 @@ function parseAgentFile(content: string, filePath: string, source: AgentSource):
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
const systemPromptMode = (data.systemPrompt ?? 'append') as SystemPromptMode;
|
|
112
|
-
if (!['replace', 'append'].includes(systemPromptMode)) {
|
|
112
|
+
if (!['replace', 'replace-all', 'append'].includes(systemPromptMode)) {
|
|
113
113
|
throw new Error(`invalid systemPrompt: ${data.systemPrompt}`);
|
|
114
114
|
}
|
|
115
115
|
|
package/extensions/env-utils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SystemPromptModeEnv = 'replace' | 'append';
|
|
1
|
+
export type SystemPromptModeEnv = 'replace' | 'replace-all' | 'append';
|
|
2
2
|
|
|
3
3
|
export type RecursionEnv = Partial<
|
|
4
4
|
Record<
|
|
@@ -41,10 +41,11 @@ export function isSubagentProcess(env: RecursionEnv): boolean {
|
|
|
41
41
|
|
|
42
42
|
export function subagentSystemPromptMode(env: RecursionEnv): SystemPromptModeEnv | undefined {
|
|
43
43
|
const mode = env?.PI_SUBAGENT_SYSTEM_PROMPT_MODE;
|
|
44
|
-
if (mode === 'replace' || mode === 'append') return mode;
|
|
44
|
+
if (mode === 'replace' || mode === 'replace-all' || mode === 'append') return mode;
|
|
45
45
|
return undefined;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export function isSubagentReplaceSystemPrompt(env: RecursionEnv): boolean {
|
|
49
|
-
|
|
49
|
+
const mode = subagentSystemPromptMode(env);
|
|
50
|
+
return isSubagentProcess(env) && (mode === 'replace' || mode === 'replace-all');
|
|
50
51
|
}
|
|
@@ -25,6 +25,15 @@ export interface AgentToolLog {
|
|
|
25
25
|
nested?: AgentProgress;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export type SubagentSessionMode = 'none' | 'fork';
|
|
29
|
+
|
|
30
|
+
export interface SubagentSessionInfo {
|
|
31
|
+
requested: SubagentSessionMode;
|
|
32
|
+
effective: SubagentSessionMode;
|
|
33
|
+
warning?: string;
|
|
34
|
+
file?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
export interface AgentProgress {
|
|
29
38
|
agent: string;
|
|
30
39
|
status: 'running' | 'done' | 'error';
|
|
@@ -34,6 +43,7 @@ export interface AgentProgress {
|
|
|
34
43
|
startedAt: number;
|
|
35
44
|
elapsedMs: number;
|
|
36
45
|
model?: string;
|
|
46
|
+
session?: SubagentSessionInfo;
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
export interface AgentResult extends AgentProgress {
|
|
@@ -82,6 +92,7 @@ export interface RunSubagentOptions {
|
|
|
82
92
|
tempRoot?: string;
|
|
83
93
|
outputArchiveDir?: string;
|
|
84
94
|
agentDir?: string;
|
|
95
|
+
session?: SubagentSessionInfo;
|
|
85
96
|
resolvePi?: () => Promise<PiResolution> | PiResolution;
|
|
86
97
|
runner?: ProcessRunner;
|
|
87
98
|
fs?: ExecutorFs;
|
|
@@ -424,6 +435,7 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
424
435
|
startedAt,
|
|
425
436
|
elapsedMs: now() - startedAt,
|
|
426
437
|
model,
|
|
438
|
+
session: options.session ?? { requested: 'none', effective: 'none' },
|
|
427
439
|
});
|
|
428
440
|
|
|
429
441
|
const emit = (status: AgentProgress['status'] = 'running') =>
|
|
@@ -461,7 +473,7 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
461
473
|
);
|
|
462
474
|
const args = [pi.entryPoint, '--mode', 'json', '-p', '--no-skills', '--no-prompt-templates'];
|
|
463
475
|
|
|
464
|
-
if (options.agent.systemPromptMode === 'replace') args.push('--no-context-files');
|
|
476
|
+
if (options.agent.systemPromptMode === 'replace-all') args.push('--no-context-files');
|
|
465
477
|
if (options.agent.model) args.push('--model', options.agent.model);
|
|
466
478
|
args.push('--thinking', options.agent.thinking);
|
|
467
479
|
if (options.agent.tools.length > 0) args.push('--tools', options.agent.tools.join(','));
|
|
@@ -470,6 +482,9 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
470
482
|
promptFilePath,
|
|
471
483
|
);
|
|
472
484
|
args.push('--session-dir', subagentSessionDir(options.cwd, options.agentDir));
|
|
485
|
+
if (options.session?.effective === 'fork' && options.session.file) {
|
|
486
|
+
args.push('--session', options.session.file);
|
|
487
|
+
}
|
|
473
488
|
args.push(buildTaskArgument(options.task, taskFilePath));
|
|
474
489
|
|
|
475
490
|
const env: NodeJS.ProcessEnv = {
|
|
@@ -478,6 +493,7 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
478
493
|
PI_SUBAGENT_MAX_DEPTH: String(options.agent.maxDepth),
|
|
479
494
|
PI_SUBAGENT_NAME: options.agent.name,
|
|
480
495
|
PI_SUBAGENT_SYSTEM_PROMPT_MODE: options.agent.systemPromptMode,
|
|
496
|
+
PI_SUBAGENT_SESSION: options.session?.effective ?? 'none',
|
|
481
497
|
};
|
|
482
498
|
const visibleAgents = availableSubagentsForAgent(options.agent, options.availableAgents);
|
|
483
499
|
if (visibleAgents.length > 0) {
|
|
@@ -65,7 +65,7 @@ export function appendAvailableToolsAndGuidelinesBlock(
|
|
|
65
65
|
options: ToolGuidelinePromptOptions,
|
|
66
66
|
): string {
|
|
67
67
|
const block = formatAvailableToolsAndGuidelinesBlock(options);
|
|
68
|
-
if (!block || systemPrompt.includes(
|
|
68
|
+
if (!block || systemPrompt.includes(block)) {
|
|
69
69
|
return systemPrompt;
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -4,6 +4,7 @@ import { numberArg, preview, shortenPath, stringArg } from './tool-args.ts';
|
|
|
4
4
|
export interface SubagentCallArgs {
|
|
5
5
|
agent?: string;
|
|
6
6
|
task?: string;
|
|
7
|
+
session?: 'none' | 'fork';
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export interface RenderTextOptions {
|
|
@@ -195,7 +196,8 @@ export function formatSubagentResultLines(
|
|
|
195
196
|
options: RenderTextOptions,
|
|
196
197
|
): SubagentResultLine[] {
|
|
197
198
|
const icon = progress.status === 'error' ? '✗' : progress.status === 'done' ? '✓' : '▸';
|
|
198
|
-
const
|
|
199
|
+
const sessionBadge = progress.session?.effective === 'fork' ? ' [fork]' : '';
|
|
200
|
+
const statusLine = `${icon} ${progress.agent}${sessionBadge}${progress.model ? ` (${progress.model})` : ''} — ${
|
|
199
201
|
progress.tools.length
|
|
200
202
|
} tools · ${elapsedSeconds(progress.elapsedMs)}s`;
|
|
201
203
|
const toolLines = formatToolLineItems(progress, options);
|
|
@@ -203,6 +205,9 @@ export function formatSubagentResultLines(
|
|
|
203
205
|
const lines: SubagentResultLine[] = [
|
|
204
206
|
{ text: '', kind: 'blank', singleLine: false },
|
|
205
207
|
{ text: statusLine, kind: 'status', singleLine: false },
|
|
208
|
+
...(progress.session?.warning
|
|
209
|
+
? [{ text: `session: ${progress.session.warning}`, kind: 'hint' as const, singleLine: true }]
|
|
210
|
+
: []),
|
|
206
211
|
...toolLines,
|
|
207
212
|
];
|
|
208
213
|
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getMarkdownTheme,
|
|
3
3
|
keyHint,
|
|
4
|
+
SessionManager,
|
|
4
5
|
type AgentToolUpdateCallback,
|
|
5
6
|
type ExtensionAPI,
|
|
6
7
|
type ExtensionContext,
|
|
7
8
|
type ThemeColor,
|
|
8
9
|
} from '@earendil-works/pi-coding-agent';
|
|
9
10
|
import { Container, Markdown, Spacer, Text } from '@earendil-works/pi-tui';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
10
13
|
import type { AgentConfig } from './agent-loader.ts';
|
|
11
14
|
import {
|
|
12
15
|
availableSubagentsForAgent,
|
|
13
16
|
type AgentProgress,
|
|
14
17
|
type AgentResult,
|
|
15
18
|
runSubagent,
|
|
19
|
+
subagentSessionDir,
|
|
20
|
+
type SubagentSessionInfo,
|
|
21
|
+
type SubagentSessionMode,
|
|
16
22
|
} from './subagent-executor.ts';
|
|
17
23
|
import {
|
|
18
24
|
contextUsageSeverity,
|
|
@@ -30,6 +36,12 @@ const SubagentParams = {
|
|
|
30
36
|
agent: { type: 'string', description: 'Name of the agent to invoke' },
|
|
31
37
|
task: { type: 'string', description: 'Task to delegate to the agent' },
|
|
32
38
|
cwd: { type: 'string', description: 'Working directory for the agent process' },
|
|
39
|
+
session: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
enum: ['none', 'fork'],
|
|
42
|
+
description:
|
|
43
|
+
'Parent session handling: none starts a fresh subagent session, fork branches from the current session',
|
|
44
|
+
},
|
|
33
45
|
},
|
|
34
46
|
required: ['agent', 'task'],
|
|
35
47
|
additionalProperties: false,
|
|
@@ -39,6 +51,7 @@ type SubagentParamsType = {
|
|
|
39
51
|
agent: string;
|
|
40
52
|
task: string;
|
|
41
53
|
cwd?: string;
|
|
54
|
+
session?: SubagentSessionMode;
|
|
42
55
|
};
|
|
43
56
|
|
|
44
57
|
type RegisterablePi = Pick<ExtensionAPI, 'registerTool'>;
|
|
@@ -46,6 +59,7 @@ export interface RegisterSubagentToolOptions {
|
|
|
46
59
|
agents: AgentConfig[];
|
|
47
60
|
run?: typeof runSubagent;
|
|
48
61
|
env?: RecursionEnv;
|
|
62
|
+
agentDir?: string;
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
function availableAgentsText(agents: AgentConfig[]): string {
|
|
@@ -72,6 +86,76 @@ function toProgressResult(progress: AgentProgress) {
|
|
|
72
86
|
};
|
|
73
87
|
}
|
|
74
88
|
|
|
89
|
+
function freshSessionInfo(requested: SubagentSessionMode, warning?: string): SubagentSessionInfo {
|
|
90
|
+
return {
|
|
91
|
+
requested,
|
|
92
|
+
effective: 'none',
|
|
93
|
+
...(warning ? { warning } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveSubagentSession(
|
|
98
|
+
requested: SubagentSessionMode | undefined,
|
|
99
|
+
childCwd: string,
|
|
100
|
+
ctx: ExtensionContext,
|
|
101
|
+
agentDir?: string,
|
|
102
|
+
): SubagentSessionInfo {
|
|
103
|
+
const mode = requested ?? 'none';
|
|
104
|
+
if (mode === 'none') return freshSessionInfo(mode);
|
|
105
|
+
|
|
106
|
+
if (path.resolve(childCwd) !== path.resolve(ctx.cwd)) {
|
|
107
|
+
return freshSessionInfo(
|
|
108
|
+
mode,
|
|
109
|
+
`Requested fork session but cwd differs from the parent session; running with a fresh subagent session instead.`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const parentSessionFile = ctx.sessionManager?.getSessionFile();
|
|
114
|
+
if (!parentSessionFile) {
|
|
115
|
+
return freshSessionInfo(
|
|
116
|
+
mode,
|
|
117
|
+
'Requested fork session but the parent session is not persisted; running with a fresh subagent session instead.',
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (!existsSync(parentSessionFile)) {
|
|
121
|
+
return freshSessionInfo(
|
|
122
|
+
mode,
|
|
123
|
+
'Requested fork session but the parent session file was not materialized; running with a fresh subagent session instead.',
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const parentLeafId = ctx.sessionManager?.getLeafId();
|
|
128
|
+
if (!parentLeafId) {
|
|
129
|
+
return freshSessionInfo(
|
|
130
|
+
mode,
|
|
131
|
+
'Requested fork session but the parent session has no current leaf; running with a fresh subagent session instead.',
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const forkSource = SessionManager.open(
|
|
137
|
+
parentSessionFile,
|
|
138
|
+
subagentSessionDir(childCwd, agentDir),
|
|
139
|
+
childCwd,
|
|
140
|
+
);
|
|
141
|
+
const sessionFile = forkSource.createBranchedSession(parentLeafId);
|
|
142
|
+
if (!sessionFile || !existsSync(sessionFile)) {
|
|
143
|
+
return freshSessionInfo(
|
|
144
|
+
mode,
|
|
145
|
+
'Requested fork session but the forked session file was not materialized; running with a fresh subagent session instead.',
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { requested: mode, effective: 'fork', file: sessionFile };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
152
|
+
return freshSessionInfo(
|
|
153
|
+
mode,
|
|
154
|
+
`Requested fork session but creating the fork failed (${message}); running with a fresh subagent session instead.`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
75
159
|
type CollapsedTheme = {
|
|
76
160
|
fg: (name: ThemeColor, text: string) => string;
|
|
77
161
|
bold: (text: string) => string;
|
|
@@ -224,14 +308,16 @@ export function registerSubagentTool(
|
|
|
224
308
|
|
|
225
309
|
const availableSubagents = agents.map((agent) => agent.name);
|
|
226
310
|
const agentNames = [...availableSubagents].sort().join(', ');
|
|
311
|
+
const sessionGuideline =
|
|
312
|
+
'Use session: "fork" when the delegated task depends on the current conversation, prior discussion, or parent session history. Use the default session: "none" for self-contained tasks.';
|
|
227
313
|
const promptGuidelines =
|
|
228
|
-
agentNames.length > 0 ? [`Available subagents: ${agentNames}
|
|
314
|
+
agentNames.length > 0 ? [`Available subagents: ${agentNames}`, sessionGuideline] : undefined;
|
|
229
315
|
|
|
230
316
|
pi.registerTool({
|
|
231
317
|
name: 'subagent',
|
|
232
318
|
label: 'Subagent',
|
|
233
319
|
description: 'Delegate a task to a named sub-agent running in an isolated pi process.',
|
|
234
|
-
promptSnippet: 'Delegate isolated tasks with subagent({ agent, task, cwd? }).',
|
|
320
|
+
promptSnippet: 'Delegate isolated tasks with subagent({ agent, task, cwd?, session? }).',
|
|
235
321
|
promptGuidelines,
|
|
236
322
|
parameters: SubagentParams,
|
|
237
323
|
|
|
@@ -249,13 +335,17 @@ export function registerSubagentTool(
|
|
|
249
335
|
);
|
|
250
336
|
}
|
|
251
337
|
|
|
338
|
+
const childCwd = params.cwd ?? ctx.cwd;
|
|
339
|
+
const session = resolveSubagentSession(params.session, childCwd, ctx, options.agentDir);
|
|
252
340
|
const result = await runner({
|
|
253
341
|
agent,
|
|
254
342
|
task: params.task,
|
|
255
|
-
cwd:
|
|
343
|
+
cwd: childCwd,
|
|
256
344
|
signal,
|
|
257
345
|
depth: Number(env.PI_SUBAGENT_DEPTH ?? '0') + 1,
|
|
258
346
|
availableAgents: availableSubagentsForAgent(agent, availableSubagents),
|
|
347
|
+
agentDir: options.agentDir,
|
|
348
|
+
session,
|
|
259
349
|
onProgress: (progress) => onUpdate?.(toProgressResult(progress)),
|
|
260
350
|
});
|
|
261
351
|
|