pi-crew 0.5.2 → 0.5.5
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 +67 -0
- package/docs/bugs/cross-session-notification-leakage.md +82 -0
- package/docs/coding-agent-optimization.md +268 -0
- package/docs/deep-review-report.md +384 -0
- package/docs/distillation/cybersecurity-patterns.md +294 -0
- package/docs/migration-v0.4-v0.5.md +191 -0
- package/docs/optimization-plan.md +642 -0
- package/docs/pi-mono-opportunities.md +969 -0
- package/docs/pi-mono-review.md +291 -0
- package/docs/skills/REFERENCE.md +144 -0
- package/package.json +7 -6
- package/skills/artifact-analysis-loop/SKILL.md +302 -0
- package/skills/async-worker-recovery/SKILL.md +19 -1
- package/skills/child-pi-spawning/SKILL.md +19 -6
- package/skills/context-artifact-hygiene/SKILL.md +19 -2
- package/skills/delegation-patterns/SKILL.md +68 -3
- package/skills/detection-pipeline-design/SKILL.md +285 -0
- package/skills/event-log-tracing/SKILL.md +20 -6
- package/skills/git-master/SKILL.md +20 -6
- package/skills/hunting-investigation-loop/SKILL.md +401 -0
- package/skills/incident-playbook-construction/SKILL.md +383 -0
- package/skills/live-agent-lifecycle/SKILL.md +20 -6
- package/skills/mailbox-interactive/SKILL.md +19 -6
- package/skills/model-routing-context/SKILL.md +19 -1
- package/skills/multi-perspective-review/SKILL.md +19 -4
- package/skills/observability-reliability/SKILL.md +19 -2
- package/skills/orchestration/SKILL.md +20 -2
- package/skills/ownership-session-security/SKILL.md +20 -2
- package/skills/pi-extension-lifecycle/SKILL.md +20 -2
- package/skills/post-mortem/SKILL.md +7 -2
- package/skills/read-only-explorer/SKILL.md +20 -6
- package/skills/requirements-to-task-packet/SKILL.md +23 -3
- package/skills/resource-discovery-config/SKILL.md +20 -2
- package/skills/runtime-state-reader/SKILL.md +20 -2
- package/skills/safe-bash/SKILL.md +21 -6
- package/skills/scrutinize/SKILL.md +20 -2
- package/skills/secure-agent-orchestration-review/SKILL.md +29 -2
- package/skills/security-review/SKILL.md +560 -0
- package/skills/state-mutation-locking/SKILL.md +22 -2
- package/skills/systematic-debugging/SKILL.md +8 -6
- package/skills/threat-hypothesis-framework/SKILL.md +175 -0
- package/skills/ui-render-performance/SKILL.md +20 -2
- package/skills/verification-before-done/SKILL.md +17 -2
- package/skills/widget-rendering/SKILL.md +21 -6
- package/skills/workspace-isolation/SKILL.md +20 -6
- package/skills/worktree-isolation/SKILL.md +20 -6
- package/src/agents/agent-config.ts +40 -1
- package/src/config/config.ts +22 -5
- package/src/config/role-tools.ts +82 -0
- package/src/config/types.ts +4 -0
- package/src/extension/crew-cleanup.ts +114 -0
- package/src/extension/register.ts +15 -3
- package/src/extension/team-tool/run.ts +7 -7
- package/src/observability/event-bus.ts +60 -0
- package/src/runtime/background-runner.ts +8 -2
- package/src/runtime/child-pi.ts +122 -34
- package/src/runtime/crew-agent-runtime.ts +1 -0
- package/src/runtime/foreground-control.ts +87 -17
- package/src/runtime/pi-args.ts +11 -1
- package/src/runtime/pi-json-output.ts +31 -0
- package/src/runtime/progress-tracker.ts +124 -0
- package/src/runtime/skill-effectiveness.ts +473 -0
- package/src/runtime/skill-instructions.ts +37 -3
- package/src/runtime/task-runner.ts +91 -17
- package/src/runtime/team-runner.ts +11 -11
- package/src/runtime/tool-progress.ts +10 -3
- package/src/runtime/verification-gates.ts +367 -0
- package/src/schema/team-tool-schema.ts +7 -0
- package/src/state/decision-ledger.ts +92 -43
- package/src/state/event-log.ts +136 -10
- package/src/state/hook-instinct-bridge.ts +5 -5
- package/src/state/state-store.ts +3 -1
- package/src/state/types.ts +4 -0
- package/src/types/new-api-types.ts +34 -0
- package/src/ui/agent-management-overlay.ts +5 -1
- package/src/ui/crew-widget.ts +29 -15
- package/src/ui/powerbar-publisher.ts +100 -7
- package/src/ui/tool-render.ts +15 -15
- package/src/utils/session-utils.ts +52 -0
- package/src/worktree/worktree-manager.ts +32 -13
package/src/ui/tool-render.ts
CHANGED
|
@@ -90,12 +90,12 @@ export function renderTeamToolCall(
|
|
|
90
90
|
if (!context.expanded) {
|
|
91
91
|
const preview = goal.length > 60 ? goal.slice(0, 60) + "…" : goal;
|
|
92
92
|
return new Text(
|
|
93
|
-
`${theme.fg("toolTitle", theme.bold("team"))}
|
|
93
|
+
`${theme.fg("toolTitle", theme.bold("team"))} action=${theme.fg("accent", `'${action}'`)}${team}${theme.fg("dim", preview ? ` "${preview.replace(/\n/g, " ")}"` : "")}`,
|
|
94
94
|
0, 0,
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
const c = context.lastComponent instanceof Container ? (context.lastComponent.clear(), context.lastComponent) : new Container();
|
|
98
|
-
c.addChild(new Text(`${theme.fg("toolTitle", theme.bold("team"))}
|
|
98
|
+
c.addChild(new Text(`${theme.fg("toolTitle", theme.bold("team"))} action=${theme.fg("accent", `'${action}'`)}${team}`, 0, 0));
|
|
99
99
|
if (goal) { c.addChild(new Spacer(1)); c.addChild(new Text(theme.fg("text", goal), 0, 0)); }
|
|
100
100
|
return c;
|
|
101
101
|
}
|
|
@@ -110,13 +110,13 @@ export function renderAgentToolCall(
|
|
|
110
110
|
if (!context.expanded) {
|
|
111
111
|
const preview = prompt.length > 60 ? prompt.slice(0, 60) + "…" : prompt;
|
|
112
112
|
return new Text(
|
|
113
|
-
`${theme.fg("toolTitle", theme.bold("agent"))}
|
|
113
|
+
`${theme.fg("toolTitle", theme.bold("agent"))} ${theme.fg("accent", agentName)}${theme.fg("dim", preview ? ` "${preview.replace(/\n/g, " ")}"` : "")}`,
|
|
114
114
|
0, 0,
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
const c = context.lastComponent instanceof Container ? (context.lastComponent.clear(), context.lastComponent) : new Container();
|
|
118
|
-
const cwdLabel = args.cwd ? theme.fg("dim", `
|
|
119
|
-
c.addChild(new Text(`${theme.fg("toolTitle", theme.bold("agent"))}
|
|
118
|
+
const cwdLabel = args.cwd ? theme.fg("dim", ` (cwd: ${args.cwd})`) : "";
|
|
119
|
+
c.addChild(new Text(`${theme.fg("toolTitle", theme.bold("agent"))} ${theme.fg("accent", agentName)}${cwdLabel}`, 0, 0));
|
|
120
120
|
if (prompt) { c.addChild(new Spacer(1)); c.addChild(new Text(theme.fg("text", prompt), 0, 0)); }
|
|
121
121
|
return c;
|
|
122
122
|
}
|
|
@@ -153,21 +153,21 @@ export function renderAgentProgress(
|
|
|
153
153
|
const stats = `${prog?.toolCount ?? record.toolUses ?? 0} tools · ${formatDuration(durationMs)}`;
|
|
154
154
|
const modelStr = record.model ? ` (${record.model})` : "";
|
|
155
155
|
const roleLabel = record.role || record.agent || "agent";
|
|
156
|
-
addLine(`${icon}
|
|
156
|
+
addLine(`${icon} ${theme.fg("toolTitle", theme.bold(roleLabel))}${theme.fg("dim", modelStr)} — ${theme.fg("dim", stats)}`);
|
|
157
157
|
|
|
158
158
|
// Current tool (running)
|
|
159
159
|
if (isRunning && prog?.currentTool) {
|
|
160
160
|
const toolLabel = formatToolPreview(prog.currentTool, parseArgs(prog.currentToolArgs));
|
|
161
|
-
addLine(theme.fg("warning", `▸
|
|
161
|
+
addLine(theme.fg("warning", `▸ ${prog.currentTool}: ${toolLabel}`));
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
// Recent tools log
|
|
165
165
|
if (prog?.recentTools?.length) {
|
|
166
166
|
for (const tool of prog.recentTools) {
|
|
167
|
-
const detail = tool.args ? `:
|
|
167
|
+
const detail = tool.args ? `: ${tool.args}` : "";
|
|
168
168
|
const line = tool.endedAt
|
|
169
169
|
? theme.fg("muted", ` ${tool.tool}${detail}`)
|
|
170
|
-
: theme.fg("warning", `▸
|
|
170
|
+
: theme.fg("warning", `▸ ${tool.tool}${detail}`);
|
|
171
171
|
addLine(line);
|
|
172
172
|
}
|
|
173
173
|
}
|
|
@@ -231,7 +231,7 @@ export function renderTeamToolResult(
|
|
|
231
231
|
if ((result as any).runId) parts.push(`runId=${(result as any).runId}`);
|
|
232
232
|
if ((result as any).error) parts.push(theme.fg("error", `error`));
|
|
233
233
|
if ((result as any).goal && parts.length === 0) parts.push(theme.fg("dim", truncLine((result as any).goal, 116)));
|
|
234
|
-
return new Text(parts.join("
|
|
234
|
+
return new Text(parts.join(" · "), 0, 0);
|
|
235
235
|
}
|
|
236
236
|
// No details found, fall back to content
|
|
237
237
|
const text = extractText(result?.content).slice(0, 200);
|
|
@@ -253,7 +253,7 @@ export function renderTeamToolResult(
|
|
|
253
253
|
if (d.error) parts.push(theme.fg("error", `error=${d.error}`));
|
|
254
254
|
if (d.goal) parts.push(theme.fg("dim", truncLine(d.goal, 116)));
|
|
255
255
|
if (parts.length === 0) return new Text(theme.fg("muted", "(no output)"), 0, 0);
|
|
256
|
-
return new Text(parts.join("
|
|
256
|
+
return new Text(parts.join(" · "), 0, 0);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
/** agent tool result: shows agent output rows with status icons */
|
|
@@ -275,9 +275,9 @@ export function renderAgentToolResult(
|
|
|
275
275
|
: item.status === "running" ? theme.fg("warning", "⟳")
|
|
276
276
|
: theme.fg("dim", "○");
|
|
277
277
|
const label = item.agentId || "agent";
|
|
278
|
-
c.addChild(new Text(`${icon}
|
|
278
|
+
c.addChild(new Text(`${icon} ${theme.fg("toolTitle", theme.bold(label))}`, 0, 0));
|
|
279
279
|
if (item.error) {
|
|
280
|
-
c.addChild(new Text(theme.fg("error", ` Error:
|
|
280
|
+
c.addChild(new Text(theme.fg("error", ` Error: ${item.error}`), 0, 0));
|
|
281
281
|
} else if (item.output) {
|
|
282
282
|
for (const line of item.output.split("\n").slice(0, 5))
|
|
283
283
|
c.addChild(new Text(theme.fg("dim", ` ${truncLine(line, w - 2)}`), 0, 0));
|
|
@@ -293,9 +293,9 @@ export function renderAgentToolResult(
|
|
|
293
293
|
: d.status === "running" ? theme.fg("warning", "⟳")
|
|
294
294
|
: theme.fg("dim", "○");
|
|
295
295
|
const label = d.agentId;
|
|
296
|
-
c.addChild(new Text(`${icon}
|
|
296
|
+
c.addChild(new Text(`${icon} ${theme.fg("toolTitle", theme.bold(label))}`, 0, 0));
|
|
297
297
|
if (d.error) {
|
|
298
|
-
c.addChild(new Text(theme.fg("error", ` Error:
|
|
298
|
+
c.addChild(new Text(theme.fg("error", ` Error: ${d.error}`), 0, 0));
|
|
299
299
|
} else if (d.output) {
|
|
300
300
|
for (const line of d.output.split("\n").slice(0, 5))
|
|
301
301
|
c.addChild(new Text(theme.fg("dim", ` ${truncLine(line, w - 2)}`), 0, 0));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session ID utilities for pi-crew / pi session alignment.
|
|
3
|
+
*
|
|
4
|
+
* pi's session IDs use the format:
|
|
5
|
+
* ^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$
|
|
6
|
+
*
|
|
7
|
+
* This module provides utilities to generate valid pi session IDs
|
|
8
|
+
* that align with pi-crew run IDs for easy cross-referencing.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate session ID format per pi's requirements.
|
|
13
|
+
* Format: ^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$
|
|
14
|
+
*/
|
|
15
|
+
export function assertValidSessionId(id: string): void {
|
|
16
|
+
if (!id || !/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(id)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Invalid session id: must be non-empty, alphanumeric with '-', '_', '.' and start/end with alphanumeric`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert a pi-crew run ID to a valid pi session ID.
|
|
25
|
+
*
|
|
26
|
+
* - Strips non-alphanumeric characters
|
|
27
|
+
* - Lowercases
|
|
28
|
+
* - Prefixes with "crew-"
|
|
29
|
+
* - Truncates to 16 chars for safety
|
|
30
|
+
*
|
|
31
|
+
* @param runId - The pi-crew run ID (e.g., "team_20260528133725_02e05cc5480d0175")
|
|
32
|
+
* @returns Valid pi session ID (e.g., "crew-team20260528133")
|
|
33
|
+
*/
|
|
34
|
+
export function toPiSessionId(runId: string): string {
|
|
35
|
+
// Strip non-alphanumeric, lowercase, prefix with "crew-"
|
|
36
|
+
const sanitized = runId.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
|
|
37
|
+
return `crew-${sanitized.slice(0, 16)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate and convert a run ID to a pi session ID.
|
|
42
|
+
* Returns the session ID if valid, or undefined if conversion would produce invalid ID.
|
|
43
|
+
*/
|
|
44
|
+
export function safeToPiSessionId(runId: string): string | undefined {
|
|
45
|
+
try {
|
|
46
|
+
const sessionId = toPiSessionId(runId);
|
|
47
|
+
assertValidSessionId(sessionId);
|
|
48
|
+
return sessionId;
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -128,19 +128,38 @@ function runSetupHook(manifest: TeamRunManifest, task: TeamTaskState, repoRoot:
|
|
|
128
128
|
return [];
|
|
129
129
|
}
|
|
130
130
|
const nodeHook = hookPath.endsWith(".js") || hookPath.endsWith(".cjs") || hookPath.endsWith(".mjs");
|
|
131
|
-
//
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
shell:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
131
|
+
// For .bat/.cmd files on Windows, execute via cmd.exe /c directly
|
|
132
|
+
const isBatchFile = hookPath.endsWith(".bat") || hookPath.endsWith(".cmd");
|
|
133
|
+
// SECURITY: Never use shell:true — prevents command injection from untrusted hooks.
|
|
134
|
+
// Non-node, non-batch hooks on Windows will fail to execute rather than
|
|
135
|
+
// running through a shell that could interpret malicious filenames.
|
|
136
|
+
const useShell = false;
|
|
137
|
+
if (process.platform === "win32" && !nodeHook && !isBatchFile) {
|
|
138
|
+
logInternalError("worktree.setupHook.windowsNoShell", new Error("Non-node, non-batch hook skipped on Windows (shell:true disabled for security)"), `hook=${hookPath}`);
|
|
139
|
+
}
|
|
140
|
+
const result = isBatchFile
|
|
141
|
+
? spawnSync("cmd.exe", ["/c", hookPath], {
|
|
142
|
+
cwd: worktreePath,
|
|
143
|
+
encoding: "utf-8",
|
|
144
|
+
input: JSON.stringify({ version: 1, repoRoot, worktreePath, agentCwd: worktreePath, branch, runId: manifest.runId, taskId: task.id, agent: task.agent }),
|
|
145
|
+
timeout: cfg.setupHookTimeoutMs ?? 30_000,
|
|
146
|
+
shell: false, // cmd.exe /c handles batch files safely
|
|
147
|
+
env: sanitizeEnvSecrets(process.env, {
|
|
148
|
+
allowList: ["PATH", "HOME", "USERPROFILE", "TEMP", "TMP", "TMPDIR", "LANG", "LC_ALL", "PI_*"],
|
|
149
|
+
}),
|
|
150
|
+
windowsHide: true,
|
|
151
|
+
})
|
|
152
|
+
: spawnSync(nodeHook ? process.execPath : hookPath, nodeHook ? [hookPath] : [], {
|
|
153
|
+
cwd: worktreePath,
|
|
154
|
+
encoding: "utf-8",
|
|
155
|
+
input: JSON.stringify({ version: 1, repoRoot, worktreePath, agentCwd: worktreePath, branch, runId: manifest.runId, taskId: task.id, agent: task.agent }),
|
|
156
|
+
timeout: cfg.setupHookTimeoutMs ?? 30_000,
|
|
157
|
+
shell: useShell,
|
|
158
|
+
env: sanitizeEnvSecrets(process.env, {
|
|
159
|
+
allowList: ["PATH", "HOME", "USERPROFILE", "TEMP", "TMP", "TMPDIR", "LANG", "LC_ALL", "PI_*"],
|
|
160
|
+
}),
|
|
161
|
+
windowsHide: true,
|
|
162
|
+
});
|
|
144
163
|
if (result.error) throw new Error(`worktree setup hook failed: ${result.error.message}`);
|
|
145
164
|
if (result.status !== 0) throw new Error(`worktree setup hook failed with exit code ${result.status}: ${result.stderr || result.stdout || "no output"}`);
|
|
146
165
|
const trimmed = result.stdout.trim();
|