pi-cohort 3.0.2 → 4.0.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.
@@ -64,7 +64,7 @@ When asked to review for over-engineering, bloat, or "what can we delete", run a
64
64
 
65
65
  ## Working rules
66
66
  - Read the plan, progress, and relevant files first when available.
67
- - Repo-local `progress.md` files are allowed scratch/memory files. Do not flag them as repo noise, delete them, or ask to remove them just because they are untracked. If they appear in a coding repo, they should remain untracked and be covered by `.gitignore`.
67
+ - Top-level progress files exist only when the caller explicitly enables them. Do not ask consumer repositories to add cohort scratch files to `.gitignore`.
68
68
  - Use `bash` only for read-only inspection (e.g., `git diff`, `git log`, `git show`, test runs).
69
69
  - Do not invent issues. Only report problems you can justify from evidence.
70
70
  - Prefer small corrective edits over broad rewrites.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cohort",
3
- "version": "3.0.2",
3
+ "version": "4.0.0",
4
4
  "description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
5
5
  "author": "Jacek Juraszek",
6
6
  "license": "MIT",
@@ -119,7 +119,7 @@ const TaskItem = Type.Object({
119
119
  output: Type.Optional(OutputOverride),
120
120
  outputMode: Type.Optional(OutputModeOverride),
121
121
  reads: Type.Optional(ReadsOverride),
122
- progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking for this task" })),
122
+ progress: Type.Optional(Type.Boolean({ description: "Omit or set false to disable progress.md tracking for this task; true enables it." })),
123
123
  model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
124
124
  skill: Type.Optional(SkillOverride),
125
125
  acceptance: Type.Optional(AcceptanceOverride),
@@ -297,7 +297,7 @@ export const SubagentParams = Type.Object({
297
297
  { type: "string" },
298
298
  { type: "boolean" },
299
299
  ],
300
- description: "Output file for single agent (string), or false to disable. Relative paths resolve against cwd.",
300
+ description: "Omit or set false to disable file output; true uses the agent-configured filename; a string sets the output path. Relative paths resolve against cwd.",
301
301
  })),
302
302
  outputMode: Type.Optional(OutputModeOverride),
303
303
  skill: Type.Optional(SkillOverride),
@@ -494,7 +494,7 @@ export class ChainClarifyComponent implements Component {
494
494
  return;
495
495
  }
496
496
 
497
- if (data === "w" && this.mode !== 'parallel') {
497
+ if (data === "w") {
498
498
  this.enterEditMode("output");
499
499
  return;
500
500
  }
@@ -504,11 +504,15 @@ export class ChainClarifyComponent implements Component {
504
504
  return;
505
505
  }
506
506
 
507
- if (data === "p" && this.mode === 'chain') {
508
- const anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);
509
- const newState = !anyEnabled;
510
- for (let i = 0; i < this.agentConfigs.length; i++) {
511
- this.updateBehavior(i, "progress", newState);
507
+ if (data === "p" && this.mode !== 'single') {
508
+ if (this.mode === 'chain') {
509
+ const anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);
510
+ const newState = !anyEnabled;
511
+ for (let i = 0; i < this.agentConfigs.length; i++) {
512
+ this.updateBehavior(i, "progress", newState);
513
+ }
514
+ } else {
515
+ this.updateBehavior(this.selectedStep, "progress", !this.getEffectiveBehavior(this.selectedStep).progress);
512
516
  }
513
517
  this.tui.requestRender();
514
518
  return;
@@ -532,7 +536,9 @@ export class ChainClarifyComponent implements Component {
532
536
  buffer = template.split("\n")[0] ?? "";
533
537
  } else if (mode === "output") {
534
538
  const behavior = this.getEffectiveBehavior(this.selectedStep);
535
- buffer = behavior.output === false ? "" : (behavior.output || "");
539
+ buffer = behavior.output === false
540
+ ? (this.mode === "chain" ? "" : this.agentConfigs[this.selectedStep]?.output ?? "")
541
+ : (behavior.output || "");
536
542
  } else if (mode === "reads") {
537
543
  const behavior = this.getEffectiveBehavior(this.selectedStep);
538
544
  buffer = behavior.reads === false ? "" : (behavior.reads?.join(", ") || "");
@@ -1108,7 +1114,7 @@ export class ChainClarifyComponent implements Component {
1108
1114
  case 'single':
1109
1115
  return ` [Enter] Run • [Esc] Cancel • e m t w s ${bgLabel} `;
1110
1116
  case 'parallel':
1111
- return ` [Enter] Run • [Esc] Cancel • e m t s ${bgLabel} • ↑↓ Nav `;
1117
+ return ` [Enter] Run • [Esc] Cancel • e m t w p s ${bgLabel} • ↑↓ Nav `;
1112
1118
  case 'chain':
1113
1119
  return ` [Enter] Run • [Esc] Cancel • e m t w r p s ${bgLabel} • ↑↓ Nav `;
1114
1120
  }
@@ -1207,6 +1213,16 @@ export class ChainClarifyComponent implements Component {
1207
1213
  lines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));
1208
1214
 
1209
1215
  const behavior = this.getEffectiveBehavior(i);
1216
+ const writesValue = behavior.output === false
1217
+ ? th.fg("dim", "(disabled)")
1218
+ : (behavior.output || th.fg("dim", "(none)"));
1219
+ const writesLabel = th.fg("dim", "writes: ");
1220
+ lines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));
1221
+
1222
+ const progressValue = behavior.progress ? th.fg("success", "on") : th.fg("dim", "off");
1223
+ const progressLabel = th.fg("dim", "progress: ");
1224
+ lines.push(this.row(` ${progressLabel}${progressValue}`));
1225
+
1210
1226
  const skillsValue = behavior.skills === false
