pi-subagents 0.18.1 → 0.19.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/settings.ts CHANGED
@@ -8,6 +8,7 @@ import type { AgentConfig } from "./agents.ts";
8
8
  import { normalizeSkillInput } from "./skills.ts";
9
9
  import { CHAIN_RUNS_DIR } from "./types.ts";
10
10
  const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
11
+ const INITIAL_PROGRESS_CONTENT = "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n";
11
12
 
12
13
  // =============================================================================
13
14
  // Behavior Resolution Types
@@ -224,6 +225,10 @@ function resolveChainPath(filePath: string, chainDir: string): string {
224
225
  * Build chain instructions from resolved behavior.
225
226
  * These are appended to the task to tell the agent what to read/write.
226
227
  */
228
+ export function writeInitialProgressFile(progressDir: string): void {
229
+ fs.writeFileSync(path.join(progressDir, "progress.md"), INITIAL_PROGRESS_CONTENT);
230
+ }
231
+
227
232
  export function buildChainInstructions(
228
233
  behavior: ResolvedStepBehavior,
229
234
  chainDir: string,
package/slash-commands.ts CHANGED
@@ -8,6 +8,7 @@ import { AgentManagerComponent, type ManagerResult } from "./agent-manager.ts";
8
8
  import { SubagentsStatusComponent } from "./subagents-status.ts";
9
9
  import { discoverAvailableSkills } from "./skills.ts";
10
10
  import type { SubagentParamsLike } from "./subagent-executor.ts";
11
+ import { isParallelStep, type ChainStep } from "./settings.ts";
11
12
  import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
12
13
  import {
13
14
  applySlashUpdate,
@@ -308,48 +309,43 @@ async function openAgentManager(
308
309
  );
309
310
  if (!result) return;
310
311
 
312
+ const launchOptions: SubagentParamsLike = {
313
+ clarify: !result.skipClarify && !result.background,
314
+ agentScope: "both",
315
+ ...(result.fork ? { context: "fork" as const } : {}),
316
+ ...(result.background ? { async: true } : {}),
317
+ };
318
+
311
319
  if (result.action === "chain") {
312
320
  const chain = result.agents.map((name, i) => ({
313
321
  agent: name,
314
322
  ...(i === 0 ? { task: result.task } : {}),
315
323
  }));
316
- await runSlashSubagent(pi, ctx, {
317
- chain,
318
- task: result.task,
319
- clarify: true,
320
- agentScope: "both",
321
- });
324
+ await runSlashSubagent(pi, ctx, { chain, task: result.task, ...launchOptions });
322
325
  return;
323
326
  }
324
327
 
325
328
  if (result.action === "launch") {
326
- await runSlashSubagent(pi, ctx, {
327
- agent: result.agent,
328
- task: result.task,
329
- clarify: !result.skipClarify,
330
- agentScope: "both",
331
- });
329
+ await runSlashSubagent(pi, ctx, { agent: result.agent, task: result.task, ...launchOptions });
332
330
  } else if (result.action === "launch-chain") {
333
- const chainParam = result.chain.steps.map((step) => ({
334
- agent: step.agent,
335
- task: step.task || undefined,
336
- output: step.output,
337
- reads: step.reads,
338
- progress: step.progress,
339
- skill: step.skills,
340
- model: step.model,
341
- }));
342
- await runSlashSubagent(pi, ctx, {
343
- chain: chainParam,
344
- task: result.task,
345
- clarify: !result.skipClarify,
346
- agentScope: "both",
331
+ const chainParam = (result.chain.steps as unknown as ChainStep[]).map((step) => {
332
+ if (isParallelStep(step)) return result.worktree ? { ...step, worktree: true } : { ...step };
333
+ return {
334
+ agent: step.agent,
335
+ task: step.task || undefined,
336
+ output: step.output,
337
+ reads: step.reads,
338
+ progress: step.progress,
339
+ skill: step.skill ?? (step as typeof step & { skills?: string[] | false }).skills,
340
+ model: step.model,
341
+ };
347
342
  });
343
+ await runSlashSubagent(pi, ctx, { chain: chainParam, task: result.task, ...launchOptions });
348
344
  } else if (result.action === "parallel") {
349
345
  await runSlashSubagent(pi, ctx, {
350
346
  tasks: result.tasks,
351
- clarify: !result.skipClarify,
352
- agentScope: "both",
347
+ ...launchOptions,
348
+ ...(result.worktree ? { worktree: true } : {}),
353
349
  });
354
350
  }
355
351
  }
@@ -503,8 +499,11 @@ export function registerSlashCommands(
503
499
  const tasks = parsed.steps.map(({ name, config, task: stepTask }) => ({
504
500
  agent: name,
505
501
  task: stepTask ?? parsed.task,
502
+ ...(config.output !== undefined ? { output: config.output } : {}),
503
+ ...(config.reads !== undefined ? { reads: config.reads } : {}),
506
504
  ...(config.model ? { model: config.model } : {}),
507
505
  ...(config.skill !== undefined ? { skill: config.skill } : {}),
506
+ ...(config.progress !== undefined ? { progress: config.progress } : {}),
508
507
  }));
509
508
  const params: SubagentParamsLike = { tasks, clarify: false, agentScope: "both" };
510
509
  if (bg) params.async = true;
@@ -14,16 +14,20 @@ import { resolveModelCandidate } from "./model-fallback.ts";
14
14
  import { aggregateParallelOutputs } from "./parallel-utils.ts";
15
15
  import { recordRun } from "./run-history.ts";
16
16
  import {
17
+ buildChainInstructions,
18
+ writeInitialProgressFile,
17
19
  getStepAgents,
18
20
  isParallelStep,
19
21
  resolveStepBehavior,
20
22
  type ChainStep,
23
+ type ResolvedStepBehavior,
21
24
  type SequentialStep,
25
+ type StepOverrides,
22
26
  } from "./settings.ts";
23
27
  import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
24
28
  import { executeAsyncChain, executeAsyncSingle, isAsyncAvailable } from "./async-execution.ts";
25
29
  import { createForkContextResolver } from "./fork-context.ts";
26
- import { applyIntercomBridgeToAgent, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "./intercom-bridge.ts";
30
+ import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "./intercom-bridge.ts";
27
31
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "./subagent-control.ts";
28
32
  import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
29
33
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "./utils.ts";
@@ -46,6 +50,7 @@ import {
46
50
  type ControlEvent,
47
51
  type Details,
48
52
  type ExtensionConfig,
53
+ type IntercomEventBus,
49
54
  type MaxOutputConfig,
50
55
  type ResolvedControlConfig,
51
56
  type SingleResult,
@@ -68,6 +73,9 @@ interface TaskParam {
68
73
  task: string;
69
74
  cwd?: string;
70
75
  count?: number;
76
+ output?: string | boolean;
77
+ reads?: string[] | boolean;
78
+ progress?: boolean;
71
79
  model?: string;
72
80
  skill?: string | string[] | boolean;
73
81
  }
@@ -544,6 +552,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
544
552
  cwd: task.cwd,
545
553
  ...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),
546
554
  ...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
555
+ ...(task.output === true ? (agentConfigs[index]?.output ? { output: agentConfigs[index]!.output } : {}) : task.output !== undefined ? { output: task.output } : {}),
556
+ ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
557
+ ...(task.progress !== undefined ? { progress: task.progress } : {}),
547
558
  }));
548
559
  return executeAsyncChain(id, {
549
560
  chain: [{
@@ -669,6 +680,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
669
680
  task: params.task,
670
681
  agents,
671
682
  ctx,
683
+ intercomEvents: deps.pi.events,
672
684
  signal,
673
685
  runId,
674
686
  cwd: effectiveCwd,
@@ -741,6 +753,7 @@ interface ForegroundParallelRunInput {
741
753
  taskTexts: string[];
742
754
  agents: AgentConfig[];
743
755
  ctx: ExtensionContext;
756
+ intercomEvents: IntercomEventBus;
744
757
  signal: AbortSignal;
745
758
  runId: string;
746
759
  sessionDirForIndex: (idx?: number) => string | undefined;
@@ -749,12 +762,12 @@ interface ForegroundParallelRunInput {
749
762
  artifactConfig: ArtifactConfig;
750
763
  artifactsDir: string;
751
764
  maxOutput?: MaxOutputConfig;
752
- paramsCwd?: string;
765
+ paramsCwd: string;
753
766
  maxSubagentDepths: number[];
754
767
  availableModels: ModelInfo[];
755
768
  modelOverrides: (string | undefined)[];
756
- skillOverrides: (string[] | false | undefined)[];
757
769
  behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
770
+ firstProgressIndex: number;
758
771
  controlConfig: ResolvedControlConfig;
759
772
  onControlEvent?: (event: ControlEvent) => void;
760
773
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
@@ -822,12 +835,11 @@ function buildChainWorktreeTaskCwdError(chain: ChainStep[], sharedCwd: string):
822
835
 
823
836
  function resolveParallelTaskCwd(
824
837
  task: TaskParam,
825
- paramsCwd: string | undefined,
838
+ paramsCwd: string,
826
839
  worktreeSetup: WorktreeSetup | undefined,
827
840
  index: number,
828
- ): string | undefined {
841
+ ): string {
829
842
  if (worktreeSetup) return worktreeSetup.worktrees[index]!.agentCwd;
830
- if (!paramsCwd) return task.cwd;
831
843
  return resolveChildCwd(paramsCwd, task.cwd);
832
844
  }
833
845
 
@@ -842,11 +854,46 @@ function buildParallelWorktreeSuffix(
842
854
  return formatWorktreeDiffSummary(diffs);
843
855
  }
844
856
 
857
+ function findDuplicateParallelOutputPath(input: {
858
+ tasks: TaskParam[];
859
+ behaviors: ResolvedStepBehavior[];
860
+ paramsCwd: string;
861
+ ctxCwd: string;
862
+ worktreeSetup?: WorktreeSetup;
863
+ }): string | undefined {
864
+ const seen = new Map<string, { index: number; agent: string }>();
865
+ for (let index = 0; index < input.tasks.length; index++) {
866
+ const behavior = input.behaviors[index];
867
+ if (!behavior?.output) continue;
868
+ const task = input.tasks[index]!;
869
+ const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
870
+ const outputPath = resolveSingleOutputPath(behavior.output, input.ctxCwd, taskCwd);
871
+ if (!outputPath) continue;
872
+ const previous = seen.get(outputPath);
873
+ if (previous) {
874
+ return `Parallel tasks ${previous.index + 1} (${previous.agent}) and ${index + 1} (${task.agent}) resolve output to the same path: ${outputPath}. Use distinct output paths.`;
875
+ }
876
+ seen.set(outputPath, { index, agent: task.agent });
877
+ }
878
+ return undefined;
879
+ }
880
+
845
881
  async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
846
882
  return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
847
- const overrideSkills = input.skillOverrides[index];
848
- const effectiveSkills = overrideSkills === undefined ? input.behaviors[index]?.skills : overrideSkills;
883
+ const behavior = input.behaviors[index];
884
+ const effectiveSkills = behavior?.skills;
849
885
  const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
886
+ const readInstructions = behavior
887
+ ? buildChainInstructions({ ...behavior, output: false, progress: false }, taskCwd, false)
888
+ : { prefix: "", suffix: "" };
889
+ const progressInstructions = behavior
890
+ ? buildChainInstructions({ ...behavior, output: false, reads: false }, input.paramsCwd, index === input.firstProgressIndex)
891
+ : { prefix: "", suffix: "" };
892
+ const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd);
893
+ const taskText = injectSingleOutputInstruction(
894
+ `${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
895
+ outputPath,
896
+ );
850
897
  const interruptController = new AbortController();
851
898
  if (input.foregroundControl) {
852
899
  input.foregroundControl.currentAgent = task.agent;
@@ -861,10 +908,13 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
861
908
  return true;
862
909
  };
863
910
  }
864
- return runSync(input.ctx.cwd, input.agents, task.agent, input.taskTexts[index]!, {
911
+ const agentConfig = input.agents.find((agent) => agent.name === task.agent);
912
+ return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
865
913
  cwd: taskCwd,
866
914
  signal: input.signal,
867
915
  interruptSignal: interruptController.signal,
916
+ allowIntercomDetach: agentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
917
+ intercomEvents: input.intercomEvents,
868
918
  runId: input.runId,
869
919
  index,
870
920
  sessionDir: input.sessionDirForIndex(index),
@@ -873,6 +923,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
873
923
  artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
874
924
  artifactConfig: input.artifactConfig,
875
925
  maxOutput: input.maxOutput,
926
+ outputPath,
876
927
  maxSubagentDepth: input.maxSubagentDepths[index],
877
928
  controlConfig: input.controlConfig,
878
929
  onControlEvent: input.onControlEvent,
@@ -983,16 +1034,23 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
983
1034
  fullId: `${m.provider}/${m.id}`,
984
1035
  }));
985
1036
  let taskTexts = tasks.map((t) => t.task);
986
- const modelOverrides: (string | undefined)[] = tasks.map((t, i) =>
987
- resolveModelCandidate(t.model ?? agentConfigs[i]?.model, availableModels, currentProvider),
988
- );
989
1037
  const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
990
1038
  normalizeSkillInput(t.skill),
991
1039
  );
1040
+ const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
1041
+ ...(task.output !== undefined ? { output: task.output === true ? agentConfigs[index]?.output ?? false : task.output } : {}),
1042
+ ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
1043
+ ...(task.progress !== undefined ? { progress: task.progress } : {}),
1044
+ ...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
1045
+ ...(task.model ? { model: task.model } : {}),
1046
+ }));
1047
+ const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
1048
+ resolveModelCandidate(behaviorOverrides[i]?.model ?? agentConfigs[i]?.model, availableModels, currentProvider),
1049
+ );
992
1050
 
993
1051
  if (params.clarify === true && ctx.hasUI) {
994
1052
  const behaviors = agentConfigs.map((c, i) =>
995
- resolveStepBehavior(c, { skills: skillOverrides[i] }),
1053
+ resolveStepBehavior(c, behaviorOverrides[i]!),
996
1054
  );
997
1055
  const availableSkills = discoverAvailableSkills(effectiveCwd);
998
1056
 
@@ -1021,8 +1079,17 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1021
1079
  taskTexts = result.templates;
1022
1080
  for (let i = 0; i < result.behaviorOverrides.length; i++) {
1023
1081
  const override = result.behaviorOverrides[i];
1024
- if (override?.model) modelOverrides[i] = override.model;
1025
- if (override?.skills !== undefined) skillOverrides[i] = override.skills;
1082
+ if (override?.model) {
1083
+ modelOverrides[i] = override.model;
1084
+ behaviorOverrides[i]!.model = override.model;
1085
+ }
1086
+ if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
1087
+ if (override?.reads !== undefined) behaviorOverrides[i]!.reads = override.reads;
1088
+ if (override?.progress !== undefined) behaviorOverrides[i]!.progress = override.progress;
1089
+ if (override?.skills !== undefined) {
1090
+ skillOverrides[i] = override.skills;
1091
+ behaviorOverrides[i]!.skills = override.skills;
1092
+ }
1026
1093
  }
1027
1094
 
1028
1095
  if (result.runInBackground) {
@@ -1046,6 +1113,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1046
1113
  cwd: t.cwd,
1047
1114
  ...(modelOverrides[i] ? { model: modelOverrides[i] } : {}),
1048
1115
  ...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
1116
+ ...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i]!.output } : {}),
1117
+ ...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
1118
+ ...(behaviorOverrides[i]?.progress !== undefined ? { progress: behaviorOverrides[i]!.progress } : {}),
1049
1119
  }));
1050
1120
  return executeAsyncChain(id, {
1051
1121
  chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }],
@@ -1070,7 +1140,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1070
1140
  }
1071
1141
  }
1072
1142
 
1073
- const behaviors = agentConfigs.map((config) => resolveStepBehavior(config, {}));
1143
+ const behaviors = agentConfigs.map((config, index) => resolveStepBehavior(config, behaviorOverrides[index]!));
1144
+ const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress);
1074
1145
  const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
1075
1146
  const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
1076
1147
  const foregroundControl = deps.state.foregroundControls.get(runId);
@@ -1085,6 +1156,18 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1085
1156
  if (errorResult) return errorResult;
1086
1157
 
1087
1158
  try {
1159
+ const duplicateOutputError = findDuplicateParallelOutputPath({
1160
+ tasks,
1161
+ behaviors,
1162
+ paramsCwd: effectiveCwd,
1163
+ ctxCwd: ctx.cwd,
1164
+ worktreeSetup,
1165
+ });
1166
+ if (duplicateOutputError) return buildParallelModeError(duplicateOutputError);
1167
+
1168
+ const parallelProgressPrecreated = firstProgressIndex !== -1;
1169
+ if (parallelProgressPrecreated) writeInitialProgressFile(effectiveCwd);
1170
+
1088
1171
  if (params.context === "fork") {
1089
1172
  for (let i = 0; i < taskTexts.length; i++) {
1090
1173
  taskTexts[i] = wrapForkTask(taskTexts[i]!);
@@ -1096,6 +1179,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1096
1179
  taskTexts,
1097
1180
  agents,
1098
1181
  ctx,
1182
+ intercomEvents: deps.pi.events,
1099
1183
  signal,
1100
1184
  runId,
1101
1185
  sessionDirForIndex,
@@ -1107,8 +1191,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1107
1191
  paramsCwd: effectiveCwd,
1108
1192
  availableModels,
1109
1193
  modelOverrides,
1110
- skillOverrides,
1111
1194
  behaviors,
1195
+ firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
1112
1196
  controlConfig,
1113
1197
  onControlEvent,
1114
1198
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
@@ -1345,7 +1429,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1345
1429
  cwd: effectiveCwd,
1346
1430
  signal,
1347
1431
  interruptSignal: interruptController.signal,
1348
- allowIntercomDetach: agentConfig.systemPrompt?.includes("Intercom orchestration channel:") === true,
1432
+ allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
1349
1433
  intercomEvents: deps.pi.events,
1350
1434
  runId,
1351
1435
  sessionDir: sessionDirForIndex(0),
@@ -51,6 +51,7 @@ import {
51
51
  formatWorktreeTaskCwdConflict,
52
52
  type WorktreeSetup,
53
53
  } from "./worktree.ts";
54
+ import { writeInitialProgressFile } from "./settings.ts";
54
55
 
55
56
  interface SubagentRunConfig {
56
57
  id: string;
@@ -817,6 +818,12 @@ function appendParallelWorktreeSummary(
817
818
  return `${previousOutput}\n\n${diffSummary}`;
818
819
  }
819
820
 
821
+ function ensureParallelProgressFile(cwd: string, group: Extract<RunnerStep, { parallel: SubagentStep[] }>): void {
822
+ const progressPath = path.join(cwd, "progress.md");
823
+ if (!group.parallel.some((task) => task.task.includes(`Update progress at: ${progressPath}`))) return;
824
+ writeInitialProgressFile(cwd);
825
+ }
826
+
820
827
  async function runSubagent(config: SubagentRunConfig): Promise<void> {
821
828
  const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
822
829
  config;
@@ -1039,6 +1046,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1039
1046
  }
1040
1047
 
1041
1048
  try {
1049
+ if (group.worktree) ensureParallelProgressFile(cwd, group);
1042
1050
  const groupStartTime = Date.now();
1043
1051
  markParallelGroupRunning({
1044
1052
  statusPayload,