pi-subagents 0.12.2 → 0.12.4
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 +16 -0
- package/README.md +66 -6
- package/agent-management.ts +12 -5
- package/agent-manager-chain-detail.ts +2 -2
- package/agent-manager-detail.ts +9 -7
- package/agent-manager-edit.ts +4 -4
- package/agent-manager-list.ts +2 -2
- package/agent-manager-parallel.ts +3 -3
- package/agent-manager.ts +28 -14
- package/agent-scope.ts +1 -1
- package/agent-selection.ts +1 -1
- package/agent-serializer.ts +5 -1
- package/agent-templates.ts +1 -1
- package/agents.ts +31 -9
- package/artifacts.ts +1 -1
- package/async-execution.ts +28 -9
- package/chain-clarify.ts +39 -17
- package/chain-execution.ts +37 -12
- package/chain-serializer.ts +2 -2
- package/execution.ts +20 -11
- package/formatters.ts +3 -3
- package/index.ts +16 -16
- package/notify.ts +1 -1
- package/package.json +8 -1
- package/parallel-utils.ts +1 -0
- package/pi-spawn.ts +6 -9
- package/render.ts +7 -7
- package/schemas.ts +1 -1
- package/settings.ts +2 -2
- package/single-output.ts +50 -10
- package/skills.ts +5 -2
- package/subagent-executor.ts +55 -7
- package/subagent-runner.ts +32 -22
- package/types.ts +31 -5
- package/utils.ts +5 -1
- package/worktree.ts +240 -17
package/chain-clarify.ts
CHANGED
|
@@ -11,12 +11,12 @@ import { matchesKey, visibleWidth, truncateToWidth } from "@mariozechner/pi-tui"
|
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
12
|
import * as os from "node:os";
|
|
13
13
|
import * as path from "node:path";
|
|
14
|
-
import type { AgentConfig, ChainConfig, ChainStepConfig } from "./agents.
|
|
15
|
-
import type { ResolvedStepBehavior } from "./settings.
|
|
16
|
-
import type { TextEditorState } from "./text-editor.
|
|
17
|
-
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.
|
|
18
|
-
import { updateFrontmatterField } from "./agent-serializer.
|
|
19
|
-
import { serializeChain } from "./chain-serializer.
|
|
14
|
+
import type { AgentConfig, ChainConfig, ChainStepConfig } from "./agents.ts";
|
|
15
|
+
import type { ResolvedStepBehavior } from "./settings.ts";
|
|
16
|
+
import type { TextEditorState } from "./text-editor.ts";
|
|
17
|
+
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
|
|
18
|
+
import { updateFrontmatterField } from "./agent-serializer.ts";
|
|
19
|
+
import { serializeChain } from "./chain-serializer.ts";
|
|
20
20
|
|
|
21
21
|
/** Clarify TUI mode */
|
|
22
22
|
export type ClarifyMode = 'single' | 'parallel' | 'chain';
|
|
@@ -92,20 +92,42 @@ export class ChainClarifyComponent implements Component {
|
|
|
92
92
|
private savingChain = false;
|
|
93
93
|
/** Run in background (async) mode */
|
|
94
94
|
private runInBackground = false;
|
|
95
|
+
private tui: TUI;
|
|
96
|
+
private theme: Theme;
|
|
97
|
+
private agentConfigs: AgentConfig[];
|
|
98
|
+
private templates: string[];
|
|
99
|
+
private originalTask: string;
|
|
100
|
+
private chainDir: string | undefined;
|
|
101
|
+
private resolvedBehaviors: ResolvedStepBehavior[];
|
|
102
|
+
private availableModels: ModelInfo[];
|
|
103
|
+
private availableSkills: Array<{ name: string; source: string; description?: string }>;
|
|
104
|
+
private done: (result: ChainClarifyResult) => void;
|
|
105
|
+
private mode: ClarifyMode;
|
|
95
106
|
|
|
96
107
|
constructor(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
tui: TUI,
|
|
109
|
+
theme: Theme,
|
|
110
|
+
agentConfigs: AgentConfig[],
|
|
111
|
+
templates: string[],
|
|
112
|
+
originalTask: string,
|
|
113
|
+
chainDir: string | undefined, // undefined for single/parallel modes
|
|
114
|
+
resolvedBehaviors: ResolvedStepBehavior[],
|
|
115
|
+
availableModels: ModelInfo[],
|
|
116
|
+
availableSkills: Array<{ name: string; source: string; description?: string }>,
|
|
117
|
+
done: (result: ChainClarifyResult) => void,
|
|
118
|
+
mode: ClarifyMode = 'chain', // Mode: 'single', 'parallel', or 'chain'
|
|
108
119
|
) {
|
|
120
|
+
this.tui = tui;
|
|
121
|
+
this.theme = theme;
|
|
122
|
+
this.agentConfigs = agentConfigs;
|
|
123
|
+
this.templates = templates;
|
|
124
|
+
this.originalTask = originalTask;
|
|
125
|
+
this.chainDir = chainDir;
|
|
126
|
+
this.resolvedBehaviors = resolvedBehaviors;
|
|
127
|
+
this.availableModels = availableModels;
|
|
128
|
+
this.availableSkills = availableSkills;
|
|
129
|
+
this.done = done;
|
|
130
|
+
this.mode = mode;
|
|
109
131
|
// Initialize filtered models
|
|
110
132
|
this.filteredModels = [...availableModels];
|
|
111
133
|
this.filteredSkills = [...availableSkills];
|
package/chain-execution.ts
CHANGED
|
@@ -6,8 +6,8 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
8
8
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
9
|
-
import type { AgentConfig } from "./agents.
|
|
10
|
-
import { ChainClarifyComponent, type ChainClarifyResult, type BehaviorOverride, type ModelInfo } from "./chain-clarify.
|
|
9
|
+
import type { AgentConfig } from "./agents.ts";
|
|
10
|
+
import { ChainClarifyComponent, type ChainClarifyResult, type BehaviorOverride, type ModelInfo } from "./chain-clarify.ts";
|
|
11
11
|
import {
|
|
12
12
|
resolveChainTemplates,
|
|
13
13
|
createChainDir,
|
|
@@ -24,12 +24,12 @@ import {
|
|
|
24
24
|
type ParallelTaskResult,
|
|
25
25
|
type ResolvedStepBehavior,
|
|
26
26
|
type ResolvedTemplates,
|
|
27
|
-
} from "./settings.
|
|
28
|
-
import { discoverAvailableSkills, normalizeSkillInput } from "./skills.
|
|
29
|
-
import { runSync } from "./execution.
|
|
30
|
-
import { buildChainSummary } from "./formatters.
|
|
31
|
-
import {
|
|
32
|
-
import { recordRun } from "./run-history.
|
|
27
|
+
} from "./settings.ts";
|
|
28
|
+
import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
|
|
29
|
+
import { runSync } from "./execution.ts";
|
|
30
|
+
import { buildChainSummary } from "./formatters.ts";
|
|
31
|
+
import { getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
32
|
+
import { recordRun } from "./run-history.ts";
|
|
33
33
|
import {
|
|
34
34
|
cleanupWorktrees,
|
|
35
35
|
createWorktrees,
|
|
@@ -46,7 +46,8 @@ import {
|
|
|
46
46
|
type Details,
|
|
47
47
|
type SingleResult,
|
|
48
48
|
MAX_CONCURRENCY,
|
|
49
|
-
|
|
49
|
+
resolveChildMaxSubagentDepth,
|
|
50
|
+
} from "./types.ts";
|
|
50
51
|
|
|
51
52
|
/** Resolve a model name to its full provider/model format */
|
|
52
53
|
function resolveModelFullId(modelName: string | undefined, availableModels: ModelInfo[]): string | undefined {
|
|
@@ -102,6 +103,7 @@ interface ParallelChainRunInput {
|
|
|
102
103
|
chainAgents: string[];
|
|
103
104
|
totalSteps: number;
|
|
104
105
|
worktreeSetup?: WorktreeSetup;
|
|
106
|
+
maxSubagentDepth: number;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details {
|
|
@@ -191,11 +193,16 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
191
193
|
const effectiveModel =
|
|
192
194
|
(task.model ? resolveModelFullId(task.model, input.availableModels) : null)
|
|
193
195
|
?? resolveModelFullId(taskAgentConfig?.model, input.availableModels);
|
|
196
|
+
const maxSubagentDepth = resolveChildMaxSubagentDepth(input.maxSubagentDepth, taskAgentConfig?.maxSubagentDepth);
|
|
194
197
|
|
|
195
198
|
const taskCwd = input.worktreeSetup
|
|
196
199
|
? input.worktreeSetup.worktrees[taskIndex]!.agentCwd
|
|
197
200
|
: (task.cwd ?? input.cwd);
|
|
198
201
|
|
|
202
|
+
const outputPath = typeof behavior.output === "string"
|
|
203
|
+
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(input.chainDir, behavior.output))
|
|
204
|
+
: undefined;
|
|
205
|
+
|
|
199
206
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskStr, {
|
|
200
207
|
cwd: taskCwd,
|
|
201
208
|
signal: input.signal,
|
|
@@ -206,6 +213,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
206
213
|
share: input.shareEnabled,
|
|
207
214
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
208
215
|
artifactConfig: input.artifactConfig,
|
|
216
|
+
outputPath,
|
|
217
|
+
maxSubagentDepth,
|
|
209
218
|
modelOverride: effectiveModel,
|
|
210
219
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
211
220
|
onUpdate: input.onUpdate
|
|
@@ -256,6 +265,9 @@ export interface ChainExecutionParams {
|
|
|
256
265
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
257
266
|
chainSkills?: string[];
|
|
258
267
|
chainDir?: string;
|
|
268
|
+
maxSubagentDepth: number;
|
|
269
|
+
worktreeSetupHook?: string;
|
|
270
|
+
worktreeSetupHookTimeoutMs?: number;
|
|
259
271
|
}
|
|
260
272
|
|
|
261
273
|
export interface ChainExecutionResult {
|
|
@@ -458,7 +470,12 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
458
470
|
);
|
|
459
471
|
}
|
|
460
472
|
try {
|
|
461
|
-
worktreeSetup = createWorktrees(parallelCwd, `${runId}-s${stepIndex}`, step.parallel.length
|
|
473
|
+
worktreeSetup = createWorktrees(parallelCwd, `${runId}-s${stepIndex}`, step.parallel.length, {
|
|
474
|
+
agents: step.parallel.map((task) => task.agent),
|
|
475
|
+
setupHook: params.worktreeSetupHook
|
|
476
|
+
? { hookPath: params.worktreeSetupHook, timeoutMs: params.worktreeSetupHookTimeoutMs }
|
|
477
|
+
: undefined,
|
|
478
|
+
});
|
|
462
479
|
} catch (error) {
|
|
463
480
|
const message = error instanceof Error ? error.message : String(error);
|
|
464
481
|
return buildChainExecutionErrorResult(message, {
|
|
@@ -506,6 +523,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
506
523
|
chainAgents,
|
|
507
524
|
totalSteps,
|
|
508
525
|
worktreeSetup,
|
|
526
|
+
maxSubagentDepth: params.maxSubagentDepth,
|
|
509
527
|
});
|
|
510
528
|
globalTaskIndex += step.parallel.length;
|
|
511
529
|
|
|
@@ -551,7 +569,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
551
569
|
return {
|
|
552
570
|
agent: result.agent,
|
|
553
571
|
taskIndex: i,
|
|
554
|
-
output:
|
|
572
|
+
output: getSingleResultOutput(result),
|
|
555
573
|
exitCode: result.exitCode,
|
|
556
574
|
error: result.error,
|
|
557
575
|
outputTargetPath,
|
|
@@ -629,6 +647,11 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
629
647
|
?? resolveModelFullId(agentConfig.model, availableModels);
|
|
630
648
|
|
|
631
649
|
// Run step
|
|
650
|
+
const outputPath = typeof behavior.output === "string"
|
|
651
|
+
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
652
|
+
: undefined;
|
|
653
|
+
const maxSubagentDepth = resolveChildMaxSubagentDepth(params.maxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
654
|
+
|
|
632
655
|
const r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
|
|
633
656
|
cwd: seqStep.cwd ?? cwd,
|
|
634
657
|
signal,
|
|
@@ -639,6 +662,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
639
662
|
share: shareEnabled,
|
|
640
663
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
641
664
|
artifactConfig,
|
|
665
|
+
outputPath,
|
|
666
|
+
maxSubagentDepth,
|
|
642
667
|
modelOverride: effectiveModel,
|
|
643
668
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
644
669
|
onUpdate: onUpdate
|
|
@@ -709,7 +734,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
709
734
|
};
|
|
710
735
|
}
|
|
711
736
|
|
|
712
|
-
prev =
|
|
737
|
+
prev = getSingleResultOutput(r);
|
|
713
738
|
}
|
|
714
739
|
}
|
|
715
740
|
|
package/chain-serializer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ChainConfig, ChainStepConfig } from "./agents.
|
|
2
|
-
import { parseFrontmatter } from "./frontmatter.
|
|
1
|
+
import type { ChainConfig, ChainStepConfig } from "./agents.ts";
|
|
2
|
+
import { parseFrontmatter } from "./frontmatter.ts";
|
|
3
3
|
|
|
4
4
|
function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
|
|
5
5
|
const lines = sectionBody.split("\n");
|
package/execution.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import type { Message } from "@mariozechner/pi-ai";
|
|
7
|
-
import type { AgentConfig } from "./agents.
|
|
7
|
+
import type { AgentConfig } from "./agents.ts";
|
|
8
8
|
import {
|
|
9
9
|
ensureArtifactsDir,
|
|
10
10
|
getArtifactPaths,
|
|
11
11
|
writeArtifact,
|
|
12
12
|
writeMetadata,
|
|
13
|
-
} from "./artifacts.
|
|
13
|
+
} from "./artifacts.ts";
|
|
14
14
|
import {
|
|
15
15
|
type AgentProgress,
|
|
16
16
|
type ArtifactPaths,
|
|
@@ -19,18 +19,19 @@ import {
|
|
|
19
19
|
DEFAULT_MAX_OUTPUT,
|
|
20
20
|
truncateOutput,
|
|
21
21
|
getSubagentDepthEnv,
|
|
22
|
-
} from "./types.
|
|
22
|
+
} from "./types.ts";
|
|
23
23
|
import {
|
|
24
24
|
getFinalOutput,
|
|
25
25
|
findLatestSessionFile,
|
|
26
26
|
detectSubagentError,
|
|
27
27
|
extractToolArgsPreview,
|
|
28
28
|
extractTextFromContent,
|
|
29
|
-
} from "./utils.
|
|
30
|
-
import { buildSkillInjection, resolveSkills } from "./skills.
|
|
31
|
-
import { getPiSpawnCommand } from "./pi-spawn.
|
|
32
|
-
import { createJsonlWriter } from "./jsonl-writer.
|
|
33
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "./pi-args.
|
|
29
|
+
} from "./utils.ts";
|
|
30
|
+
import { buildSkillInjection, resolveSkills } from "./skills.ts";
|
|
31
|
+
import { getPiSpawnCommand } from "./pi-spawn.ts";
|
|
32
|
+
import { createJsonlWriter } from "./jsonl-writer.ts";
|
|
33
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "./pi-args.ts";
|
|
34
|
+
import { captureSingleOutputSnapshot, resolveSingleOutput } from "./single-output.ts";
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Run a subagent synchronously (blocking until complete)
|
|
@@ -59,6 +60,7 @@ export async function runSync(
|
|
|
59
60
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
60
61
|
const effectiveModel = modelOverride ?? agent.model;
|
|
61
62
|
const modelArg = applyThinkingSuffix(effectiveModel, agent.thinking);
|
|
63
|
+
const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
|
|
62
64
|
|
|
63
65
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
64
66
|
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkills(skillNames, runtimeCwd);
|
|
@@ -125,7 +127,7 @@ export async function runSync(
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv() };
|
|
130
|
+
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
129
131
|
|
|
130
132
|
let closeJsonlWriter: (() => Promise<void>) | undefined;
|
|
131
133
|
const exitCode = await new Promise<number>((resolve) => {
|
|
@@ -299,9 +301,17 @@ export async function runSync(
|
|
|
299
301
|
durationMs: progress.durationMs,
|
|
300
302
|
};
|
|
301
303
|
|
|
304
|
+
let fullOutput = getFinalOutput(result.messages);
|
|
305
|
+
if (options.outputPath && result.exitCode === 0) {
|
|
306
|
+
const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, outputSnapshot);
|
|
307
|
+
fullOutput = resolvedOutput.fullOutput;
|
|
308
|
+
result.savedOutputPath = resolvedOutput.savedPath;
|
|
309
|
+
result.outputSaveError = resolvedOutput.saveError;
|
|
310
|
+
}
|
|
311
|
+
result.finalOutput = fullOutput;
|
|
312
|
+
|
|
302
313
|
if (artifactPathsResult && artifactConfig?.enabled !== false) {
|
|
303
314
|
result.artifactPaths = artifactPathsResult;
|
|
304
|
-
const fullOutput = getFinalOutput(result.messages);
|
|
305
315
|
|
|
306
316
|
if (artifactConfig?.includeOutput !== false) {
|
|
307
317
|
writeArtifact(artifactPathsResult.outputPath, fullOutput);
|
|
@@ -332,7 +342,6 @@ export async function runSync(
|
|
|
332
342
|
}
|
|
333
343
|
} else if (maxOutput) {
|
|
334
344
|
const config = { ...DEFAULT_MAX_OUTPUT, ...maxOutput };
|
|
335
|
-
const fullOutput = getFinalOutput(result.messages);
|
|
336
345
|
const truncationResult = truncateOutput(fullOutput, config);
|
|
337
346
|
if (truncationResult.truncated) {
|
|
338
347
|
result.truncation = truncationResult;
|
package/formatters.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
-
import type { Usage, SingleResult } from "./types.
|
|
8
|
-
import type { ChainStep, SequentialStep } from "./settings.
|
|
9
|
-
import { isParallelStep } from "./settings.
|
|
7
|
+
import type { Usage, SingleResult } from "./types.ts";
|
|
8
|
+
import type { ChainStep, SequentialStep } from "./settings.ts";
|
|
9
|
+
import { isParallelStep } from "./settings.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Format token count with k suffix for large numbers
|
package/index.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Toggle: async parameter (default: false, configurable via config.json)
|
|
10
10
|
*
|
|
11
11
|
* Config file: ~/.pi/agent/extensions/subagent/config.json
|
|
12
|
-
* { "asyncByDefault": true }
|
|
12
|
+
* { "asyncByDefault": true, "maxSubagentDepth": 1, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import * as fs from "node:fs";
|
|
@@ -18,20 +18,20 @@ import * as path from "node:path";
|
|
|
18
18
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
19
19
|
import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
20
20
|
import { Box, Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
21
|
-
import { discoverAgents } from "./agents.
|
|
22
|
-
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "./artifacts.
|
|
23
|
-
import { cleanupOldChainDirs } from "./settings.
|
|
24
|
-
import { renderWidget, renderSubagentResult } from "./render.
|
|
25
|
-
import { SubagentParams, StatusParams } from "./schemas.
|
|
26
|
-
import { findByPrefix, readStatus } from "./utils.
|
|
27
|
-
import { createSubagentExecutor } from "./subagent-executor.
|
|
28
|
-
import { createAsyncJobTracker } from "./async-job-tracker.
|
|
29
|
-
import { createResultWatcher } from "./result-watcher.
|
|
30
|
-
import { registerSlashCommands } from "./slash-commands.
|
|
31
|
-
import { registerPromptTemplateDelegationBridge } from "./prompt-template-bridge.
|
|
32
|
-
import { registerSlashSubagentBridge } from "./slash-bridge.
|
|
33
|
-
import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "./slash-live-state.
|
|
34
|
-
import { formatAsyncRunList, listAsyncRuns } from "./async-status.
|
|
21
|
+
import { discoverAgents } from "./agents.ts";
|
|
22
|
+
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "./artifacts.ts";
|
|
23
|
+
import { cleanupOldChainDirs } from "./settings.ts";
|
|
24
|
+
import { renderWidget, renderSubagentResult } from "./render.ts";
|
|
25
|
+
import { SubagentParams, StatusParams } from "./schemas.ts";
|
|
26
|
+
import { findByPrefix, readStatus } from "./utils.ts";
|
|
27
|
+
import { createSubagentExecutor } from "./subagent-executor.ts";
|
|
28
|
+
import { createAsyncJobTracker } from "./async-job-tracker.ts";
|
|
29
|
+
import { createResultWatcher } from "./result-watcher.ts";
|
|
30
|
+
import { registerSlashCommands } from "./slash-commands.ts";
|
|
31
|
+
import { registerPromptTemplateDelegationBridge } from "./prompt-template-bridge.ts";
|
|
32
|
+
import { registerSlashSubagentBridge } from "./slash-bridge.ts";
|
|
33
|
+
import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "./slash-live-state.ts";
|
|
34
|
+
import { formatAsyncRunList, listAsyncRuns } from "./async-status.ts";
|
|
35
35
|
import {
|
|
36
36
|
type Details,
|
|
37
37
|
type ExtensionConfig,
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
RESULTS_DIR,
|
|
42
42
|
SLASH_RESULT_TYPE,
|
|
43
43
|
WIDGET_KEY,
|
|
44
|
-
} from "./types.
|
|
44
|
+
} from "./types.ts";
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Derive subagent session base directory from parent session file.
|
package/notify.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
6
|
-
import { buildCompletionKey, getGlobalSeenMap, markSeenWithTtl } from "./completion-dedupe.
|
|
6
|
+
import { buildCompletionKey, getGlobalSeenMap, markSeenWithTtl } from "./completion-dedupe.ts";
|
|
7
7
|
|
|
8
8
|
interface ChainStepResult {
|
|
9
9
|
agent: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,6 +46,13 @@
|
|
|
46
46
|
"./notify.ts"
|
|
47
47
|
]
|
|
48
48
|
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@mariozechner/pi-agent-core": "*",
|
|
51
|
+
"@mariozechner/pi-ai": "*",
|
|
52
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
53
|
+
"@mariozechner/pi-tui": "*",
|
|
54
|
+
"@sinclair/typebox": "*"
|
|
55
|
+
},
|
|
49
56
|
"devDependencies": {
|
|
50
57
|
"@marcfargas/pi-test-harness": "^0.5.0",
|
|
51
58
|
"@mariozechner/pi-agent-core": "^0.65.0",
|
package/parallel-utils.ts
CHANGED
package/pi-spawn.ts
CHANGED
|
@@ -83,15 +83,12 @@ export function resolveWindowsPiCliScript(deps: PiSpawnDeps = {}): string | unde
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export function getPiSpawnCommand(args: string[], deps: PiSpawnDeps = {}): PiSpawnCommand {
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
args: [piCliPath, ...args],
|
|
93
|
-
};
|
|
94
|
-
}
|
|
86
|
+
const piCliPath = resolveWindowsPiCliScript(deps);
|
|
87
|
+
if (piCliPath) {
|
|
88
|
+
return {
|
|
89
|
+
command: deps.execPath ?? process.execPath,
|
|
90
|
+
args: [piCliPath, ...args],
|
|
91
|
+
};
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
return { command: "pi", args };
|
package/render.ts
CHANGED
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
type Details,
|
|
11
11
|
MAX_WIDGET_JOBS,
|
|
12
12
|
WIDGET_KEY,
|
|
13
|
-
} from "./types.
|
|
14
|
-
import { formatTokens, formatUsage, formatDuration, formatToolCall, shortenPath } from "./formatters.
|
|
15
|
-
import {
|
|
13
|
+
} from "./types.ts";
|
|
14
|
+
import { formatTokens, formatUsage, formatDuration, formatToolCall, shortenPath } from "./formatters.ts";
|
|
15
|
+
import { getDisplayItems, getLastActivity, getOutputTail, getSingleResultOutput } from "./utils.ts";
|
|
16
16
|
|
|
17
17
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
18
18
|
|
|
@@ -199,7 +199,7 @@ export function renderSubagentResult(
|
|
|
199
199
|
? theme.fg("success", "ok")
|
|
200
200
|
: theme.fg("error", "X");
|
|
201
201
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
202
|
-
const output = r.truncation?.text ||
|
|
202
|
+
const output = r.truncation?.text || getSingleResultOutput(r);
|
|
203
203
|
|
|
204
204
|
const progressInfo = isRunning && r.progress
|
|
205
205
|
? ` | ${r.progress.toolCount} tools, ${formatTokens(r.progress.tokens)} tok, ${formatDuration(r.progress.durationMs)}`
|
|
@@ -283,7 +283,7 @@ export function renderSubagentResult(
|
|
|
283
283
|
const hasEmptyWithoutTarget = d.results.some((r) =>
|
|
284
284
|
r.exitCode === 0
|
|
285
285
|
&& r.progress?.status !== "running"
|
|
286
|
-
&& hasEmptyTextOutputWithoutOutputTarget(r.task,
|
|
286
|
+
&& hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)),
|
|
287
287
|
);
|
|
288
288
|
const icon = hasRunning
|
|
289
289
|
? theme.fg("warning", "...")
|
|
@@ -337,7 +337,7 @@ export function renderSubagentResult(
|
|
|
337
337
|
const isComplete = result && result.exitCode === 0 && result.progress?.status !== "running";
|
|
338
338
|
const isEmptyWithoutTarget = Boolean(result)
|
|
339
339
|
&& Boolean(isComplete)
|
|
340
|
-
&& hasEmptyTextOutputWithoutOutputTarget(result.task,
|
|
340
|
+
&& hasEmptyTextOutputWithoutOutputTarget(result.task, getSingleResultOutput(result));
|
|
341
341
|
const isCurrent = i === (d.currentStepIndex ?? d.results.length);
|
|
342
342
|
const stepIcon = isFailed
|
|
343
343
|
? theme.fg("error", "✗")
|
|
@@ -395,7 +395,7 @@ export function renderSubagentResult(
|
|
|
395
395
|
const rProg = r.progress || progressFromArray || r.progressSummary;
|
|
396
396
|
const rRunning = rProg?.status === "running";
|
|
397
397
|
|
|
398
|
-
const resultOutput =
|
|
398
|
+
const resultOutput = getSingleResultOutput(r);
|
|
399
399
|
const statusIcon = rRunning
|
|
400
400
|
? theme.fg("warning", "●")
|
|
401
401
|
: r.exitCode !== 0
|
package/schemas.ts
CHANGED
|
@@ -70,7 +70,7 @@ export const SubagentParams = Type.Object({
|
|
|
70
70
|
})),
|
|
71
71
|
// Agent/chain configuration for create/update (nested to avoid conflicts with execution fields)
|
|
72
72
|
config: Type.Optional(Type.Any({
|
|
73
|
-
description: "Agent or chain config for create/update. Agent: name, description, scope ('user'|'project', default 'user'), systemPrompt, model, tools (comma-separated), extensions (comma-separated), skills (comma-separated), thinking, output, reads, progress. Chain: name, description, scope, steps (array of {agent, task?, output?, reads?, model?, skills?, progress?}). Presence of 'steps' creates a chain instead of an agent."
|
|
73
|
+
description: "Agent or chain config for create/update. Agent: name, description, scope ('user'|'project', default 'user'), systemPrompt, model, tools (comma-separated), extensions (comma-separated), skills (comma-separated), thinking, output, reads, progress, maxSubagentDepth. Chain: name, description, scope, steps (array of {agent, task?, output?, reads?, model?, skills?, progress?}). Presence of 'steps' creates a chain instead of an agent."
|
|
74
74
|
})),
|
|
75
75
|
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?}, ...]" })),
|
|
76
76
|
worktree: Type.Optional(Type.Boolean({
|
package/settings.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import type { AgentConfig } from "./agents.
|
|
9
|
-
import { normalizeSkillInput } from "./skills.
|
|
8
|
+
import type { AgentConfig } from "./agents.ts";
|
|
9
|
+
import { normalizeSkillInput } from "./skills.ts";
|
|
10
10
|
|
|
11
11
|
const CHAIN_RUNS_DIR = path.join(os.tmpdir(), "pi-chain-runs");
|
|
12
12
|
const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
package/single-output.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
|
+
export interface SingleOutputSnapshot {
|
|
5
|
+
exists: boolean;
|
|
6
|
+
mtimeMs?: number;
|
|
7
|
+
size?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export function resolveSingleOutputPath(
|
|
5
11
|
output: string | false | undefined,
|
|
6
12
|
runtimeCwd: string,
|
|
@@ -19,6 +25,16 @@ export function injectSingleOutputInstruction(task: string, outputPath: string |
|
|
|
19
25
|
return `${task}\n\n---\n**Output:** Write your findings to: ${outputPath}`;
|
|
20
26
|
}
|
|
21
27
|
|
|
28
|
+
export function captureSingleOutputSnapshot(outputPath: string | undefined): SingleOutputSnapshot | undefined {
|
|
29
|
+
if (!outputPath) return undefined;
|
|
30
|
+
try {
|
|
31
|
+
const stat = fs.statSync(outputPath);
|
|
32
|
+
return { exists: true, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
33
|
+
} catch {
|
|
34
|
+
return { exists: false };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
export function persistSingleOutput(
|
|
23
39
|
outputPath: string | undefined,
|
|
24
40
|
fullOutput: string,
|
|
@@ -33,23 +49,47 @@ export function persistSingleOutput(
|
|
|
33
49
|
}
|
|
34
50
|
}
|
|
35
51
|
|
|
52
|
+
export function resolveSingleOutput(
|
|
53
|
+
outputPath: string | undefined,
|
|
54
|
+
fallbackOutput: string,
|
|
55
|
+
beforeRun: SingleOutputSnapshot | undefined,
|
|
56
|
+
): { fullOutput: string; savedPath?: string; saveError?: string } {
|
|
57
|
+
if (!outputPath) return { fullOutput: fallbackOutput };
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const stat = fs.statSync(outputPath);
|
|
61
|
+
const changedSinceStart = !beforeRun?.exists
|
|
62
|
+
|| stat.mtimeMs !== beforeRun.mtimeMs
|
|
63
|
+
|| stat.size !== beforeRun.size;
|
|
64
|
+
if (changedSinceStart) {
|
|
65
|
+
return {
|
|
66
|
+
fullOutput: fs.readFileSync(outputPath, "utf-8"),
|
|
67
|
+
savedPath: outputPath,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
71
|
+
|
|
72
|
+
const save = persistSingleOutput(outputPath, fallbackOutput);
|
|
73
|
+
if (save.savedPath) return { fullOutput: fallbackOutput, savedPath: save.savedPath };
|
|
74
|
+
return { fullOutput: fallbackOutput, saveError: save.error };
|
|
75
|
+
}
|
|
76
|
+
|
|
36
77
|
export function finalizeSingleOutput(params: {
|
|
37
78
|
fullOutput: string;
|
|
38
79
|
truncatedOutput?: string;
|
|
39
80
|
outputPath?: string;
|
|
40
81
|
exitCode: number;
|
|
82
|
+
savedPath?: string;
|
|
83
|
+
saveError?: string;
|
|
41
84
|
}): { displayOutput: string; savedPath?: string; saveError?: string } {
|
|
42
85
|
let displayOutput = params.truncatedOutput || params.fullOutput;
|
|
43
|
-
if (params.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
displayOutput += `\n\n⚠️ Failed to save output to: ${params.outputPath}\n${save.error}`;
|
|
51
|
-
return { displayOutput, saveError: save.error };
|
|
52
|
-
}
|
|
86
|
+
if (params.exitCode === 0 && params.savedPath) {
|
|
87
|
+
displayOutput += `\n\n📄 Output saved to: ${params.savedPath}`;
|
|
88
|
+
return { displayOutput, savedPath: params.savedPath };
|
|
89
|
+
}
|
|
90
|
+
if (params.exitCode === 0 && params.saveError && params.outputPath) {
|
|
91
|
+
displayOutput += `\n\n⚠️ Failed to save output to: ${params.outputPath}\n${params.saveError}`;
|
|
92
|
+
return { displayOutput, saveError: params.saveError };
|
|
53
93
|
}
|
|
54
94
|
return { displayOutput };
|
|
55
95
|
}
|
package/skills.ts
CHANGED
|
@@ -187,7 +187,9 @@ function collectSettingsSkillPaths(cwd: string): string[] {
|
|
|
187
187
|
function buildSkillPaths(cwd: string): string[] {
|
|
188
188
|
const defaultSkillPaths = [
|
|
189
189
|
path.join(cwd, CONFIG_DIR, "skills"),
|
|
190
|
+
path.join(cwd, ".agents", "skills"),
|
|
190
191
|
path.join(AGENT_DIR, "skills"),
|
|
192
|
+
path.join(os.homedir(), ".agents", "skills"),
|
|
191
193
|
];
|
|
192
194
|
const packagePaths = collectPackageSkillPaths(cwd);
|
|
193
195
|
const settingsPaths = collectSettingsSkillPaths(cwd);
|
|
@@ -203,10 +205,11 @@ function inferSkillSource(sourceInfo: { source: string; scope: string }, filePat
|
|
|
203
205
|
// Fallback: infer from file path when sourceInfo isn't specific enough
|
|
204
206
|
// (e.g. scope === "temporary" for skills loaded via explicit skillPaths)
|
|
205
207
|
const projectRoot = path.resolve(cwd, CONFIG_DIR);
|
|
206
|
-
const
|
|
208
|
+
const altProjectRoot = path.resolve(cwd, ".agents");
|
|
209
|
+
const isProjectScoped = isWithinPath(filePath, projectRoot) || isWithinPath(filePath, altProjectRoot);
|
|
207
210
|
if (isProjectScoped) return "project";
|
|
208
211
|
|
|
209
|
-
const isUserScoped = isWithinPath(filePath, AGENT_DIR);
|
|
212
|
+
const isUserScoped = isWithinPath(filePath, AGENT_DIR) || isWithinPath(filePath, path.join(os.homedir(), ".agents"));
|
|
210
213
|
if (isUserScoped) return "user";
|
|
211
214
|
|
|
212
215
|
const globalRoot = getGlobalNpmRoot();
|