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/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; // Room for single ellipsis character
37
+ const targetWidth = maxWidth - 1;
39
38
  let result = "";
40
39
  let currentWidth = 0;
41
- let activeStyles: string[] = []; // Track ALL active styles (not just last)
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 = []; // Reset clears all styles
50
+ activeStyles = [];
53
51
  } else {
54
- activeStyles.push(code); // Stack styles (bold + color, etc.)
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; // Skip re-render, nothing changed
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", "X");
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", `⚠️ ${r.skillsWarning}`), w), 0, 0));
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", "X");
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: pending`), 0, 0));
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", ` ⚠️ ${r.skillsWarning}`), w), 0, 0));
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.js";
360
- export { aggregateParallelOutputs } from "./parallel-utils.js";
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\n📄 Output saved to: ${params.savedPath}`;
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\n⚠️ Failed to save output to: ${params.outputPath}\n${params.saveError}`;
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.js";
5
- import { AgentManagerComponent, type ManagerResult } from "./agent-manager.js";
6
- import { SubagentsStatusComponent } from "./subagents-status.js";
7
- import { discoverAvailableSkills } from "./skills.js";
8
- import type { SubagentParamsLike } from "./subagent-executor.js";
9
- import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.js";
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.js";
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.js";
25
+ } from "./types.ts";
26
26
 
27
27
  interface InlineConfig {
28
28
  output?: string | false;
@@ -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[], params.cwd ?? ctx.cwd);
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: params.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: params.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: params.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: params.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 ?? sharedCwd;
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 ?? paramsCwd;
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(params.cwd ?? ctx.cwd);
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: params.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: params.cwd,
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(params.cwd ?? ctx.cwd);
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: params.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, params.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: params.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, params, ctx);
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(params);
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(ctx.cwd, scope).agents;
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
+ }