@dreki-gg/pi-subagent 0.8.3 → 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,21 @@
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
+
3
19
  ## 0.8.3
4
20
 
5
21
  ### 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;
@@ -43,12 +43,61 @@ import { extractRecentConversation } from './synthesis.js';
43
43
  import type { AgentResult } from './agent-runner-types.js';
44
44
  import { getFinalText } from './agent-result-utils.js';
45
45
  import { buildHandoffFromResult, renderHandoffForPrompt } from './handoffs.js';
46
- import { emptyUsage, spawnPiAgent } from './spawn-utils.js';
46
+ import { emptyUsage, spawnPiAgent, type ToolExecutionStartEvent } from './spawn-utils.js';
47
47
 
48
48
  const MAX_PARALLEL_TASKS = 8;
49
49
  const MAX_CONCURRENCY = 4;
50
50
  const COLLAPSED_ITEM_COUNT = 10;
51
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
+
52
101
  function formatTokens(count: number): string {
53
102
  if (count < 1000) return count.toString();
54
103
  if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
@@ -1439,13 +1488,29 @@ export default function (pi: ExtensionAPI) {
1439
1488
  `Running agent: ${agent.name} [${agent.source}${forked ? ', forked' : ', inline'}${selectedModel ? `, model=${selectedModel}` : ''}${selectedThinking ? `, thinking=${selectedThinking}` : ''}]`,
1440
1489
  'info',
1441
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...');
1442
1499
 
1443
1500
  const result = await runAgent(commandCwd, agent.name, task, {
1444
1501
  agentScope,
1445
1502
  resolvedPaths,
1446
1503
  model,
1447
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
+ },
1448
1512
  });
1513
+ commandUi.setWorkingMessage();
1449
1514
  const failed = result.exitCode !== 0 || result.stopReason === 'error';
1450
1515
  const output = getFinalText(result).trim();
1451
1516
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreki-gg/pi-subagent",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Subagent tool and direct agent runs for pi — isolated agents, parallel scouts, manager workflows, and bundled prompts",
5
5
  "keywords": [
6
6
  "pi-package"