@melihmucuk/pi-crew 1.0.18 → 1.0.20

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/README.md CHANGED
@@ -34,7 +34,7 @@ Lists available subagent definitions and active subagents owned by the current s
34
34
 
35
35
  #### `crew_spawn`
36
36
 
37
- Spawns a subagent in an isolated session. The subagent runs in the background with its own context window, tools, and skills. When it finishes, the result is delivered to the session that spawned it as a steering message that triggers a new turn. If that session is not active, the result is queued until you switch back to it.
37
+ Spawns a subagent in an isolated session. Each spawn includes a concise `brief` label for session lists and a full self-contained `task`. The subagent runs in the background with its own context window, tools, and skills. When it finishes, the result is delivered to the session that spawned it as a steering message that triggers a new turn. If that session is not active, the result is queued until you switch back to it.
38
38
 
39
39
  ```
40
40
  "spawn scout and find all API endpoints and their authentication methods"
@@ -100,14 +100,14 @@ A bundled orchestration skill that provides best practices for delegating work t
100
100
 
101
101
  pi-crew ships with six subagent definitions that cover common workflows:
102
102
 
103
- | Subagent | Purpose | Tools | Model |
104
- | -------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------- | --------------------------- |
105
- | **scout** | Investigates codebase and returns structured findings. Read-only. Use before planning or implementing to gather context. | read, grep, find, ls, bash | openai-codex/gpt-5.5 |
106
- | **planner** | Analyzes requirements and produces a step-by-step implementation plan. Read-only. Does not write code. Interactive. | read, grep, find, ls, bash | openai-codex/gpt-5.5 |
107
- | **oracle** | Evaluates critical decisions, surfaces blind spots, and challenges assumptions. Read-only. Does not implement. Interactive. | read, grep, find, ls, bash | openai-codex/gpt-5.5 |
108
- | **code-reviewer** | Reviews code changes for bugs, security issues, and correctness. Read-only. Does not fix issues. | read, grep, find, ls, bash | openai-codex/gpt-5.2 |
109
- | **quality-reviewer** | Reviews code structure for maintainability, duplication, and complexity. Read-only. Does not look for bugs. | read, grep, find, ls, bash | openai-codex/gpt-5.2 |
110
- | **worker** | Implements code changes, fixes, and refactors autonomously. Has full read-write access to the codebase. | all | openai-codex/gpt-5.5 |
103
+ | Subagent | Purpose | Tools | Model | Thinking |
104
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------- | --------------------------- | -------- |
105
+ | **scout** | Investigates codebase and returns structured findings. Read-only. | read, grep, find, ls, bash | openai-codex/gpt-5.5 | off |
106
+ | **planner** | Produces deterministic implementation plans. Read-only. Does not write code. | read, grep, find, ls, bash | openai-codex/gpt-5.5 | high |
107
+ | **oracle** | Evaluates critical decisions, surfaces blind spots, and challenges assumptions. Read-only. | read, grep, find, ls, bash | openai-codex/gpt-5.5 | xhigh |
108
+ | **code-reviewer** | Reviews scoped code for actionable bugs. Read-only. | read, grep, find, ls, bash | openai-codex/gpt-5.5 | high |
109
+ | **quality-reviewer** | Reviews scoped code for maintainability, duplication, and complexity. Read-only. | read, grep, find, ls, bash | openai-codex/gpt-5.5 | high |
110
+ | **worker** | Implements scoped code changes safely and verifies them. | all | openai-codex/gpt-5.5 | low |
111
111
 
112
112
  Read-only bundled subagents still keep `bash` for inspection workflows like `git` and `ast-grep`. This is an instruction-level contract, not a sandbox boundary.
113
113
 
@@ -1,26 +1,32 @@
1
1
  ---
2
2
  name: code-reviewer
3
- description: Reviews changed code for actionable bugs. Read-only.
4
- model: openai-codex/gpt-5.2
3
+ description: Reviews scoped code for actionable bugs. Read-only.
4
+ model: openai-codex/gpt-5.5
5
5
  thinking: high
6
6
  tools: read, grep, find, ls, bash
7
7
  ---
8
8
 
9
- You are a read-only code reviewer. Your goal is not to find something; it is to decide whether the changed code contains realistic, actionable bugs. An empty review is a valid successful outcome. Reply in the user's language.
9
+ You are a read-only code reviewer. Your goal is not to find something; it is to decide whether the reviewed scope contains realistic, actionable bugs. An empty review is a valid successful outcome. Reply in the user's language.
10
10
 
11
11
  Do not modify files. Use bash only for read-only inspection. Do not run builds, tests, typechecks, formatters, installers, or commands that may change project state.
12
12
 
13
13
  ## Scope
14
14
 
15
- Review the provided scope. If none is provided, review uncommitted changes. For commits, branches, PRs, files, or "latest" requests, inspect the corresponding diff. If "latest" is requested, review the last 5 commits unless a count is given.
15
+ Review the provided scope. If none is provided, review uncommitted changes.
16
16
 
17
- For large or broad diffs, summarize coverage by area with brief risk notes, then deeply review only the highest-risk changed files: business logic, auth, data mutation, error handling, and public APIs. Avoid exhaustive file inventories.
17
+ For commits, branches, PRs, files, directories, modules, or "latest" requests, inspect the corresponding diff or code. If "latest" is requested, review the last 5 commits unless a count is given.
18
18
 
19
- Review changed-code issues only. Pre-existing code is reportable only when the change triggers it or makes it relevant.
19
+ If "full", "codebase", or whole-repo review is requested, perform a bounded bug audit: map the highest-risk areas, deeply inspect selected files, state coverage/skipped areas briefly, and do not imply exhaustive coverage.
20
+
21
+ For large or broad scopes, prioritize highest-risk areas: business logic, auth/security, data mutation, persistence, external integrations, concurrency/async, error handling, and public APIs.
22
+
23
+ For changed-code scopes, report pre-existing issues only when the change triggers or makes them relevant. For full-codebase scopes, report existing issues only when directly evidenced, realistically triggerable, and worth acting on now.
20
24
 
21
25
  ## Method
22
26
 
23
- Diffs are not enough. Before reporting a finding, read the full changed file involved. Trace direct callers/callees or nearby patterns only when needed. Check local conventions only when relevant. Stop expanding context when it stops adding evidence.
27
+ Diffs are not enough. Before reporting a finding, read the full relevant file involved. Trace direct callers/callees or nearby patterns only when needed. Check local conventions only when relevant. Stop expanding context when it stops adding evidence.
28
+
29
+ For full-codebase scopes, make findings only from files and paths you directly inspected; verify any caller, route, config, schema, or runtime assumption the finding depends on.
24
30
 
25
31
  Do not report findings from skipped or unreviewed files. A finding requires direct inspection of the relevant file or diff context; if a file was skipped, only mention it as skipped, not as evidence for a finding.
26
32
 
@@ -40,17 +46,15 @@ Report the same finding pattern at most twice, then list other affected location
40
46
 
41
47
  ## Severity
42
48
 
43
- - Critical: proven realistic security, data loss, or severe breakage.
44
- - Major: realistic bug likely to affect users, developers, or operations.
45
- - Minor: real non-blocking bug or high-risk coverage gap.
49
+ - Critical: urgent, high-impact issue within this reviewer's scope that can cause severe user, data, security, operational, or near-term development breakage.
50
+ - Major: realistic issue within this reviewer's scope likely to affect users, developers, operations, or maintainability enough to act on soon.
51
+ - Minor: real but non-blocking issue within this reviewer's scope, localized maintenance friction, or high-risk coverage gap.
46
52
 
47
53
  ## Output
48
54
 
49
55
  If no findings:
50
56
 
51
57
  **No issues found.**
52
- Reviewed: [files]
53
- Overall confidence: [high/medium]
54
58
 
55
59
  For each finding:
56
60
 
@@ -58,6 +62,7 @@ For each finding:
58
62
  File: `path:line`
59
63
  Issue: what is wrong
60
64
  Evidence: what you verified
65
+ Impact: concrete consequence
61
66
  Fix: suggested correction
62
67
 
63
68
  Be direct, concise, and unpadded.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: quality-reviewer
3
- description: Reviews changed code for maintainability, duplication, and complexity. Read-only.
4
- model: openai-codex/gpt-5.2
3
+ description: Reviews scoped code for maintainability, duplication, and complexity. Read-only.
4
+ model: openai-codex/gpt-5.5
5
5
  thinking: high
6
6
  tools: read, grep, find, ls, bash
7
7
  ---
@@ -16,7 +16,7 @@ Do not modify files. Use bash only for read-only inspection. Do not run builds,
16
16
 
17
17
  Review the provided scope. If none is provided, review uncommitted changes. For files, directories, modules, commits, branches, PRs, or "latest" requests, inspect the corresponding code or diff. If "latest" is requested, review the last 5 commits unless a count is given.
18
18
 
19
- If "full" or "codebase" is requested, first produce a structural risk map, then deeply review only the highest-risk areas.
19
+ If "full", "codebase", or whole-repo review is requested, first produce a structural risk map, then deeply review only the highest-risk areas, state coverage/skipped areas briefly, and do not imply exhaustive coverage.
20
20
 
21
21
  For large or broad scopes, summarize coverage by area with brief structural notes, then deeply review the highest-risk areas/files: large files, dependency-heavy files, widely imported files, or files crossing module boundaries. Avoid exhaustive file inventories; state skipped areas briefly.
22
22
 
@@ -48,32 +48,23 @@ Default stance: no new abstraction unless it reduces present-day duplication or
48
48
 
49
49
  ## Severity
50
50
 
51
- - High: structure will materially hinder near-term changes or debugging.
52
- - Medium: noticeable maintenance friction with concrete evidence.
53
- - Minor: small structural friction on a realistic future change/debug path.
51
+ - Critical: urgent, high-impact issue within this reviewer's scope that can cause severe user, data, security, operational, or near-term development breakage.
52
+ - Major: realistic issue within this reviewer's scope likely to affect users, developers, operations, or maintainability enough to act on soon.
53
+ - Minor: real but non-blocking issue within this reviewer's scope, localized maintenance friction, or high-risk coverage gap.
54
54
 
55
55
  ## Output
56
56
 
57
57
  If no findings:
58
58
 
59
59
  **No issues found.**
60
- Reviewed: [files]
61
- Overall health: [brief assessment]
62
60
 
63
61
  For each finding:
64
62
 
65
63
  **[SEVERITY] Category: Title**
66
64
  File: `path:line`
67
- Issue: structural problem
68
- Impact: concrete future change/debug task made harder
65
+ Issue: what is wrong
69
66
  Evidence: what you verified
70
- Fix: specific refactoring approach
71
-
72
- End with:
73
-
74
- **Quality Review Summary**
75
- Files reviewed: [count]
76
- Findings: [count by severity]
77
- Overall health: [one sentence]
67
+ Impact: concrete consequence
68
+ Fix: suggested correction
78
69
 
79
70
  Be direct, concise, and unpadded.
package/extension/crew.ts CHANGED
@@ -8,7 +8,6 @@ import {
8
8
  type SendMessageFn,
9
9
  type SteeringPayload,
10
10
  type SubagentStatus,
11
- sendRemainingNote,
12
11
  sendSteeringMessage,
13
12
  } from "./ui.js";
14
13
 
@@ -28,6 +27,7 @@ export interface SubagentState {
28
27
  id: string;
29
28
  agentConfig: AgentConfig;
30
29
  task: string;
30
+ brief: string;
31
31
  status: SubagentStatus;
32
32
  ownerSessionId: string;
33
33
  session: AgentSession | null;
@@ -36,7 +36,6 @@ export interface SubagentState {
36
36
  model: string | undefined;
37
37
  error?: string;
38
38
  result?: string;
39
- promptAbortController?: AbortController;
40
39
  unsubscribe?: () => void;
41
40
  }
42
41
 
@@ -65,6 +64,7 @@ export interface SpawnContext {
65
64
  agentDir: string;
66
65
  parentSessionFile?: string;
67
66
  onWarning?: (message: string) => void;
67
+ brief?: string;
68
68
  }
69
69
 
70
70
  type SettledSubagentStatus = Extract<SubagentStatus, "done" | "waiting" | "error" | "aborted">;
@@ -151,7 +151,7 @@ export class CrewRuntime {
151
151
  ctx: SpawnContext,
152
152
  extensionResolvedPath: string,
153
153
  ): string {
154
- const state = this.createAgent(agentConfig, task, ownerSessionId);
154
+ const state = this.createAgent(agentConfig, task, ctx.brief ?? "", ownerSessionId);
155
155
  this.refreshWidgetFor(ownerSessionId);
156
156
  this.runner.start(state, {
157
157
  cwd,
@@ -228,12 +228,13 @@ export class CrewRuntime {
228
228
  .map(buildActiveAgentSummary);
229
229
  }
230
230
 
231
- private createAgent(agentConfig: AgentConfig, task: string, ownerSessionId: string): SubagentState {
231
+ private createAgent(agentConfig: AgentConfig, task: string, brief: string, ownerSessionId: string): SubagentState {
232
232
  const id = generateId(agentConfig.name, new Set(this.agents.keys()));
233
233
  const state: SubagentState = {
234
234
  id,
235
235
  agentConfig,
236
236
  task,
237
+ brief,
237
238
  status: "running",
238
239
  ownerSessionId,
239
240
  session: null,
@@ -271,7 +272,6 @@ export class CrewRuntime {
271
272
 
272
273
  private disposeAgent(state: SubagentState): void {
273
274
  state.unsubscribe?.();
274
- state.promptAbortController = undefined;
275
275
  state.session?.dispose();
276
276
  this.agents.delete(state.id);
277
277
  this.refreshWidgetFor(state.ownerSessionId);
@@ -320,8 +320,6 @@ export class CrewRuntime {
320
320
  private schedulePendingFlushFor(sessionId: string): void {
321
321
  if (!this.pendingMessages.some((entry) => entry.ownerSessionId === sessionId)) return;
322
322
 
323
- // Delay flush to next macrotask. session_start fires before pi-core reconnects the
324
- // agent event listener; synchronous delivery can lose JSONL persistence.
325
323
  this.flushScheduled = true;
326
324
  this.scheduleFlush(() => {
327
325
  this.flushScheduled = false;
@@ -370,14 +368,23 @@ export class CrewRuntime {
370
368
 
371
369
  const remaining = this.countRunningForOwner(ownerSessionId, payload.id);
372
370
  const isIdle = this.activeBinding.isIdle();
373
- const triggerResultTurn = !(isIdle && remaining > 0);
371
+ const triggerResultTurn = payload.status === "waiting" || !(isIdle && remaining > 0);
374
372
 
375
373
  sendSteeringMessage(payload, this.activeBinding.sendMessage, { isIdle, triggerTurn: triggerResultTurn });
376
- sendRemainingNote(remaining, this.activeBinding.sendMessage, { isIdle, triggerTurn: isIdle && remaining > 0 });
377
374
  }
378
375
  }
379
376
 
377
+ const CREW_RUNTIME_VERSION = 2;
380
378
  const crewRuntimeKey = Symbol.for("pi-crew.runtime");
381
- const globalWithCrewRuntime = globalThis as typeof globalThis & Record<symbol, CrewRuntime | undefined>;
379
+ const globalWithCrewRuntime = globalThis as typeof globalThis & Record<symbol, (CrewRuntime & { __piCrewRuntimeVersion?: number }) | undefined>;
382
380
 
383
- export const crewRuntime = globalWithCrewRuntime[crewRuntimeKey] ??= new CrewRuntime();
381
+ function createCrewRuntime(): CrewRuntime & { __piCrewRuntimeVersion?: number } {
382
+ const runtime = new CrewRuntime() as CrewRuntime & { __piCrewRuntimeVersion?: number };
383
+ runtime.__piCrewRuntimeVersion = CREW_RUNTIME_VERSION;
384
+ return runtime;
385
+ }
386
+
387
+ const existingRuntime = globalWithCrewRuntime[crewRuntimeKey];
388
+ export const crewRuntime = existingRuntime?.__piCrewRuntimeVersion === CREW_RUNTIME_VERSION
389
+ ? existingRuntime
390
+ : (globalWithCrewRuntime[crewRuntimeKey] = createCrewRuntime());
@@ -1,12 +1,25 @@
1
1
  import { dirname } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
4
- import { crewRuntime } from "./crew.js";
4
+ import { crewRuntime, type CrewRuntime } from "./crew.js";
5
5
  import { registerCrewTools } from "./tools.js";
6
6
  import { registerCrewMessageRenderers, updateWidget } from "./ui.js";
7
7
 
8
8
  const extensionDir = dirname(fileURLToPath(import.meta.url));
9
9
 
10
+ interface ProcessHooks {
11
+ once(event: "SIGINT", listener: () => void): unknown;
12
+ on(event: "beforeExit", listener: () => void): unknown;
13
+ exit(code?: number): never;
14
+ }
15
+
16
+ interface RegisterPiCrewExtensionOptions {
17
+ crew?: CrewRuntime;
18
+ extensionDir?: string;
19
+ processHooks?: ProcessHooks;
20
+ processHooksSetupKey?: symbol;
21
+ }
22
+
10
23
  // Process-level cleanup for subagents on exit
11
24
  const processHooksSetupKey = Symbol.for("pi-crew.processHooksSetup");
12
25
  const globalWithProcessHooks = globalThis as typeof globalThis & Record<
@@ -14,29 +27,30 @@ const globalWithProcessHooks = globalThis as typeof globalThis & Record<
14
27
  boolean | undefined
15
28
  >;
16
29
 
17
- function setupProcessHooks() {
18
- if (globalWithProcessHooks[processHooksSetupKey]) return;
19
- globalWithProcessHooks[processHooksSetupKey] = true;
30
+ function setupProcessHooks(crew: CrewRuntime, processHooks: ProcessHooks, setupKey: symbol) {
31
+ if (globalWithProcessHooks[setupKey]) return;
32
+ globalWithProcessHooks[setupKey] = true;
20
33
 
21
- process.once("SIGINT", () => {
22
- crewRuntime.abortAll();
23
- process.exit(130);
34
+ processHooks.once("SIGINT", () => {
35
+ crew.abortAll();
36
+ processHooks.exit(130);
24
37
  });
25
- process.on("beforeExit", () => crewRuntime.abortAll());
38
+ processHooks.on("beforeExit", () => crew.abortAll());
26
39
  }
27
40
 
28
- export default function (pi: ExtensionAPI) {
41
+ export function registerPiCrewExtension(pi: ExtensionAPI, options: RegisterPiCrewExtensionOptions = {}) {
42
+ const crew = options.crew ?? crewRuntime;
29
43
  let currentCtx: ExtensionContext | undefined;
30
44
 
31
- setupProcessHooks();
45
+ setupProcessHooks(crew, options.processHooks ?? process, options.processHooksSetupKey ?? processHooksSetupKey);
32
46
 
33
47
  const refreshWidget = () => {
34
- if (currentCtx) updateWidget(currentCtx, crewRuntime);
48
+ if (currentCtx) updateWidget(currentCtx, crew);
35
49
  };
36
50
 
37
51
  const activateSession = (ctx: ExtensionContext) => {
38
52
  currentCtx = ctx;
39
- crewRuntime.activateSession(
53
+ crew.activateSession(
40
54
  {
41
55
  sessionId: ctx.sessionManager.getSessionId(),
42
56
  isIdle: () => ctx.isIdle(),
@@ -52,13 +66,17 @@ export default function (pi: ExtensionAPI) {
52
66
 
53
67
  pi.on("session_shutdown", (event, ctx) => {
54
68
  const sessionId = ctx.sessionManager.getSessionId();
55
- crewRuntime.deactivateSession(sessionId);
69
+ crew.deactivateSession(sessionId);
56
70
 
57
71
  if (event.reason === "quit") {
58
- crewRuntime.abortAll();
72
+ crew.abortAll();
59
73
  }
60
74
  });
61
75
 
62
- registerCrewTools(pi, crewRuntime, extensionDir);
76
+ registerCrewTools(pi, crew, options.extensionDir ?? extensionDir);
63
77
  registerCrewMessageRenderers(pi);
64
78
  }
79
+
80
+ export default function (pi: ExtensionAPI) {
81
+ registerPiCrewExtension(pi);
82
+ }
@@ -12,7 +12,6 @@ import type { AgentConfig } from "./catalog.js";
12
12
  import { SUPPORTED_TOOL_NAMES, type SupportedToolName } from "./catalog.js";
13
13
  import type { SubagentState } from "./crew.js";
14
14
  import type { SubagentStatus } from "./ui.js";
15
- import { runPromptWithOverflowRecovery } from "./overflow-recovery.js";
16
15
 
17
16
  export interface BootstrapContext {
18
17
  model: Model<Api> | undefined;
@@ -176,6 +175,16 @@ function isAborted(state: SubagentState): boolean {
176
175
  return state.status === "aborted";
177
176
  }
178
177
 
178
+ function normalizeSessionNamePart(value: string): string {
179
+ return value.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
180
+ }
181
+
182
+ export function formatSubagentSessionName(state: Pick<SubagentState, "agentConfig" | "brief" | "id">): string {
183
+ const agentName = normalizeSessionNamePart(state.agentConfig.name) || "subagent";
184
+ const brief = normalizeSessionNamePart(state.brief) || state.id;
185
+ return `crew: ${agentName} · ${brief}`;
186
+ }
187
+
179
188
  export class SubagentSessionRunner implements SubagentRunner {
180
189
  constructor(private readonly callbacks: SubagentRunnerCallbacks) {}
181
190
 
@@ -188,10 +197,7 @@ export class SubagentSessionRunner implements SubagentRunner {
188
197
  }
189
198
 
190
199
  abort(state: SubagentState): void {
191
- state.promptAbortController?.abort();
192
- state.promptAbortController = undefined;
193
200
  state.session?.abortCompaction();
194
- state.session?.abortRetry();
195
201
  state.session?.abort().catch(() => {});
196
202
  }
197
203
 
@@ -215,31 +221,23 @@ export class SubagentSessionRunner implements SubagentRunner {
215
221
  return false;
216
222
  }
217
223
  state.session = session;
224
+ session.setSessionName(formatSubagentSessionName(state));
218
225
  return true;
219
226
  }
220
227
 
221
228
  private async runPromptCycle(state: SubagentState, prompt: string): Promise<void> {
222
229
  if (isAborted(state)) return;
223
230
 
224
- const abortController = new AbortController();
225
- state.promptAbortController = abortController;
226
-
227
231
  try {
228
- const recovery = await runPromptWithOverflowRecovery(state.session!, prompt, abortController.signal);
232
+ await state.session!.prompt(prompt);
229
233
  if (isAborted(state)) return;
230
234
 
231
235
  const outcome = getPromptOutcome(state);
232
- if (recovery === "failed" && outcome.status !== "error") {
233
- this.callbacks.onSettled(state, "error", { error: "Context overflow recovery failed" });
234
- return;
235
- }
236
236
  this.callbacks.onSettled(state, outcome.status, outcome);
237
237
  } catch (err) {
238
238
  if (isAborted(state)) return;
239
239
  const error = err instanceof Error ? err.message : String(err);
240
240
  this.callbacks.onSettled(state, "error", { error });
241
- } finally {
242
- state.promptAbortController = undefined;
243
241
  }
244
242
  }
245
243
 
@@ -175,24 +175,29 @@ export function registerCrewTools(pi: ExtensionAPI, crew: CrewRuntime, extension
175
175
  },
176
176
  });
177
177
 
178
- registerActionTool<{ subagent: string; task: string }>(pi, {
178
+ registerActionTool<{ subagent: string; brief: string; task: string }>(pi, {
179
179
  name: "crew_spawn",
180
180
  label: "Spawn Crew",
181
181
  description:
182
182
  "Spawn a non-blocking subagent that runs in an isolated session. The subagent works independently while your session stays interactive. Results are delivered back to your session as steering messages.",
183
183
  parameters: Type.Object({
184
184
  subagent: Type.String({ description: "Subagent name from crew_list" }),
185
- task: Type.String({ description: "Task to delegate to the subagent" }),
185
+ brief: Type.String({ description: "Concise task label for session lists, ideally under 80 characters. This is not the full task." }),
186
+ task: Type.String({ description: "Full self-contained task to delegate to the subagent" }),
186
187
  }),
187
188
  promptSnippet: "Spawn a non-blocking subagent. Use crew_list first to see available subagents.",
188
189
  promptGuidelines: [
189
190
  "crew_spawn: Spawn a discovered subagent for one clearly delegated, self-contained task.",
190
- "crew_spawn: Include only needed context: constraints, relevant files, acceptance criteria, and expected output.",
191
+ "crew_spawn: Provide brief as a concise human-readable task label for session lists, ideally under 80 characters; do not put the full task there.",
192
+ "crew_spawn: Include only needed context in task: constraints, relevant files, acceptance criteria, and expected output.",
191
193
  "crew_spawn: After spawning, ownership transfers to the subagent; do not work on that task yourself.",
192
194
  "crew_spawn: Results arrive as steering messages; do not poll crew_list or fabricate results.",
193
195
  "crew_spawn: Use the bundled pi-crew skill for detailed delegation patterns.",
194
196
  ],
195
197
  action: (params, ctx) => {
198
+ const brief = params.brief.trim();
199
+ if (!brief) return toolError("brief is required and must not be empty.");
200
+
196
201
  const toolCtx = getToolContext(ctx);
197
202
  const { agents, warnings } = discoverAgents(toolCtx.cwd);
198
203
  notifyDiscoveryWarnings(ctx, shownDiscoveryWarnings, warnings);
@@ -208,6 +213,7 @@ export function registerCrewTools(pi: ExtensionAPI, crew: CrewRuntime, extension
208
213
  toolCtx.cwd,
209
214
  toolCtx.callerSessionId,
210
215
  {
216
+ brief,
211
217
  model: ctx.model,
212
218
  modelRegistry: ctx.modelRegistry,
213
219
  agentDir: getAgentDir(),
@@ -218,11 +224,13 @@ export function registerCrewTools(pi: ExtensionAPI, crew: CrewRuntime, extension
218
224
  );
219
225
  return toolSuccess(
220
226
  `Subagent '${subagent.name}' spawned as ${id}. Result will be delivered as a steering message when done.`,
221
- { id, agentName: subagent.name, task: params.task },
227
+ { id, agentName: subagent.name, brief, task: params.task },
222
228
  );
223
229
  },
224
230
  renderCall(args, theme, _context) {
225
- return renderCrewCall(theme, "crew_spawn", args.subagent || "...", args.task);
231
+ const subagent = args.subagent || "...";
232
+ const title = args.brief ? `${subagent} · ${args.brief}` : subagent;
233
+ return renderCrewCall(theme, "crew_spawn", title, args.task);
226
234
  },
227
235
  });
228
236
 
package/extension/ui.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { pathToFileURL } from "node:url";
1
2
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
3
  import {
3
4
  type ExtensionAPI,
@@ -104,23 +105,6 @@ export function sendSteeringMessage(
104
105
  );
105
106
  }
106
107
 
107
- export function sendRemainingNote(
108
- remainingCount: number,
109
- sendMessage: SendMessageFn,
110
- opts: { isIdle: boolean; triggerTurn: boolean },
111
- ): void {
112
- if (remainingCount <= 0) return;
113
- sendWithDeliveryPolicy(
114
- {
115
- customType: "crew-remaining",
116
- content: `⏳ ${remainingCount} subagent(s) still running`,
117
- display: true,
118
- },
119
- sendMessage,
120
- opts,
121
- );
122
- }
123
-
124
108
  export function sendCrewListActiveWarning(
125
109
  sendMessage: SendMessageFn,
126
110
  opts: { isIdle: boolean; triggerTurn: boolean },
@@ -161,6 +145,11 @@ function renderWarningMessage(content: unknown, theme: MessageRendererTheme): Bo
161
145
  return box;
162
146
  }
163
147
 
148
+ function linkFilePath(filePath: string): string {
149
+ const url = pathToFileURL(filePath).href;
150
+ return `\x1b]8;;${url}\x07${filePath}\x1b]8;;\x07`;
151
+ }
152
+
164
153
  export function registerCrewMessageRenderers(pi: ExtensionAPI): void {
165
154
  pi.registerMessageRenderer("crew-result", (message, { expanded }, theme) => {
166
155
  const details = message.details as CrewResultMessageDetails | undefined;
@@ -175,7 +164,7 @@ export function registerCrewMessageRenderers(pi: ExtensionAPI): void {
175
164
  box.addChild(new Text(header, 0, 0));
176
165
 
177
166
  if (details?.sessionFile) {
178
- box.addChild(new Text(theme.fg("muted", `📁 ${details.sessionFile}`), 0, 0));
167
+ box.addChild(new Text(theme.fg("muted", `📁 ${linkFilePath(details.sessionFile)}`), 0, 0));
179
168
  }
180
169
 
181
170
  if (body) {
@@ -193,7 +182,6 @@ export function registerCrewMessageRenderers(pi: ExtensionAPI): void {
193
182
  return box;
194
183
  });
195
184
 
196
- pi.registerMessageRenderer("crew-remaining", (message, _options, theme) => renderWarningMessage(message.content, theme));
197
185
  pi.registerMessageRenderer("crew-list-warning", (message, _options, theme) => renderWarningMessage(message.content, theme));
198
186
  }
199
187
 
@@ -260,7 +248,7 @@ function syncWidgetText(state: WidgetState, agents: ActiveAgentSummary[]): void
260
248
  }
261
249
 
262
250
  export function updateWidget(ctx: ExtensionContext, crew: CrewRuntime): void {
263
- if (!ctx.hasUI) {
251
+ if (ctx.mode !== "tui") {
264
252
  clearWidget();
265
253
  return;
266
254
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melihmucuk/pi-crew",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "type": "module",
5
5
  "description": "Non-blocking subagent orchestration for pi coding agent",
6
6
  "files": [
@@ -43,13 +43,13 @@
43
43
  "typebox": "*"
44
44
  },
45
45
  "devDependencies": {
46
- "@earendil-works/pi-agent-core": "^0.75.4",
47
- "@earendil-works/pi-ai": "^0.75.4",
48
- "@earendil-works/pi-coding-agent": "^0.75.4",
49
- "@earendil-works/pi-tui": "^0.75.4",
46
+ "@earendil-works/pi-agent-core": "^0.78.1",
47
+ "@earendil-works/pi-ai": "^0.78.1",
48
+ "@earendil-works/pi-coding-agent": "^0.78.1",
49
+ "@earendil-works/pi-tui": "^0.78.1",
50
50
  "@types/node": "^22.19.17",
51
51
  "tsx": "^4.22.3",
52
- "typebox": "^1.1.38",
52
+ "typebox": "^1.2.1",
53
53
  "typescript": "^5.9.3"
54
54
  }
55
55
  }
@@ -10,7 +10,7 @@ You are a review orchestrator, not a reviewer. Resolve the review scope, gather
10
10
 
11
11
  ## Scope
12
12
 
13
- Use the user's scope when provided. Otherwise rely on each reviewer’s default scope. If “latest” or “recent” is requested, review the last 5 commits unless a count is given.
13
+ Use the user's scope when provided. Otherwise rely on each reviewer’s default scope. If “latest” or “recent” is requested, review the last 5 commits unless a count is given. If “full”, “codebase”, or whole-repo review is requested, treat it as an explicit non-default scope and pass that scope to reviewers.
14
14
 
15
15
  Gather minimal review context: why the changes were made, expected behavior/outcome, feature or bug intent, notable fixes since any prior review, verification already run, and user instructions that are specific to this review.
16
16
 
@@ -33,6 +33,8 @@ If you include a Goal, make it specific to the change intent, not the reviewer r
33
33
 
34
34
  For default reviews, do not include a Scope section or mention uncommitted/current repo changes in the subagent brief unless needed to disambiguate scope. If you need to state task-specific emphasis, use `Review focus:` instead of `Scope:`.
35
35
 
36
+ For full/codebase requests, state that the requested scope is a bounded full-codebase review.
37
+
36
38
  Do not echo the raw user instruction if it is already represented in the intent summary; quote it only when exact wording matters.
37
39
 
38
40
  Do not restate reviewer-role boilerplate implied by the selected reviewer, such as telling `code-reviewer` to find actionable bugs or telling `quality-reviewer` to review maintainability. Do not include default scope, generic non-goals, acceptance criteria, output format, edit permissions, or severity rules unless the user explicitly overrides them.
@@ -49,26 +51,33 @@ You may do a minimal spot-check only when a finding is ambiguous, high-impact, o
49
51
 
50
52
  Reply in the user's language. Apply the gate before merging.
51
53
 
54
+ For each accepted finding, preserve enough detail to act without reading subagent logs:
55
+
56
+ **[SEVERITY] Category: Title**
57
+ Source: `code-reviewer` | `quality-reviewer` | `both`
58
+ File: `path:line`
59
+ Issue: what is wrong
60
+ Evidence: what was verified
61
+ Impact: concrete consequence
62
+ Fix: specific suggested correction
63
+
64
+ Do not forward findings as summaries only. If evidence, location, or fix is missing and cannot be inferred from the reviewer result, omit the finding or report it as insufficiently evidenced.
65
+
52
66
  Sections:
53
67
 
54
- ### Consensus Findings
55
- Issues clearly reported by both reviewers.
68
+ ### Findings
69
+ List all accepted findings in severity order. Use `Source:` to identify `code-reviewer`, `quality-reviewer`, or `both`.
56
70
 
57
- ### Code Review Findings
58
- Accepted findings only from `code-reviewer`.
71
+ If both reviewers report no accepted findings, write only:
59
72
 
60
- ### Quality Review Findings
61
- Accepted findings only from `quality-reviewer`.
73
+ No accepted findings.
62
74
 
63
- ### Final Summary
64
- - Review scope
65
- - Reviewers run and any failures
66
- - Consensus findings count
67
- - Code review findings count
68
- - Quality review findings count
69
- - Overall assessment
75
+ ### Summary
76
+ - Scope: [review scope]
77
+ - Reviewers: [completed reviewers and any failures]
78
+ - Findings: [count by severity]
79
+ - Result: [one-sentence overall assessment]
70
80
 
71
81
  Rules:
72
82
  - Do not repeat overlapping findings.
73
- - Do not present a single-reviewer finding as consensus.
74
- - If both reviewers report no accepted findings, say so clearly.
83
+ - Mark a finding as `Source: both` only when both reviewers clearly reported the same issue.
@@ -2,7 +2,12 @@
2
2
 
3
3
  ## Delegation Checklist
4
4
 
5
- Before `crew_spawn`, ensure the brief is self-contained but not mechanically templated. Include only information that helps this specific subagent do this specific task:
5
+ Before `crew_spawn`, provide:
6
+
7
+ - `brief`: a concise human-readable task label for session lists, ideally under 80 characters. Use a few words for intent/outcome; do not include the full task, acceptance criteria, long paths, secrets, or mechanical repo state.
8
+ - `task`: a self-contained delegated task body, not mechanically templated.
9
+
10
+ In `task`, include only information that helps this specific subagent do this specific task:
6
11
 
7
12
  - Intent, expected outcome, and relevant user decisions.
8
13
  - User-provided references, plus a concise summary after reading them when practical.
@@ -76,7 +81,7 @@ If ownership overlaps, serialize the work.
76
81
  ## Tool Notes
77
82
 
78
83
  - `crew_list`: discovery before a new spawn decision or requested status snapshot; never completion polling.
79
- - `crew_spawn`: self-contained delegation; ownership transfers after spawn.
84
+ - `crew_spawn`: provide `brief` plus a self-contained `task`; ownership transfers after spawn.
80
85
  - `crew_respond`: send a follow-up to a waiting interactive subagent; fire-and-forget.
81
86
  - `crew_done`: close a waiting interactive subagent when complete.
82
87
  - `crew_abort`: abort active owned subagents only when obsolete, wrong, or cancelled.
@@ -21,6 +21,11 @@ See [REFERENCE.md](REFERENCE.md) for examples and detailed handling patterns.
21
21
 
22
22
  ## Spawn Brief
23
23
 
24
+ Every `crew_spawn` requires both `brief` and `task`:
25
+
26
+ - `brief`: concise human-readable task label for session lists, ideally under 80 characters. Write the intent/outcome in a few words; do not include the full task, acceptance criteria, long paths, secrets, or mechanical repo state.
27
+ - `task`: self-contained delegated work body with the context the subagent needs.
28
+
24
29
  Send a self-contained task, but do not fill a template mechanically. Use only sections that add task-specific value, for example:
25
30
 
26
31
  ```md
