pi-subagents 0.17.5 → 0.18.1

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.
@@ -44,6 +44,7 @@ import {
44
44
  type AgentProgress,
45
45
  type ArtifactConfig,
46
46
  type ArtifactPaths,
47
+ type ControlEvent,
47
48
  type Details,
48
49
  type ResolvedControlConfig,
49
50
  type SingleResult,
@@ -84,12 +85,17 @@ interface ParallelChainRunInput {
84
85
  artifactsDir: string;
85
86
  signal?: AbortSignal;
86
87
  onUpdate?: (r: AgentToolResult<Details>) => void;
88
+ onControlEvent?: (event: ControlEvent) => void;
87
89
  controlConfig: ResolvedControlConfig;
90
+ childIntercomTarget?: (agent: string, index: number) => string | undefined;
88
91
  foregroundControl?: {
89
92
  updatedAt: number;
90
93
  currentAgent?: string;
91
94
  currentIndex?: number;
92
95
  currentActivityState?: ActivityState;
96
+ lastActivityAt?: number;
97
+ currentTool?: string;
98
+ currentToolStartedAt?: number;
93
99
  interrupt?: () => boolean;
94
100
  };
95
101
  results: SingleResult[];
@@ -200,12 +206,12 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
200
206
  if (input.foregroundControl) {
201
207
  input.foregroundControl.currentAgent = task.agent;
202
208
  input.foregroundControl.currentIndex = input.globalTaskIndex + taskIndex;
203
- input.foregroundControl.currentActivityState = "starting";
209
+ input.foregroundControl.currentActivityState = undefined;
204
210
  input.foregroundControl.updatedAt = Date.now();
205
211
  input.foregroundControl.interrupt = () => {
206
212
  if (interruptController.signal.aborted) return false;
207
213
  interruptController.abort();
208
- input.foregroundControl!.currentActivityState = "paused";
214
+ input.foregroundControl!.currentActivityState = undefined;
209
215
  input.foregroundControl!.updatedAt = Date.now();
210
216
  return true;
211
217
  };
@@ -225,6 +231,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
225
231
  outputPath,
226
232
  maxSubagentDepth,
227
233
  controlConfig: input.controlConfig,
234
+ onControlEvent: input.onControlEvent,
235
+ intercomSessionName: input.childIntercomTarget?.(task.agent, input.globalTaskIndex + taskIndex),
228
236
  modelOverride: effectiveModel,
229
237
  availableModels: input.availableModels,
230
238
  preferredModelProvider: input.ctx.model?.provider,
@@ -238,6 +246,9 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
238
246
  input.foregroundControl.currentAgent = task.agent;
239
247
  input.foregroundControl.currentIndex = input.globalTaskIndex + taskIndex;
240
248
  input.foregroundControl.currentActivityState = current?.activityState;
249
+ input.foregroundControl.lastActivityAt = current?.lastActivityAt;
250
+ input.foregroundControl.currentTool = current?.currentTool;
251
+ input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
241
252
  input.foregroundControl.updatedAt = Date.now();
242
253
  }
243
254
  input.onUpdate?.({
@@ -287,12 +298,17 @@ export interface ChainExecutionParams {
287
298
  includeProgress?: boolean;
288
299
  clarify?: boolean;
289
300
  onUpdate?: (r: AgentToolResult<Details>) => void;
301
+ onControlEvent?: (event: ControlEvent) => void;
290
302
  controlConfig: ResolvedControlConfig;
303
+ childIntercomTarget?: (agent: string, index: number) => string | undefined;
291
304
  foregroundControl?: {
292
305
  updatedAt: number;
293
306
  currentAgent?: string;
294
307
  currentIndex?: number;
295
308
  currentActivityState?: ActivityState;
309
+ lastActivityAt?: number;
310
+ currentTool?: string;
311
+ currentToolStartedAt?: number;
296
312
  interrupt?: () => boolean;
297
313
  };
298
314
  chainSkills?: string[];
@@ -332,7 +348,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
332
348
  includeProgress,
333
349
  clarify,
334
350
  onUpdate,
351
+ onControlEvent,
335
352
  controlConfig,
353
+ childIntercomTarget,
336
354
  foregroundControl,
337
355
  chainSkills: chainSkillsParam,
338
356
  chainDir: chainDirBase,
@@ -533,6 +551,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
533
551
  chainAgents,
534
552
  totalSteps,
535
553
  controlConfig,
554
+ onControlEvent,
555
+ childIntercomTarget,
536
556
  foregroundControl,
537
557
  worktreeSetup,
538
558
  maxSubagentDepth: params.maxSubagentDepth,
@@ -674,12 +694,12 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
674
694
  if (foregroundControl) {
675
695
  foregroundControl.currentAgent = seqStep.agent;
676
696
  foregroundControl.currentIndex = globalTaskIndex;
677
- foregroundControl.currentActivityState = "starting";
697
+ foregroundControl.currentActivityState = undefined;
678
698
  foregroundControl.updatedAt = Date.now();
679
699
  foregroundControl.interrupt = () => {
680
700
  if (interruptController.signal.aborted) return false;
681
701
  interruptController.abort();
682
- foregroundControl.currentActivityState = "paused";
702
+ foregroundControl.currentActivityState = undefined;
683
703
  foregroundControl.updatedAt = Date.now();
684
704
  return true;
685
705
  };
@@ -699,6 +719,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
699
719
  outputPath,
700
720
  maxSubagentDepth,
701
721
  controlConfig,
722
+ onControlEvent,
723
+ intercomSessionName: childIntercomTarget?.(seqStep.agent, globalTaskIndex),
702
724
  modelOverride: effectiveModel,
703
725
  availableModels,
704
726
  preferredModelProvider: ctx.model?.provider,
@@ -712,6 +734,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
712
734
  foregroundControl.currentAgent = seqStep.agent;
713
735
  foregroundControl.currentIndex = globalTaskIndex;
714
736
  foregroundControl.currentActivityState = current?.activityState;
737
+ foregroundControl.lastActivityAt = current?.lastActivityAt;
738
+ foregroundControl.currentTool = current?.currentTool;
739
+ foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
715
740
  foregroundControl.updatedAt = Date.now();
716
741
  }
717
742
  onUpdate({
package/execution.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { spawn } from "node:child_process";
6
+ import { existsSync } from "node:fs";
6
7
  import type { Message } from "@mariozechner/pi-ai";
7
8
  import type { AgentConfig } from "./agents.ts";
8
9
  import {
@@ -28,8 +29,10 @@ import {
28
29
  import {
29
30
  DEFAULT_CONTROL_CONFIG,
30
31
  buildControlEvent,
32
+ claimControlNotification,
31
33
  deriveActivityState,
32
34
  shouldEmitControlEvent,
35
+ shouldNotifyControlEvent,
33
36
  } from "./subagent-control.ts";
34
37
  import {
35
38
  getFinalOutput,
@@ -135,6 +138,7 @@ async function runSingleAttempt(
135
138
  systemPrompt: shared.systemPrompt,
136
139
  mcpDirectTools: agent.mcpDirectTools,
137
140
  promptFileStem: agent.name,
141
+ intercomSessionName: options.intercomSessionName,
138
142
  });
139
143
 
140
144
  const result: SingleResult = {
@@ -150,8 +154,6 @@ async function runSingleAttempt(
150
154
  };
151
155
  const startTime = Date.now();
152
156
  const controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;
153
- let hasSeenActivity = false;
154
- let pausedByInterrupt = false;
155
157
  let interruptedByControl = false;
156
158
  const allControlEvents: ControlEvent[] = [];
157
159
  let pendingControlEvents: ControlEvent[] = [];
@@ -160,7 +162,6 @@ async function runSingleAttempt(
160
162
  index: options.index ?? 0,
161
163
  agent: agent.name,
162
164
  status: "running",
163
- activityState: controlConfig.enabled ? "starting" : undefined,
164
165
  task,
165
166
  skills: shared.resolvedSkillNames,
166
167
  recentTools: [],
@@ -274,34 +275,39 @@ async function runSingleAttempt(
274
275
  return events;
275
276
  };
276
277
 
278
+ const emittedControlEventKeys = new Set<string>();
279
+ const emitControlEvent = (event: ControlEvent) => {
280
+ if (shouldNotifyControlEvent(controlConfig, event) && !claimControlNotification(controlConfig, event, emittedControlEventKeys)) return;
281
+ allControlEvents.push(event);
282
+ pendingControlEvents.push(event);
283
+ options.onControlEvent?.(event);
284
+ };
285
+
277
286
  const updateActivityState = (now: number): boolean => {
278
287
  const next = deriveActivityState({
279
288
  config: controlConfig,
280
289
  startedAt: startTime,
281
290
  lastActivityAt: progress.lastActivityAt,
282
- hasSeenActivity,
283
- paused: pausedByInterrupt,
284
291
  now,
285
292
  });
286
- if (!next || next === progress.activityState) return false;
293
+ if (next === progress.activityState) return false;
287
294
  const previous = progress.activityState;
288
295
  progress.activityState = next;
289
296
  if (shouldEmitControlEvent(controlConfig, previous, next)) {
290
- const event = buildControlEvent({
297
+ emitControlEvent(buildControlEvent({
291
298
  from: previous,
292
299
  to: next,
293
300
  runId: options.runId,
294
301
  agent: agent.name,
295
302
  index: options.index,
296
303
  ts: now,
297
- });
298
- allControlEvents.push(event);
299
- pendingControlEvents.push(event);
300
- options.onControlEvent?.(event);
304
+ lastActivityAt: progress.lastActivityAt,
305
+ }));
301
306
  }
302
307
  return true;
303
308
  };
304
309
 
310
+
305
311
  const emitUpdateSnapshot = (text: string) => {
306
312
  if (!options.onUpdate || processClosed) return;
307
313
  const progressSnapshot = snapshotProgress(progress);
@@ -338,7 +344,6 @@ async function runSingleAttempt(
338
344
  const now = Date.now();
339
345
  progress.durationMs = now - startTime;
340
346
  progress.lastActivityAt = now;
341
- hasSeenActivity = true;
342
347
  updateActivityState(now);
343
348
 
344
349
  if (evt.type === "tool_execution_start") {
@@ -481,27 +486,11 @@ async function runSingleAttempt(
481
486
  const interrupt = () => {
482
487
  if (processClosed || detached || settled) return;
483
488
  interruptedByControl = true;
484
- pausedByInterrupt = true;
485
489
  progress.status = "running";
486
490
  progress.durationMs = Date.now() - startTime;
487
491
  result.interrupted = true;
488
492
  result.finalOutput = "Interrupted. Waiting for explicit next action.";
489
- const now = Date.now();
490
- const previous = progress.activityState;
491
- progress.activityState = "paused";
492
- if (shouldEmitControlEvent(controlConfig, previous, "paused")) {
493
- const event = buildControlEvent({
494
- from: previous,
495
- to: "paused",
496
- runId: options.runId,
497
- agent: agent.name,
498
- index: options.index,
499
- ts: now,
500
- });
501
- allControlEvents.push(event);
502
- pendingControlEvents.push(event);
503
- options.onControlEvent?.(event);
504
- }
493
+ progress.activityState = undefined;
505
494
  fireUpdate();
506
495
  trySignalChild(proc, "SIGINT");
507
496
  setTimeout(() => {
@@ -523,7 +512,7 @@ async function runSingleAttempt(
523
512
  result.error = undefined;
524
513
  result.finalOutput = result.finalOutput || "Interrupted. Waiting for explicit next action.";
525
514
  result.controlEvents = allControlEvents.length ? allControlEvents : undefined;
526
- progress.activityState = "paused";
515
+ progress.activityState = undefined;
527
516
  progress.durationMs = Date.now() - startTime;
528
517
  result.progressSummary = {
529
518
  toolCount: progress.toolCount,
@@ -738,12 +727,11 @@ export async function runSync(
738
727
  if (truncationResult.truncated) result.truncation = truncationResult;
739
728
  }
740
729
 
741
- if (shareEnabled) {
742
- const sessionFile = options.sessionFile
743
- ?? (options.sessionDir ? findLatestSessionFile(options.sessionDir) : null);
744
- if (sessionFile) {
745
- result.sessionFile = sessionFile;
746
- }
730
+ if (options.sessionFile && (existsSync(options.sessionFile) || result.messages?.length)) {
731
+ result.sessionFile = options.sessionFile;
732
+ } else if (shareEnabled && options.sessionDir) {
733
+ const sessionFile = findLatestSessionFile(options.sessionDir);
734
+ if (sessionFile) result.sessionFile = sessionFile;
747
735
  }
748
736
 
749
737
  return result;