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,175 @@
|
|
|
1
|
+
import type { ResolvedControlConfig } from "../../shared/types.ts";
|
|
2
|
+
|
|
3
|
+
interface LongRunningNoticeMetrics {
|
|
4
|
+
startedAt: number;
|
|
5
|
+
now: number;
|
|
6
|
+
turns: number;
|
|
7
|
+
tokens: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type LongRunningTriggerReason = "time_threshold" | "turn_threshold" | "token_threshold";
|
|
11
|
+
|
|
12
|
+
interface FailedMutatingAttempt {
|
|
13
|
+
tool: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
error: string;
|
|
16
|
+
ts: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface MutatingFailureState {
|
|
20
|
+
consecutiveFailures: number;
|
|
21
|
+
lastFailureAt?: number;
|
|
22
|
+
recentFailures: FailedMutatingAttempt[];
|
|
23
|
+
lastMutatingPath?: string;
|
|
24
|
+
repeatedPathFailures: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const MUTATING_BASH_PATTERNS = [
|
|
28
|
+
/(^|[;&|()\s])rm\s+/,
|
|
29
|
+
/(^|[;&|()\s])mv\s+/,
|
|
30
|
+
/(^|[;&|()\s])cp\s+/,
|
|
31
|
+
/(^|[;&|()\s])mkdir\s+/,
|
|
32
|
+
/(^|[;&|()\s])touch\s+/,
|
|
33
|
+
/(^|[;&|()\s])git\s+apply\b/,
|
|
34
|
+
/(^|[;&|()\s])patch\s+/,
|
|
35
|
+
/(^|[;&|()\s])sed\s+[^\n;&|]*\s-i\b/,
|
|
36
|
+
/(^|[;&|()\s])perl\s+[^\n;&|]*\s-pi\b/,
|
|
37
|
+
/(^|[;&|()]|\n)\s*tee\s+[^|&;]+/,
|
|
38
|
+
/\b(writeFile|writeFileSync|appendFile|appendFileSync)\b/,
|
|
39
|
+
/\bwrite_text\s*\(/,
|
|
40
|
+
/\bopen\s*\([^)]*,\s*["'][wa]/,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const MUTATING_FAILURE_HINTS = [
|
|
44
|
+
"failed",
|
|
45
|
+
"error",
|
|
46
|
+
"no exact match",
|
|
47
|
+
"did not match",
|
|
48
|
+
"malformed",
|
|
49
|
+
"rejected",
|
|
50
|
+
"unable",
|
|
51
|
+
"cannot",
|
|
52
|
+
"could not",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
export function resolveCurrentPath(toolName: string | undefined, args: Record<string, unknown> | undefined): string | undefined {
|
|
56
|
+
if (!toolName || !args) return undefined;
|
|
57
|
+
const direct = ["path", "file", "filename", "target", "cwd"];
|
|
58
|
+
for (const key of direct) {
|
|
59
|
+
const value = args[key];
|
|
60
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
61
|
+
}
|
|
62
|
+
if (toolName === "bash") {
|
|
63
|
+
const command = typeof args.command === "string" ? args.command : undefined;
|
|
64
|
+
if (!command) return undefined;
|
|
65
|
+
const redirect = command.match(/(?:>|>>|tee\s+)(\S+)/);
|
|
66
|
+
if (redirect?.[1]) return redirect[1];
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasUnquotedFileRedirection(command: string): boolean {
|
|
72
|
+
let inSingle = false;
|
|
73
|
+
let inDouble = false;
|
|
74
|
+
for (let i = 0; i < command.length; i++) {
|
|
75
|
+
const char = command[i]!;
|
|
76
|
+
if (char === "'" && !inDouble) {
|
|
77
|
+
inSingle = !inSingle;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (char === '"' && !inSingle) {
|
|
81
|
+
inDouble = !inDouble;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (inSingle || inDouble) continue;
|
|
85
|
+
if (char !== ">") continue;
|
|
86
|
+
if (command[i - 1] === "-") continue;
|
|
87
|
+
const isDouble = command[i + 1] === ">";
|
|
88
|
+
let cursor = i + (isDouble ? 2 : 1);
|
|
89
|
+
while (cursor < command.length && /\s/.test(command[cursor]!)) cursor++;
|
|
90
|
+
if (cursor >= command.length) continue;
|
|
91
|
+
const targetStart = command[cursor]!;
|
|
92
|
+
if (targetStart === "&" || targetStart === "|" || targetStart === ";") continue;
|
|
93
|
+
if (targetStart === "(" || targetStart === ")") continue;
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isMutatingBashCommand(command: string): boolean {
|
|
100
|
+
return hasUnquotedFileRedirection(command) || MUTATING_BASH_PATTERNS.some((pattern) => pattern.test(command));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function isMutatingTool(toolName: string | undefined, args: Record<string, unknown> | undefined): boolean {
|
|
104
|
+
if (!toolName) return false;
|
|
105
|
+
if (toolName === "edit" || toolName === "write") return true;
|
|
106
|
+
if (toolName !== "bash") return false;
|
|
107
|
+
const command = typeof args?.command === "string" ? args.command : "";
|
|
108
|
+
if (!command.trim()) return false;
|
|
109
|
+
return isMutatingBashCommand(command);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function didMutatingToolFail(text: string): boolean {
|
|
113
|
+
const lowered = text.toLowerCase();
|
|
114
|
+
return MUTATING_FAILURE_HINTS.some((hint) => lowered.includes(hint));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function nextLongRunningTrigger(
|
|
118
|
+
config: ResolvedControlConfig,
|
|
119
|
+
metrics: LongRunningNoticeMetrics,
|
|
120
|
+
): LongRunningTriggerReason | undefined {
|
|
121
|
+
if (metrics.now - metrics.startedAt >= config.activeNoticeAfterMs) return "time_threshold";
|
|
122
|
+
if (config.activeNoticeAfterTurns !== undefined && metrics.turns >= config.activeNoticeAfterTurns) return "turn_threshold";
|
|
123
|
+
if (config.activeNoticeAfterTokens !== undefined && metrics.tokens >= config.activeNoticeAfterTokens) return "token_threshold";
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function resetMutatingFailureState(state: MutatingFailureState): void {
|
|
128
|
+
state.consecutiveFailures = 0;
|
|
129
|
+
state.lastFailureAt = undefined;
|
|
130
|
+
state.recentFailures = [];
|
|
131
|
+
state.lastMutatingPath = undefined;
|
|
132
|
+
state.repeatedPathFailures = 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function createMutatingFailureState(): MutatingFailureState {
|
|
136
|
+
return {
|
|
137
|
+
consecutiveFailures: 0,
|
|
138
|
+
recentFailures: [],
|
|
139
|
+
repeatedPathFailures: 0,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function recordMutatingFailure(
|
|
144
|
+
state: MutatingFailureState,
|
|
145
|
+
input: FailedMutatingAttempt,
|
|
146
|
+
windowMs: number,
|
|
147
|
+
): void {
|
|
148
|
+
if (state.lastFailureAt === undefined || input.ts - state.lastFailureAt > windowMs) {
|
|
149
|
+
state.consecutiveFailures = 0;
|
|
150
|
+
state.recentFailures = [];
|
|
151
|
+
state.repeatedPathFailures = 0;
|
|
152
|
+
state.lastMutatingPath = undefined;
|
|
153
|
+
}
|
|
154
|
+
state.lastFailureAt = input.ts;
|
|
155
|
+
state.consecutiveFailures += 1;
|
|
156
|
+
if (input.path && state.lastMutatingPath === input.path) {
|
|
157
|
+
state.repeatedPathFailures += 1;
|
|
158
|
+
} else if (input.path) {
|
|
159
|
+
state.lastMutatingPath = input.path;
|
|
160
|
+
state.repeatedPathFailures = 1;
|
|
161
|
+
}
|
|
162
|
+
state.recentFailures.push(input);
|
|
163
|
+
if (state.recentFailures.length > 3) state.recentFailures.shift();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function shouldEscalateMutatingFailures(state: MutatingFailureState, threshold: number): boolean {
|
|
167
|
+
return state.consecutiveFailures >= threshold || state.repeatedPathFailures >= threshold;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function summarizeRecentMutatingFailures(state: MutatingFailureState): string | undefined {
|
|
171
|
+
if (state.recentFailures.length === 0) return undefined;
|
|
172
|
+
return state.recentFailures
|
|
173
|
+
.map((entry) => `${entry.tool}${entry.path ? `(${entry.path})` : ""}: ${entry.error}`)
|
|
174
|
+
.join(" | ");
|
|
175
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { ModelInfo as AvailableModelInfo } from "../../shared/model-info.ts";
|
|
2
|
+
import type { Usage } from "../../shared/types.ts";
|
|
3
|
+
|
|
4
|
+
export type { AvailableModelInfo };
|
|
5
|
+
|
|
6
|
+
interface ModelAttemptSummary {
|
|
7
|
+
model: string;
|
|
8
|
+
success: boolean;
|
|
9
|
+
exitCode?: number | null;
|
|
10
|
+
error?: string;
|
|
11
|
+
usage?: Usage;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function splitThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {
|
|
15
|
+
const colonIdx = model.lastIndexOf(":");
|
|
16
|
+
if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
|
|
17
|
+
return {
|
|
18
|
+
baseModel: model.substring(0, colonIdx),
|
|
19
|
+
thinkingSuffix: model.substring(colonIdx),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveModelCandidate(
|
|
24
|
+
model: string | undefined,
|
|
25
|
+
availableModels: AvailableModelInfo[] | undefined,
|
|
26
|
+
preferredProvider?: string,
|
|
27
|
+
): string | undefined {
|
|
28
|
+
if (!model) return undefined;
|
|
29
|
+
if (model.includes("/")) return model;
|
|
30
|
+
if (!availableModels || availableModels.length === 0) return model;
|
|
31
|
+
|
|
32
|
+
const { baseModel, thinkingSuffix } = splitThinkingSuffix(model);
|
|
33
|
+
const matches = availableModels.filter((entry) => entry.id === baseModel);
|
|
34
|
+
if (preferredProvider) {
|
|
35
|
+
const preferredMatch = matches.find((entry) => entry.provider === preferredProvider);
|
|
36
|
+
if (preferredMatch) return `${preferredMatch.fullId}${thinkingSuffix}`;
|
|
37
|
+
}
|
|
38
|
+
if (matches.length !== 1) return model;
|
|
39
|
+
return `${matches[0]!.fullId}${thinkingSuffix}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildModelCandidates(
|
|
43
|
+
primaryModel: string | undefined,
|
|
44
|
+
fallbackModels: string[] | undefined,
|
|
45
|
+
availableModels: AvailableModelInfo[] | undefined,
|
|
46
|
+
preferredProvider?: string,
|
|
47
|
+
): string[] {
|
|
48
|
+
const seen = new Set<string>();
|
|
49
|
+
const candidates: string[] = [];
|
|
50
|
+
for (const raw of [primaryModel, ...(fallbackModels ?? [])]) {
|
|
51
|
+
if (!raw) continue;
|
|
52
|
+
const normalized = resolveModelCandidate(raw.trim(), availableModels, preferredProvider);
|
|
53
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
54
|
+
seen.add(normalized);
|
|
55
|
+
candidates.push(normalized);
|
|
56
|
+
}
|
|
57
|
+
return candidates;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const RETRYABLE_MODEL_FAILURE_PATTERNS = [
|
|
61
|
+
/rate\s*limit/i,
|
|
62
|
+
/too many requests/i,
|
|
63
|
+
/\b429\b/,
|
|
64
|
+
/quota/i,
|
|
65
|
+
/billing/i,
|
|
66
|
+
/credit/i,
|
|
67
|
+
/auth(?:entication)?/i,
|
|
68
|
+
/unauthori[sz]ed/i,
|
|
69
|
+
/forbidden/i,
|
|
70
|
+
/api key/i,
|
|
71
|
+
/token expired/i,
|
|
72
|
+
/invalid key/i,
|
|
73
|
+
/provider.*unavailable/i,
|
|
74
|
+
/model.*unavailable/i,
|
|
75
|
+
/model.*disabled/i,
|
|
76
|
+
/model.*not found/i,
|
|
77
|
+
/unknown model/i,
|
|
78
|
+
/overloaded/i,
|
|
79
|
+
/service unavailable/i,
|
|
80
|
+
/temporar(?:ily)? unavailable/i,
|
|
81
|
+
/connection refused/i,
|
|
82
|
+
/fetch failed/i,
|
|
83
|
+
/network error/i,
|
|
84
|
+
/socket hang up/i,
|
|
85
|
+
/upstream/i,
|
|
86
|
+
/timed? out/i,
|
|
87
|
+
/timeout/i,
|
|
88
|
+
/\b502\b/,
|
|
89
|
+
/\b503\b/,
|
|
90
|
+
/\b504\b/,
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
export function isRetryableModelFailure(error: string | undefined): boolean {
|
|
94
|
+
if (!error) return false;
|
|
95
|
+
return RETRYABLE_MODEL_FAILURE_PATTERNS.some((pattern) => pattern.test(error));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function formatModelAttemptNote(attempt: ModelAttemptSummary, nextModel?: string): string {
|
|
99
|
+
const failure = attempt.error?.trim() || `exit ${attempt.exitCode ?? 1}`;
|
|
100
|
+
return nextModel
|
|
101
|
+
? `[fallback] ${attempt.model} failed: ${failure}. Retrying with ${nextModel}.`
|
|
102
|
+
: `[fallback] ${attempt.model} failed: ${failure}.`;
|
|
103
|
+
}
|