@@ -35,6 +40,8 @@ Omit sections that would only restate the selected subagent’s role, default sc
35
40
 
36
41
  Include only information that helps this specific subagent do this specific task: intent, expected outcome, relevant decisions, exact errors/output, unusual constraints, and file paths or entry points that genuinely clarify the task. Use short Markdown sections and bullets when they improve scanability, especially for multi-part intent, constraints, observations, requirements, or acceptance criteria; avoid dense paragraphs.
37
42
 
43
+ For repeated workflows, make each task independent. Do not assume a new subagent knows earlier loop results, owner-session discussion, or what another subagent saw. If prior findings, fixes, decisions, or verification matter, summarize the concrete facts or point to durable artifacts the subagent can inspect. Avoid vague references like “we fixed the first review findings” unless you also state what those findings/fixes were or define the current review target without relying on that history.
44
+
38
45
  Do not restate boilerplate implied by the selected subagent’s role, name, or description. Avoid repeating default scope, output format, edit permissions, or repo guidance. Subagents run in the same cwd as the orchestrator, so do not include mechanical Git state they can inspect themselves, such as full changed-file lists, staged/unstaged/untracked inventories, branch/cwd details, or generic project constraints, unless those details define a non-default scope or prevent ambiguity.
39
46
 
40
47
  If the user points to a plan, spec, issue, design, or doc as task intent, read it when practical and summarize the relevant intent instead of merely passing the path. Prefer explaining why the work matters and what outcome is expected over restating repository state.
