pi-cohort 7.2.3 ā 7.3.1
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 +12 -0
- package/README.md +30 -1
- package/package.json +1 -1
- package/skills/pi-cohort/SKILL.md +10 -0
- package/skills/pi-cohort/reference/config-fields.md +1 -0
- package/src/agents/agent-management.ts +9 -1
- package/src/agents/agents.ts +2 -1
- package/src/agents/chain-serializer.ts +14 -0
- package/src/extension/schemas.ts +4 -0
- package/src/runs/background/async-execution.ts +3 -0
- package/src/runs/background/notify.ts +8 -2
- package/src/runs/background/subagent-runner.ts +57 -21
- package/src/runs/foreground/chain-execution.ts +148 -54
- package/src/runs/shared/chain-outputs.ts +49 -9
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/parallel-utils.ts +3 -0
- package/src/runs/shared/stop-conditions.ts +100 -0
- package/src/runs/shared/worktree.ts +9 -4
- package/src/shared/formatters.ts +64 -15
- package/src/shared/settings.ts +4 -1
- package/src/shared/types.ts +37 -0
- package/src/slash/slash-commands.ts +1 -0
|
@@ -18,7 +18,7 @@ interface WorktreeInfo {
|
|
|
18
18
|
syntheticPaths: string[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
interface WorktreeDiff {
|
|
21
|
+
export interface WorktreeDiff {
|
|
22
22
|
index: number;
|
|
23
23
|
agent: string;
|
|
24
24
|
branch: string;
|
|
@@ -448,11 +448,10 @@ function captureWorktreeDiff(
|
|
|
448
448
|
removeSyntheticPathsBeforeDiff(worktree);
|
|
449
449
|
runGitChecked(worktree.path, ["add", "-A"]);
|
|
450
450
|
const diffStat = runGitChecked(worktree.path, ["diff", "--cached", "--stat", setup.baseCommit]).trim();
|
|
451
|
-
|
|
451
|
+
runGitChecked(worktree.path, ["diff", "--cached", "--binary", `--output=${path.resolve(patchPath)}`, setup.baseCommit]);
|
|
452
452
|
const numstat = runGitChecked(worktree.path, ["diff", "--cached", "--numstat", setup.baseCommit]);
|
|
453
|
-
fs.writeFileSync(patchPath, patch, "utf-8");
|
|
454
453
|
|
|
455
|
-
if (
|
|
454
|
+
if (fs.statSync(patchPath).size === 0) {
|
|
456
455
|
return emptyDiff(worktree.index, agent, worktree.branch, patchPath);
|
|
457
456
|
}
|
|
458
457
|
|
|
@@ -490,6 +489,12 @@ function hasWorktreeChanges(diff: WorktreeDiff): boolean {
|
|
|
490
489
|
return diff.filesChanged > 0 || diff.insertions > 0 || diff.deletions > 0 || diff.diffStat.trim().length > 0;
|
|
491
490
|
}
|
|
492
491
|
|
|
492
|
+
/** The patch a later chain step may reference: only a clean capture with real changes qualifies. */
|
|
493
|
+
export function patchPathForDiff(diff: WorktreeDiff): string | undefined {
|
|
494
|
+
if (diff.captureError) return undefined;
|
|
495
|
+
return hasWorktreeChanges(diff) ? diff.patchPath : undefined;
|
|
496
|
+
}
|
|
497
|
+
|
|
493
498
|
export function createWorktrees(cwd: string, runId: string, count: number, options?: CreateWorktreesOptions): WorktreeSetup {
|
|
494
499
|
const repo = resolveRepoState(cwd);
|
|
495
500
|
const setupHook = resolveWorktreeSetupHook(repo.toplevel, options?.setupHook);
|
package/src/shared/formatters.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
-
import type { Usage, SingleResult } from "./types.ts";
|
|
7
|
+
import type { Usage, SingleResult, ChainTaskOutputEntry, StopRecord } from "./types.ts";
|
|
8
|
+
import { formatStopCondition } from "../runs/shared/stop-conditions.ts";
|
|
8
9
|
import type { ChainStep } from "./settings.ts";
|
|
9
10
|
import { isDynamicParallelStep, isParallelStep } from "./settings.ts";
|
|
10
11
|
import { splitKnownThinkingSuffix, THINKING_LEVELS } from "./model-info.ts";
|
|
@@ -52,15 +53,62 @@ export function formatDuration(ms: number): string {
|
|
|
52
53
|
return `${Math.floor(ms / 60000)}m${Math.floor((ms % 60000) / 1000)}s`;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
export interface ChainSummaryOptions {
|
|
57
|
+
stop?: StopRecord;
|
|
58
|
+
outputs?: ChainTaskOutputEntry[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderTaskHeading(entry: ChainTaskOutputEntry): string {
|
|
62
|
+
const asPart = entry.as ? `, as ${entry.as}` : "";
|
|
63
|
+
const base = `#### Task ${entry.taskIndex + 1} (${entry.agent}${asPart})`;
|
|
64
|
+
if (entry.exitCode === -1) return `${base} - skipped (fail-fast)`;
|
|
65
|
+
if (entry.exitCode !== 0) return `${base} - failed`;
|
|
66
|
+
return base;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** `## Outputs`, grouped by step in execution order; skipped tasks get a heading only, failed tasks their error line. */
|
|
70
|
+
export function buildChainOutputsSection(entries: ChainTaskOutputEntry[]): string {
|
|
71
|
+
const lines: string[] = ["## Outputs"];
|
|
72
|
+
let currentStep = -1;
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (entry.stepIndex !== currentStep) {
|
|
75
|
+
currentStep = entry.stepIndex;
|
|
76
|
+
lines.push(`### Step ${currentStep + 1}`);
|
|
77
|
+
}
|
|
78
|
+
lines.push(renderTaskHeading(entry));
|
|
79
|
+
if (entry.exitCode === -1) continue;
|
|
80
|
+
if (entry.exitCode !== 0) {
|
|
81
|
+
lines.push(entry.error || "failed");
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (entry.text) lines.push(entry.text);
|
|
85
|
+
if (entry.structured !== undefined) lines.push("```json", JSON.stringify(entry.structured), "```");
|
|
86
|
+
}
|
|
87
|
+
return lines.join("\n");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function buildChainStopSection(stop: StopRecord): string {
|
|
91
|
+
return `## Stop\n\`\`\`json\n${JSON.stringify(stop, null, 2)}\n\`\`\``;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function appendChainSections(text: string, options: ChainSummaryOptions | undefined): string {
|
|
95
|
+
if (!options) return text;
|
|
96
|
+
const sections: string[] = [];
|
|
97
|
+
if (options.stop) sections.push(buildChainStopSection(options.stop));
|
|
98
|
+
if (options.outputs) sections.push(buildChainOutputsSection(options.outputs));
|
|
99
|
+
return sections.length ? `${text}\n\n${sections.join("\n\n")}\n` : text;
|
|
100
|
+
}
|
|
101
|
+
|
|
55
102
|
/**
|
|
56
|
-
* Build a summary string for a completed/failed chain
|
|
103
|
+
* Build a summary string for a completed/failed/stopped chain
|
|
57
104
|
*/
|
|
58
105
|
export function buildChainSummary(
|
|
59
106
|
steps: ChainStep[],
|
|
60
107
|
results: SingleResult[],
|
|
61
108
|
chainDir: string,
|
|
62
|
-
status: "completed" | "failed",
|
|
109
|
+
status: "completed" | "failed" | "stopped",
|
|
63
110
|
failedStep?: { index: number; error: string },
|
|
111
|
+
options?: ChainSummaryOptions,
|
|
64
112
|
): string {
|
|
65
113
|
const stepNames = steps
|
|
66
114
|
.map((step) => (isParallelStep(step) ? `parallel[${step.parallel.length}]` : isDynamicParallelStep(step) ? `expand:${step.parallel.agent}` : step.agent))
|
|
@@ -77,20 +125,21 @@ export function buildChainSummary(
|
|
|
77
125
|
}
|
|
78
126
|
const skillsLine = allSkills.size > 0 ? `š§ Skills: ${[...allSkills].join(", ")}` : "";
|
|
79
127
|
|
|
80
|
-
|
|
81
|
-
const stepWord = results.length === 1 ? "step" : "steps";
|
|
82
|
-
return `ā
Chain completed: ${stepNames} (${results.length} ${stepWord}, ${durationStr})${skillsLine ? `\n${skillsLine}` : ""}
|
|
83
|
-
|
|
84
|
-
š Progress: ${hasProgress ? progressPath : "(none)"}
|
|
85
|
-
š Artifacts: ${chainDir}`;
|
|
86
|
-
} else {
|
|
87
|
-
const stepInfo = failedStep ? ` at step ${failedStep.index + 1}` : "";
|
|
88
|
-
const errorInfo = failedStep?.error ? `: ${failedStep.error}` : "";
|
|
89
|
-
return `ā Chain failed${stepInfo}${errorInfo}${skillsLine ? `\n${skillsLine}` : ""}
|
|
128
|
+
const footer = `\n\nš Progress: ${hasProgress ? progressPath : "(none)"}\nš Artifacts: ${chainDir}`;
|
|
90
129
|
|
|
91
|
-
|
|
92
|
-
|
|
130
|
+
if (status === "stopped" && options?.stop) {
|
|
131
|
+
const header = `ā¹ Chain stopped at step ${options.stop.stepIndex + 1}/${steps.length} by condition ${formatStopCondition(options.stop.condition)}`;
|
|
132
|
+
return appendChainSections(`${header}${skillsLine ? `\n${skillsLine}` : ""}${footer}`, options);
|
|
133
|
+
}
|
|
134
|
+
if (status === "completed" || status === "stopped") {
|
|
135
|
+
const stepWord = results.length === 1 ? "step" : "steps";
|
|
136
|
+
const header = `ā
Chain completed: ${stepNames} (${results.length} ${stepWord}, ${durationStr})${skillsLine ? `\n${skillsLine}` : ""}`;
|
|
137
|
+
return appendChainSections(`${header}${footer}`, options);
|
|
93
138
|
}
|
|
139
|
+
const stepInfo = failedStep ? ` at step ${failedStep.index + 1}` : "";
|
|
140
|
+
const errorInfo = failedStep?.error ? `: ${failedStep.error}` : "";
|
|
141
|
+
const header = `ā Chain failed${stepInfo}${errorInfo}${skillsLine ? `\n${skillsLine}` : ""}`;
|
|
142
|
+
return appendChainSections(`${header}${footer}`, options);
|
|
94
143
|
}
|
|
95
144
|
|
|
96
145
|
/**
|
package/src/shared/settings.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import type { AgentConfig } from "../agents/agents.ts";
|
|
8
8
|
import { normalizeSkillInput } from "../agents/skills.ts";
|
|
9
|
-
import { CHAIN_RUNS_DIR, type AcceptanceInput, type JsonSchemaObject, type OutputMode } from "./types.ts";
|
|
9
|
+
import { CHAIN_RUNS_DIR, type AcceptanceInput, type JsonSchemaObject, type OutputMode, type StopCondition } from "./types.ts";
|
|
10
10
|
const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
11
11
|
const INITIAL_PROGRESS_CONTENT = "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n";
|
|
12
12
|
|
|
@@ -56,6 +56,7 @@ export interface SequentialStep {
|
|
|
56
56
|
skill?: string | string[] | false;
|
|
57
57
|
model?: string;
|
|
58
58
|
acceptance?: AcceptanceInput;
|
|
59
|
+
stopOn?: StopCondition;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/** Parallel task item within a parallel step */
|
|
@@ -104,6 +105,7 @@ export interface DynamicParallelStep {
|
|
|
104
105
|
phase?: string;
|
|
105
106
|
label?: string;
|
|
106
107
|
acceptance?: AcceptanceInput;
|
|
108
|
+
stopOn?: StopCondition;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
/** Parallel step: multiple agents running concurrently */
|
|
@@ -113,6 +115,7 @@ export interface ParallelStep {
|
|
|
113
115
|
failFast?: boolean;
|
|
114
116
|
worktree?: boolean;
|
|
115
117
|
cwd?: string;
|
|
118
|
+
stopOn?: StopCondition;
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
/** Union type for chain steps */
|
package/src/shared/types.ts
CHANGED
|
@@ -23,11 +23,47 @@ export type OutputMode = "inline" | "file-only";
|
|
|
23
23
|
|
|
24
24
|
export type JsonSchemaObject = Record<string, unknown>;
|
|
25
25
|
|
|
26
|
+
export interface StopCondition {
|
|
27
|
+
path: string;
|
|
28
|
+
in: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface StopMatch {
|
|
32
|
+
stepIndex: number;
|
|
33
|
+
taskIndex?: number;
|
|
34
|
+
as?: string;
|
|
35
|
+
label?: string;
|
|
36
|
+
agent: string;
|
|
37
|
+
value: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface StopRecord {
|
|
41
|
+
reason: "condition";
|
|
42
|
+
stepIndex: number;
|
|
43
|
+
condition: StopCondition;
|
|
44
|
+
matches: StopMatch[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** One finished task's output as rendered in the chain result's `## Outputs` section. */
|
|
48
|
+
export interface ChainTaskOutputEntry {
|
|
49
|
+
stepIndex: number;
|
|
50
|
+
taskIndex: number;
|
|
51
|
+
agent: string;
|
|
52
|
+
as?: string;
|
|
53
|
+
label?: string;
|
|
54
|
+
text: string;
|
|
55
|
+
structured?: unknown;
|
|
56
|
+
exitCode: number | null | undefined;
|
|
57
|
+
error?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
26
60
|
export interface ChainOutputMapEntry {
|
|
27
61
|
text: string;
|
|
28
62
|
structured?: unknown;
|
|
29
63
|
agent: string;
|
|
30
64
|
stepIndex: number;
|
|
65
|
+
patchPath?: string;
|
|
66
|
+
patchError?: string;
|
|
31
67
|
}
|
|
32
68
|
|
|
33
69
|
export type ChainOutputMap = Record<string, ChainOutputMapEntry>;
|
|
@@ -435,6 +471,7 @@ export interface Details {
|
|
|
435
471
|
currentStepIndex?: number; // 0-indexed current step (for running chains)
|
|
436
472
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
437
473
|
outputs?: ChainOutputMap;
|
|
474
|
+
stop?: StopRecord;
|
|
438
475
|
}
|
|
439
476
|
|
|
440
477
|
// ============================================================================
|