pi-subagents 0.14.0 → 0.15.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 +30 -4
- package/README.md +88 -7
- package/agent-management.ts +28 -7
- package/agent-manager-detail.ts +5 -2
- package/agent-manager-edit.ts +46 -6
- package/agent-manager-list.ts +3 -3
- package/agent-manager.ts +28 -2
- package/agent-serializer.ts +6 -0
- package/agents/context-builder.md +24 -26
- package/agents/delegate.md +4 -1
- package/agents/planner.md +21 -15
- package/agents/researcher.md +23 -25
- package/agents/reviewer.md +22 -14
- package/agents/scout.md +24 -19
- package/agents/worker.md +20 -8
- package/agents.ts +121 -13
- package/async-execution.ts +18 -12
- package/async-job-tracker.ts +3 -3
- package/async-status.ts +3 -3
- package/chain-clarify.ts +4 -4
- package/chain-execution.ts +19 -17
- package/execution.ts +3 -3
- package/formatters.ts +14 -12
- package/index.ts +1 -1
- package/install.mjs +3 -3
- package/package.json +1 -1
- package/parallel-utils.ts +9 -20
- package/pi-args.ts +13 -6
- package/prompt-template-bridge.ts +19 -8
- package/render.ts +23 -50
- package/schemas.ts +1 -1
- package/settings.ts +2 -2
- package/single-output.ts +2 -2
- package/slash-commands.ts +8 -8
- package/subagent-executor.ts +40 -27
- package/subagent-prompt-runtime.ts +67 -0
- package/subagent-runner.ts +7 -12
- package/types.ts +1 -1
- package/utils.ts +53 -12
- package/worktree.ts +2 -1
package/render.ts
CHANGED
|
@@ -20,7 +20,6 @@ function getTermWidth(): number {
|
|
|
20
20
|
return process.stdout.columns || 120;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// Grapheme segmenter for proper Unicode handling (shared instance)
|
|
24
23
|
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
25
24
|
|
|
26
25
|
/**
|
|
@@ -35,42 +34,38 @@ const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
|
35
34
|
function truncLine(text: string, maxWidth: number): string {
|
|
36
35
|
if (visibleWidth(text) <= maxWidth) return text;
|
|
37
36
|
|
|
38
|
-
const targetWidth = maxWidth - 1;
|
|
37
|
+
const targetWidth = maxWidth - 1;
|
|
39
38
|
let result = "";
|
|
40
39
|
let currentWidth = 0;
|
|
41
|
-
let activeStyles: string[] = [];
|
|
40
|
+
let activeStyles: string[] = [];
|
|
42
41
|
let i = 0;
|
|
43
42
|
|
|
44
43
|
while (i < text.length) {
|
|
45
|
-
// Check for ANSI escape code
|
|
46
44
|
const ansiMatch = text.slice(i).match(/^\x1b\[[0-9;]*m/);
|
|
47
45
|
if (ansiMatch) {
|
|
48
46
|
const code = ansiMatch[0];
|
|
49
47
|
result += code;
|
|
50
48
|
|
|
51
49
|
if (code === "\x1b[0m" || code === "\x1b[m") {
|
|
52
|
-
activeStyles = [];
|
|
50
|
+
activeStyles = [];
|
|
53
51
|
} else {
|
|
54
|
-
activeStyles.push(code);
|
|
52
|
+
activeStyles.push(code);
|
|
55
53
|
}
|
|
56
54
|
i += code.length;
|
|
57
55
|
continue;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
// Find end of non-ANSI text segment
|
|
61
58
|
let end = i;
|
|
62
59
|
while (end < text.length && !text.slice(end).match(/^\x1b\[[0-9;]*m/)) {
|
|
63
60
|
end++;
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
// Segment into graphemes for proper Unicode handling
|
|
67
63
|
const textPortion = text.slice(i, end);
|
|
68
64
|
for (const seg of segmenter.segment(textPortion)) {
|
|
69
65
|
const grapheme = seg.segment;
|
|
70
66
|
const graphemeWidth = visibleWidth(grapheme);
|
|
71
67
|
|
|
72
68
|
if (currentWidth + graphemeWidth > targetWidth) {
|
|
73
|
-
// Re-apply all active styles before ellipsis to preserve background/colors
|
|
74
69
|
return result + activeStyles.join("") + "…";
|
|
75
70
|
}
|
|
76
71
|
|
|
@@ -80,16 +75,11 @@ function truncLine(text: string, maxWidth: number): string {
|
|
|
80
75
|
i = end;
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
// Reached end without exceeding width (shouldn't happen given initial check)
|
|
84
78
|
return result + activeStyles.join("") + "…";
|
|
85
79
|
}
|
|
86
80
|
|
|
87
|
-
// Track last rendered widget state to avoid no-op re-renders
|
|
88
81
|
let lastWidgetHash = "";
|
|
89
82
|
|
|
90
|
-
/**
|
|
91
|
-
* Compute a simple hash of job states for change detection
|
|
92
|
-
*/
|
|
93
83
|
function computeWidgetHash(jobs: AsyncJobState[]): string {
|
|
94
84
|
return jobs.slice(0, MAX_WIDGET_JOBS).map(job =>
|
|
95
85
|
`${job.asyncId}:${job.status}:${job.currentStep}:${job.updatedAt}:${job.totalTokens?.total ?? 0}`
|
|
@@ -124,13 +114,11 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
124
114
|
return;
|
|
125
115
|
}
|
|
126
116
|
|
|
127
|
-
// Check if anything changed since last render
|
|
128
|
-
// Always re-render if any displayed job is running (output tail updates constantly)
|
|
129
117
|
const displayedJobs = jobs.slice(0, MAX_WIDGET_JOBS);
|
|
130
118
|
const hasRunningJobs = displayedJobs.some(job => job.status === "running");
|
|
131
119
|
const newHash = computeWidgetHash(jobs);
|
|
132
120
|
if (!hasRunningJobs && newHash === lastWidgetHash) {
|
|
133
|
-
return;
|
|
121
|
+
return;
|
|
134
122
|
}
|
|
135
123
|
lastWidgetHash = newHash;
|
|
136
124
|
|
|
@@ -194,12 +182,12 @@ export function renderSubagentResult(
|
|
|
194
182
|
const r = d.results[0];
|
|
195
183
|
const isRunning = r.progress?.status === "running";
|
|
196
184
|
const icon = isRunning
|
|
197
|
-
? theme.fg("warning", "
|
|
185
|
+
? theme.fg("warning", "running")
|
|
198
186
|
: r.detached
|
|
199
|
-
? theme.fg("warning", "
|
|
187
|
+
? theme.fg("warning", "detached")
|
|
200
188
|
: r.exitCode === 0
|
|
201
189
|
? theme.fg("success", "ok")
|
|
202
|
-
: theme.fg("error", "
|
|
190
|
+
: theme.fg("error", "failed");
|
|
203
191
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
204
192
|
const output = r.truncation?.text || getSingleResultOutput(r);
|
|
205
193
|
|
|
@@ -265,7 +253,7 @@ export function renderSubagentResult(
|
|
|
265
253
|
c.addChild(new Text(truncLine(theme.fg("dim", `Skills: ${r.skills.join(", ")}`), w), 0, 0));
|
|
266
254
|
}
|
|
267
255
|
if (r.skillsWarning) {
|
|
268
|
-
c.addChild(new Text(truncLine(theme.fg("warning",
|
|
256
|
+
c.addChild(new Text(truncLine(theme.fg("warning", `Warning: ${r.skillsWarning}`), w), 0, 0));
|
|
269
257
|
}
|
|
270
258
|
if (r.attemptedModels && r.attemptedModels.length > 1) {
|
|
271
259
|
c.addChild(new Text(truncLine(theme.fg("dim", `Fallbacks: ${r.attemptedModels.join(" → ")}`), w), 0, 0));
|
|
@@ -291,12 +279,12 @@ export function renderSubagentResult(
|
|
|
291
279
|
&& hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)),
|
|
292
280
|
);
|
|
293
281
|
const icon = hasRunning
|
|
294
|
-
? theme.fg("warning", "
|
|
282
|
+
? theme.fg("warning", "running")
|
|
295
283
|
: hasEmptyWithoutTarget
|
|
296
|
-
? theme.fg("warning", "
|
|
284
|
+
? theme.fg("warning", "warning")
|
|
297
285
|
: ok === d.results.length
|
|
298
286
|
? theme.fg("success", "ok")
|
|
299
|
-
: theme.fg("error", "
|
|
287
|
+
: theme.fg("error", "failed");
|
|
300
288
|
|
|
301
289
|
const totalSummary =
|
|
302
290
|
d.progressSummary ||
|
|
@@ -323,17 +311,11 @@ export function renderSubagentResult(
|
|
|
323
311
|
|
|
324
312
|
const modeLabel = d.mode;
|
|
325
313
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
326
|
-
// For parallel-in-chain, show task count (results) for consistency with step display
|
|
327
|
-
// For sequential chains, show logical step count
|
|
328
314
|
const hasParallelInChain = d.chainAgents?.some((a) => a.startsWith("["));
|
|
329
315
|
const totalCount = hasParallelInChain ? d.results.length : (d.totalSteps ?? d.results.length);
|
|
330
316
|
const currentStep = d.currentStepIndex !== undefined ? d.currentStepIndex + 1 : ok + 1;
|
|
331
317
|
const stepInfo = hasRunning ? ` ${currentStep}/${totalCount}` : ` ${ok}/${totalCount}`;
|
|
332
318
|
|
|
333
|
-
// Build chain visualization: "scout → planner" with status icons
|
|
334
|
-
// Note: Only works correctly for sequential chains. Chains with parallel steps
|
|
335
|
-
// (indicated by "[agent1+agent2]" format) have multiple results per step,
|
|
336
|
-
// breaking the 1:1 mapping between chainAgents and results.
|
|
337
319
|
const chainVis = d.chainAgents?.length && !hasParallelInChain
|
|
338
320
|
? d.chainAgents
|
|
339
321
|
.map((agent, i) => {
|
|
@@ -345,14 +327,14 @@ export function renderSubagentResult(
|
|
|
345
327
|
&& hasEmptyTextOutputWithoutOutputTarget(result.task, getSingleResultOutput(result));
|
|
346
328
|
const isCurrent = i === (d.currentStepIndex ?? d.results.length);
|
|
347
329
|
const stepIcon = isFailed
|
|
348
|
-
? theme.fg("error", "
|
|
330
|
+
? theme.fg("error", "failed")
|
|
349
331
|
: isEmptyWithoutTarget
|
|
350
|
-
? theme.fg("warning", "
|
|
332
|
+
? theme.fg("warning", "warning")
|
|
351
333
|
: isComplete
|
|
352
|
-
? theme.fg("success", "
|
|
334
|
+
? theme.fg("success", "done")
|
|
353
335
|
: isCurrent && hasRunning
|
|
354
|
-
? theme.fg("warning", "
|
|
355
|
-
: theme.fg("dim", "
|
|
336
|
+
? theme.fg("warning", "running")
|
|
337
|
+
: theme.fg("dim", "pending");
|
|
356
338
|
return `${stepIcon} ${agent}`;
|
|
357
339
|
})
|
|
358
340
|
.join(theme.fg("dim", " → "))
|
|
@@ -367,15 +349,10 @@ export function renderSubagentResult(
|
|
|
367
349
|
0,
|
|
368
350
|
),
|
|
369
351
|
);
|
|
370
|
-
// Show chain visualization
|
|
371
352
|
if (chainVis) {
|
|
372
353
|
c.addChild(new Text(truncLine(` ${chainVis}`, w), 0, 0));
|
|
373
354
|
}
|
|
374
355
|
|
|
375
|
-
// === STATIC STEP LAYOUT (like clarification UI) ===
|
|
376
|
-
// Each step gets a fixed section with task/output/status
|
|
377
|
-
// Note: For chains with parallel steps, chainAgents indices don't map 1:1 to results
|
|
378
|
-
// (parallel steps produce multiple results). Fall back to result-based iteration.
|
|
379
356
|
const useResultsDirectly = hasParallelInChain || !d.chainAgents?.length;
|
|
380
357
|
const stepsToShow = useResultsDirectly ? d.results.length : d.chainAgents!.length;
|
|
381
358
|
|
|
@@ -388,9 +365,8 @@ export function renderSubagentResult(
|
|
|
388
365
|
: (d.chainAgents![i] || r?.agent || `step-${i + 1}`);
|
|
389
366
|
|
|
390
367
|
if (!r) {
|
|
391
|
-
// Pending step
|
|
392
368
|
c.addChild(new Text(truncLine(theme.fg("dim", ` Step ${i + 1}: ${agentName}`), w), 0, 0));
|
|
393
|
-
c.addChild(new Text(theme.fg("dim", ` status:
|
|
369
|
+
c.addChild(new Text(theme.fg("dim", ` status: pending`), 0, 0));
|
|
394
370
|
c.addChild(new Spacer(1));
|
|
395
371
|
continue;
|
|
396
372
|
}
|
|
@@ -402,12 +378,12 @@ export function renderSubagentResult(
|
|
|
402
378
|
|
|
403
379
|
const resultOutput = getSingleResultOutput(r);
|
|
404
380
|
const statusIcon = rRunning
|
|
405
|
-
? theme.fg("warning", "
|
|
381
|
+
? theme.fg("warning", "running")
|
|
406
382
|
: r.exitCode !== 0
|
|
407
|
-
? theme.fg("error", "
|
|
383
|
+
? theme.fg("error", "failed")
|
|
408
384
|
: hasEmptyTextOutputWithoutOutputTarget(r.task, resultOutput)
|
|
409
|
-
? theme.fg("warning", "
|
|
410
|
-
: theme.fg("success", "
|
|
385
|
+
? theme.fg("warning", "warning")
|
|
386
|
+
: theme.fg("success", "done");
|
|
411
387
|
const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
|
|
412
388
|
const modelDisplay = r.model ? theme.fg("dim", ` (${r.model})`) : "";
|
|
413
389
|
const stepHeader = rRunning
|
|
@@ -430,7 +406,7 @@ export function renderSubagentResult(
|
|
|
430
406
|
c.addChild(new Text(truncLine(theme.fg("dim", ` skills: ${r.skills.join(", ")}`), w), 0, 0));
|
|
431
407
|
}
|
|
432
408
|
if (r.skillsWarning) {
|
|
433
|
-
c.addChild(new Text(truncLine(theme.fg("warning", `
|
|
409
|
+
c.addChild(new Text(truncLine(theme.fg("warning", ` Warning: ${r.skillsWarning}`), w), 0, 0));
|
|
434
410
|
}
|
|
435
411
|
if (r.attemptedModels && r.attemptedModels.length > 1) {
|
|
436
412
|
c.addChild(new Text(truncLine(theme.fg("dim", ` fallbacks: ${r.attemptedModels.join(" → ")}`), w), 0, 0));
|
|
@@ -440,7 +416,6 @@ export function renderSubagentResult(
|
|
|
440
416
|
if (rProg.skills?.length) {
|
|
441
417
|
c.addChild(new Text(truncLine(theme.fg("accent", ` skills: ${rProg.skills.join(", ")}`), w), 0, 0));
|
|
442
418
|
}
|
|
443
|
-
// Current tool for running step
|
|
444
419
|
if (rProg.currentTool) {
|
|
445
420
|
const maxToolArgsLen = Math.max(50, w - 20);
|
|
446
421
|
const toolArgsPreview = rProg.currentToolArgs
|
|
@@ -453,7 +428,6 @@ export function renderSubagentResult(
|
|
|
453
428
|
: rProg.currentTool;
|
|
454
429
|
c.addChild(new Text(truncLine(theme.fg("warning", ` > ${toolLine}`), w), 0, 0));
|
|
455
430
|
}
|
|
456
|
-
// Recent tools
|
|
457
431
|
if (rProg.recentTools?.length) {
|
|
458
432
|
for (const t of rProg.recentTools.slice(-3)) {
|
|
459
433
|
const maxArgsLen = Math.max(40, w - 30);
|
|
@@ -463,7 +437,6 @@ export function renderSubagentResult(
|
|
|
463
437
|
c.addChild(new Text(truncLine(theme.fg("dim", ` ${t.tool}: ${argsPreview}`), w), 0, 0));
|
|
464
438
|
}
|
|
465
439
|
}
|
|
466
|
-
// Recent output - let truncLine handle truncation entirely
|
|
467
440
|
const recentLines = (rProg.recentOutput ?? []).slice(-5);
|
|
468
441
|
for (const line of recentLines) {
|
|
469
442
|
c.addChild(new Text(truncLine(theme.fg("dim", ` ${line}`), w), 0, 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, 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."
|
|
73
|
+
description: "Agent or chain config for create/update. Agent: name, description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, 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
|
@@ -356,5 +356,5 @@ export function createParallelDirs(
|
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
export type { ParallelTaskResult } from "./parallel-utils.
|
|
360
|
-
export { aggregateParallelOutputs } from "./parallel-utils.
|
|
359
|
+
export type { ParallelTaskResult } from "./parallel-utils.ts";
|
|
360
|
+
export { aggregateParallelOutputs } from "./parallel-utils.ts";
|
package/single-output.ts
CHANGED
|
@@ -84,11 +84,11 @@ export function finalizeSingleOutput(params: {
|
|
|
84
84
|
}): { displayOutput: string; savedPath?: string; saveError?: string } {
|
|
85
85
|
let displayOutput = params.truncatedOutput || params.fullOutput;
|
|
86
86
|
if (params.exitCode === 0 && params.savedPath) {
|
|
87
|
-
displayOutput += `\n\
|
|
87
|
+
displayOutput += `\n\nOutput saved to: ${params.savedPath}`;
|
|
88
88
|
return { displayOutput, savedPath: params.savedPath };
|
|
89
89
|
}
|
|
90
90
|
if (params.exitCode === 0 && params.saveError && params.outputPath) {
|
|
91
|
-
displayOutput += `\n\
|
|
91
|
+
displayOutput += `\n\nFailed to save output to: ${params.outputPath}\n${params.saveError}`;
|
|
92
92
|
return { displayOutput, saveError: params.saveError };
|
|
93
93
|
}
|
|
94
94
|
return { displayOutput };
|
package/slash-commands.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
3
3
|
import { Key, matchesKey } from "@mariozechner/pi-tui";
|
|
4
|
-
import { discoverAgents, discoverAgentsAll } from "./agents.
|
|
5
|
-
import { AgentManagerComponent, type ManagerResult } from "./agent-manager.
|
|
6
|
-
import { SubagentsStatusComponent } from "./subagents-status.
|
|
7
|
-
import { discoverAvailableSkills } from "./skills.
|
|
8
|
-
import type { SubagentParamsLike } from "./subagent-executor.
|
|
9
|
-
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.
|
|
4
|
+
import { discoverAgents, discoverAgentsAll } from "./agents.ts";
|
|
5
|
+
import { AgentManagerComponent, type ManagerResult } from "./agent-manager.ts";
|
|
6
|
+
import { SubagentsStatusComponent } from "./subagents-status.ts";
|
|
7
|
+
import { discoverAvailableSkills } from "./skills.ts";
|
|
8
|
+
import type { SubagentParamsLike } from "./subagent-executor.ts";
|
|
9
|
+
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
|
|
10
10
|
import {
|
|
11
11
|
applySlashUpdate,
|
|
12
12
|
buildSlashInitialResult,
|
|
13
13
|
failSlashResult,
|
|
14
14
|
finalizeSlashResult,
|
|
15
|
-
} from "./slash-live-state.
|
|
15
|
+
} from "./slash-live-state.ts";
|
|
16
16
|
import {
|
|
17
17
|
MAX_PARALLEL,
|
|
18
18
|
SLASH_RESULT_TYPE,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
SLASH_SUBAGENT_STARTED_EVENT,
|
|
23
23
|
SLASH_SUBAGENT_UPDATE_EVENT,
|
|
24
24
|
type SubagentState,
|
|
25
|
-
} from "./types.
|
|
25
|
+
} from "./types.ts";
|
|
26
26
|
|
|
27
27
|
interface InlineConfig {
|
|
28
28
|
output?: string | false;
|
package/subagent-executor.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { executeAsyncChain, executeAsyncSingle, isAsyncAvailable } from "./async
|
|
|
25
25
|
import { createForkContextResolver } from "./fork-context.ts";
|
|
26
26
|
import { applyIntercomBridgeToAgent, resolveIntercomBridge, resolveIntercomSessionTarget } from "./intercom-bridge.ts";
|
|
27
27
|
import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
|
|
28
|
-
import { getSingleResultOutput, mapConcurrent } from "./utils.ts";
|
|
28
|
+
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd } from "./utils.ts";
|
|
29
29
|
import {
|
|
30
30
|
cleanupWorktrees,
|
|
31
31
|
createWorktrees,
|
|
@@ -101,6 +101,7 @@ interface ExecutorDeps {
|
|
|
101
101
|
|
|
102
102
|
interface ExecutionContextData {
|
|
103
103
|
params: SubagentParamsLike;
|
|
104
|
+
effectiveCwd: string;
|
|
104
105
|
ctx: ExtensionContext;
|
|
105
106
|
signal: AbortSignal;
|
|
106
107
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
@@ -116,6 +117,10 @@ interface ExecutionContextData {
|
|
|
116
117
|
effectiveAsync: boolean;
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
121
|
+
return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
122
|
+
}
|
|
123
|
+
|
|
119
124
|
function validateExecutionInput(
|
|
120
125
|
params: SubagentParamsLike,
|
|
121
126
|
agents: AgentConfig[],
|
|
@@ -333,6 +338,7 @@ function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["
|
|
|
333
338
|
function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentToolResult<Details> | null {
|
|
334
339
|
const {
|
|
335
340
|
params,
|
|
341
|
+
effectiveCwd,
|
|
336
342
|
agents,
|
|
337
343
|
ctx,
|
|
338
344
|
shareEnabled,
|
|
@@ -347,7 +353,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
347
353
|
if (!effectiveAsync) return null;
|
|
348
354
|
|
|
349
355
|
if (hasChain && params.chain) {
|
|
350
|
-
const chainWorktreeTaskCwdError = buildChainWorktreeTaskCwdError(params.chain as ChainStep[],
|
|
356
|
+
const chainWorktreeTaskCwdError = buildChainWorktreeTaskCwdError(params.chain as ChainStep[], effectiveCwd);
|
|
351
357
|
if (chainWorktreeTaskCwdError) {
|
|
352
358
|
return {
|
|
353
359
|
content: [{ type: "text", text: chainWorktreeTaskCwdError }],
|
|
@@ -387,7 +393,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
387
393
|
agents,
|
|
388
394
|
ctx: asyncCtx,
|
|
389
395
|
availableModels,
|
|
390
|
-
cwd:
|
|
396
|
+
cwd: effectiveCwd,
|
|
391
397
|
maxOutput: params.maxOutput,
|
|
392
398
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
393
399
|
artifactConfig,
|
|
@@ -422,7 +428,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
422
428
|
agentConfig: a,
|
|
423
429
|
ctx: asyncCtx,
|
|
424
430
|
availableModels,
|
|
425
|
-
cwd:
|
|
431
|
+
cwd: effectiveCwd,
|
|
426
432
|
maxOutput: params.maxOutput,
|
|
427
433
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
428
434
|
artifactConfig,
|
|
@@ -444,6 +450,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
444
450
|
async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
445
451
|
const {
|
|
446
452
|
params,
|
|
453
|
+
effectiveCwd,
|
|
447
454
|
agents,
|
|
448
455
|
ctx,
|
|
449
456
|
signal,
|
|
@@ -467,7 +474,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
467
474
|
ctx,
|
|
468
475
|
signal,
|
|
469
476
|
runId,
|
|
470
|
-
cwd:
|
|
477
|
+
cwd: effectiveCwd,
|
|
471
478
|
shareEnabled,
|
|
472
479
|
sessionDirForIndex,
|
|
473
480
|
sessionFileForIndex,
|
|
@@ -508,7 +515,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
508
515
|
id: m.id,
|
|
509
516
|
fullId: `${m.provider}/${m.id}`,
|
|
510
517
|
})),
|
|
511
|
-
cwd:
|
|
518
|
+
cwd: effectiveCwd,
|
|
512
519
|
maxOutput: params.maxOutput,
|
|
513
520
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
514
521
|
artifactConfig,
|
|
@@ -595,7 +602,7 @@ function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string):
|
|
|
595
602
|
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
596
603
|
const step = chain[stepIndex]!;
|
|
597
604
|
if (!isParallelStep(step) || !step.worktree) continue;
|
|
598
|
-
const stepCwd = step.cwd
|
|
605
|
+
const stepCwd = resolveChildCwd(sharedCwd, step.cwd);
|
|
599
606
|
const conflict = findWorktreeTaskCwdConflict(step.parallel, stepCwd);
|
|
600
607
|
if (!conflict) continue;
|
|
601
608
|
const detail = formatWorktreeTaskCwdConflict(conflict, stepCwd);
|
|
@@ -611,7 +618,8 @@ function resolveParallelTaskCwd(
|
|
|
611
618
|
index: number,
|
|
612
619
|
): string | undefined {
|
|
613
620
|
if (worktreeSetup) return worktreeSetup.worktrees[index]!.agentCwd;
|
|
614
|
-
return task.cwd
|
|
621
|
+
if (!paramsCwd) return task.cwd;
|
|
622
|
+
return resolveChildCwd(paramsCwd, task.cwd);
|
|
615
623
|
}
|
|
616
624
|
|
|
617
625
|
function buildParallelWorktreeSuffix(
|
|
@@ -672,6 +680,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
672
680
|
async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
673
681
|
const {
|
|
674
682
|
params,
|
|
683
|
+
effectiveCwd,
|
|
675
684
|
agents,
|
|
676
685
|
ctx,
|
|
677
686
|
signal,
|
|
@@ -714,7 +723,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
714
723
|
resolveChildMaxSubagentDepth(currentMaxSubagentDepth, config.maxSubagentDepth),
|
|
715
724
|
);
|
|
716
725
|
|
|
717
|
-
const effectiveCwd = params.cwd ?? ctx.cwd;
|
|
718
726
|
if (params.worktree) {
|
|
719
727
|
const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd);
|
|
720
728
|
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
@@ -738,7 +746,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
738
746
|
const behaviors = agentConfigs.map((c, i) =>
|
|
739
747
|
resolveStepBehavior(c, { skills: skillOverrides[i] }),
|
|
740
748
|
);
|
|
741
|
-
const availableSkills = discoverAvailableSkills(
|
|
749
|
+
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
742
750
|
|
|
743
751
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
744
752
|
(tui, theme, _kb, done) =>
|
|
@@ -796,7 +804,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
796
804
|
agents,
|
|
797
805
|
ctx: asyncCtx,
|
|
798
806
|
availableModels,
|
|
799
|
-
cwd:
|
|
807
|
+
cwd: effectiveCwd,
|
|
800
808
|
maxOutput: params.maxOutput,
|
|
801
809
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
802
810
|
artifactConfig,
|
|
@@ -844,7 +852,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
844
852
|
artifactConfig,
|
|
845
853
|
artifactsDir,
|
|
846
854
|
maxOutput: params.maxOutput,
|
|
847
|
-
paramsCwd:
|
|
855
|
+
paramsCwd: effectiveCwd,
|
|
848
856
|
availableModels,
|
|
849
857
|
modelOverrides,
|
|
850
858
|
skillOverrides,
|
|
@@ -885,12 +893,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
885
893
|
|
|
886
894
|
return {
|
|
887
895
|
content: [{ type: "text", text: fullContent }],
|
|
888
|
-
details: {
|
|
896
|
+
details: compactForegroundDetails({
|
|
889
897
|
mode: "parallel",
|
|
890
898
|
results,
|
|
891
899
|
progress: params.includeProgress ? allProgress : undefined,
|
|
892
900
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
893
|
-
},
|
|
901
|
+
}),
|
|
894
902
|
};
|
|
895
903
|
} finally {
|
|
896
904
|
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
@@ -900,6 +908,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
900
908
|
async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
|
|
901
909
|
const {
|
|
902
910
|
params,
|
|
911
|
+
effectiveCwd,
|
|
903
912
|
agents,
|
|
904
913
|
ctx,
|
|
905
914
|
signal,
|
|
@@ -943,7 +952,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
943
952
|
|
|
944
953
|
if (params.clarify === true && ctx.hasUI) {
|
|
945
954
|
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput, skills: skillOverride });
|
|
946
|
-
const availableSkills = discoverAvailableSkills(
|
|
955
|
+
const availableSkills = discoverAvailableSkills(effectiveCwd);
|
|
947
956
|
|
|
948
957
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
949
958
|
(tui, theme, _kb, done) =>
|
|
@@ -994,7 +1003,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
994
1003
|
agentConfig,
|
|
995
1004
|
ctx: asyncCtx,
|
|
996
1005
|
availableModels,
|
|
997
|
-
cwd:
|
|
1006
|
+
cwd: effectiveCwd,
|
|
998
1007
|
maxOutput: params.maxOutput,
|
|
999
1008
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1000
1009
|
artifactConfig,
|
|
@@ -1015,7 +1024,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1015
1024
|
task = wrapForkTask(task);
|
|
1016
1025
|
}
|
|
1017
1026
|
const cleanTask = task;
|
|
1018
|
-
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd,
|
|
1027
|
+
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, effectiveCwd);
|
|
1019
1028
|
task = injectSingleOutputInstruction(task, outputPath);
|
|
1020
1029
|
|
|
1021
1030
|
let effectiveSkills: string[] | undefined;
|
|
@@ -1026,7 +1035,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1026
1035
|
}
|
|
1027
1036
|
|
|
1028
1037
|
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
1029
|
-
cwd:
|
|
1038
|
+
cwd: effectiveCwd,
|
|
1030
1039
|
signal,
|
|
1031
1040
|
allowIntercomDetach: agentConfig.systemPrompt?.includes("Intercom orchestration channel:") === true,
|
|
1032
1041
|
intercomEvents: deps.pi.events,
|
|
@@ -1063,37 +1072,37 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1063
1072
|
if (r.detached) {
|
|
1064
1073
|
return {
|
|
1065
1074
|
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}` }],
|
|
1066
|
-
details: {
|
|
1075
|
+
details: compactForegroundDetails({
|
|
1067
1076
|
mode: "single",
|
|
1068
1077
|
results: [r],
|
|
1069
1078
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1070
1079
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1071
1080
|
truncation: r.truncation,
|
|
1072
|
-
},
|
|
1081
|
+
}),
|
|
1073
1082
|
};
|
|
1074
1083
|
}
|
|
1075
1084
|
|
|
1076
1085
|
if (r.exitCode !== 0)
|
|
1077
1086
|
return {
|
|
1078
1087
|
content: [{ type: "text", text: r.error || "Failed" }],
|
|
1079
|
-
details: {
|
|
1088
|
+
details: compactForegroundDetails({
|
|
1080
1089
|
mode: "single",
|
|
1081
1090
|
results: [r],
|
|
1082
1091
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1083
1092
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1084
1093
|
truncation: r.truncation,
|
|
1085
|
-
},
|
|
1094
|
+
}),
|
|
1086
1095
|
isError: true,
|
|
1087
1096
|
};
|
|
1088
1097
|
return {
|
|
1089
1098
|
content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }],
|
|
1090
|
-
details: {
|
|
1099
|
+
details: compactForegroundDetails({
|
|
1091
1100
|
mode: "single",
|
|
1092
1101
|
results: [r],
|
|
1093
1102
|
progress: params.includeProgress ? allProgress : undefined,
|
|
1094
1103
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
1095
1104
|
truncation: r.truncation,
|
|
1096
|
-
},
|
|
1105
|
+
}),
|
|
1097
1106
|
};
|
|
1098
1107
|
}
|
|
1099
1108
|
|
|
@@ -1114,6 +1123,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1114
1123
|
ctx: ExtensionContext,
|
|
1115
1124
|
): Promise<AgentToolResult<Details>> => {
|
|
1116
1125
|
deps.state.baseCwd = ctx.cwd;
|
|
1126
|
+
const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
|
|
1127
|
+
const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
|
|
1117
1128
|
if (params.action) {
|
|
1118
1129
|
const validActions = ["list", "get", "create", "update", "delete"];
|
|
1119
1130
|
if (!validActions.includes(params.action)) {
|
|
@@ -1123,7 +1134,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1123
1134
|
details: { mode: "management" as const, results: [] },
|
|
1124
1135
|
};
|
|
1125
1136
|
}
|
|
1126
|
-
return handleManagementAction(params.action,
|
|
1137
|
+
return handleManagementAction(params.action, paramsWithResolvedCwd, { ...ctx, cwd: requestCwd });
|
|
1127
1138
|
}
|
|
1128
1139
|
|
|
1129
1140
|
const { blocked, depth, maxDepth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
@@ -1143,14 +1154,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1143
1154
|
};
|
|
1144
1155
|
}
|
|
1145
1156
|
|
|
1146
|
-
const normalized = normalizeRepeatedParallelCounts(
|
|
1157
|
+
const normalized = normalizeRepeatedParallelCounts(paramsWithResolvedCwd);
|
|
1147
1158
|
if (normalized.error) return normalized.error;
|
|
1148
1159
|
const normalizedParams = normalized.params!;
|
|
1149
1160
|
|
|
1150
1161
|
const scope: AgentScope = resolveExecutionAgentScope(normalizedParams.agentScope);
|
|
1162
|
+
const effectiveCwd = normalizedParams.cwd ?? ctx.cwd;
|
|
1151
1163
|
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
1152
1164
|
deps.state.currentSessionId = parentSessionFile ?? `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1153
|
-
const discoveredAgents = deps.discoverAgents(
|
|
1165
|
+
const discoveredAgents = deps.discoverAgents(effectiveCwd, scope).agents;
|
|
1154
1166
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
1155
1167
|
const intercomBridge = resolveIntercomBridge({
|
|
1156
1168
|
config: deps.config.intercomBridge,
|
|
@@ -1227,6 +1239,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
1227
1239
|
|
|
1228
1240
|
const execData: ExecutionContextData = {
|
|
1229
1241
|
params: normalizedParams,
|
|
1242
|
+
effectiveCwd,
|
|
1230
1243
|
ctx,
|
|
1231
1244
|
signal,
|
|
1232
1245
|
onUpdate: onUpdateWithContext,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export const SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV = "PI_SUBAGENT_INHERIT_PROJECT_CONTEXT";
|
|
4
|
+
export const SUBAGENT_INHERIT_SKILLS_ENV = "PI_SUBAGENT_INHERIT_SKILLS";
|
|
5
|
+
|
|
6
|
+
const PROJECT_CONTEXT_HEADER = "\n\n# Project Context\n\nProject-specific instructions and guidelines:\n\n";
|
|
7
|
+
const SKILLS_HEADER = "\n\nThe following skills provide specialized instructions for specific tasks.";
|
|
8
|
+
const DATE_HEADER = "\nCurrent date:";
|
|
9
|
+
|
|
10
|
+
function readBooleanEnv(name: string): boolean | undefined {
|
|
11
|
+
const value = process.env[name];
|
|
12
|
+
if (value === undefined) return undefined;
|
|
13
|
+
return value !== "0";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function findSectionEnd(prompt: string, startIndex: number, nextHeaders: string[]): number {
|
|
17
|
+
let endIndex = prompt.length;
|
|
18
|
+
for (const header of nextHeaders) {
|
|
19
|
+
const index = prompt.indexOf(header, startIndex);
|
|
20
|
+
if (index !== -1 && index < endIndex) {
|
|
21
|
+
endIndex = index;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return endIndex;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function stripProjectContext(prompt: string): string {
|
|
28
|
+
const startIndex = prompt.indexOf(PROJECT_CONTEXT_HEADER);
|
|
29
|
+
if (startIndex === -1) return prompt;
|
|
30
|
+
const endIndex = findSectionEnd(prompt, startIndex + PROJECT_CONTEXT_HEADER.length, [SKILLS_HEADER, DATE_HEADER]);
|
|
31
|
+
return `${prompt.slice(0, startIndex)}${prompt.slice(endIndex)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function stripInheritedSkills(prompt: string): string {
|
|
35
|
+
const startIndex = prompt.indexOf(SKILLS_HEADER);
|
|
36
|
+
if (startIndex === -1) return prompt;
|
|
37
|
+
const endIndex = findSectionEnd(prompt, startIndex + SKILLS_HEADER.length, [DATE_HEADER]);
|
|
38
|
+
return `${prompt.slice(0, startIndex)}${prompt.slice(endIndex)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function rewriteSubagentPrompt(
|
|
42
|
+
prompt: string,
|
|
43
|
+
options: { inheritProjectContext: boolean; inheritSkills: boolean },
|
|
44
|
+
): string {
|
|
45
|
+
let rewritten = prompt;
|
|
46
|
+
if (!options.inheritProjectContext) {
|
|
47
|
+
rewritten = stripProjectContext(rewritten);
|
|
48
|
+
}
|
|
49
|
+
if (!options.inheritSkills) {
|
|
50
|
+
rewritten = stripInheritedSkills(rewritten);
|
|
51
|
+
}
|
|
52
|
+
return rewritten;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default function registerSubagentPromptRuntime(pi: ExtensionAPI): void {
|
|
56
|
+
pi.on("before_agent_start", async (event) => {
|
|
57
|
+
const inheritProjectContext = readBooleanEnv(SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV);
|
|
58
|
+
const inheritSkills = readBooleanEnv(SUBAGENT_INHERIT_SKILLS_ENV);
|
|
59
|
+
if (inheritProjectContext === undefined && inheritSkills === undefined) return;
|
|
60
|
+
const rewritten = rewriteSubagentPrompt(event.systemPrompt, {
|
|
61
|
+
inheritProjectContext: inheritProjectContext ?? true,
|
|
62
|
+
inheritSkills: inheritSkills ?? true,
|
|
63
|
+
});
|
|
64
|
+
if (rewritten === event.systemPrompt) return;
|
|
65
|
+
return { systemPrompt: rewritten };
|
|
66
|
+
});
|
|
67
|
+
}
|