@@ -44,7 +51,7 @@ If the user points to a plan, spec, issue, design, or doc as task intent, read i
44
51
  - Wait for subagent results before using them. Never invent or predict results.
45
52
  - Evaluate each result against the task acceptance criteria.
46
53
  - If results conflict, are incomplete, or miss criteria, state that clearly and use a follow-up or new spawn only when needed.
47
- - After spawning, continue only with unrelated work or end the turn.
54
+ - After spawning, do not work on the delegated task; wait for results, continue only with unrelated work, or end the turn.
48
55
 
49
56
  ## Interactive Subagents
50
57
 
@@ -1,211 +0,0 @@
1
- import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
2
-
3
- const OVERFLOW_RECOVERY_TIMEOUT_MS = 120_000;
4
-
5
- /**
6
- * Short grace period for the first terminal agent_end after prompt() resolves.
7
- * If this window expires, we still wait the full recovery timeout.
8
- */
9
- const INITIAL_AGENT_END_WAIT_MS = 5_000;
10
-
11
- type PhaseWaitResult = "done" | "timeout" | "cancelled";
12
-
13
- export type OverflowRecoveryResult = "none" | "recovered" | "failed";
14
-
15
- interface DeferredPhase {
16
- promise: Promise<void>;
17
- resolve: () => void;
18
- isDone: () => boolean;
19
- }
20
-
21
- function createDeferredPhase(): DeferredPhase {
22
- let done = false;
23
- let resolveFn: (() => void) | undefined;
24
-
25
- const promise = new Promise<void>((resolve) => {
26
- resolveFn = () => {
27
- if (done) return;
28
- done = true;
29
- resolve();
30
- };
31
- });
32
-
33
- return {
34
- promise,
35
- resolve: () => resolveFn?.(),
36
- isDone: () => done,
37
- };
38
- }
39
-
40
- class OverflowRecoveryTracker {
41
- private overflowDetected = false;
42
- private compactionWillRetry = false;
43
-
44
- private autoRetryActive = false;
45
- private readonly initialAgentEnd = createDeferredPhase();
46
- private compactionEnd: DeferredPhase | undefined;
47
- private retryAgentEnd: DeferredPhase | undefined;
48
- private overflowAutoRetryEnd: DeferredPhase | undefined;
49
- private timers: ReturnType<typeof setTimeout>[] = [];
50
-
51
- handleEvent(event: AgentSessionEvent): void {
52
- switch (event.type) {
53
- case "agent_end":
54
- this.onAgentEnd();
55
- break;
56
- case "compaction_start":
57
- this.onCompactionStart(event.reason);
58
- break;
59
- case "compaction_end":
60
- this.onCompactionEnd(event.reason, event.willRetry);
61
- break;
62
- case "auto_retry_start":
63
- this.onAutoRetryStart();
64
- break;
65
- case "auto_retry_end":
66
- this.onAutoRetryEnd();
67
- break;
68
- default:
69
- break;
70
- }
71
- }
72
-
73
- async awaitCompletion(signal: AbortSignal): Promise<OverflowRecoveryResult> {
74
- const cancelPromise = new Promise<void>((resolve) => {
75
- if (signal.aborted) {
76
- resolve();
77
- return;
78
- }
79
- signal.addEventListener("abort", () => resolve(), { once: true });
80
- });
81
-
82
- try {
83
- let initialEnd = await this.waitForPhase(
84
- this.initialAgentEnd.promise,
85
- INITIAL_AGENT_END_WAIT_MS,
86
- cancelPromise,
87
- );
88
-
89
- if (initialEnd === "timeout") {
90
- initialEnd = await this.waitForPhase(
91
- this.initialAgentEnd.promise,
92
- OVERFLOW_RECOVERY_TIMEOUT_MS,
93
- cancelPromise,
94
- );
95
- }
96
-
97
- if (initialEnd !== "done") {
98
- return this.overflowDetected ? "failed" : "none";
99
- }
100
-
101
- if (!this.overflowDetected) return "none";
102
-
103
- if (this.compactionEnd) {
104
- const compactionEnd = await this.waitForPhase(
105
- this.compactionEnd.promise,
106
- OVERFLOW_RECOVERY_TIMEOUT_MS,
107
- cancelPromise,
108
- );
109
- if (compactionEnd !== "done") return "failed";
110
- }
111
-
112
- if (!this.compactionWillRetry) return "failed";
113
-
114
- if (this.retryAgentEnd) {
115
- const retryEnd = await this.waitForPhase(
116
- this.retryAgentEnd.promise,
117
- OVERFLOW_RECOVERY_TIMEOUT_MS,
118
- cancelPromise,
119
- );
120
- if (retryEnd !== "done") return "failed";
121
- }
122
-
123
- if (this.overflowAutoRetryEnd) {
124
- const autoRetryEnd = await this.waitForPhase(
125
- this.overflowAutoRetryEnd.promise,
126
- OVERFLOW_RECOVERY_TIMEOUT_MS,
127
- cancelPromise,
128
- );
129
- if (autoRetryEnd !== "done") return "failed";
130
- }
131
-
132
- return "recovered";
133
- } finally {
134
- for (const timer of this.timers) clearTimeout(timer);
135
- }
136
- }
137
-
138
- private async waitForPhase(
139
- phasePromise: Promise<void>,
140
- timeoutMs: number,
141
- cancelPromise: Promise<void>,
142
- ): Promise<PhaseWaitResult> {
143
- return Promise.race([
144
- phasePromise.then(() => "done" as const),
145
- cancelPromise.then(() => "cancelled" as const),
146
- new Promise<"timeout">((resolve) => {
147
- this.timers.push(setTimeout(() => resolve("timeout"), timeoutMs));
148
- }),
149
- ]);
150
- }
151
-
152
- // agent_end can be followed immediately by auto_retry_start in the same
153
- // _processAgentEvent tick. Resolve on microtask so we can ignore retrying
154
- // attempts and only accept terminal agent_end events.
155
- private onAgentEnd(): void {
156
- queueMicrotask(() => {
157
- if (this.autoRetryActive) return;
158
-
159
- if (!this.initialAgentEnd.isDone()) {
160
- this.initialAgentEnd.resolve();
161
- return;
162
- }
163
-
164
- this.retryAgentEnd?.resolve();
165
- });
166
- }
167
-
168
- private onCompactionStart(reason: "manual" | "threshold" | "overflow"): void {
169
- if (reason !== "overflow") return;
170
- this.overflowDetected = true;
171
- this.compactionEnd ??= createDeferredPhase();
172
- }
173
-
174
- private onCompactionEnd(reason: "manual" | "threshold" | "overflow", willRetry: boolean): void {
175
- if (reason !== "overflow") return;
176
-
177
- this.compactionWillRetry = willRetry;
178
- if (willRetry) {
179
- this.retryAgentEnd ??= createDeferredPhase();
180
- }
181
- this.compactionEnd?.resolve();
182
- }
183
-
184
- private onAutoRetryStart(): void {
185
- this.autoRetryActive = true;
186
- if (this.overflowDetected) {
187
- this.overflowAutoRetryEnd ??= createDeferredPhase();
188
- }
189
- }
190
-
191
- private onAutoRetryEnd(): void {
192
- this.autoRetryActive = false;
193
- this.overflowAutoRetryEnd?.resolve();
194
- }
195
- }
196
-
197
- export async function runPromptWithOverflowRecovery(
198
- session: AgentSession,
199
- text: string,
200
- signal: AbortSignal,
201
- ): Promise<OverflowRecoveryResult> {
202
- const tracker = new OverflowRecoveryTracker();
203
- const unsubscribe = session.subscribe((event) => tracker.handleEvent(event));
204
-
205
- try {
206
- await session.prompt(text);
207
- return await tracker.awaitCompletion(signal);
208
- } finally {
209
- unsubscribe();
210
- }
211
- }