@dreki-gg/pi-subagent 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @dreki-gg/pi-subagent
|
|
2
2
|
|
|
3
|
+
## 0.8.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`376864c`](https://github.com/dreki-gg/pi-extensions/commit/376864c37cefa47530363b47055311269c1724a8) Thanks [@jalbarrang](https://github.com/jalbarrang)! - feat(subagent): show live tool-aware status in working message during /run-agent
|
|
8
|
+
|
|
9
|
+
Previously `/run-agent` only showed a transient notification, leaving users with no visibility into what the background agent was doing (especially after a fork-at session switch that visually looks like a reload). Now the working message updates in real-time as the agent works:
|
|
10
|
+
|
|
11
|
+
- `scout · starting...`
|
|
12
|
+
- `scout · reading …/src/utils.ts`
|
|
13
|
+
- `scout · $ bun test --filter...`
|
|
14
|
+
- `scout · editing …/config.json`
|
|
15
|
+
- `scout · thinking...`
|
|
16
|
+
|
|
17
|
+
Added `onToolExecutionStart` callback to `spawnPiAgent` and `runAgent` to surface `tool_execution_start` events from the JSON stream.
|
|
18
|
+
|
|
19
|
+
## 0.8.3
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [`6f0b219`](https://github.com/dreki-gg/pi-extensions/commit/6f0b219ac357ce1607a7a8211fd1c66bd35c62f1) Thanks [@jalbarrang](https://github.com/jalbarrang)! - fix(subagent): resolve TDZ crash when onMessage/onToolResult callbacks fire before spawnResult is assigned
|
|
24
|
+
|
|
25
|
+
Previously, `runSingleAgent` declared `const spawnResult = await spawnPiAgent({...})` and referenced `spawnResult` inside the `onMessage`/`onToolResult` callbacks. Since callbacks fire during the await (before the const is assigned), this caused a `ReferenceError: Cannot access 'spawnResult' before initialization`. Now uses `let` with a guard to safely accumulate messages when the result is not yet available.
|
|
26
|
+
|
|
3
27
|
## 0.8.2
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ResolvedPaths } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import { discoverAgents, type AgentScope } from './agents.js';
|
|
3
3
|
import type { AgentResult } from './agent-runner-types.js';
|
|
4
|
-
import { emptyUsage, spawnPiAgent } from './spawn-utils.js';
|
|
4
|
+
import { emptyUsage, spawnPiAgent, type ToolExecutionStartEvent } from './spawn-utils.js';
|
|
5
5
|
|
|
6
6
|
export type OnPhaseUpdate = (phaseName: string, agentName: string, result: AgentResult) => void;
|
|
7
7
|
|
|
@@ -11,6 +11,7 @@ export interface RunAgentOptions {
|
|
|
11
11
|
model?: string;
|
|
12
12
|
thinking?: string;
|
|
13
13
|
onUpdate?: OnPhaseUpdate;
|
|
14
|
+
onToolExecutionStart?: (event: ToolExecutionStartEvent) => void;
|
|
14
15
|
phaseName?: string;
|
|
15
16
|
signal?: AbortSignal;
|
|
16
17
|
resolvedPaths?: ResolvedPaths;
|
|
@@ -76,6 +77,7 @@ export async function runAgent(
|
|
|
76
77
|
options.onUpdate(options.phaseName ?? 'unknown', agentName, { ...result });
|
|
77
78
|
}
|
|
78
79
|
},
|
|
80
|
+
onToolExecutionStart: options.onToolExecutionStart,
|
|
79
81
|
});
|
|
80
82
|
|
|
81
83
|
result.exitCode = spawnResult.exitCode;
|
|
@@ -27,26 +27,77 @@ import {
|
|
|
27
27
|
SettingsManager,
|
|
28
28
|
} from '@earendil-works/pi-coding-agent';
|
|
29
29
|
import type { ResolvedPaths } from '@earendil-works/pi-coding-agent';
|
|
30
|
-
import { Box, Container, Markdown, Spacer, Text, type AutocompleteItem } from '@earendil-works/pi-tui';
|
|
31
|
-
import { Type } from 'typebox';
|
|
32
30
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
Box,
|
|
32
|
+
Container,
|
|
33
|
+
Markdown,
|
|
34
|
+
Spacer,
|
|
35
|
+
Text,
|
|
36
|
+
type AutocompleteItem,
|
|
37
|
+
} from '@earendil-works/pi-tui';
|
|
38
|
+
import { Type } from 'typebox';
|
|
39
|
+
import { type AgentConfig, type AgentScope, type AgentSource, discoverAgents } from './agents.js';
|
|
38
40
|
import { formatRunAgentUsage, parseRunAgentArgs } from './run-agent-args.js';
|
|
39
41
|
import { runAgent } from './agent-runner.js';
|
|
40
42
|
import { extractRecentConversation } from './synthesis.js';
|
|
41
43
|
import type { AgentResult } from './agent-runner-types.js';
|
|
42
44
|
import { getFinalText } from './agent-result-utils.js';
|
|
43
45
|
import { buildHandoffFromResult, renderHandoffForPrompt } from './handoffs.js';
|
|
44
|
-
import { emptyUsage, spawnPiAgent } from './spawn-utils.js';
|
|
46
|
+
import { emptyUsage, spawnPiAgent, type ToolExecutionStartEvent } from './spawn-utils.js';
|
|
45
47
|
|
|
46
48
|
const MAX_PARALLEL_TASKS = 8;
|
|
47
49
|
const MAX_CONCURRENCY = 4;
|
|
48
50
|
const COLLAPSED_ITEM_COUNT = 10;
|
|
49
51
|
|
|
52
|
+
function describeToolExecution(event: ToolExecutionStartEvent): string {
|
|
53
|
+
const shortenPath = (p: string) => {
|
|
54
|
+
const home = os.homedir();
|
|
55
|
+
const shortened = p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
56
|
+
// Show only the last 2 path segments for brevity
|
|
57
|
+
const parts = shortened.split('/');
|
|
58
|
+
return parts.length > 3 ? `…/${parts.slice(-2).join('/')}` : shortened;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const args = event.args;
|
|
62
|
+
switch (event.toolName) {
|
|
63
|
+
case 'bash': {
|
|
64
|
+
const cmd = (args.command as string) || '';
|
|
65
|
+
const preview = cmd.length > 50 ? `${cmd.slice(0, 50)}…` : cmd;
|
|
66
|
+
return `$ ${preview}`;
|
|
67
|
+
}
|
|
68
|
+
case 'read': {
|
|
69
|
+
const p = ((args.file_path || args.path) as string) || '';
|
|
70
|
+
return `reading ${shortenPath(p)}`;
|
|
71
|
+
}
|
|
72
|
+
case 'write': {
|
|
73
|
+
const p = ((args.file_path || args.path) as string) || '';
|
|
74
|
+
return `writing ${shortenPath(p)}`;
|
|
75
|
+
}
|
|
76
|
+
case 'edit': {
|
|
77
|
+
const p = ((args.file_path || args.path) as string) || '';
|
|
78
|
+
return `editing ${shortenPath(p)}`;
|
|
79
|
+
}
|
|
80
|
+
case 'grep': {
|
|
81
|
+
const pattern = (args.pattern as string) || '';
|
|
82
|
+
return `grep /${pattern}/`;
|
|
83
|
+
}
|
|
84
|
+
case 'find': {
|
|
85
|
+
const pattern = (args.pattern as string) || '*';
|
|
86
|
+
return `find ${pattern}`;
|
|
87
|
+
}
|
|
88
|
+
case 'ls':
|
|
89
|
+
return `ls ${shortenPath((args.path as string) || '.')}`;
|
|
90
|
+
case 'web_search':
|
|
91
|
+
return `searching: ${(args.query as string)?.slice(0, 40) || '…'}`;
|
|
92
|
+
case 'web_visit':
|
|
93
|
+
return 'visiting page';
|
|
94
|
+
case 'subagent':
|
|
95
|
+
return `spawning ${(args.agent as string) || 'subagent'}`;
|
|
96
|
+
default:
|
|
97
|
+
return event.toolName;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
50
101
|
function formatTokens(count: number): string {
|
|
51
102
|
if (count < 1000) return count.toString();
|
|
52
103
|
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
@@ -247,7 +298,6 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
|
|
|
247
298
|
return results;
|
|
248
299
|
}
|
|
249
300
|
|
|
250
|
-
|
|
251
301
|
type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
|
|
252
302
|
|
|
253
303
|
async function runSingleAgent(
|
|
@@ -303,7 +353,8 @@ async function runSingleAgent(
|
|
|
303
353
|
}
|
|
304
354
|
};
|
|
305
355
|
|
|
306
|
-
|
|
356
|
+
let spawnResult: Awaited<ReturnType<typeof spawnPiAgent>> | undefined;
|
|
357
|
+
spawnResult = await spawnPiAgent({
|
|
307
358
|
cwd: cwd ?? defaultCwd,
|
|
308
359
|
agentName: agent.name,
|
|
309
360
|
task,
|
|
@@ -313,15 +364,23 @@ async function runSingleAgent(
|
|
|
313
364
|
tools: agent.tools,
|
|
314
365
|
signal,
|
|
315
366
|
onMessage: (msg) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
367
|
+
if (!spawnResult) {
|
|
368
|
+
currentResult.messages = [...currentResult.messages, msg];
|
|
369
|
+
} else {
|
|
370
|
+
currentResult.messages = spawnResult.messages;
|
|
371
|
+
currentResult.usage = spawnResult.usage;
|
|
372
|
+
currentResult.model = spawnResult.model ?? currentResult.model;
|
|
373
|
+
currentResult.stopReason = spawnResult.stopReason;
|
|
374
|
+
currentResult.errorMessage = spawnResult.errorMessage;
|
|
375
|
+
}
|
|
321
376
|
emitUpdate();
|
|
322
377
|
},
|
|
323
|
-
onToolResult: () => {
|
|
324
|
-
|
|
378
|
+
onToolResult: (msg) => {
|
|
379
|
+
if (!spawnResult) {
|
|
380
|
+
currentResult.messages = [...currentResult.messages, msg];
|
|
381
|
+
} else {
|
|
382
|
+
currentResult.messages = spawnResult.messages;
|
|
383
|
+
}
|
|
325
384
|
emitUpdate();
|
|
326
385
|
},
|
|
327
386
|
});
|
|
@@ -373,13 +432,19 @@ const SubagentParams = Type.Object({
|
|
|
373
432
|
Type.String({ description: 'Optional default model override for the run or all steps/tasks' }),
|
|
374
433
|
),
|
|
375
434
|
thinking: Type.Optional(
|
|
376
|
-
Type.String({
|
|
435
|
+
Type.String({
|
|
436
|
+
description: 'Optional default reasoning level override for the run or all steps/tasks',
|
|
437
|
+
}),
|
|
377
438
|
),
|
|
378
439
|
tasks: Type.Optional(
|
|
379
|
-
Type.Array(TaskItem, {
|
|
440
|
+
Type.Array(TaskItem, {
|
|
441
|
+
description: 'Array of {agent, task, model?, thinking?} for parallel execution',
|
|
442
|
+
}),
|
|
380
443
|
),
|
|
381
444
|
chain: Type.Optional(
|
|
382
|
-
Type.Array(ChainItem, {
|
|
445
|
+
Type.Array(ChainItem, {
|
|
446
|
+
description: 'Array of {agent, task, model?, thinking?} for sequential execution',
|
|
447
|
+
}),
|
|
383
448
|
),
|
|
384
449
|
agentScope: Type.Optional(AgentScopeSchema),
|
|
385
450
|
confirmProjectAgents: Type.Optional(
|
|
@@ -424,9 +489,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
424
489
|
if (!trimmedEnd) return replacement;
|
|
425
490
|
if (/\s$/.test(argumentText)) return `${trimmedEnd} ${replacement}`;
|
|
426
491
|
const lastSpace = trimmedEnd.lastIndexOf(' ');
|
|
427
|
-
return lastSpace === -1
|
|
428
|
-
? replacement
|
|
429
|
-
: `${trimmedEnd.slice(0, lastSpace + 1)}${replacement}`;
|
|
492
|
+
return lastSpace === -1 ? replacement : `${trimmedEnd.slice(0, lastSpace + 1)}${replacement}`;
|
|
430
493
|
}
|
|
431
494
|
|
|
432
495
|
function buildArgumentCompletions(
|
|
@@ -456,9 +519,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
456
519
|
return discoverAgents(autocompleteCwd, scope, resolvedPaths).agents;
|
|
457
520
|
}
|
|
458
521
|
|
|
459
|
-
async function getAutocompleteModels(
|
|
460
|
-
|
|
461
|
-
|
|
522
|
+
async function getAutocompleteModels(
|
|
523
|
+
scope: AgentScope,
|
|
524
|
+
): Promise<Array<{ value: string; label?: string; description?: string }>> {
|
|
462
525
|
const items = new Map<string, { value: string; label?: string; description?: string }>();
|
|
463
526
|
|
|
464
527
|
try {
|
|
@@ -1238,7 +1301,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1238
1301
|
title,
|
|
1239
1302
|
modeLine,
|
|
1240
1303
|
theme.fg('muted', `Task: ${details.task}`),
|
|
1241
|
-
failed && details.errorMessage
|
|
1304
|
+
failed && details.errorMessage
|
|
1305
|
+
? theme.fg('error', `Error: ${details.errorMessage}`)
|
|
1306
|
+
: previewLines,
|
|
1242
1307
|
usageLine ? theme.fg('dim', usageLine) : undefined,
|
|
1243
1308
|
theme.fg('muted', '(Ctrl+O to expand)'),
|
|
1244
1309
|
]
|
|
@@ -1347,7 +1412,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1347
1412
|
{ value: '--model', description: 'Override the agent model for this run' },
|
|
1348
1413
|
{ value: '--thinking', description: 'Override the reasoning level for this run' },
|
|
1349
1414
|
{ value: '--reasoning-level', description: 'Alias for --thinking' },
|
|
1350
|
-
{
|
|
1415
|
+
{
|
|
1416
|
+
value: '--yes-project-agents',
|
|
1417
|
+
description: 'Skip the project-agent trust confirmation prompt',
|
|
1418
|
+
},
|
|
1351
1419
|
...agents.map((agent) => ({
|
|
1352
1420
|
value: agent.name,
|
|
1353
1421
|
label: agent.name,
|
|
@@ -1363,7 +1431,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1363
1431
|
return;
|
|
1364
1432
|
}
|
|
1365
1433
|
|
|
1366
|
-
const { agentName, explicitTask, agentScope, confirmProjectAgents, model, thinking } =
|
|
1434
|
+
const { agentName, explicitTask, agentScope, confirmProjectAgents, model, thinking } =
|
|
1435
|
+
parsed.options;
|
|
1367
1436
|
if (!agentName) {
|
|
1368
1437
|
ctx.ui.notify(formatRunAgentUsage(), 'warning');
|
|
1369
1438
|
return;
|
|
@@ -1394,7 +1463,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1394
1463
|
const conversation = extractRecentConversation(ctx);
|
|
1395
1464
|
const task = buildRunAgentTask(conversation, explicitTask);
|
|
1396
1465
|
if (!task) {
|
|
1397
|
-
ctx.ui.notify(
|
|
1466
|
+
ctx.ui.notify(
|
|
1467
|
+
`No conversation context and no task specified.\n\n${formatRunAgentUsage()}`,
|
|
1468
|
+
'warning',
|
|
1469
|
+
);
|
|
1398
1470
|
return;
|
|
1399
1471
|
}
|
|
1400
1472
|
|
|
@@ -1416,13 +1488,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
1416
1488
|
`Running agent: ${agent.name} [${agent.source}${forked ? ', forked' : ', inline'}${selectedModel ? `, model=${selectedModel}` : ''}${selectedThinking ? `, thinking=${selectedThinking}` : ''}]`,
|
|
1417
1489
|
'info',
|
|
1418
1490
|
);
|
|
1491
|
+
let lastToolDescription = '';
|
|
1492
|
+
const updateWorkingStatus = (toolDesc?: string) => {
|
|
1493
|
+
if (toolDesc) lastToolDescription = toolDesc;
|
|
1494
|
+
const parts = [agent.name];
|
|
1495
|
+
if (lastToolDescription) parts.push(lastToolDescription);
|
|
1496
|
+
commandUi.setWorkingMessage(parts.join(' · '));
|
|
1497
|
+
};
|
|
1498
|
+
updateWorkingStatus('starting...');
|
|
1419
1499
|
|
|
1420
1500
|
const result = await runAgent(commandCwd, agent.name, task, {
|
|
1421
1501
|
agentScope,
|
|
1422
1502
|
resolvedPaths,
|
|
1423
1503
|
model,
|
|
1424
1504
|
thinking,
|
|
1505
|
+
onUpdate: () => {
|
|
1506
|
+
// When a turn completes and the agent is thinking about next steps
|
|
1507
|
+
updateWorkingStatus('thinking...');
|
|
1508
|
+
},
|
|
1509
|
+
onToolExecutionStart: (event) => {
|
|
1510
|
+
updateWorkingStatus(describeToolExecution(event));
|
|
1511
|
+
},
|
|
1425
1512
|
});
|
|
1513
|
+
commandUi.setWorkingMessage();
|
|
1426
1514
|
const failed = result.exitCode !== 0 || result.stopReason === 'error';
|
|
1427
1515
|
const output = getFinalText(result).trim();
|
|
1428
1516
|
|
|
@@ -1486,5 +1574,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
1486
1574
|
await runAndReport(ctx.cwd, ctx.ui, (message) => pi.sendMessage(message), false);
|
|
1487
1575
|
},
|
|
1488
1576
|
});
|
|
1489
|
-
|
|
1490
1577
|
}
|
|
@@ -57,6 +57,12 @@ function cleanupTempFiles(
|
|
|
57
57
|
if (tmpPromptDir) try { fs.rmdirSync(tmpPromptDir); } catch { /* ignore */ }
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export interface ToolExecutionStartEvent {
|
|
61
|
+
toolCallId: string;
|
|
62
|
+
toolName: string;
|
|
63
|
+
args: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
|
|
60
66
|
export interface SpawnPiAgentOptions {
|
|
61
67
|
cwd: string;
|
|
62
68
|
agentName: string;
|
|
@@ -68,6 +74,7 @@ export interface SpawnPiAgentOptions {
|
|
|
68
74
|
signal?: AbortSignal;
|
|
69
75
|
onMessage?: (msg: Message) => void;
|
|
70
76
|
onToolResult?: (msg: Message) => void;
|
|
77
|
+
onToolExecutionStart?: (event: ToolExecutionStartEvent) => void;
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
export interface SpawnPiAgentResult {
|
|
@@ -158,6 +165,14 @@ export async function spawnPiAgent(options: SpawnPiAgentOptions): Promise<SpawnP
|
|
|
158
165
|
result.messages.push(event.message as Message);
|
|
159
166
|
options.onToolResult?.(event.message as Message);
|
|
160
167
|
}
|
|
168
|
+
|
|
169
|
+
if (event.type === 'tool_execution_start' && event.toolName) {
|
|
170
|
+
options.onToolExecutionStart?.({
|
|
171
|
+
toolCallId: event.toolCallId ?? '',
|
|
172
|
+
toolName: event.toolName,
|
|
173
|
+
args: event.args ?? {},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
161
176
|
};
|
|
162
177
|
|
|
163
178
|
proc.stdout.on('data', (data: Buffer) => {
|
package/package.json
CHANGED