pi-crew 0.6.1 → 0.6.4
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 +194 -0
- package/README.md +81 -33
- package/docs/issue-29-analysis.md +189 -0
- package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +1268 -0
- package/docs/ui-optimization-plan.md +447 -0
- package/package.json +2 -2
- package/src/config/config.ts +106 -15
- package/src/errors.ts +107 -0
- package/src/extension/async-notifier.ts +6 -2
- package/src/extension/crew-cleanup.ts +8 -5
- package/src/extension/management.ts +464 -109
- package/src/extension/register.ts +213 -35
- package/src/extension/registration/brief-tool-overrides.ts +400 -0
- package/src/extension/registration/commands.ts +27 -2
- package/src/extension/registration/subagent-helpers.ts +2 -2
- package/src/extension/registration/subagent-tools.ts +9 -4
- package/src/extension/registration/team-tool.ts +17 -10
- package/src/extension/registration/viewers.ts +2 -2
- package/src/extension/team-tool/api.ts +3 -3
- package/src/extension/team-tool/cancel.ts +3 -3
- package/src/extension/team-tool/explain.ts +1 -1
- package/src/extension/team-tool/handle-schedule.ts +40 -0
- package/src/extension/team-tool/inspect.ts +3 -3
- package/src/extension/team-tool/lifecycle-actions.ts +4 -4
- package/src/extension/team-tool/respond.ts +2 -2
- package/src/extension/team-tool/run.ts +64 -14
- package/src/extension/team-tool/status.ts +1 -1
- package/src/extension/team-tool-types.ts +2 -0
- package/src/extension/team-tool.ts +173 -46
- package/src/hooks/registry.ts +77 -13
- package/src/hooks/types.ts +9 -0
- package/src/plugins/plugin-define.ts +6 -0
- package/src/plugins/plugin-registry.ts +32 -0
- package/src/plugins/plugins/index.ts +3 -0
- package/src/plugins/plugins/nextjs.ts +19 -0
- package/src/plugins/plugins/vite.ts +14 -0
- package/src/plugins/plugins/vitest.ts +14 -0
- package/src/runtime/async-runner.ts +35 -7
- package/src/runtime/background-runner.ts +529 -144
- package/src/runtime/chain-parser.ts +5 -1
- package/src/runtime/chain-runner.ts +9 -3
- package/src/runtime/checkpoint.ts +262 -180
- package/src/runtime/child-pi.ts +164 -37
- package/src/runtime/crash-recovery.ts +25 -14
- package/src/runtime/crew-agent-records.ts +2 -0
- package/src/runtime/diagnostic-export.ts +1 -1
- package/src/runtime/dynamic-script-runner.ts +10 -7
- package/src/runtime/foreground-watchdog.ts +1 -1
- package/src/runtime/heartbeat-watcher.ts +19 -2
- package/src/runtime/intercom-bridge.ts +7 -0
- package/src/runtime/iteration-hooks.ts +1 -1
- package/src/runtime/manifest-cache.ts +4 -0
- package/src/runtime/orphan-worker-registry.ts +444 -0
- package/src/runtime/parent-guard.ts +70 -16
- package/src/runtime/pi-args.ts +437 -14
- package/src/runtime/pi-spawn.ts +1 -0
- package/src/runtime/post-checks.ts +1 -1
- package/src/runtime/retry-runner.ts +9 -3
- package/src/runtime/run-tracker.ts +38 -10
- package/src/runtime/sandbox.ts +17 -2
- package/src/runtime/scheduler.ts +25 -2
- package/src/runtime/skill-effectiveness.ts +105 -62
- package/src/runtime/skill-instructions.ts +110 -32
- package/src/runtime/stale-reconciler.ts +310 -69
- package/src/runtime/subagent-manager.ts +251 -57
- package/src/runtime/task-health.ts +76 -0
- package/src/runtime/task-id.ts +20 -0
- package/src/runtime/task-runner/live-executor.ts +1 -1
- package/src/runtime/task-runner/progress.ts +1 -1
- package/src/runtime/task-runner/state-helpers.ts +110 -6
- package/src/runtime/task-runner.ts +92 -70
- package/src/runtime/team-runner.ts +186 -20
- package/src/schema/team-tool-schema.ts +27 -9
- package/src/skills/discover-skills.ts +9 -3
- package/src/state/active-run-registry.ts +170 -38
- package/src/state/artifact-store.ts +25 -13
- package/src/state/atomic-write-v2.ts +86 -0
- package/src/state/atomic-write.ts +346 -55
- package/src/state/blob-store.ts +178 -10
- package/src/state/crew-init.ts +161 -28
- package/src/state/decision-ledger.ts +172 -111
- package/src/state/event-log-rotation.ts +82 -52
- package/src/state/event-log.ts +254 -70
- package/src/state/health-store.ts +71 -0
- package/src/state/locks.ts +102 -20
- package/src/state/mailbox.ts +45 -7
- package/src/state/observation-store.ts +4 -1
- package/src/state/run-graph.ts +141 -130
- package/src/state/run-metrics.ts +24 -8
- package/src/state/session-state-map.ts +51 -0
- package/src/state/state-store.ts +330 -43
- package/src/ui/live-run-sidebar.ts +1 -1
- package/src/ui/loaders.ts +4 -0
- package/src/ui/overlays/agent-picker-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-detail-overlay.ts +1 -1
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/run-action-dispatcher.ts +2 -2
- package/src/ui/run-snapshot-cache.ts +1 -1
- package/src/ui/status-colors.ts +5 -1
- package/src/ui/theme-adapter.ts +80 -1
- package/src/ui/tool-progress-formatter.ts +9 -5
- package/src/ui/tool-render.ts +4 -0
- package/src/ui/tool-renderers/brief-mode.ts +207 -0
- package/src/ui/tool-renderers/index.ts +627 -0
- package/src/ui/widget/index.ts +224 -0
- package/src/ui/widget/widget-formatters.ts +148 -0
- package/src/ui/widget/widget-model.ts +90 -0
- package/src/ui/widget/widget-renderer.ts +130 -0
- package/src/ui/widget/widget-types.ts +37 -0
- package/src/utils/env-filter.ts +66 -0
- package/src/utils/file-coalescer.ts +9 -1
- package/src/utils/guards.ts +110 -0
- package/src/utils/paths.ts +80 -5
- package/src/utils/redaction.ts +6 -1
- package/src/utils/safe-paths.ts +281 -10
- package/src/worktree/cleanup.ts +112 -24
- package/src/worktree/worktree-manager.ts +128 -15
- package/test-bugs-all.mjs +10 -6
- package/test-integration-check.ts +4 -0
|
@@ -3,15 +3,24 @@ import * as path from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
5
5
|
import type { TeamRole } from "../teams/team-config.ts";
|
|
6
|
+
import {
|
|
7
|
+
isSafePathId,
|
|
8
|
+
resolveContainedPath,
|
|
9
|
+
resolveRealContainedPath,
|
|
10
|
+
} from "../utils/safe-paths.ts";
|
|
6
11
|
import type { WorkflowStep } from "../workflows/workflow-config.ts";
|
|
7
|
-
import { isSafePathId, resolveContainedPath, resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
8
12
|
import {
|
|
13
|
+
CONFIDENCE_THRESHOLDS,
|
|
9
14
|
getWeightedSkillsForRole,
|
|
10
15
|
registerSkillEffectivenessHooks,
|
|
11
|
-
CONFIDENCE_THRESHOLDS,
|
|
12
16
|
} from "./skill-effectiveness.ts";
|
|
13
17
|
|
|
14
|
-
const PACKAGE_SKILLS_DIR = path.resolve(
|
|
18
|
+
const PACKAGE_SKILLS_DIR = path.resolve(
|
|
19
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
20
|
+
"..",
|
|
21
|
+
"..",
|
|
22
|
+
"skills",
|
|
23
|
+
);
|
|
15
24
|
const MAX_SKILL_CHARS = 1500;
|
|
16
25
|
const MAX_TOTAL_CHARS = 6000;
|
|
17
26
|
const MAX_SKILL_NAME_CHARS = 80;
|
|
@@ -23,7 +32,11 @@ const DEFAULT_ROLE_SKILLS: Record<string, string[]> = {
|
|
|
23
32
|
analyst: ["read-only-explorer", "requirements-to-task-packet"],
|
|
24
33
|
planner: ["delegation-patterns", "requirements-to-task-packet"],
|
|
25
34
|
critic: ["read-only-explorer", "multi-perspective-review"],
|
|
26
|
-
executor: [
|
|
35
|
+
executor: [
|
|
36
|
+
"state-mutation-locking",
|
|
37
|
+
"safe-bash",
|
|
38
|
+
"verification-before-done",
|
|
39
|
+
],
|
|
27
40
|
reviewer: ["read-only-explorer", "multi-perspective-review"],
|
|
28
41
|
// SECURITY NOTE: The following skill names are trusted package-level skills.
|
|
29
42
|
// If a project has a skills/ directory containing subdirectories with these names,
|
|
@@ -31,7 +44,10 @@ const DEFAULT_ROLE_SKILLS: Record<string, string[]> = {
|
|
|
31
44
|
// project dir before package dir) and their content injected verbatim into prompts.
|
|
32
45
|
// The "Applicable Skills" block will add an untrusted-content warning for project skills,
|
|
33
46
|
// but be aware this is a potential supply-chain risk in multi-contributor projects.
|
|
34
|
-
"security-reviewer": [
|
|
47
|
+
"security-reviewer": [
|
|
48
|
+
"secure-agent-orchestration-review",
|
|
49
|
+
"ownership-session-security",
|
|
50
|
+
],
|
|
35
51
|
"test-engineer": ["verification-before-done", "safe-bash"],
|
|
36
52
|
verifier: ["verification-before-done", "runtime-state-reader"],
|
|
37
53
|
writer: ["context-artifact-hygiene", "verify-evidence"],
|
|
@@ -50,11 +66,18 @@ export interface RenderSkillInstructionsInput extends ResolveTaskSkillsInput {
|
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
function isValidSkillName(name: string): boolean {
|
|
53
|
-
return
|
|
69
|
+
return (
|
|
70
|
+
name.length > 0 &&
|
|
71
|
+
name.length <= MAX_SKILL_NAME_CHARS &&
|
|
72
|
+
isSafePathId(name)
|
|
73
|
+
);
|
|
54
74
|
}
|
|
55
75
|
|
|
56
76
|
function sanitizeSkillName(name: string): string {
|
|
57
|
-
return
|
|
77
|
+
return (
|
|
78
|
+
name.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, MAX_SKILL_NAME_CHARS) ||
|
|
79
|
+
"invalid"
|
|
80
|
+
);
|
|
58
81
|
}
|
|
59
82
|
|
|
60
83
|
function unique(items: string[]): string[] {
|
|
@@ -69,11 +92,18 @@ function unique(items: string[]): string[] {
|
|
|
69
92
|
return result;
|
|
70
93
|
}
|
|
71
94
|
|
|
72
|
-
export function normalizeSkillOverride(
|
|
95
|
+
export function normalizeSkillOverride(
|
|
96
|
+
value: string | string[] | boolean | undefined,
|
|
97
|
+
): string[] | false | undefined {
|
|
73
98
|
if (value === false) return false;
|
|
74
|
-
if (typeof value === "string")
|
|
99
|
+
if (typeof value === "string")
|
|
100
|
+
return value
|
|
101
|
+
.split(",")
|
|
102
|
+
.map((entry) => entry.trim())
|
|
103
|
+
.filter(Boolean);
|
|
75
104
|
if (value === true) return undefined;
|
|
76
|
-
if (Array.isArray(value))
|
|
105
|
+
if (Array.isArray(value))
|
|
106
|
+
return value.map((entry) => entry.trim()).filter(Boolean);
|
|
77
107
|
return undefined;
|
|
78
108
|
}
|
|
79
109
|
|
|
@@ -81,13 +111,17 @@ export function defaultSkillsForRole(role: string): string[] {
|
|
|
81
111
|
return DEFAULT_ROLE_SKILLS[role] ?? [];
|
|
82
112
|
}
|
|
83
113
|
|
|
84
|
-
function collectTaskSkillNames(
|
|
114
|
+
function collectTaskSkillNames(
|
|
115
|
+
input: ResolveTaskSkillsInput | undefined,
|
|
116
|
+
): string[] {
|
|
85
117
|
if (!input) return [];
|
|
86
118
|
if (input.override === false) return [];
|
|
87
|
-
const roleDefaultsDisabled =
|
|
119
|
+
const roleDefaultsDisabled =
|
|
120
|
+
input.teamRole?.skills === false || input.step?.skills === false;
|
|
88
121
|
const names = roleDefaultsDisabled ? [] : defaultSkillsForRole(input.role);
|
|
89
122
|
if (input.agent?.skills?.length) names.push(...input.agent.skills);
|
|
90
|
-
if (Array.isArray(input.teamRole?.skills))
|
|
123
|
+
if (Array.isArray(input.teamRole?.skills))
|
|
124
|
+
names.push(...input.teamRole.skills);
|
|
91
125
|
if (Array.isArray(input.step?.skills)) names.push(...input.step.skills);
|
|
92
126
|
if (Array.isArray(input.override)) names.push(...input.override);
|
|
93
127
|
return unique(names);
|
|
@@ -103,10 +137,12 @@ export function resolveTaskSkillNames(input: ResolveTaskSkillsInput): string[] {
|
|
|
103
137
|
// See: SECURITY-ISSUES.md SEC-003
|
|
104
138
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
105
139
|
|
|
106
|
-
function candidateSkillDirs(
|
|
140
|
+
function candidateSkillDirs(
|
|
141
|
+
cwd: string,
|
|
142
|
+
): Array<{ root: string; source: "project" | "package" }> {
|
|
107
143
|
return [
|
|
108
|
-
{ root: PACKAGE_SKILLS_DIR, source: "package" },
|
|
109
|
-
{ root: path.resolve(cwd, "skills"), source: "project" },
|
|
144
|
+
{ root: PACKAGE_SKILLS_DIR, source: "package" }, // ✓ Trusted first
|
|
145
|
+
{ root: path.resolve(cwd, "skills"), source: "project" }, // ⚠️ Override second
|
|
110
146
|
];
|
|
111
147
|
}
|
|
112
148
|
|
|
@@ -120,7 +156,10 @@ interface CachedSkillMarkdown {
|
|
|
120
156
|
|
|
121
157
|
const skillReadCache = new Map<string, CachedSkillMarkdown>();
|
|
122
158
|
|
|
123
|
-
function rememberSkill(
|
|
159
|
+
function rememberSkill(
|
|
160
|
+
key: string,
|
|
161
|
+
value: CachedSkillMarkdown,
|
|
162
|
+
): CachedSkillMarkdown {
|
|
124
163
|
if (skillReadCache.has(key)) skillReadCache.delete(key);
|
|
125
164
|
skillReadCache.set(key, value);
|
|
126
165
|
while (skillReadCache.size > SKILL_CACHE_MAX_ENTRIES) {
|
|
@@ -144,7 +183,12 @@ function cachedSkillFresh(value: CachedSkillMarkdown): boolean {
|
|
|
144
183
|
}
|
|
145
184
|
}
|
|
146
185
|
|
|
147
|
-
function readSkillMarkdown(
|
|
186
|
+
function readSkillMarkdown(
|
|
187
|
+
cwd: string,
|
|
188
|
+
name: string,
|
|
189
|
+
):
|
|
190
|
+
| { path: string; source: "project" | "package"; content: string }
|
|
191
|
+
| undefined {
|
|
148
192
|
if (!isValidSkillName(name)) return undefined;
|
|
149
193
|
const cacheKey = `${path.resolve(cwd)}:${name}`;
|
|
150
194
|
const cached = skillReadCache.get(cacheKey);
|
|
@@ -158,9 +202,14 @@ function readSkillMarkdown(cwd: string, name: string): { path: string; source: "
|
|
|
158
202
|
if (fs.lstatSync(contained).isSymbolicLink()) continue;
|
|
159
203
|
const filePath = resolveRealContainedPath(entry.root, relative);
|
|
160
204
|
const stat = fs.statSync(filePath);
|
|
161
|
-
return rememberSkill(cacheKey, {
|
|
162
|
-
|
|
163
|
-
|
|
205
|
+
return rememberSkill(cacheKey, {
|
|
206
|
+
path: filePath,
|
|
207
|
+
source: entry.source,
|
|
208
|
+
content: fs.readFileSync(filePath, "utf-8"),
|
|
209
|
+
mtimeMs: stat.mtimeMs,
|
|
210
|
+
size: stat.size,
|
|
211
|
+
});
|
|
212
|
+
} catch {}
|
|
164
213
|
}
|
|
165
214
|
return undefined;
|
|
166
215
|
}
|
|
@@ -168,7 +217,9 @@ function readSkillMarkdown(cwd: string, name: string): { path: string; source: "
|
|
|
168
217
|
function frontmatterDescription(content: string): string | undefined {
|
|
169
218
|
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
170
219
|
if (!match) return undefined;
|
|
171
|
-
const line = match[1]
|
|
220
|
+
const line = match[1]
|
|
221
|
+
.split(/\r?\n/)
|
|
222
|
+
.find((entry) => entry.startsWith("description:"));
|
|
172
223
|
return line?.slice("description:".length).trim();
|
|
173
224
|
}
|
|
174
225
|
|
|
@@ -179,8 +230,12 @@ function stripFrontmatter(content: string): string {
|
|
|
179
230
|
function compactSkillContent(content: string): string {
|
|
180
231
|
const body = stripFrontmatter(content);
|
|
181
232
|
if (body.length <= MAX_SKILL_CHARS) return body;
|
|
182
|
-
const preferred =
|
|
183
|
-
|
|
233
|
+
const preferred =
|
|
234
|
+
body.split(/\r?\n## Verification\r?\n/)[0]?.trim() ?? body;
|
|
235
|
+
const truncated =
|
|
236
|
+
preferred.length > MAX_SKILL_CHARS
|
|
237
|
+
? preferred.slice(0, MAX_SKILL_CHARS - 40).trimEnd()
|
|
238
|
+
: preferred;
|
|
184
239
|
return `${truncated}\n\n[skill instructions truncated]`;
|
|
185
240
|
}
|
|
186
241
|
|
|
@@ -197,7 +252,11 @@ export interface RenderedSkillInstructions {
|
|
|
197
252
|
}>;
|
|
198
253
|
}
|
|
199
254
|
|
|
200
|
-
export function renderSkillInstructions(
|
|
255
|
+
export function renderSkillInstructions(
|
|
256
|
+
input: RenderSkillInstructionsInput & {
|
|
257
|
+
runId?: string;
|
|
258
|
+
} = {} as RenderSkillInstructionsInput & { runId?: string },
|
|
259
|
+
): RenderedSkillInstructions {
|
|
201
260
|
const allNames = collectTaskSkillNames(input);
|
|
202
261
|
const names = allNames.slice(0, MAX_SELECTED_SKILLS);
|
|
203
262
|
const overflowCount = Math.max(0, allNames.length - names.length);
|
|
@@ -208,12 +267,18 @@ export function renderSkillInstructions(input: RenderSkillInstructionsInput & {
|
|
|
208
267
|
let omittedCount = overflowCount;
|
|
209
268
|
|
|
210
269
|
// ECC INSTINCT: Get confidence-weighted skills if runId is provided
|
|
211
|
-
let weightedSkills: RenderedSkillInstructions["weightedSkills"]
|
|
270
|
+
let weightedSkills: RenderedSkillInstructions["weightedSkills"];
|
|
212
271
|
if (input.runId) {
|
|
213
272
|
// Register effectiveness hooks once per process
|
|
214
273
|
registerSkillEffectivenessHooks();
|
|
215
|
-
const weighted = getWeightedSkillsForRole(
|
|
216
|
-
|
|
274
|
+
const weighted = getWeightedSkillsForRole(
|
|
275
|
+
input.cwd,
|
|
276
|
+
input.role,
|
|
277
|
+
names,
|
|
278
|
+
input.runId,
|
|
279
|
+
CONFIDENCE_THRESHOLDS.TENTATIVE,
|
|
280
|
+
);
|
|
281
|
+
weightedSkills = weighted.map((w) => ({
|
|
217
282
|
skillId: w.skillId,
|
|
218
283
|
confidence: w.confidence,
|
|
219
284
|
behavior: w.behavior,
|
|
@@ -237,13 +302,26 @@ export function renderSkillInstructions(input: RenderSkillInstructionsInput & {
|
|
|
237
302
|
}
|
|
238
303
|
skillPaths.push(path.dirname(loaded.path));
|
|
239
304
|
const description = frontmatterDescription(loaded.content);
|
|
240
|
-
const source =
|
|
305
|
+
const source =
|
|
306
|
+
loaded.source === "project"
|
|
307
|
+
? `project:skills/${safeName}`
|
|
308
|
+
: `package:skills/${safeName}`;
|
|
241
309
|
|
|
242
310
|
// ECC INSTINCT: Add confidence annotation from weighted skills
|
|
243
|
-
const weighted = weightedSkills?.find(w => w.skillId === name);
|
|
244
|
-
const confidenceNote = weighted
|
|
311
|
+
const weighted = weightedSkills?.find((w) => w.skillId === name);
|
|
312
|
+
const confidenceNote = weighted
|
|
313
|
+
? ` [Confidence: ${(weighted.confidence * 100).toFixed(0)}% — ${weighted.threshold}]`
|
|
314
|
+
: "";
|
|
245
315
|
|
|
246
|
-
const header = [
|
|
316
|
+
const header = [
|
|
317
|
+
`## ${safeName}`,
|
|
318
|
+
description
|
|
319
|
+
? `Description: ${description}${confidenceNote}`
|
|
320
|
+
: undefined,
|
|
321
|
+
`Source: ${source}`,
|
|
322
|
+
]
|
|
323
|
+
.filter(Boolean)
|
|
324
|
+
.join("\n");
|
|
247
325
|
const rawContent = compactSkillContent(loaded.content);
|
|
248
326
|
// Wrap skill content with provenance markers to help LLMs distinguish skill instructions
|
|
249
327
|
const wrappedContent = `<!-- skill: ${safeName} -->\n${rawContent}\n<!-- end-skill: ${safeName} -->`;
|