@dreki-gg/pi-subagent 0.5.0 → 0.7.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 +31 -0
- package/README.md +99 -49
- package/agents/advisor.md +37 -0
- package/agents/bug-prover.md +42 -0
- package/agents/docs-scout.md +2 -2
- package/agents/manager.md +52 -0
- package/agents/planner.md +16 -1
- package/agents/reviewer.md +65 -8
- package/agents/scout.md +5 -1
- package/agents/validator.md +42 -0
- package/agents/worker.md +39 -3
- package/docs/orchestration-principles.md +132 -0
- package/docs/plans/01_structured-handoffs-and-synthesis.plan.md +376 -0
- package/docs/plans/02_clean-context-review-loop.plan.md +240 -0
- package/docs/plans/03_parallel-recon-single-writer-docs.plan.md +199 -0
- package/docs/plans/04_manager-workflow.plan.md +248 -0
- package/docs/plans/05_advisor-routing.plan.md +244 -0
- package/extensions/subagent/agent-result-utils.ts +12 -0
- package/extensions/subagent/agent-runner-types.ts +23 -0
- package/extensions/subagent/{delegate-executor.ts → agent-runner.ts} +10 -29
- package/extensions/subagent/agents.ts +11 -0
- package/extensions/subagent/handoffs.ts +273 -0
- package/extensions/subagent/index.ts +494 -196
- package/extensions/subagent/{delegate-args.ts → run-agent-args.ts} +35 -29
- package/package.json +3 -6
- package/skills/spawn-subagents/SKILL.md +80 -8
- package/extensions/subagent/delegate-types.ts +0 -62
- package/extensions/subagent/delegate-ui.ts +0 -132
- package/extensions/subagent/workflows.ts +0 -150
- package/prompts/implement-and-review.md +0 -10
- package/prompts/implement.md +0 -10
- package/prompts/scout-and-plan.md +0 -9
|
@@ -25,43 +25,30 @@ import { StringEnum } from '@mariozechner/pi-ai';
|
|
|
25
25
|
import {
|
|
26
26
|
type ExtensionAPI,
|
|
27
27
|
type ExtensionCommandContext,
|
|
28
|
+
AuthStorage,
|
|
28
29
|
DefaultPackageManager,
|
|
29
30
|
getAgentDir,
|
|
30
31
|
getMarkdownTheme,
|
|
32
|
+
ModelRegistry,
|
|
31
33
|
SettingsManager,
|
|
32
34
|
withFileMutationQueue,
|
|
33
35
|
} from '@mariozechner/pi-coding-agent';
|
|
34
36
|
import type { ResolvedPaths } from '@mariozechner/pi-coding-agent';
|
|
35
|
-
import { Container, Markdown, Spacer, Text } from '@mariozechner/pi-tui';
|
|
36
|
-
import { Type } from '
|
|
37
|
+
import { Box, Container, Markdown, Spacer, Text, type AutocompleteItem } from '@mariozechner/pi-tui';
|
|
38
|
+
import { Type } from 'typebox';
|
|
37
39
|
import {
|
|
38
40
|
type AgentConfig,
|
|
39
41
|
type AgentScope,
|
|
40
42
|
type AgentSource,
|
|
41
43
|
bundledAgentsDir,
|
|
42
|
-
discoverAgents,
|
|
43
44
|
discoverAgentsWithPackages,
|
|
44
45
|
} from './agents.js';
|
|
45
|
-
import {
|
|
46
|
-
import { runAgent
|
|
47
|
-
import {
|
|
48
|
-
import type {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
PhaseDefinition,
|
|
52
|
-
PhaseResult,
|
|
53
|
-
UsageStats as DelegateUsageStats,
|
|
54
|
-
} from './delegate-types.js';
|
|
55
|
-
import {
|
|
56
|
-
aggregateUsage,
|
|
57
|
-
confirmPlan,
|
|
58
|
-
confirmSynthesis,
|
|
59
|
-
formatFullSummary,
|
|
60
|
-
formatPhaseHeader,
|
|
61
|
-
getFinalText,
|
|
62
|
-
pickWorkflow,
|
|
63
|
-
} from './delegate-ui.js';
|
|
64
|
-
import { getWorkflow, suggestWorkflow } from './workflows.js';
|
|
46
|
+
import { formatRunAgentUsage, parseRunAgentArgs } from './run-agent-args.js';
|
|
47
|
+
import { runAgent } from './agent-runner.js';
|
|
48
|
+
import { extractRecentConversation } from './synthesis.js';
|
|
49
|
+
import type { AgentResult } from './agent-runner-types.js';
|
|
50
|
+
import { getFinalText } from './agent-result-utils.js';
|
|
51
|
+
import { buildHandoffFromResult, renderHandoffForPrompt } from './handoffs.js';
|
|
65
52
|
|
|
66
53
|
const MAX_PARALLEL_TASKS = 8;
|
|
67
54
|
const MAX_CONCURRENCY = 4;
|
|
@@ -182,6 +169,20 @@ interface UsageStats {
|
|
|
182
169
|
turns: number;
|
|
183
170
|
}
|
|
184
171
|
|
|
172
|
+
interface RunAgentSummaryDetails {
|
|
173
|
+
agent: string;
|
|
174
|
+
agentSource: AgentSource;
|
|
175
|
+
agentScope: AgentScope;
|
|
176
|
+
sessionStrategy: string;
|
|
177
|
+
forked: boolean;
|
|
178
|
+
exitCode: number;
|
|
179
|
+
stopReason?: string;
|
|
180
|
+
errorMessage?: string;
|
|
181
|
+
usage: UsageStats;
|
|
182
|
+
task: string;
|
|
183
|
+
output: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
185
186
|
interface SingleResult {
|
|
186
187
|
agent: string;
|
|
187
188
|
agentSource: AgentSource | 'unknown';
|
|
@@ -295,6 +296,8 @@ async function runSingleAgent(
|
|
|
295
296
|
agentName: string,
|
|
296
297
|
task: string,
|
|
297
298
|
cwd: string | undefined,
|
|
299
|
+
modelOverride: string | undefined,
|
|
300
|
+
thinkingOverride: string | undefined,
|
|
298
301
|
step: number | undefined,
|
|
299
302
|
signal: AbortSignal | undefined,
|
|
300
303
|
onUpdate: OnUpdateCallback | undefined,
|
|
@@ -324,9 +327,12 @@ async function runSingleAgent(
|
|
|
324
327
|
};
|
|
325
328
|
}
|
|
326
329
|
|
|
330
|
+
const selectedModel = modelOverride ?? agent.model;
|
|
331
|
+
const selectedThinking = thinkingOverride ?? agent.thinking;
|
|
332
|
+
|
|
327
333
|
const args: string[] = ['--mode', 'json', '-p', '--no-session'];
|
|
328
|
-
if (
|
|
329
|
-
if (
|
|
334
|
+
if (selectedModel) args.push('--model', selectedModel);
|
|
335
|
+
if (selectedThinking) args.push('--thinking', selectedThinking);
|
|
330
336
|
if (agent.tools && agent.tools.length > 0) args.push('--tools', agent.tools.join(','));
|
|
331
337
|
|
|
332
338
|
let tmpPromptDir: string | null = null;
|
|
@@ -348,7 +354,7 @@ async function runSingleAgent(
|
|
|
348
354
|
contextTokens: 0,
|
|
349
355
|
turns: 0,
|
|
350
356
|
},
|
|
351
|
-
model:
|
|
357
|
+
model: selectedModel,
|
|
352
358
|
step,
|
|
353
359
|
};
|
|
354
360
|
|
|
@@ -473,12 +479,20 @@ async function runSingleAgent(
|
|
|
473
479
|
const TaskItem = Type.Object({
|
|
474
480
|
agent: Type.String({ description: 'Name of the agent to invoke' }),
|
|
475
481
|
task: Type.String({ description: 'Task to delegate to the agent' }),
|
|
482
|
+
model: Type.Optional(Type.String({ description: 'Optional model override for this task' })),
|
|
483
|
+
thinking: Type.Optional(
|
|
484
|
+
Type.String({ description: 'Optional reasoning level override for this task' }),
|
|
485
|
+
),
|
|
476
486
|
cwd: Type.Optional(Type.String({ description: 'Working directory for the agent process' })),
|
|
477
487
|
});
|
|
478
488
|
|
|
479
489
|
const ChainItem = Type.Object({
|
|
480
490
|
agent: Type.String({ description: 'Name of the agent to invoke' }),
|
|
481
491
|
task: Type.String({ description: 'Task with optional {previous} placeholder for prior output' }),
|
|
492
|
+
model: Type.Optional(Type.String({ description: 'Optional model override for this step' })),
|
|
493
|
+
thinking: Type.Optional(
|
|
494
|
+
Type.String({ description: 'Optional reasoning level override for this step' }),
|
|
495
|
+
),
|
|
482
496
|
cwd: Type.Optional(Type.String({ description: 'Working directory for the agent process' })),
|
|
483
497
|
});
|
|
484
498
|
|
|
@@ -493,11 +507,17 @@ const SubagentParams = Type.Object({
|
|
|
493
507
|
Type.String({ description: 'Name of the agent to invoke (for single mode)' }),
|
|
494
508
|
),
|
|
495
509
|
task: Type.Optional(Type.String({ description: 'Task to delegate (for single mode)' })),
|
|
510
|
+
model: Type.Optional(
|
|
511
|
+
Type.String({ description: 'Optional default model override for the run or all steps/tasks' }),
|
|
512
|
+
),
|
|
513
|
+
thinking: Type.Optional(
|
|
514
|
+
Type.String({ description: 'Optional default reasoning level override for the run or all steps/tasks' }),
|
|
515
|
+
),
|
|
496
516
|
tasks: Type.Optional(
|
|
497
|
-
Type.Array(TaskItem, { description: 'Array of {agent, task} for parallel execution' }),
|
|
517
|
+
Type.Array(TaskItem, { description: 'Array of {agent, task, model?, thinking?} for parallel execution' }),
|
|
498
518
|
),
|
|
499
519
|
chain: Type.Optional(
|
|
500
|
-
Type.Array(ChainItem, { description: 'Array of {agent, task} for sequential execution' }),
|
|
520
|
+
Type.Array(ChainItem, { description: 'Array of {agent, task, model?, thinking?} for sequential execution' }),
|
|
501
521
|
),
|
|
502
522
|
agentScope: Type.Optional(AgentScopeSchema),
|
|
503
523
|
confirmProjectAgents: Type.Optional(
|
|
@@ -532,12 +552,99 @@ async function tryResolvePackagePaths(cwd: string): Promise<ResolvedPaths | unde
|
|
|
532
552
|
}
|
|
533
553
|
|
|
534
554
|
export default function (pi: ExtensionAPI) {
|
|
555
|
+
let autocompleteCwd = process.cwd();
|
|
556
|
+
|
|
557
|
+
function parseArgumentText(argumentText: string): {
|
|
558
|
+
completedTokens: string[];
|
|
559
|
+
currentToken: string;
|
|
560
|
+
} {
|
|
561
|
+
const endsWithSpace = argumentText.length > 0 && /\s$/.test(argumentText);
|
|
562
|
+
const tokens = argumentText.trim().length > 0 ? argumentText.trim().split(/\s+/) : [];
|
|
563
|
+
return {
|
|
564
|
+
completedTokens: endsWithSpace ? tokens : tokens.slice(0, -1),
|
|
565
|
+
currentToken: endsWithSpace ? '' : (tokens.at(-1) ?? ''),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function replaceCurrentToken(argumentText: string, replacement: string): string {
|
|
570
|
+
const trimmedEnd = argumentText.replace(/\s+$/, '');
|
|
571
|
+
if (!trimmedEnd) return replacement;
|
|
572
|
+
if (/\s$/.test(argumentText)) return `${trimmedEnd} ${replacement}`;
|
|
573
|
+
const lastSpace = trimmedEnd.lastIndexOf(' ');
|
|
574
|
+
return lastSpace === -1
|
|
575
|
+
? replacement
|
|
576
|
+
: `${trimmedEnd.slice(0, lastSpace + 1)}${replacement}`;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function buildArgumentCompletions(
|
|
580
|
+
argumentText: string,
|
|
581
|
+
items: Array<{ value: string; label?: string; description?: string }>,
|
|
582
|
+
): AutocompleteItem[] | null {
|
|
583
|
+
const { currentToken } = parseArgumentText(argumentText);
|
|
584
|
+
const query = currentToken.toLowerCase();
|
|
585
|
+
const filtered = items.filter((item) => {
|
|
586
|
+
if (!query) return true;
|
|
587
|
+
return (
|
|
588
|
+
item.value.toLowerCase().startsWith(query) ||
|
|
589
|
+
item.label?.toLowerCase().includes(query) ||
|
|
590
|
+
item.description?.toLowerCase().includes(query)
|
|
591
|
+
);
|
|
592
|
+
});
|
|
593
|
+
if (filtered.length === 0) return null;
|
|
594
|
+
return filtered.map((item) => ({
|
|
595
|
+
value: replaceCurrentToken(argumentText, item.value),
|
|
596
|
+
label: item.label ?? item.value,
|
|
597
|
+
description: item.description,
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function getAutocompleteAgents(scope: AgentScope): Promise<AgentConfig[]> {
|
|
602
|
+
const resolvedPaths = await tryResolvePackagePaths(autocompleteCwd);
|
|
603
|
+
return discoverAgentsWithPackages(autocompleteCwd, scope, resolvedPaths).agents;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function getAutocompleteModels(scope: AgentScope): Promise<
|
|
607
|
+
Array<{ value: string; label?: string; description?: string }>
|
|
608
|
+
> {
|
|
609
|
+
const items = new Map<string, { value: string; label?: string; description?: string }>();
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const authStorage = AuthStorage.create();
|
|
613
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
614
|
+
const available = await modelRegistry.getAvailable();
|
|
615
|
+
for (const model of available) {
|
|
616
|
+
const value = `${model.provider}/${model.id}`;
|
|
617
|
+
items.set(value, {
|
|
618
|
+
value,
|
|
619
|
+
label: value,
|
|
620
|
+
description: 'Configured model with available credentials',
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
} catch {
|
|
624
|
+
// Fall back to models referenced by discovered agents.
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const agents = await getAutocompleteAgents(scope);
|
|
628
|
+
for (const agent of agents) {
|
|
629
|
+
if (!agent.model) continue;
|
|
630
|
+
if (items.has(agent.model)) continue;
|
|
631
|
+
items.set(agent.model, {
|
|
632
|
+
value: agent.model,
|
|
633
|
+
label: agent.model,
|
|
634
|
+
description: `Default model on agent ${agent.name}`,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return [...items.values()];
|
|
639
|
+
}
|
|
640
|
+
|
|
535
641
|
pi.registerTool({
|
|
536
642
|
name: 'subagent',
|
|
537
643
|
label: 'Subagent',
|
|
538
644
|
description: [
|
|
539
645
|
'Delegate tasks to specialized subagents with isolated context.',
|
|
540
646
|
'Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).',
|
|
647
|
+
'Optional per-run or per-step model and reasoning-level overrides are supported via model/thinking.',
|
|
541
648
|
'Default agent scope is "user" (from ~/.pi/agent/agents).',
|
|
542
649
|
'To enable project-local agents in .pi/agents, set agentScope: "both" (or "project").',
|
|
543
650
|
].join(' '),
|
|
@@ -635,6 +742,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
635
742
|
step.agent,
|
|
636
743
|
taskWithContext,
|
|
637
744
|
step.cwd,
|
|
745
|
+
step.model ?? params.model,
|
|
746
|
+
step.thinking ?? params.thinking,
|
|
638
747
|
i + 1,
|
|
639
748
|
signal,
|
|
640
749
|
chainUpdate,
|
|
@@ -663,7 +772,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
663
772
|
isError: true,
|
|
664
773
|
};
|
|
665
774
|
}
|
|
666
|
-
|
|
775
|
+
const finalOutput = getFinalOutput(result.messages) || '(no output)';
|
|
776
|
+
previousOutput = renderHandoffForPrompt(
|
|
777
|
+
buildHandoffFromResult({
|
|
778
|
+
agent: step.agent,
|
|
779
|
+
step: i + 1,
|
|
780
|
+
task: step.task,
|
|
781
|
+
output: finalOutput,
|
|
782
|
+
}),
|
|
783
|
+
);
|
|
667
784
|
}
|
|
668
785
|
return {
|
|
669
786
|
content: [
|
|
@@ -738,6 +855,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
738
855
|
t.agent,
|
|
739
856
|
t.task,
|
|
740
857
|
t.cwd,
|
|
858
|
+
t.model ?? params.model,
|
|
859
|
+
t.thinking ?? params.thinking,
|
|
741
860
|
undefined,
|
|
742
861
|
signal,
|
|
743
862
|
// Per-task update callback
|
|
@@ -778,6 +897,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
778
897
|
params.agent,
|
|
779
898
|
params.task,
|
|
780
899
|
params.cwd,
|
|
900
|
+
params.model,
|
|
901
|
+
params.thinking,
|
|
781
902
|
undefined,
|
|
782
903
|
signal,
|
|
783
904
|
onUpdate,
|
|
@@ -1145,84 +1266,132 @@ export default function (pi: ExtensionAPI) {
|
|
|
1145
1266
|
},
|
|
1146
1267
|
});
|
|
1147
1268
|
|
|
1148
|
-
// ──
|
|
1149
|
-
|
|
1150
|
-
function emptyDelegateUsage(): DelegateUsageStats {
|
|
1151
|
-
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 };
|
|
1152
|
-
}
|
|
1269
|
+
// ── Command helpers ───────────────────────────────────────────────────────
|
|
1153
1270
|
|
|
1154
|
-
function
|
|
1155
|
-
return results
|
|
1156
|
-
.map((r) => {
|
|
1157
|
-
const output = getFinalText(r);
|
|
1158
|
-
return `## ${r.agent}\n${output}`;
|
|
1159
|
-
})
|
|
1160
|
-
.join('\n\n');
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
async function executePhase(
|
|
1164
|
-
cwd: string,
|
|
1165
|
-
phase: PhaseDefinition,
|
|
1166
|
-
synthesis: string,
|
|
1167
|
-
previousOutput: string,
|
|
1271
|
+
async function confirmProjectAgentsIfNeeded(
|
|
1168
1272
|
ctx: ExtensionCommandContext,
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
'
|
|
1273
|
+
agents: AgentConfig[],
|
|
1274
|
+
projectAgentsDir: string | null,
|
|
1275
|
+
confirmProjectAgents: boolean,
|
|
1276
|
+
): Promise<boolean> {
|
|
1277
|
+
if (!ctx.hasUI || !confirmProjectAgents || agents.length === 0) return true;
|
|
1278
|
+
|
|
1279
|
+
const names = Array.from(new Set(agents.map((agent) => agent.name))).join(', ');
|
|
1280
|
+
const dir = projectAgentsDir ?? '(unknown)';
|
|
1281
|
+
return ctx.ui.confirm(
|
|
1282
|
+
'Run project-local agents?',
|
|
1283
|
+
`Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`,
|
|
1179
1284
|
);
|
|
1285
|
+
}
|
|
1180
1286
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
agentScope,
|
|
1185
|
-
onUpdate: (_phaseName, _agentName, _result) => {
|
|
1186
|
-
// Streaming updates happen per-message
|
|
1187
|
-
},
|
|
1188
|
-
phaseName: phase.name,
|
|
1189
|
-
resolvedPaths,
|
|
1190
|
-
});
|
|
1191
|
-
} else {
|
|
1192
|
-
const result = await runAgent(cwd, phase.agents[0], task, {
|
|
1193
|
-
agentScope,
|
|
1194
|
-
phaseName: phase.name,
|
|
1195
|
-
resolvedPaths,
|
|
1196
|
-
});
|
|
1197
|
-
results = [result];
|
|
1198
|
-
}
|
|
1287
|
+
function buildRunAgentTask(conversation: string, explicitTask?: string): string | undefined {
|
|
1288
|
+
const trimmedConversation = conversation.trim();
|
|
1289
|
+
const trimmedTask = explicitTask?.trim();
|
|
1199
1290
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
.filter((r) => r.exitCode !== 0 || r.stopReason === 'error')
|
|
1204
|
-
.map((r) => `${r.agent}: ${r.errorMessage || r.stderr || '(unknown error)'}`)
|
|
1205
|
-
.join('\n');
|
|
1291
|
+
if (trimmedTask && trimmedConversation) {
|
|
1292
|
+
return `Assigned task:\n${trimmedTask}\n\nRelevant main-session context:\n${trimmedConversation}`;
|
|
1293
|
+
}
|
|
1206
1294
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1295
|
+
if (trimmedTask) return trimmedTask;
|
|
1296
|
+
if (trimmedConversation) {
|
|
1297
|
+
return `Use the following main-session context to determine and complete the task:\n\n${trimmedConversation}`;
|
|
1209
1298
|
}
|
|
1210
1299
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1300
|
+
return undefined;
|
|
1301
|
+
}
|
|
1213
1302
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1303
|
+
function formatRunAgentSummary(options: {
|
|
1304
|
+
agent: AgentConfig;
|
|
1305
|
+
agentScope: AgentScope;
|
|
1306
|
+
task: string;
|
|
1307
|
+
result: AgentResult;
|
|
1308
|
+
forked: boolean;
|
|
1309
|
+
}): string {
|
|
1310
|
+
const { agent, agentScope, task, result, forked } = options;
|
|
1311
|
+
const output = getFinalText(result).trim();
|
|
1312
|
+
const usage = formatUsageStats(result.usage, result.model);
|
|
1313
|
+
const failed = result.exitCode !== 0 || result.stopReason === 'error';
|
|
1314
|
+
const status = failed ? 'failed' : 'completed';
|
|
1315
|
+
const lines = [
|
|
1316
|
+
'## Agent Run',
|
|
1317
|
+
`- Agent: \`${agent.name}\``,
|
|
1318
|
+
`- Source: ${agent.source}`,
|
|
1319
|
+
`- Scope: ${agentScope}`,
|
|
1320
|
+
`- Session strategy: ${agent.sessionStrategy ?? 'inline'}`,
|
|
1321
|
+
`- Session handling: ${forked ? 'forked current branch at active entry' : 'inline in current session'}`,
|
|
1322
|
+
`- Status: ${status}`,
|
|
1323
|
+
'',
|
|
1324
|
+
'## Task',
|
|
1325
|
+
task,
|
|
1326
|
+
'',
|
|
1327
|
+
'## Output',
|
|
1328
|
+
output || '_No output returned._',
|
|
1329
|
+
];
|
|
1330
|
+
|
|
1331
|
+
if (failed) {
|
|
1332
|
+
const errorText = result.errorMessage?.trim() || result.stderr.trim();
|
|
1333
|
+
if (errorText) {
|
|
1334
|
+
lines.push('', '## Error', errorText);
|
|
1220
1335
|
}
|
|
1221
1336
|
}
|
|
1222
1337
|
|
|
1223
|
-
|
|
1338
|
+
if (usage) {
|
|
1339
|
+
lines.push('', '## Usage', usage);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
return lines.join('\n');
|
|
1224
1343
|
}
|
|
1225
1344
|
|
|
1345
|
+
pi.registerMessageRenderer('run-agent-summary', (message, { expanded }, theme) => {
|
|
1346
|
+
const details = message.details as RunAgentSummaryDetails | undefined;
|
|
1347
|
+
if (!details) return new Text(typeof message.content === 'string' ? message.content : '', 0, 0);
|
|
1348
|
+
|
|
1349
|
+
const failed = details.exitCode !== 0 || details.stopReason === 'error';
|
|
1350
|
+
const icon = failed ? theme.fg('error', '✗') : theme.fg('success', '✓');
|
|
1351
|
+
const title = `${icon} ${theme.fg('toolTitle', theme.bold(details.agent))}${theme.fg('muted', ` (${details.agentSource})`)}`;
|
|
1352
|
+
const modeLine = theme.fg(
|
|
1353
|
+
'dim',
|
|
1354
|
+
`${details.forked ? 'forked branch' : 'inline'} • ${details.agentScope} • ${details.sessionStrategy}`,
|
|
1355
|
+
);
|
|
1356
|
+
const usageLine = formatUsageStats(details.usage);
|
|
1357
|
+
const outputPreview = details.output.trim() || '(no output)';
|
|
1358
|
+
|
|
1359
|
+
if (!expanded) {
|
|
1360
|
+
const previewLines = outputPreview.split('\n').slice(0, 3).join('\n');
|
|
1361
|
+
const text = [
|
|
1362
|
+
title,
|
|
1363
|
+
modeLine,
|
|
1364
|
+
theme.fg('muted', `Task: ${details.task}`),
|
|
1365
|
+
failed && details.errorMessage ? theme.fg('error', `Error: ${details.errorMessage}`) : previewLines,
|
|
1366
|
+
usageLine ? theme.fg('dim', usageLine) : undefined,
|
|
1367
|
+
theme.fg('muted', '(Ctrl+O to expand)'),
|
|
1368
|
+
]
|
|
1369
|
+
.filter((line): line is string => Boolean(line))
|
|
1370
|
+
.join('\n');
|
|
1371
|
+
const box = new Box(1, 1, (text) => theme.bg('customMessageBg', text));
|
|
1372
|
+
box.addChild(new Text(text, 0, 0));
|
|
1373
|
+
return box;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
const mdTheme = getMarkdownTheme();
|
|
1377
|
+
const box = new Box(1, 1, (text) => theme.bg('customMessageBg', text));
|
|
1378
|
+
box.addChild(new Text(title, 0, 0));
|
|
1379
|
+
box.addChild(new Text(modeLine, 0, 0));
|
|
1380
|
+
if (usageLine) box.addChild(new Text(theme.fg('dim', usageLine), 0, 0));
|
|
1381
|
+
box.addChild(new Spacer(1));
|
|
1382
|
+
box.addChild(new Text(theme.fg('muted', 'Task'), 0, 0));
|
|
1383
|
+
box.addChild(new Text(theme.fg('text', details.task), 0, 0));
|
|
1384
|
+
if (failed && details.errorMessage) {
|
|
1385
|
+
box.addChild(new Spacer(1));
|
|
1386
|
+
box.addChild(new Text(theme.fg('error', 'Error'), 0, 0));
|
|
1387
|
+
box.addChild(new Text(theme.fg('error', details.errorMessage), 0, 0));
|
|
1388
|
+
}
|
|
1389
|
+
box.addChild(new Spacer(1));
|
|
1390
|
+
box.addChild(new Text(theme.fg('muted', 'Output'), 0, 0));
|
|
1391
|
+
box.addChild(new Markdown(outputPreview, 0, 0, mdTheme));
|
|
1392
|
+
return box;
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1226
1395
|
async function listMdFiles(dir: string): Promise<Set<string>> {
|
|
1227
1396
|
if (!existsSync(dir)) return new Set();
|
|
1228
1397
|
try {
|
|
@@ -1233,9 +1402,55 @@ export default function (pi: ExtensionAPI) {
|
|
|
1233
1402
|
}
|
|
1234
1403
|
}
|
|
1235
1404
|
|
|
1405
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
1406
|
+
autocompleteCwd = ctx.cwd;
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1236
1409
|
pi.registerCommand('delegate-agents', {
|
|
1237
1410
|
description: 'List, customize, or reset delegate agents',
|
|
1411
|
+
getArgumentCompletions: async (argumentText) => {
|
|
1412
|
+
const userDir = join(getAgentDir(), 'agents');
|
|
1413
|
+
const { completedTokens } = parseArgumentText(argumentText);
|
|
1414
|
+
const action = completedTokens[0];
|
|
1415
|
+
|
|
1416
|
+
if (!action) {
|
|
1417
|
+
return buildArgumentCompletions(argumentText, [
|
|
1418
|
+
{ value: 'list', description: 'Show bundled and overridden agents' },
|
|
1419
|
+
{ value: 'reset', description: 'Restore bundled agents by removing user overrides' },
|
|
1420
|
+
{ value: 'edit', description: 'Copy a bundled agent into your user agents directory' },
|
|
1421
|
+
]);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
if (action === 'reset') {
|
|
1425
|
+
if (completedTokens.length > 1) return null;
|
|
1426
|
+
const bundled = await listMdFiles(bundledAgentsDir);
|
|
1427
|
+
const user = await listMdFiles(userDir);
|
|
1428
|
+
const resettable = [...user].filter((name) => bundled.has(name)).sort();
|
|
1429
|
+
return buildArgumentCompletions(argumentText, [
|
|
1430
|
+
{ value: '--all', description: 'Reset all user overrides that have bundled versions' },
|
|
1431
|
+
...resettable.map((name) => ({
|
|
1432
|
+
value: name,
|
|
1433
|
+
description: 'Reset this user override to the bundled version',
|
|
1434
|
+
})),
|
|
1435
|
+
]);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (action === 'edit') {
|
|
1439
|
+
if (completedTokens.length > 1) return null;
|
|
1440
|
+
const bundled = [...(await listMdFiles(bundledAgentsDir))].sort();
|
|
1441
|
+
return buildArgumentCompletions(
|
|
1442
|
+
argumentText,
|
|
1443
|
+
bundled.map((name) => ({
|
|
1444
|
+
value: name,
|
|
1445
|
+
description: 'Copy this bundled agent into your user agents directory',
|
|
1446
|
+
})),
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
return null;
|
|
1451
|
+
},
|
|
1238
1452
|
handler: async (args, ctx) => {
|
|
1453
|
+
autocompleteCwd = ctx.cwd;
|
|
1239
1454
|
const userDir = join(getAgentDir(), 'agents');
|
|
1240
1455
|
const parts = args?.trim().split(/\s+/) || [];
|
|
1241
1456
|
const action = parts[0] || 'list';
|
|
@@ -1328,136 +1543,219 @@ export default function (pi: ExtensionAPI) {
|
|
|
1328
1543
|
},
|
|
1329
1544
|
});
|
|
1330
1545
|
|
|
1331
|
-
pi.registerCommand('
|
|
1546
|
+
pi.registerCommand('run-agent', {
|
|
1332
1547
|
description:
|
|
1333
|
-
'
|
|
1548
|
+
'Run a single agent directly — honors agent sessionStrategy metadata such as fork-at',
|
|
1549
|
+
getArgumentCompletions: async (argumentText) => {
|
|
1550
|
+
const { completedTokens } = parseArgumentText(argumentText);
|
|
1551
|
+
let agentScope: AgentScope = 'user';
|
|
1552
|
+
let expectsScopeValue = false;
|
|
1553
|
+
let expectsThinkingValue = false;
|
|
1554
|
+
let expectsModelValue = false;
|
|
1555
|
+
let agentName: string | undefined;
|
|
1556
|
+
|
|
1557
|
+
for (const token of completedTokens) {
|
|
1558
|
+
if (expectsScopeValue) {
|
|
1559
|
+
if (token === 'user' || token === 'project' || token === 'both') {
|
|
1560
|
+
agentScope = token;
|
|
1561
|
+
}
|
|
1562
|
+
expectsScopeValue = false;
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
if (expectsThinkingValue) {
|
|
1566
|
+
expectsThinkingValue = false;
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (expectsModelValue) {
|
|
1570
|
+
expectsModelValue = false;
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
if (token === '--scope') {
|
|
1574
|
+
expectsScopeValue = true;
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
if (token === '--thinking' || token === '--reasoning-level') {
|
|
1578
|
+
expectsThinkingValue = true;
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
if (token === '--model') {
|
|
1582
|
+
expectsModelValue = true;
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
if (token === '--yes-project-agents') continue;
|
|
1586
|
+
if (token.startsWith('--')) continue;
|
|
1587
|
+
if (!agentName) agentName = token;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
if (expectsScopeValue) {
|
|
1591
|
+
return buildArgumentCompletions(argumentText, [
|
|
1592
|
+
{ value: 'user', description: 'Only user/global agents from ~/.pi/agent/agents' },
|
|
1593
|
+
{ value: 'project', description: 'Only project-local agents from .pi/agents' },
|
|
1594
|
+
{ value: 'both', description: 'User/global agents plus project-local agents' },
|
|
1595
|
+
]);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
if (expectsThinkingValue) {
|
|
1599
|
+
return buildArgumentCompletions(argumentText, [
|
|
1600
|
+
{ value: 'off', description: 'Disable reasoning if the model supports it' },
|
|
1601
|
+
{ value: 'minimal', description: 'Minimal reasoning effort' },
|
|
1602
|
+
{ value: 'low', description: 'Lower reasoning effort' },
|
|
1603
|
+
{ value: 'medium', description: 'Balanced reasoning effort' },
|
|
1604
|
+
{ value: 'high', description: 'Higher reasoning effort' },
|
|
1605
|
+
{ value: 'xhigh', description: 'Maximum reasoning effort on supported models' },
|
|
1606
|
+
]);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
if (expectsModelValue) {
|
|
1610
|
+
const models = await getAutocompleteModels(agentScope);
|
|
1611
|
+
return buildArgumentCompletions(argumentText, models);
|
|
1612
|
+
}
|
|
1613
|
+
if (agentName) return null;
|
|
1614
|
+
|
|
1615
|
+
const agents = await getAutocompleteAgents(agentScope);
|
|
1616
|
+
return buildArgumentCompletions(argumentText, [
|
|
1617
|
+
{ value: '--scope', description: 'Choose which agent directories are searched' },
|
|
1618
|
+
{ value: '--model', description: 'Override the agent model for this run' },
|
|
1619
|
+
{ value: '--thinking', description: 'Override the reasoning level for this run' },
|
|
1620
|
+
{ value: '--reasoning-level', description: 'Alias for --thinking' },
|
|
1621
|
+
{ value: '--yes-project-agents', description: 'Skip the project-agent trust confirmation prompt' },
|
|
1622
|
+
...agents.map((agent) => ({
|
|
1623
|
+
value: agent.name,
|
|
1624
|
+
label: agent.name,
|
|
1625
|
+
description: `${agent.source} — ${agent.description}`,
|
|
1626
|
+
})),
|
|
1627
|
+
]);
|
|
1628
|
+
},
|
|
1334
1629
|
handler: async (args, ctx) => {
|
|
1335
|
-
|
|
1630
|
+
autocompleteCwd = ctx.cwd;
|
|
1631
|
+
const parsed = parseRunAgentArgs(args);
|
|
1336
1632
|
if (!parsed.ok) {
|
|
1337
1633
|
ctx.ui.notify(parsed.error, 'warning');
|
|
1338
1634
|
return;
|
|
1339
1635
|
}
|
|
1340
1636
|
|
|
1341
|
-
const { explicitTask, agentScope,
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
// Step 1: Synthesize
|
|
1345
|
-
const conversation = extractRecentConversation(ctx);
|
|
1346
|
-
if (!conversation.trim() && !explicitTask) {
|
|
1347
|
-
ctx.ui.notify(`No conversation context and no task specified.\n\n${formatDelegateUsage()}`, 'warning');
|
|
1637
|
+
const { agentName, explicitTask, agentScope, confirmProjectAgents, model, thinking } = parsed.options;
|
|
1638
|
+
if (!agentName) {
|
|
1639
|
+
ctx.ui.notify(formatRunAgentUsage(), 'warning');
|
|
1348
1640
|
return;
|
|
1349
1641
|
}
|
|
1350
1642
|
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
const synthResult = await runAgent(ctx.cwd, 'planner', prompt, { agentScope, resolvedPaths });
|
|
1355
|
-
const synthesis = getFinalText(synthResult);
|
|
1643
|
+
const resolvedPaths = await tryResolvePackagePaths(ctx.cwd);
|
|
1644
|
+
const discovery = discoverAgentsWithPackages(ctx.cwd, agentScope, resolvedPaths);
|
|
1645
|
+
const agent = discovery.agents.find((candidate) => candidate.name === agentName);
|
|
1356
1646
|
|
|
1357
|
-
if (!
|
|
1358
|
-
|
|
1647
|
+
if (!agent) {
|
|
1648
|
+
const available = discovery.agents.map((candidate) => candidate.name).join(', ') || 'none';
|
|
1649
|
+
ctx.ui.notify(`Unknown agent: "${agentName}". Available: ${available}`, 'warning');
|
|
1359
1650
|
return;
|
|
1360
1651
|
}
|
|
1361
1652
|
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
// Step 3: Pick workflow
|
|
1372
|
-
const workflow = workflowId
|
|
1373
|
-
? getWorkflow(workflowId)
|
|
1374
|
-
: ctx.hasUI
|
|
1375
|
-
? await pickWorkflow(ctx, synthesis)
|
|
1376
|
-
: getWorkflow(suggestWorkflow(synthesis));
|
|
1377
|
-
if (!workflow) {
|
|
1378
|
-
ctx.ui.notify('Delegate cancelled — no workflow selected.', 'info');
|
|
1653
|
+
const requestedProjectAgents = agent.source === 'project' ? [agent] : [];
|
|
1654
|
+
const approved = await confirmProjectAgentsIfNeeded(
|
|
1655
|
+
ctx,
|
|
1656
|
+
requestedProjectAgents,
|
|
1657
|
+
discovery.projectAgentsDir,
|
|
1658
|
+
confirmProjectAgents,
|
|
1659
|
+
);
|
|
1660
|
+
if (!approved) {
|
|
1661
|
+
ctx.ui.notify('Run cancelled — project-local agents not approved.', 'info');
|
|
1379
1662
|
return;
|
|
1380
1663
|
}
|
|
1381
1664
|
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
.filter((agent): agent is AgentConfig => agent?.source === 'project');
|
|
1388
|
-
|
|
1389
|
-
if (requestedProjectAgents.length > 0) {
|
|
1390
|
-
const names = Array.from(new Set(requestedProjectAgents.map((agent) => agent.name))).join(', ');
|
|
1391
|
-
const dir = discovery.projectAgentsDir ?? '(unknown)';
|
|
1392
|
-
const ok = await ctx.ui.confirm(
|
|
1393
|
-
'Run project-local agents?',
|
|
1394
|
-
`Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`,
|
|
1395
|
-
);
|
|
1396
|
-
if (!ok) {
|
|
1397
|
-
ctx.ui.notify('Delegate cancelled — project-local agents not approved.', 'info');
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1665
|
+
const conversation = extractRecentConversation(ctx);
|
|
1666
|
+
const task = buildRunAgentTask(conversation, explicitTask);
|
|
1667
|
+
if (!task) {
|
|
1668
|
+
ctx.ui.notify(`No conversation context and no task specified.\n\n${formatRunAgentUsage()}`, 'warning');
|
|
1669
|
+
return;
|
|
1401
1670
|
}
|
|
1402
1671
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1672
|
+
const runAndReport = async (
|
|
1673
|
+
commandCwd: string,
|
|
1674
|
+
commandUi: typeof ctx.ui,
|
|
1675
|
+
sendSummary: (message: {
|
|
1676
|
+
customType: 'run-agent-summary';
|
|
1677
|
+
content: string;
|
|
1678
|
+
display: true;
|
|
1679
|
+
details: RunAgentSummaryDetails;
|
|
1680
|
+
}) => Promise<void> | void,
|
|
1681
|
+
forked: boolean,
|
|
1682
|
+
) => {
|
|
1683
|
+
const selectedModel = model ?? agent.model;
|
|
1684
|
+
const selectedThinking = thinking ?? agent.thinking;
|
|
1685
|
+
|
|
1686
|
+
commandUi.notify(
|
|
1687
|
+
`Running agent: ${agent.name} [${agent.source}${forked ? ', forked' : ', inline'}${selectedModel ? `, model=${selectedModel}` : ''}${selectedThinking ? `, thinking=${selectedThinking}` : ''}]`,
|
|
1688
|
+
'info',
|
|
1689
|
+
);
|
|
1414
1690
|
|
|
1415
|
-
|
|
1416
|
-
const phaseResult = await executePhase(
|
|
1417
|
-
ctx.cwd,
|
|
1418
|
-
phaseDef,
|
|
1419
|
-
synthesis,
|
|
1420
|
-
previousOutput,
|
|
1421
|
-
ctx,
|
|
1691
|
+
const result = await runAgent(commandCwd, agent.name, task, {
|
|
1422
1692
|
agentScope,
|
|
1423
1693
|
resolvedPaths,
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
agents: phaseResult.results,
|
|
1430
|
-
};
|
|
1694
|
+
model,
|
|
1695
|
+
thinking,
|
|
1696
|
+
});
|
|
1697
|
+
const failed = result.exitCode !== 0 || result.stopReason === 'error';
|
|
1698
|
+
const output = getFinalText(result).trim();
|
|
1431
1699
|
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
state.totalUsage.cacheRead += phaseUsage.cacheRead;
|
|
1437
|
-
state.totalUsage.cacheWrite += phaseUsage.cacheWrite;
|
|
1438
|
-
state.totalUsage.cost += phaseUsage.cost;
|
|
1439
|
-
state.totalUsage.turns += phaseUsage.turns;
|
|
1700
|
+
commandUi.notify(
|
|
1701
|
+
failed ? `Agent "${agent.name}" failed.` : `Agent "${agent.name}" complete.`,
|
|
1702
|
+
failed ? 'error' : 'info',
|
|
1703
|
+
);
|
|
1440
1704
|
|
|
1441
|
-
|
|
1705
|
+
await sendSummary({
|
|
1706
|
+
customType: 'run-agent-summary',
|
|
1707
|
+
content: formatRunAgentSummary({
|
|
1708
|
+
agent,
|
|
1709
|
+
agentScope,
|
|
1710
|
+
task,
|
|
1711
|
+
result,
|
|
1712
|
+
forked,
|
|
1713
|
+
}),
|
|
1714
|
+
display: true,
|
|
1715
|
+
details: {
|
|
1716
|
+
agent: agent.name,
|
|
1717
|
+
agentSource: agent.source,
|
|
1718
|
+
agentScope,
|
|
1719
|
+
sessionStrategy: agent.sessionStrategy ?? 'inline',
|
|
1720
|
+
forked,
|
|
1721
|
+
exitCode: result.exitCode,
|
|
1722
|
+
stopReason: result.stopReason,
|
|
1723
|
+
errorMessage: result.errorMessage,
|
|
1724
|
+
usage: result.usage,
|
|
1725
|
+
task,
|
|
1726
|
+
output,
|
|
1727
|
+
} satisfies RunAgentSummaryDetails,
|
|
1728
|
+
});
|
|
1729
|
+
};
|
|
1442
1730
|
|
|
1443
|
-
|
|
1731
|
+
if (agent.sessionStrategy === 'fork-at') {
|
|
1732
|
+
const leafId = ctx.sessionManager.getLeafId?.();
|
|
1733
|
+
if (!leafId) {
|
|
1734
|
+
ctx.ui.notify(
|
|
1735
|
+
`Agent "${agent.name}" prefers fork-at, but the current session cannot be forked. Running inline instead.`,
|
|
1736
|
+
'warning',
|
|
1737
|
+
);
|
|
1738
|
+
} else {
|
|
1739
|
+
const forkResult = await ctx.fork(leafId, {
|
|
1740
|
+
position: 'at',
|
|
1741
|
+
withSession: async (replacementCtx) => {
|
|
1742
|
+
await runAndReport(
|
|
1743
|
+
replacementCtx.cwd,
|
|
1744
|
+
replacementCtx.ui,
|
|
1745
|
+
(message) => replacementCtx.sendMessage(message),
|
|
1746
|
+
true,
|
|
1747
|
+
);
|
|
1748
|
+
},
|
|
1749
|
+
});
|
|
1750
|
+
if (forkResult.cancelled) {
|
|
1751
|
+
ctx.ui.notify(`Run cancelled — unable to fork session for "${agent.name}".`, 'info');
|
|
1752
|
+
}
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1444
1755
|
}
|
|
1445
1756
|
|
|
1446
|
-
|
|
1447
|
-
const summary = formatFullSummary(state);
|
|
1448
|
-
ctx.ui.notify(`Delegate complete — ${workflow.label}`, 'info');
|
|
1449
|
-
|
|
1450
|
-
pi.sendMessage({
|
|
1451
|
-
customType: 'delegate-summary',
|
|
1452
|
-
content: summary,
|
|
1453
|
-
display: true,
|
|
1454
|
-
details: {
|
|
1455
|
-
workflow: workflow.id,
|
|
1456
|
-
agentScope,
|
|
1457
|
-
phaseCount: state.phases.length,
|
|
1458
|
-
totalUsage: state.totalUsage,
|
|
1459
|
-
},
|
|
1460
|
-
});
|
|
1757
|
+
await runAndReport(ctx.cwd, ctx.ui, (message) => pi.sendMessage(message), false);
|
|
1461
1758
|
},
|
|
1462
1759
|
});
|
|
1760
|
+
|
|
1463
1761
|
}
|