pi-cohort 2.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.
- package/CHANGELOG.md +1151 -0
- package/LICENSE +22 -0
- package/README.md +1220 -0
- package/agents/context-builder.md +45 -0
- package/agents/delegate.md +12 -0
- package/agents/oracle.md +73 -0
- package/agents/planner.md +55 -0
- package/agents/reviewer.md +91 -0
- package/agents/scout.md +50 -0
- package/agents/worker.md +67 -0
- package/package.json +87 -0
- package/prompts/gather-context-and-clarify.md +13 -0
- package/prompts/parallel-cleanup.md +59 -0
- package/prompts/parallel-context-build.md +55 -0
- package/prompts/parallel-handoff-plan.md +61 -0
- package/prompts/parallel-review.md +54 -0
- package/prompts/review-loop.md +41 -0
- package/skills/pi-cohort/SKILL.md +818 -0
- package/src/agents/agent-management.ts +685 -0
- package/src/agents/agent-scope.ts +6 -0
- package/src/agents/agent-selection.ts +23 -0
- package/src/agents/agent-serializer.ts +83 -0
- package/src/agents/agents.ts +1141 -0
- package/src/agents/chain-serializer.ts +251 -0
- package/src/agents/frontmatter.ts +29 -0
- package/src/agents/identity.ts +30 -0
- package/src/agents/skills.ts +632 -0
- package/src/extension/config.ts +16 -0
- package/src/extension/control-notices.ts +92 -0
- package/src/extension/doctor.ts +236 -0
- package/src/extension/fanout-child.ts +170 -0
- package/src/extension/grand-total.ts +109 -0
- package/src/extension/index.ts +630 -0
- package/src/extension/schemas.ts +306 -0
- package/src/intercom/intercom-bridge.ts +379 -0
- package/src/intercom/result-intercom.ts +377 -0
- package/src/runs/background/async-execution.ts +796 -0
- package/src/runs/background/async-job-tracker.ts +320 -0
- package/src/runs/background/async-resume.ts +345 -0
- package/src/runs/background/async-status.ts +335 -0
- package/src/runs/background/completion-dedupe.ts +63 -0
- package/src/runs/background/notify.ts +108 -0
- package/src/runs/background/parallel-groups.ts +45 -0
- package/src/runs/background/result-watcher.ts +307 -0
- package/src/runs/background/run-id-resolver.ts +83 -0
- package/src/runs/background/run-status.ts +272 -0
- package/src/runs/background/stale-run-reconciler.ts +336 -0
- package/src/runs/background/subagent-runner.ts +2326 -0
- package/src/runs/background/top-level-async.ts +13 -0
- package/src/runs/foreground/chain-clarify.ts +1333 -0
- package/src/runs/foreground/chain-execution.ts +1187 -0
- package/src/runs/foreground/execution.ts +1028 -0
- package/src/runs/foreground/subagent-executor.ts +2580 -0
- package/src/runs/shared/acceptance.ts +605 -0
- package/src/runs/shared/chain-outputs.ts +101 -0
- package/src/runs/shared/completion-guard.ts +143 -0
- package/src/runs/shared/dynamic-fanout.ts +293 -0
- package/src/runs/shared/long-running-guard.ts +175 -0
- package/src/runs/shared/model-fallback.ts +103 -0
- package/src/runs/shared/nested-events.ts +822 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +115 -0
- package/src/runs/shared/parallel-utils.ts +136 -0
- package/src/runs/shared/pi-args.ts +221 -0
- package/src/runs/shared/pi-spawn.ts +115 -0
- package/src/runs/shared/run-history.ts +60 -0
- package/src/runs/shared/single-output.ts +164 -0
- package/src/runs/shared/structured-output.ts +77 -0
- package/src/runs/shared/subagent-control.ts +287 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +220 -0
- package/src/runs/shared/workflow-graph.ts +206 -0
- package/src/runs/shared/worktree.ts +577 -0
- package/src/shared/artifacts.ts +98 -0
- package/src/shared/atomic-json.ts +16 -0
- package/src/shared/file-coalescer.ts +40 -0
- package/src/shared/fork-context.ts +76 -0
- package/src/shared/formatters.ts +133 -0
- package/src/shared/jsonl-writer.ts +81 -0
- package/src/shared/model-info.ts +78 -0
- package/src/shared/post-exit-stdio-guard.ts +85 -0
- package/src/shared/session-identity.ts +10 -0
- package/src/shared/session-tokens.ts +46 -0
- package/src/shared/settings.ts +447 -0
- package/src/shared/status-format.ts +59 -0
- package/src/shared/types.ts +1072 -0
- package/src/shared/utils.ts +451 -0
- package/src/slash/prompt-template-bridge.ts +397 -0
- package/src/slash/slash-bridge.ts +174 -0
- package/src/slash/slash-commands.ts +567 -0
- package/src/slash/slash-live-state.ts +292 -0
- package/src/tui/render-helpers.ts +80 -0
- package/src/tui/render.ts +1476 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { ChainConfig, ChainStepConfig } from "./agents.ts";
|
|
2
|
+
import { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./identity.ts";
|
|
3
|
+
import { parseFrontmatter } from "./frontmatter.ts";
|
|
4
|
+
import { ChainOutputValidationError, validateChainOutputBindings } from "../runs/shared/chain-outputs.ts";
|
|
5
|
+
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
6
|
+
import type { ChainStep } from "../shared/settings.ts";
|
|
7
|
+
|
|
8
|
+
function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
|
|
9
|
+
const lines = sectionBody.split("\n");
|
|
10
|
+
const blankIndex = lines.findIndex((line) => line.trim() === "");
|
|
11
|
+
const configLines = blankIndex === -1 ? lines : lines.slice(0, blankIndex);
|
|
12
|
+
const task = (blankIndex === -1 ? "" : lines.slice(blankIndex + 1).join("\n")).trim();
|
|
13
|
+
|
|
14
|
+
const step: ChainStepConfig = { agent, task };
|
|
15
|
+
for (const line of configLines) {
|
|
16
|
+
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
17
|
+
if (!match) continue;
|
|
18
|
+
const key = match[1].trim().toLowerCase();
|
|
19
|
+
const rawValue = match[2].trim();
|
|
20
|
+
|
|
21
|
+
if (key === "output") {
|
|
22
|
+
if (rawValue === "false") step.output = false;
|
|
23
|
+
else if (rawValue) step.output = rawValue;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (key === "phase") {
|
|
27
|
+
if (rawValue) step.phase = rawValue;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (key === "label") {
|
|
31
|
+
if (rawValue) step.label = rawValue;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (key === "as") {
|
|
35
|
+
if (rawValue) step.as = rawValue;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (key === "outputschema") {
|
|
39
|
+
if (rawValue.startsWith("{") || rawValue.startsWith("[")) {
|
|
40
|
+
throw new Error("Inline outputSchema values are not supported in .chain.md files; use a schema file path.");
|
|
41
|
+
}
|
|
42
|
+
if (rawValue) step.outputSchema = rawValue;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (key === "outputmode") {
|
|
46
|
+
if (rawValue === "inline" || rawValue === "file-only") step.outputMode = rawValue;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (key === "reads") {
|
|
50
|
+
if (rawValue === "false") {
|
|
51
|
+
step.reads = false;
|
|
52
|
+
} else {
|
|
53
|
+
const reads = rawValue
|
|
54
|
+
.split(",")
|
|
55
|
+
.map((v) => v.trim())
|
|
56
|
+
.filter(Boolean);
|
|
57
|
+
step.reads = reads.length > 0 ? reads : false;
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (key === "model") {
|
|
62
|
+
if (rawValue) step.model = rawValue;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (key === "skills") {
|
|
66
|
+
if (rawValue === "false") {
|
|
67
|
+
step.skills = false;
|
|
68
|
+
} else {
|
|
69
|
+
const skills = rawValue
|
|
70
|
+
.split(",")
|
|
71
|
+
.map((v) => v.trim())
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
step.skills = skills.length > 0 ? skills : false;
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (key === "progress") {
|
|
78
|
+
if (rawValue === "true") step.progress = true;
|
|
79
|
+
else if (rawValue === "false") step.progress = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return step;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function parseChain(content: string, source: "user" | "project", filePath: string): ChainConfig {
|
|
87
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
88
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
89
|
+
throw new Error("Chain frontmatter must include name and description");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const matches = [...body.matchAll(/^##\s+(.+)[^\S\n]*$/gm)];
|
|
93
|
+
const steps: ChainStepConfig[] = [];
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < matches.length; i++) {
|
|
96
|
+
const match = matches[i]!;
|
|
97
|
+
const agent = match[1]!.trim();
|
|
98
|
+
const lineEndOffset = body[match.index! + match[0].length] === "\n" ? 1 : 0;
|
|
99
|
+
const sectionStart = match.index! + match[0].length + lineEndOffset;
|
|
100
|
+
const sectionEnd = i + 1 < matches.length ? matches[i + 1]!.index! : body.length;
|
|
101
|
+
const sectionBody = body.slice(sectionStart, sectionEnd).trimEnd();
|
|
102
|
+
steps.push(parseStepBody(agent, sectionBody));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const localName = frontmatter.name;
|
|
106
|
+
const parsedPackage = parsePackageName(frontmatter.package, `Chain '${localName}' package`);
|
|
107
|
+
if (parsedPackage.error) throw new Error(parsedPackage.error);
|
|
108
|
+
const packageName = parsedPackage.packageName;
|
|
109
|
+
const extraFields: Record<string, string> = {};
|
|
110
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
111
|
+
if (key === "name" || key === "package" || key === "description") continue;
|
|
112
|
+
extraFields[key] = value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
name: buildRuntimeName(localName, packageName),
|
|
117
|
+
localName,
|
|
118
|
+
packageName,
|
|
119
|
+
description: frontmatter.description,
|
|
120
|
+
source,
|
|
121
|
+
filePath,
|
|
122
|
+
steps,
|
|
123
|
+
extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function parseJsonChain(content: string, source: "user" | "project", filePath: string): ChainConfig {
|
|
128
|
+
let parsed: unknown;
|
|
129
|
+
try {
|
|
130
|
+
parsed = JSON.parse(content);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
throw new Error(`Invalid JSON chain '${filePath}': ${message}`);
|
|
134
|
+
}
|
|
135
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
136
|
+
throw new Error(`JSON chain '${filePath}' must contain an object root.`);
|
|
137
|
+
}
|
|
138
|
+
const input = parsed as Record<string, unknown>;
|
|
139
|
+
if (typeof input.name !== "string" || !input.name.trim()) {
|
|
140
|
+
throw new Error(`JSON chain '${filePath}' must include string name.`);
|
|
141
|
+
}
|
|
142
|
+
if (typeof input.description !== "string" || !input.description.trim()) {
|
|
143
|
+
throw new Error(`JSON chain '${filePath}' must include string description.`);
|
|
144
|
+
}
|
|
145
|
+
if (!Array.isArray(input.chain)) {
|
|
146
|
+
throw new Error(`JSON chain '${filePath}' must include array chain.`);
|
|
147
|
+
}
|
|
148
|
+
for (let i = 0; i < input.chain.length; i++) {
|
|
149
|
+
const step = input.chain[i];
|
|
150
|
+
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
151
|
+
throw new Error(`JSON chain '${filePath}' step ${i + 1} must be an object.`);
|
|
152
|
+
}
|
|
153
|
+
const stepRecord = step as Record<string, unknown>;
|
|
154
|
+
const acceptanceErrors = validateAcceptanceInput(stepRecord.acceptance, `step ${i + 1} acceptance`);
|
|
155
|
+
if (acceptanceErrors.length > 0) {
|
|
156
|
+
throw new Error(`Invalid JSON chain '${filePath}': ${acceptanceErrors.join(" ")}`);
|
|
157
|
+
}
|
|
158
|
+
const parallel = stepRecord.parallel;
|
|
159
|
+
if (Array.isArray(parallel)) {
|
|
160
|
+
for (let taskIndex = 0; taskIndex < parallel.length; taskIndex++) {
|
|
161
|
+
const task = parallel[taskIndex];
|
|
162
|
+
if (!task || typeof task !== "object" || Array.isArray(task)) continue;
|
|
163
|
+
const taskErrors = validateAcceptanceInput((task as Record<string, unknown>).acceptance, `step ${i + 1} parallel task ${taskIndex + 1} acceptance`);
|
|
164
|
+
if (taskErrors.length > 0) {
|
|
165
|
+
throw new Error(`Invalid JSON chain '${filePath}': ${taskErrors.join(" ")}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} else if (parallel && typeof parallel === "object") {
|
|
169
|
+
const templateErrors = validateAcceptanceInput((parallel as Record<string, unknown>).acceptance, `step ${i + 1} dynamic template acceptance`);
|
|
170
|
+
if (templateErrors.length > 0) {
|
|
171
|
+
throw new Error(`Invalid JSON chain '${filePath}': ${templateErrors.join(" ")}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
validateChainOutputBindings(input.chain as ChainStep[], { maxItems: Number.MAX_SAFE_INTEGER });
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (error instanceof ChainOutputValidationError) throw new Error(`Invalid JSON chain '${filePath}': ${error.message}`);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
const parsedPackage = parsePackageName(typeof input.package === "string" ? input.package : undefined, `Chain '${input.name}' package`);
|
|
182
|
+
if (parsedPackage.error) throw new Error(parsedPackage.error);
|
|
183
|
+
const extraFields: Record<string, string> = {};
|
|
184
|
+
for (const [key, value] of Object.entries(input)) {
|
|
185
|
+
if (key === "name" || key === "package" || key === "description" || key === "chain") continue;
|
|
186
|
+
if (typeof value === "string") extraFields[key] = value;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
name: buildRuntimeName(input.name.trim(), parsedPackage.packageName),
|
|
190
|
+
localName: input.name.trim(),
|
|
191
|
+
packageName: parsedPackage.packageName,
|
|
192
|
+
description: input.description.trim(),
|
|
193
|
+
source,
|
|
194
|
+
filePath,
|
|
195
|
+
steps: input.chain as ChainStepConfig[],
|
|
196
|
+
extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function serializeJsonChain(config: ChainConfig): string {
|
|
201
|
+
const root: Record<string, unknown> = {
|
|
202
|
+
name: frontmatterNameForConfig(config),
|
|
203
|
+
description: config.description,
|
|
204
|
+
chain: config.steps,
|
|
205
|
+
};
|
|
206
|
+
if (config.packageName) root.package = config.packageName;
|
|
207
|
+
if (config.extraFields) {
|
|
208
|
+
for (const [key, value] of Object.entries(config.extraFields)) {
|
|
209
|
+
if (key !== "name" && key !== "description" && key !== "package" && key !== "chain") root[key] = value;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return `${JSON.stringify(root, null, 2)}\n`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function serializeChain(config: ChainConfig): string {
|
|
216
|
+
const lines: string[] = [];
|
|
217
|
+
lines.push("---");
|
|
218
|
+
lines.push(`name: ${frontmatterNameForConfig(config)}`);
|
|
219
|
+
if (config.packageName) lines.push(`package: ${config.packageName}`);
|
|
220
|
+
lines.push(`description: ${config.description}`);
|
|
221
|
+
if (config.extraFields) {
|
|
222
|
+
for (const [key, value] of Object.entries(config.extraFields)) {
|
|
223
|
+
lines.push(`${key}: ${value}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
lines.push("---");
|
|
227
|
+
lines.push("");
|
|
228
|
+
|
|
229
|
+
for (let i = 0; i < config.steps.length; i++) {
|
|
230
|
+
const step = config.steps[i]!;
|
|
231
|
+
lines.push(`## ${step.agent}`);
|
|
232
|
+
if (step.output === false) lines.push("output: false");
|
|
233
|
+
else if (step.output) lines.push(`output: ${step.output}`);
|
|
234
|
+
if (step.phase) lines.push(`phase: ${step.phase}`);
|
|
235
|
+
if (step.label) lines.push(`label: ${step.label}`);
|
|
236
|
+
if (step.as) lines.push(`as: ${step.as}`);
|
|
237
|
+
if (step.outputSchema) lines.push(`outputSchema: ${step.outputSchema}`);
|
|
238
|
+
if (step.outputMode) lines.push(`outputMode: ${step.outputMode}`);
|
|
239
|
+
if (step.reads === false) lines.push("reads: false");
|
|
240
|
+
else if (Array.isArray(step.reads) && step.reads.length > 0) lines.push(`reads: ${step.reads.join(", ")}`);
|
|
241
|
+
if (step.model) lines.push(`model: ${step.model}`);
|
|
242
|
+
if (step.skills === false) lines.push("skills: false");
|
|
243
|
+
else if (Array.isArray(step.skills) && step.skills.length > 0) lines.push(`skills: ${step.skills.join(", ")}`);
|
|
244
|
+
if (step.progress !== undefined) lines.push(`progress: ${step.progress ? "true" : "false"}`);
|
|
245
|
+
lines.push("");
|
|
246
|
+
lines.push(step.task ?? "");
|
|
247
|
+
if (i < config.steps.length - 1) lines.push("");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return `${lines.join("\n")}\n`;
|
|
251
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
|
|
2
|
+
const frontmatter: Record<string, string> = {};
|
|
3
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
4
|
+
|
|
5
|
+
if (!normalized.startsWith("---")) {
|
|
6
|
+
return { frontmatter, body: normalized };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
10
|
+
if (endIndex === -1) {
|
|
11
|
+
return { frontmatter, body: normalized };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const frontmatterBlock = normalized.slice(4, endIndex);
|
|
15
|
+
const body = normalized.slice(endIndex + 4).trim();
|
|
16
|
+
|
|
17
|
+
for (const line of frontmatterBlock.split("\n")) {
|
|
18
|
+
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
19
|
+
if (match) {
|
|
20
|
+
let value = match[2].trim();
|
|
21
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
22
|
+
value = value.slice(1, -1);
|
|
23
|
+
}
|
|
24
|
+
frontmatter[match[1]] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { frontmatter, body };
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AgentConfig, ChainConfig } from "./agents.ts";
|
|
2
|
+
|
|
3
|
+
const IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*$/;
|
|
4
|
+
|
|
5
|
+
function normalizePackageName(value: string | undefined): string | undefined {
|
|
6
|
+
const trimmed = value?.trim();
|
|
7
|
+
if (!trimmed) return undefined;
|
|
8
|
+
return trimmed.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9.-]/g, "").replace(/-+/g, "-").replace(/\.+/g, ".").replace(/(?:^[-.]+|[-.]+$)/g, "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parsePackageName(value: unknown, label = "package"): { packageName?: string; error?: string } {
|
|
12
|
+
if (value === undefined || value === false || value === "") return { packageName: undefined };
|
|
13
|
+
if (typeof value !== "string") return { error: `${label} must be a string or false when provided.` };
|
|
14
|
+
const packageName = normalizePackageName(value);
|
|
15
|
+
if (!packageName || !IDENTIFIER_PATTERN.test(packageName)) return { error: `${label} is invalid after sanitization.` };
|
|
16
|
+
return { packageName };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildRuntimeName(localName: string, packageName?: string): string {
|
|
20
|
+
const trimmedPackage = packageName?.trim();
|
|
21
|
+
return trimmedPackage ? `${trimmedPackage}.${localName}` : localName;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function frontmatterNameForConfig(config: Pick<AgentConfig | ChainConfig, "name" | "localName" | "packageName">): string {
|
|
25
|
+
if (config.localName) return config.localName;
|
|
26
|
+
if (config.packageName && config.name.startsWith(`${config.packageName}.`)) {
|
|
27
|
+
return config.name.slice(config.packageName.length + 1);
|
|
28
|
+
}
|
|
29
|
+
return config.name;
|
|
30
|
+
}
|