1211
1227
  ? th.fg("dim", "(disabled)")
1212
1228
  : (behavior.skills?.length ? behavior.skills.join(", ") : th.fg("dim", "(none)"));
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
5
5
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
6
- import { type AgentConfig, type AgentScope } from "../../agents/agents.ts";
6
+ import type { AgentConfig, AgentScope } from "../../agents/agents.ts";
7
7
  import { getArtifactsDir } from "../../shared/artifacts.ts";
8
8
  import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
9
9
  import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
@@ -37,7 +37,7 @@ import { createForkContextResolver } from "../../shared/fork-context.ts";
37
37
  import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
38
38
  import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
39
39
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
40
- import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
40
+ import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeTopLevelOutput, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
41
41
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "../../shared/utils.ts";
42
42
  import {
43
43
  attachNestedChildrenToResultChildren,
@@ -1102,10 +1102,10 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1102
1102
  cwd: task.cwd,
1103
1103
  ...(modelOverrides[index] ? { model: modelOverrides[index] } : {}),
1104
1104
  ...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
1105
- ...(task.output === true ? (agentConfigs[index]?.output ? { output: agentConfigs[index]!.output } : {}) : task.output !== undefined ? { output: task.output } : {}),
1105
+ output: normalizeTopLevelOutput(task.output, agentConfigs[index]?.output),
1106
1106
  ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
1107
1107
  ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
1108
- ...(task.progress !== undefined ? { progress: task.progress } : {}),
1108
+ progress: task.progress ?? false,
1109
1109
  ...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
1110
1110
  }));
1111
1111
  return executeAsyncChain(id, {
@@ -1174,8 +1174,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
1174
1174
  details: { mode: "single" as const, results: [] },
1175
1175
  };
1176
1176
  }
1177
- const rawOutput = params.output !== undefined ? params.output : a.output;
1178
- const effectiveOutput = normalizeSingleOutputOverride(rawOutput, a.output);
1177
+ const effectiveOutput = normalizeTopLevelOutput(params.output, a.output);
1179
1178
  const effectiveOutputMode = params.outputMode ?? "inline";
1180
1179
  const normalizedSkills = normalizeSkillInput(params.skill);
1181
1180
  const skills = normalizedSkills === false ? [] : normalizedSkills;
@@ -1630,10 +1629,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1630
1629
  normalizeSkillInput(t.skill),
1631
1630
  );
1632
1631
  const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
1633
- ...(task.output !== undefined ? { output: task.output === true ? agentConfigs[index]?.output ?? false : task.output } : {}),
1632
+ output: normalizeTopLevelOutput(task.output, agentConfigs[index]?.output),
1634
1633
  ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
1635
1634
  ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
1636
- ...(task.progress !== undefined ? { progress: task.progress } : {}),
1635
+ progress: task.progress ?? false,
1637
1636
  ...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
1638
1637
  ...(task.model ? { model: task.model } : {}),
1639
1638
  }));
@@ -1939,8 +1938,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1939
1938
  currentProvider,
1940
1939
  );
1941
1940
  let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
1942
- const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
1943
- let effectiveOutput = normalizeSingleOutputOverride(rawOutput, agentConfig.output);
1941
+ let effectiveOutput = normalizeTopLevelOutput(params.output, agentConfig.output);
1944
1942
  const effectiveOutputMode = params.outputMode ?? "inline";
1945
1943
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
1946
1944
  const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, agentConfig.maxSubagentDepth);
@@ -1974,7 +1972,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1974
1972
  task = result.templates[0]!;
1975
1973
  const override = result.behaviorOverrides[0];
1976
1974
  if (override?.model) modelOverride = override.model;
1977
- if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
1975
+ if (override?.output !== undefined) effectiveOutput = normalizeTopLevelOutput(override.output, agentConfig.output);
1978
1976
  if (override?.skills !== undefined) skillOverride = override.skills;
1979
1977
 
1980
1978
  if (result.runInBackground) {
@@ -18,6 +18,15 @@ export function normalizeSingleOutputOverride(
18
18
  return undefined;
19
19
  }
20
20
 
21
+ export function normalizeTopLevelOutput(
22
+ output: string | boolean | undefined,
23
+ defaultOutput: string | undefined,
24
+ ): string | false {
25
+ if (output === undefined || (typeof output === "string" && output.trim().length === 0)) return false;
26
+ const normalizedOutput = normalizeSingleOutputOverride(output, defaultOutput);
27
+ return typeof normalizedOutput === "string" && normalizedOutput.trim().length === 0 ? false : normalizedOutput ?? false;
28
+ }
29
+
21
30
  export function resolveSingleOutputPath(
22
31
  output: string | boolean | undefined,
23
32
  runtimeCwd: string,