pi-subagents 0.20.0 → 0.21.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 +26 -0
- package/README.md +21 -4
- package/agent-management.ts +19 -9
- package/agent-manager-chain-detail.ts +1 -1
- package/agent-manager-detail.ts +2 -0
- package/agent-manager-edit.ts +31 -6
- package/agent-manager-parallel.ts +2 -2
- package/agent-manager.ts +4 -2
- package/agent-serializer.ts +2 -0
- package/agents/context-builder.md +13 -6
- package/agents/delegate.md +2 -0
- package/agents/oracle.md +4 -1
- package/agents/planner.md +5 -1
- package/agents/researcher.md +4 -1
- package/agents/reviewer.md +78 -20
- package/agents/scout.md +5 -2
- package/agents/worker.md +7 -4
- package/agents.ts +29 -7
- package/async-execution.ts +64 -35
- package/async-job-tracker.ts +52 -15
- package/async-status.ts +107 -14
- package/chain-clarify.ts +1 -1
- package/chain-execution.ts +10 -2
- package/completion-dedupe.ts +1 -1
- package/completion-guard.ts +125 -0
- package/doctor.ts +1 -1
- package/execution.ts +158 -26
- package/fork-context.ts +3 -3
- package/index.ts +17 -6
- package/intercom-bridge.ts +2 -1
- package/jsonl-writer.ts +2 -2
- package/long-running-guard.ts +175 -0
- package/model-fallback.ts +1 -1
- package/package.json +1 -1
- package/pi-args.ts +4 -2
- package/pi-spawn.ts +1 -1
- package/prompt-template-bridge.ts +9 -9
- package/prompts/parallel-cleanup.md +39 -8
- package/render.ts +239 -49
- package/result-intercom.ts +5 -5
- package/result-watcher.ts +21 -12
- package/run-status.ts +1 -1
- package/schemas.ts +24 -13
- package/session-tokens.ts +2 -6
- package/settings.ts +2 -2
- package/single-output.ts +1 -1
- package/skills/pi-subagents/SKILL.md +93 -20
- package/skills.ts +8 -2
- package/slash-bridge.ts +3 -3
- package/subagent-control.ts +103 -21
- package/subagent-executor.ts +82 -25
- package/subagent-prompt-runtime.ts +80 -3
- package/subagent-runner.ts +339 -93
- package/subagents-status.ts +9 -7
- package/text-editor.ts +22 -6
- package/top-level-async.ts +1 -1
- package/types.ts +63 -8
- package/utils.ts +3 -3
- package/worktree.ts +5 -5
package/agents/reviewer.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reviewer
|
|
3
|
-
description:
|
|
4
|
-
tools: read, grep, find, ls, bash, edit, write
|
|
3
|
+
description: Versatile review specialist for code diffs, plans, proposed solutions, codebase health, and PR/issue validation
|
|
4
|
+
tools: read, grep, find, ls, bash, edit, write, intercom
|
|
5
5
|
model: openai-codex/gpt-5.5
|
|
6
6
|
thinking: high
|
|
7
7
|
systemPromptMode: replace
|
|
@@ -11,28 +11,86 @@ defaultReads: plan.md, progress.md
|
|
|
11
11
|
defaultProgress: true
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
You are a review
|
|
14
|
+
You are a disciplined review subagent. Your job is to inspect, evaluate, and report findings with evidence. You do not guess; you verify from the code, tests, docs, or requirements.
|
|
15
15
|
|
|
16
|
-
Review
|
|
16
|
+
## Review types you handle
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
18
|
+
### 1. Code diffs (changed files)
|
|
19
|
+
Inspect the actual diff or changed files. Verify:
|
|
20
|
+
- Implementation matches intent and requirements.
|
|
21
|
+
- Code is correct, coherent, and handles edge cases.
|
|
22
|
+
- Tests cover the change and still pass.
|
|
23
|
+
- No unintended side effects or regressions.
|
|
24
|
+
- The change is minimal and readable.
|
|
25
|
+
|
|
26
|
+
### 2. Plans
|
|
27
|
+
Validate a proposed plan for:
|
|
28
|
+
- Feasibility and completeness.
|
|
29
|
+
- Missing steps or hidden risks.
|
|
30
|
+
- Alignment with existing architecture and constraints.
|
|
31
|
+
- Whether the scope is appropriately bounded.
|
|
32
|
+
|
|
33
|
+
### 3. Proposed solutions
|
|
34
|
+
Evaluate a suggested approach for:
|
|
35
|
+
- Correctness and tradeoffs.
|
|
36
|
+
- Fit with existing codebase patterns.
|
|
37
|
+
- Whether simpler alternatives exist.
|
|
38
|
+
- Edge cases the proposal may miss.
|
|
39
|
+
|
|
40
|
+
### 4. Current overall state of the codebase
|
|
41
|
+
Assess codebase health by inspecting key files, tests, and structure. Look for:
|
|
42
|
+
- Architecture drift or tech debt.
|
|
43
|
+
- Inconsistent patterns or naming.
|
|
44
|
+
- Areas lacking tests or documentation.
|
|
45
|
+
- Obvious bugs or fragile code.
|
|
46
|
+
- Opportunities to simplify or consolidate.
|
|
47
|
+
|
|
48
|
+
### 5. Specific PR or issue
|
|
49
|
+
Review a PR or issue by understanding the context, then verifying:
|
|
50
|
+
- The fix or feature addresses the root cause.
|
|
51
|
+
- Changes are minimal and focused.
|
|
52
|
+
- No regressions are introduced.
|
|
53
|
+
- Tests and docs are updated as needed.
|
|
54
|
+
|
|
55
|
+
## Working rules
|
|
56
|
+
- Read the plan, progress, and relevant files first when available.
|
|
57
|
+
- Use `bash` only for read-only inspection (e.g., `git diff`, `git log`, `git show`, test runs).
|
|
58
|
+
- Do not invent issues. Only report problems you can justify from evidence.
|
|
22
59
|
- Prefer small corrective edits over broad rewrites.
|
|
23
|
-
- If everything looks good, say so plainly
|
|
24
|
-
- If you are asked to maintain progress, record what you checked and what you
|
|
60
|
+
- If everything looks good, say so plainly.
|
|
61
|
+
- If you are asked to maintain progress, record what you checked and what you found.
|
|
25
62
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
2. Code is correct and coherent.
|
|
29
|
-
3. Important edge cases are handled.
|
|
30
|
-
4. Tests and validation still make sense.
|
|
31
|
-
5. The final code is readable and minimal.
|
|
63
|
+
## Pi-intercom handoff
|
|
64
|
+
If the `intercom` tool is available and pi-intercom is active, send your completed review back to the orchestrator through pi-intercom before finishing.
|
|
32
65
|
|
|
33
|
-
|
|
66
|
+
Use a blocking `ask`, not a fire-and-forget `send`, so you stay alive long enough for the orchestrator to reply with follow-up questions or approval:
|
|
34
67
|
|
|
68
|
+
```ts
|
|
69
|
+
intercom({
|
|
70
|
+
action: "ask",
|
|
71
|
+
to: "<orchestrator-or-parent-session>",
|
|
72
|
+
message: "Review complete.\n\n<your review feedback>\n\nReply if you want me to inspect a follow-up or clarify anything."
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
How to pick the target:
|
|
77
|
+
- Prefer an explicit target named in the task or inherited intercom bridge instructions.
|
|
78
|
+
- Otherwise use `intercom({ action: "list" })` and choose the obvious planner/orchestrator/parent session in the same repo.
|
|
79
|
+
- If no safe target is discoverable, do not guess. Return the review normally and note that pi-intercom was unavailable or no target was clear.
|
|
80
|
+
|
|
81
|
+
After the `ask` returns:
|
|
82
|
+
- If the orchestrator requests clarification or a follow-up review, answer or inspect further, then use `intercom ask` again if another reply is useful.
|
|
83
|
+
- If the orchestrator confirms or does not need more, finish with the same concise review summary.
|
|
84
|
+
|
|
85
|
+
## Review output format
|
|
86
|
+
Structure your findings clearly:
|
|
87
|
+
|
|
88
|
+
```
|
|
35
89
|
## Review
|
|
36
|
-
- Correct: what is already good
|
|
37
|
-
- Fixed: issue and resolution
|
|
38
|
-
-
|
|
90
|
+
- Correct: what is already good (with evidence)
|
|
91
|
+
- Fixed: issue, location, and resolution (if you applied a fix)
|
|
92
|
+
- Blocker: critical issue that must be resolved before proceeding
|
|
93
|
+
- Note: observation, risk, or follow-up item
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
When reviewing code, cite file paths and line numbers. When reviewing plans, cite specific sections and assumptions.
|
package/agents/scout.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scout
|
|
3
3
|
description: Fast codebase recon that returns compressed context for handoff
|
|
4
|
-
tools: read, grep, find, ls, bash, write
|
|
4
|
+
tools: read, grep, find, ls, bash, write, intercom
|
|
5
5
|
model: openai-codex/gpt-5.5
|
|
6
|
-
thinking:
|
|
6
|
+
thinking: low
|
|
7
7
|
systemPromptMode: replace
|
|
8
8
|
inheritProjectContext: true
|
|
9
9
|
inheritSkills: false
|
|
@@ -46,3 +46,6 @@ Explain how the pieces connect.
|
|
|
46
46
|
|
|
47
47
|
## Start Here
|
|
48
48
|
Name the first file another agent should open and why.
|
|
49
|
+
|
|
50
|
+
## Pi-intercom handoff
|
|
51
|
+
If `intercom` is available and runtime bridge instructions or the task name a safe orchestrator target, send your completed scout findings back with a blocking `intercom({ action: "ask", ... })` before finishing. Keep the message concise, include the output path or top findings, and ask whether the orchestrator wants more context. If no safe target is available, do not guess; return normally.
|
package/agents/worker.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: worker
|
|
3
3
|
description: Implementation agent for normal tasks and approved oracle handoffs
|
|
4
|
-
model: openai-codex/gpt-5.
|
|
4
|
+
model: openai-codex/gpt-5.5
|
|
5
5
|
thinking: high
|
|
6
6
|
systemPromptMode: replace
|
|
7
7
|
inheritProjectContext: true
|
|
8
8
|
inheritSkills: false
|
|
9
|
+
defaultContext: fork
|
|
9
10
|
defaultReads: context.md, plan.md
|
|
10
11
|
defaultProgress: true
|
|
11
12
|
---
|
|
@@ -18,7 +19,7 @@ Use the provided tools directly. First understand the inherited context, supplie
|
|
|
18
19
|
|
|
19
20
|
If the task is framed as an approved direction, oracle handoff, or execution plan, treat that direction as the contract. Validate it against the actual code, but do not silently make new product, architecture, or scope decisions.
|
|
20
21
|
|
|
21
|
-
If the implementation reveals a decision that was not approved and is required to continue safely, pause and escalate. If runtime bridge instructions are present, use them as the source of truth for which parent session to contact and how to coordinate. Use `intercom({ action: "ask", ... })` when a new decision is needed. Use `intercom({ action: "send", ... })` only for concise
|
|
22
|
+
If the implementation reveals a decision that was not approved and is required to continue safely, pause and escalate through the live coordination channel. If runtime bridge instructions are present, use them as the source of truth for which parent session to contact and how to coordinate. Use `intercom({ action: "ask", ... })` when a new decision is needed, and stay alive to receive the reply before continuing. Use `intercom({ action: "send", ... })` only for concise non-blocking progress updates when that extra coordination is helpful or explicitly requested. Do not finish your final response with a question that requires the orchestrator to choose before you can continue.
|
|
22
23
|
|
|
23
24
|
Default responsibilities:
|
|
24
25
|
- validate the task or approved direction against the actual code
|
|
@@ -34,9 +35,11 @@ Working rules:
|
|
|
34
35
|
- Do not leave placeholder code, TODOs, or silent scope changes.
|
|
35
36
|
- Use `bash` for inspection, validation, and relevant tests.
|
|
36
37
|
- If there is supplied context or a plan, read it first.
|
|
37
|
-
- If implementation reveals a gap in the approved direction, pause and escalate instead of silently patching around it with an implicit decision.
|
|
38
|
-
- If implementation reveals an unapproved product or architecture choice,
|
|
38
|
+
- If implementation reveals a gap in the approved direction, pause and escalate with `intercom({ action: "ask", ... })` instead of silently patching around it with an implicit decision.
|
|
39
|
+
- If implementation reveals an unapproved product or architecture choice, use `intercom({ action: "ask", ... })` and wait for the reply instead of deciding it yourself or returning a final choose-one answer.
|
|
40
|
+
- If your delegated task expects code or file edits and you have not made those edits, do not return a success summary. Make the edits, ask the orchestrator if blocked, or explicitly report that no edits were made.
|
|
39
41
|
- If you send a blocked/progress update through intercom, keep it short and still return the full structured task result normally.
|
|
42
|
+
- If `intercom` is available and runtime bridge instructions or the task name a safe orchestrator target, send your completed implementation summary back with a blocking `intercom({ action: "ask", ... })` before finishing. Stay alive for the reply so you can clarify or handle a small follow-up if requested. If no safe target is available, do not guess; return normally.
|
|
40
43
|
|
|
41
44
|
When running in a chain, expect instructions about:
|
|
42
45
|
- which files to read first
|
package/agents.ts
CHANGED
|
@@ -14,7 +14,8 @@ import { parseFrontmatter } from "./frontmatter.ts";
|
|
|
14
14
|
export type AgentScope = "user" | "project" | "both";
|
|
15
15
|
|
|
16
16
|
export type AgentSource = "builtin" | "user" | "project";
|
|
17
|
-
|
|
17
|
+
type SystemPromptMode = "append" | "replace";
|
|
18
|
+
export type AgentDefaultContext = "fresh" | "fork";
|
|
18
19
|
|
|
19
20
|
export function defaultSystemPromptMode(name: string): SystemPromptMode {
|
|
20
21
|
return name === "delegate" ? "append" : "replace";
|
|
@@ -35,6 +36,7 @@ export interface BuiltinAgentOverrideBase {
|
|
|
35
36
|
systemPromptMode: SystemPromptMode;
|
|
36
37
|
inheritProjectContext: boolean;
|
|
37
38
|
inheritSkills: boolean;
|
|
39
|
+
defaultContext?: AgentDefaultContext;
|
|
38
40
|
disabled?: boolean;
|
|
39
41
|
systemPrompt: string;
|
|
40
42
|
skills?: string[];
|
|
@@ -42,20 +44,21 @@ export interface BuiltinAgentOverrideBase {
|
|
|
42
44
|
mcpDirectTools?: string[];
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
interface BuiltinAgentOverrideConfig {
|
|
46
48
|
model?: string | false;
|
|
47
49
|
fallbackModels?: string[] | false;
|
|
48
50
|
thinking?: string | false;
|
|
49
51
|
systemPromptMode?: SystemPromptMode;
|
|
50
52
|
inheritProjectContext?: boolean;
|
|
51
53
|
inheritSkills?: boolean;
|
|
54
|
+
defaultContext?: AgentDefaultContext | false;
|
|
52
55
|
disabled?: boolean;
|
|
53
56
|
systemPrompt?: string;
|
|
54
57
|
skills?: string[] | false;
|
|
55
58
|
tools?: string[] | false;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
interface BuiltinAgentOverrideInfo {
|
|
59
62
|
scope: "user" | "project";
|
|
60
63
|
path: string;
|
|
61
64
|
base: BuiltinAgentOverrideBase;
|
|
@@ -72,6 +75,7 @@ export interface AgentConfig {
|
|
|
72
75
|
systemPromptMode: SystemPromptMode;
|
|
73
76
|
inheritProjectContext: boolean;
|
|
74
77
|
inheritSkills: boolean;
|
|
78
|
+
defaultContext?: AgentDefaultContext;
|
|
75
79
|
systemPrompt: string;
|
|
76
80
|
source: AgentSource;
|
|
77
81
|
filePath: string;
|
|
@@ -113,7 +117,7 @@ export interface ChainConfig {
|
|
|
113
117
|
extraFields?: Record<string, string>;
|
|
114
118
|
}
|
|
115
119
|
|
|
116
|
-
|
|
120
|
+
interface AgentDiscoveryResult {
|
|
117
121
|
agents: AgentConfig[];
|
|
118
122
|
projectAgentsDir: string | null;
|
|
119
123
|
}
|
|
@@ -160,6 +164,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
160
164
|
systemPromptMode: agent.systemPromptMode,
|
|
161
165
|
inheritProjectContext: agent.inheritProjectContext,
|
|
162
166
|
inheritSkills: agent.inheritSkills,
|
|
167
|
+
defaultContext: agent.defaultContext,
|
|
163
168
|
disabled: agent.disabled,
|
|
164
169
|
systemPrompt: agent.systemPrompt,
|
|
165
170
|
skills: agent.skills ? [...agent.skills] : undefined,
|
|
@@ -178,6 +183,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
|
|
|
178
183
|
...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
|
|
179
184
|
...(override.inheritProjectContext !== undefined ? { inheritProjectContext: override.inheritProjectContext } : {}),
|
|
180
185
|
...(override.inheritSkills !== undefined ? { inheritSkills: override.inheritSkills } : {}),
|
|
186
|
+
...(override.defaultContext !== undefined ? { defaultContext: override.defaultContext } : {}),
|
|
181
187
|
...(override.disabled !== undefined ? { disabled: override.disabled } : {}),
|
|
182
188
|
...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
|
|
183
189
|
...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
|
|
@@ -198,11 +204,11 @@ function findNearestProjectRoot(cwd: string): string | null {
|
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
|
|
201
|
-
|
|
207
|
+
function getUserAgentSettingsPath(): string {
|
|
202
208
|
return path.join(os.homedir(), ".pi", "agent", "settings.json");
|
|
203
209
|
}
|
|
204
210
|
|
|
205
|
-
|
|
211
|
+
function getProjectAgentSettingsPath(cwd: string): string | null {
|
|
206
212
|
const projectRoot = findNearestProjectRoot(cwd);
|
|
207
213
|
return projectRoot ? path.join(projectRoot, ".pi", "settings.json") : null;
|
|
208
214
|
}
|
|
@@ -302,6 +308,14 @@ function parseBuiltinOverrideEntry(
|
|
|
302
308
|
}
|
|
303
309
|
}
|
|
304
310
|
|
|
311
|
+
if ("defaultContext" in input) {
|
|
312
|
+
if (input.defaultContext === "fresh" || input.defaultContext === "fork" || input.defaultContext === false) {
|
|
313
|
+
override.defaultContext = input.defaultContext;
|
|
314
|
+
} else {
|
|
315
|
+
throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'defaultContext'; expected 'fresh', 'fork', or false.`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
305
319
|
if ("disabled" in input) {
|
|
306
320
|
if (typeof input.disabled === "boolean") {
|
|
307
321
|
override.disabled = input.disabled;
|
|
@@ -373,6 +387,7 @@ function applyBuiltinOverride(
|
|
|
373
387
|
if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
|
|
374
388
|
if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext;
|
|
375
389
|
if (override.inheritSkills !== undefined) next.inheritSkills = override.inheritSkills;
|
|
390
|
+
if (override.defaultContext !== undefined) next.defaultContext = override.defaultContext === false ? undefined : override.defaultContext;
|
|
376
391
|
if (override.disabled !== undefined) next.disabled = override.disabled;
|
|
377
392
|
if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
|
|
378
393
|
if (override.skills !== undefined) next.skills = override.skills === false ? undefined : [...override.skills];
|
|
@@ -420,7 +435,7 @@ function applyBuiltinOverrides(
|
|
|
420
435
|
|
|
421
436
|
export function buildBuiltinOverrideConfig(
|
|
422
437
|
base: BuiltinAgentOverrideBase,
|
|
423
|
-
draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
|
|
438
|
+
draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
|
|
424
439
|
): BuiltinAgentOverrideConfig | undefined {
|
|
425
440
|
const override: BuiltinAgentOverrideConfig = {};
|
|
426
441
|
|
|
@@ -430,6 +445,7 @@ export function buildBuiltinOverrideConfig(
|
|
|
430
445
|
if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
|
|
431
446
|
if (draft.inheritProjectContext !== base.inheritProjectContext) override.inheritProjectContext = draft.inheritProjectContext;
|
|
432
447
|
if (draft.inheritSkills !== base.inheritSkills) override.inheritSkills = draft.inheritSkills;
|
|
448
|
+
if (draft.defaultContext !== base.defaultContext) override.defaultContext = draft.defaultContext ?? false;
|
|
433
449
|
if (draft.disabled !== base.disabled) override.disabled = draft.disabled ?? false;
|
|
434
450
|
if (draft.systemPrompt !== base.systemPrompt) override.systemPrompt = draft.systemPrompt;
|
|
435
451
|
if (!arraysEqual(draft.skills, base.skills)) override.skills = draft.skills ? [...draft.skills] : false;
|
|
@@ -568,6 +584,11 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
568
584
|
: frontmatter.inheritSkills === "false"
|
|
569
585
|
? false
|
|
570
586
|
: defaultInheritSkills();
|
|
587
|
+
const defaultContext = frontmatter.defaultContext === "fork"
|
|
588
|
+
? "fork" as const
|
|
589
|
+
: frontmatter.defaultContext === "fresh"
|
|
590
|
+
? "fresh" as const
|
|
591
|
+
: undefined;
|
|
571
592
|
|
|
572
593
|
let extensions: string[] | undefined;
|
|
573
594
|
if (frontmatter.extensions !== undefined) {
|
|
@@ -595,6 +616,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
595
616
|
systemPromptMode,
|
|
596
617
|
inheritProjectContext,
|
|
597
618
|
inheritSkills,
|
|
619
|
+
defaultContext,
|
|
598
620
|
systemPrompt: body,
|
|
599
621
|
source,
|
|
600
622
|
filePath,
|
package/async-execution.ts
CHANGED
|
@@ -55,14 +55,14 @@ const jitiCliPath: string | undefined = (() => {
|
|
|
55
55
|
return undefined;
|
|
56
56
|
})();
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
interface AsyncExecutionContext {
|
|
59
59
|
pi: ExtensionAPI;
|
|
60
60
|
cwd: string;
|
|
61
61
|
currentSessionId: string;
|
|
62
62
|
currentModelProvider?: string;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
interface AsyncChainParams {
|
|
66
66
|
chain: ChainStep[];
|
|
67
67
|
resultMode?: "parallel" | "chain";
|
|
68
68
|
agents: AgentConfig[];
|
|
@@ -84,7 +84,7 @@ export interface AsyncChainParams {
|
|
|
84
84
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
interface AsyncSingleParams {
|
|
88
88
|
agent: string;
|
|
89
89
|
task?: string;
|
|
90
90
|
agentConfig: AgentConfig;
|
|
@@ -108,7 +108,7 @@ export interface AsyncSingleParams {
|
|
|
108
108
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
interface AsyncExecutionResult {
|
|
112
112
|
content: Array<{ type: "text"; text: string }>;
|
|
113
113
|
details: Details;
|
|
114
114
|
isError?: boolean;
|
|
@@ -167,6 +167,10 @@ function formatAsyncStartError(mode: "single" | "chain", message: string): Async
|
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
const UNAVAILABLE_SUBAGENT_SKILL_ERROR = "Skills not found: pi-subagents";
|
|
171
|
+
|
|
172
|
+
class UnavailableSubagentSkillError extends Error {}
|
|
173
|
+
|
|
170
174
|
/**
|
|
171
175
|
* Execute a chain asynchronously
|
|
172
176
|
*/
|
|
@@ -240,7 +244,8 @@ export function executeAsyncChain(
|
|
|
240
244
|
const instructionCwd = behaviorCwd ?? stepCwd;
|
|
241
245
|
const behavior = resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills);
|
|
242
246
|
const skillNames = behavior.skills === false ? [] : behavior.skills;
|
|
243
|
-
const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
|
|
247
|
+
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
|
|
248
|
+
if (missingSkills.includes("pi-subagents")) throw new UnavailableSubagentSkillError(UNAVAILABLE_SUBAGENT_SKILL_ERROR);
|
|
244
249
|
|
|
245
250
|
let systemPrompt = a.systemPrompt?.trim() ?? "";
|
|
246
251
|
if (resolvedSkills.length > 0) {
|
|
@@ -285,36 +290,42 @@ export function executeAsyncChain(
|
|
|
285
290
|
return sessionFile;
|
|
286
291
|
};
|
|
287
292
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
293
|
+
let steps: RunnerStep[];
|
|
294
|
+
try {
|
|
295
|
+
steps = chain.map((s, stepIndex) => {
|
|
296
|
+
if (isParallelStep(s)) {
|
|
297
|
+
const parallelBehaviors = s.parallel.map((task) => {
|
|
298
|
+
const agent = agents.find((candidate) => candidate.name === task.agent)!;
|
|
299
|
+
return resolveStepBehavior(agent, buildStepOverrides(task), chainSkills);
|
|
300
|
+
});
|
|
301
|
+
const progressPrecreated = parallelBehaviors.some((behavior) => behavior.progress);
|
|
302
|
+
if (progressPrecreated) {
|
|
303
|
+
if (!s.worktree) writeInitialProgressFile(runnerCwd);
|
|
304
|
+
progressInstructionCreated = true;
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
parallel: s.parallel.map((t, taskIndex) => {
|
|
308
|
+
let behaviorCwd: string | undefined;
|
|
309
|
+
if (s.worktree) {
|
|
310
|
+
try {
|
|
311
|
+
behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, taskIndex);
|
|
312
|
+
} catch {
|
|
313
|
+
behaviorCwd = undefined;
|
|
314
|
+
}
|
|
307
315
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
})
|
|
316
|
+
return buildSeqStep(t, nextSessionFile(), behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex]);
|
|
317
|
+
}),
|
|
318
|
+
concurrency: s.concurrency,
|
|
319
|
+
failFast: s.failFast,
|
|
320
|
+
worktree: s.worktree,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return buildSeqStep(s as SequentialStep, nextSessionFile());
|
|
324
|
+
});
|
|
325
|
+
} catch (error) {
|
|
326
|
+
if (error instanceof UnavailableSubagentSkillError) return formatAsyncStartError("chain", error.message);
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
318
329
|
let childTargetIndex = 0;
|
|
319
330
|
const childIntercomTargets = childIntercomTarget ? steps.flatMap((step) => {
|
|
320
331
|
if ("parallel" in step) {
|
|
@@ -365,16 +376,33 @@ export function executeAsyncChain(
|
|
|
365
376
|
const firstAgents = isParallelStep(firstStep)
|
|
366
377
|
? firstStep.parallel.map((t) => t.agent)
|
|
367
378
|
: [(firstStep as SequentialStep).agent];
|
|
379
|
+
const parallelGroups: Array<{ start: number; count: number; stepIndex: number }> = [];
|
|
380
|
+
const flatAgents: string[] = [];
|
|
381
|
+
let flatStepStart = 0;
|
|
382
|
+
for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) {
|
|
383
|
+
const step = chain[stepIndex]!;
|
|
384
|
+
if (isParallelStep(step)) {
|
|
385
|
+
parallelGroups.push({ start: flatStepStart, count: step.parallel.length, stepIndex });
|
|
386
|
+
flatAgents.push(...step.parallel.map((task) => task.agent));
|
|
387
|
+
flatStepStart += step.parallel.length;
|
|
388
|
+
} else {
|
|
389
|
+
flatAgents.push((step as SequentialStep).agent);
|
|
390
|
+
flatStepStart++;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
368
393
|
ctx.pi.events.emit(SUBAGENT_ASYNC_STARTED_EVENT, {
|
|
369
394
|
id,
|
|
370
395
|
pid: spawnResult.pid,
|
|
371
396
|
agent: firstAgents[0],
|
|
397
|
+
agents: flatAgents,
|
|
372
398
|
task: isParallelStep(firstStep)
|
|
373
399
|
? firstStep.parallel[0]?.task?.slice(0, 50)
|
|
374
400
|
: (firstStep as SequentialStep).task?.slice(0, 50),
|
|
375
401
|
chain: chain.map((s) =>
|
|
376
402
|
isParallelStep(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : (s as SequentialStep).agent,
|
|
377
403
|
),
|
|
404
|
+
chainStepCount: chain.length,
|
|
405
|
+
parallelGroups,
|
|
378
406
|
cwd: runnerCwd,
|
|
379
407
|
asyncDir,
|
|
380
408
|
});
|
|
@@ -421,7 +449,8 @@ export function executeAsyncSingle(
|
|
|
421
449
|
const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
|
|
422
450
|
const skillNames = params.skills ?? agentConfig.skills ?? [];
|
|
423
451
|
const availableModels = params.availableModels;
|
|
424
|
-
const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, runnerCwd, ctx.cwd);
|
|
452
|
+
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(skillNames, runnerCwd, ctx.cwd);
|
|
453
|
+
if (missingSkills.includes("pi-subagents")) return formatAsyncStartError("single", UNAVAILABLE_SUBAGENT_SKILL_ERROR);
|
|
425
454
|
let systemPrompt = agentConfig.systemPrompt?.trim() ?? "";
|
|
426
455
|
if (resolvedSkills.length > 0) {
|
|
427
456
|
const injection = buildSkillInjection(resolvedSkills);
|
package/async-job-tracker.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { renderWidget } from "./render.ts";
|
|
|
5
5
|
import { formatControlNoticeMessage } from "./subagent-control.ts";
|
|
6
6
|
import {
|
|
7
7
|
type AsyncJobState,
|
|
8
|
+
type AsyncParallelGroupStatus,
|
|
9
|
+
type AsyncStartedEvent,
|
|
8
10
|
type ControlEvent,
|
|
9
11
|
type SubagentState,
|
|
10
12
|
POLL_INTERVAL_MS,
|
|
@@ -13,6 +15,23 @@ import {
|
|
|
13
15
|
} from "./types.ts";
|
|
14
16
|
import { readStatus } from "./utils.ts";
|
|
15
17
|
|
|
18
|
+
|
|
19
|
+
function isValidParallelGroup(group: AsyncParallelGroupStatus, stepCount: number, chainStepCount: number): boolean {
|
|
20
|
+
return Number.isInteger(group.start)
|
|
21
|
+
&& Number.isInteger(group.count)
|
|
22
|
+
&& Number.isInteger(group.stepIndex)
|
|
23
|
+
&& group.start >= 0
|
|
24
|
+
&& group.count > 0
|
|
25
|
+
&& group.stepIndex >= 0
|
|
26
|
+
&& group.stepIndex < chainStepCount
|
|
27
|
+
&& group.start + group.count <= stepCount;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeParallelGroups(groups: AsyncParallelGroupStatus[] | undefined, stepCount: number, chainStepCount: number): AsyncParallelGroupStatus[] {
|
|
31
|
+
if (!groups?.length) return [];
|
|
32
|
+
return groups.filter((group) => isValidParallelGroup(group, stepCount, chainStepCount));
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
interface AsyncJobTrackerOptions {
|
|
17
36
|
completionRetentionMs?: number;
|
|
18
37
|
pollIntervalMs?: number;
|
|
@@ -66,8 +85,8 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
66
85
|
let parsed: unknown;
|
|
67
86
|
try {
|
|
68
87
|
parsed = JSON.parse(line);
|
|
69
|
-
} catch {
|
|
70
|
-
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`Ignoring malformed async control event in '${eventsPath}':`, error);
|
|
71
90
|
continue;
|
|
72
91
|
}
|
|
73
92
|
if (!parsed || typeof parsed !== "object" || (parsed as { type?: unknown }).type !== "subagent.control") continue;
|
|
@@ -83,7 +102,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
83
102
|
if (record.channels.includes("event")) {
|
|
84
103
|
pi.events.emit(SUBAGENT_CONTROL_EVENT, payload);
|
|
85
104
|
}
|
|
86
|
-
if (record.channels.includes("intercom") && record.intercom?.to && record.intercom.message) {
|
|
105
|
+
if (record.event.type !== "active_long_running" && record.channels.includes("intercom") && record.intercom?.to && record.intercom.message) {
|
|
87
106
|
pi.events.emit(SUBAGENT_CONTROL_INTERCOM_EVENT, {
|
|
88
107
|
...payload,
|
|
89
108
|
to: record.intercom.to,
|
|
@@ -119,15 +138,30 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
119
138
|
job.status = status.state;
|
|
120
139
|
job.activityState = status.activityState;
|
|
121
140
|
job.lastActivityAt = status.lastActivityAt ?? job.lastActivityAt;
|
|
122
|
-
job.currentTool = status.currentTool
|
|
123
|
-
job.currentToolStartedAt = status.currentToolStartedAt
|
|
141
|
+
job.currentTool = status.currentTool;
|
|
142
|
+
job.currentToolStartedAt = status.currentToolStartedAt;
|
|
143
|
+
job.currentPath = status.currentPath;
|
|
144
|
+
job.turnCount = status.turnCount ?? job.turnCount;
|
|
145
|
+
job.toolCount = status.toolCount ?? job.toolCount;
|
|
124
146
|
job.mode = status.mode;
|
|
125
147
|
job.currentStep = status.currentStep ?? job.currentStep;
|
|
126
|
-
job.stepsTotal = status.steps?.length ?? job.stepsTotal;
|
|
127
148
|
job.startedAt = status.startedAt ?? job.startedAt;
|
|
128
149
|
job.updatedAt = status.lastUpdate ?? Date.now();
|
|
129
150
|
if (status.steps?.length) {
|
|
130
|
-
|
|
151
|
+
const groups = normalizeParallelGroups(status.parallelGroups, status.steps.length, status.chainStepCount ?? status.steps.length);
|
|
152
|
+
job.hasParallelGroups = groups.length > 0 || job.hasParallelGroups;
|
|
153
|
+
const activeGroup = status.currentStep !== undefined
|
|
154
|
+
? groups.find((group) => status.currentStep! >= group.start && status.currentStep! < group.start + group.count)
|
|
155
|
+
: undefined;
|
|
156
|
+
const visibleSteps = activeGroup
|
|
157
|
+
? status.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count)
|
|
158
|
+
: status.steps;
|
|
159
|
+
job.activeParallelGroup = Boolean(activeGroup);
|
|
160
|
+
job.agents = visibleSteps.map((step) => step.agent);
|
|
161
|
+
job.stepsTotal = visibleSteps.length;
|
|
162
|
+
job.runningSteps = visibleSteps.filter((step) => step.status === "running").length;
|
|
163
|
+
job.completedSteps = visibleSteps.filter((step) => step.status === "complete" || step.status === "completed").length;
|
|
164
|
+
if (status.state === "complete") job.completedSteps = visibleSteps.length;
|
|
131
165
|
}
|
|
132
166
|
job.sessionDir = status.sessionDir ?? job.sessionDir;
|
|
133
167
|
job.outputFile = status.outputFile ?? job.outputFile;
|
|
@@ -153,23 +187,26 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
153
187
|
};
|
|
154
188
|
|
|
155
189
|
const handleStarted = (data: unknown) => {
|
|
156
|
-
const info = data as
|
|
157
|
-
id?: string;
|
|
158
|
-
asyncDir?: string;
|
|
159
|
-
agent?: string;
|
|
160
|
-
chain?: string[];
|
|
161
|
-
};
|
|
190
|
+
const info = data as AsyncStartedEvent;
|
|
162
191
|
if (!info.id) return;
|
|
163
192
|
const now = Date.now();
|
|
164
193
|
const asyncDir = info.asyncDir ?? path.join(asyncDirRoot, info.id);
|
|
165
|
-
const
|
|
194
|
+
const rawAgents = info.agents?.length ? info.agents : info.chain && info.chain.length > 0 ? info.chain : info.agent ? [info.agent] : undefined;
|
|
195
|
+
const validParallelGroups = normalizeParallelGroups(info.parallelGroups, Number.MAX_SAFE_INTEGER, info.chainStepCount ?? Number.MAX_SAFE_INTEGER);
|
|
196
|
+
const firstGroup = validParallelGroups.find((group) => group.start === 0);
|
|
197
|
+
const firstGroupCount = firstGroup?.count;
|
|
198
|
+
const agents = firstGroupCount && firstGroupCount > 0
|
|
199
|
+
? rawAgents?.slice(0, firstGroupCount)
|
|
200
|
+
: rawAgents;
|
|
166
201
|
state.asyncJobs.set(info.id, {
|
|
167
202
|
asyncId: info.id,
|
|
168
203
|
asyncDir,
|
|
169
204
|
status: "queued",
|
|
170
205
|
mode: info.chain ? "chain" : "single",
|
|
171
206
|
agents,
|
|
172
|
-
stepsTotal: agents?.length,
|
|
207
|
+
stepsTotal: firstGroupCount ?? agents?.length,
|
|
208
|
+
hasParallelGroups: validParallelGroups.length > 0,
|
|
209
|
+
activeParallelGroup: Boolean(firstGroupCount && firstGroupCount > 0),
|
|
173
210
|
startedAt: now,
|
|
174
211
|
updatedAt: now,
|
|
175
212
|
});
|