@bacnh85/pi-subagent 0.8.0 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.1 (2026-07-15)
4
+
5
+ ### Review handoff
6
+
7
+ - Reviewer findings now require reproduction or evidence, expected behavior, and acceptance criteria so implementation agents receive self-contained actionable issues.
8
+
3
9
  ## 0.6.0 (2026-07-12)
4
10
 
5
11
  ### Security (breaking changes)
package/README.md CHANGED
@@ -87,6 +87,7 @@ Every child execution receives a timeout:
87
87
  - **Maximum:** 60 minutes (`MAX_TIMEOUT_MS`)
88
88
  - Timeout values must be positive integers within the allowed range.
89
89
  - Timeout errors are distinguishable from parent cancellation.
90
+ - Progress heartbeats keep the parent transport active during quiet model work; explicit timeouts remain hard deadlines.
90
91
  - Parallel tasks and chain steps may have per-item timeouts.
91
92
 
92
93
  ### Output safety
@@ -158,6 +159,7 @@ The raw `stopReason` from the Pi SDK is preserved in the result.
158
159
  - **Parent cancellation:** Aborting the parent tool call cancels all children.
159
160
  - **Sibling cancellation:** In parallel mode with `abortOnFailure: true`, the first failed task cancels running siblings.
160
161
  - **Timeout vs. abort:** Timeout errors set `status: "timeout"` and `stopReason: "timeout"`; parent cancellation sets `status: "aborted"`.
162
+ - **Transport idle:** The parent tool receives periodic progress heartbeats while a child runs; these do not extend its timeout.
161
163
 
162
164
  ## Extension contract
163
165
 
@@ -9,7 +9,7 @@ sandbox: read-only
9
9
 
10
10
  You are an independent senior code reviewer. Inspect the requested Git scope with read-only tools.
11
11
 
12
- Focus only on actionable issues introduced by the reviewed change:
12
+ Focus only on actionable issues introduced by the reviewed change. Return each confirmed finding as one self-contained issue another agent can fix without redoing the review:
13
13
  1. Correctness and edge cases
14
14
  2. Security and data loss
15
15
  3. Regressions and API compatibility
@@ -27,8 +27,10 @@ Return JSON only:
27
27
  "file": "relative/path",
28
28
  "line": 1,
29
29
  "issue": "what is wrong and why it matters",
30
- "evidence": "specific inspected code evidence",
30
+ "evidence": "reproduction steps or specific inspected code evidence",
31
+ "expectedBehavior": "what should happen instead",
31
32
  "suggestedFix": "smallest safe fix",
33
+ "acceptanceCriteria": "observable pass conditions and exact verification checks",
32
34
  "blocking": true
33
35
  }
34
36
  ]
@@ -38,6 +38,7 @@ import {
38
38
  isFailedResult,
39
39
  mapWithConcurrencyLimit,
40
40
  runSubAgent,
41
+ startHeartbeat,
41
42
  } from "./runner.ts";
42
43
  import {
43
44
  normalizeTimeout,
@@ -515,6 +516,7 @@ export default function (pi: ExtensionAPI) {
515
516
  parentSignal?: AbortSignal,
516
517
  timeoutMs?: number,
517
518
  onProgress?: (partial: SubAgentResult) => void,
519
+ heartbeatDetails?: () => SubagentDetails,
518
520
  isReadOnly?: boolean,
519
521
  ): Promise<SubAgentResult> {
520
522
  const agent = agents.find((a) => a.name === agentName);
@@ -576,23 +578,30 @@ export default function (pi: ExtensionAPI) {
576
578
  };
577
579
  }
578
580
 
579
- const result = await runSubAgent({
580
- cwd: safeCwd,
581
- systemPrompt: params.instructions
582
- ? `${agent.systemPrompt}\n\n## Task Contract\n${params.instructions.slice(0, MAX_INSTRUCTIONS_LENGTH)}`
583
- : agent.systemPrompt,
584
- task,
585
- tools,
586
- model: resolved.model,
587
- authStorage,
588
- modelRegistry,
589
- signal: parentSignal,
590
- timeoutMs: effectiveTimeoutMs,
591
- agentName,
592
- thinkingLevel: agent.thinking,
593
- onMessage: onProgress,
594
- });
595
- return result;
581
+ const stopHeartbeat = onUpdate ? startHeartbeat(() => onUpdate({
582
+ content: [{ type: "text", text: `Subagent ${agentName} is still running…` }],
583
+ details: heartbeatDetails?.() ?? makeDetails("single")([]),
584
+ })) : undefined;
585
+ try {
586
+ return await runSubAgent({
587
+ cwd: safeCwd,
588
+ systemPrompt: params.instructions
589
+ ? `${agent.systemPrompt}\n\n## Task Contract\n${params.instructions.slice(0, MAX_INSTRUCTIONS_LENGTH)}`
590
+ : agent.systemPrompt,
591
+ task,
592
+ tools,
593
+ model: resolved.model,
594
+ authStorage,
595
+ modelRegistry,
596
+ signal: parentSignal,
597
+ timeoutMs: effectiveTimeoutMs,
598
+ agentName,
599
+ thinkingLevel: agent.thinking,
600
+ onMessage: onProgress,
601
+ });
602
+ } finally {
603
+ stopHeartbeat?.();
604
+ }
596
605
  }
597
606
 
598
607
  // --- Chain mode ---
@@ -615,6 +624,7 @@ export default function (pi: ExtensionAPI) {
615
624
  step.agent, taskWithContext, step.cwd,
616
625
  signal, step.timeout ?? params.timeout,
617
626
  (partial) => threadStore.updateThread(thread.id, { result: partial }),
627
+ () => makeDetails("chain")(results),
618
628
  );
619
629
  threadStore.updateThread(thread.id, {
620
630
  status: isFailedResult(result) ? (result.stopReason === "aborted" ? "aborted" : "failed") : "completed",
@@ -767,7 +777,8 @@ export default function (pi: ExtensionAPI) {
767
777
  const result = await runOne(
768
778
  t.agent, t.task, t.cwd,
769
779
  parallelController.signal, t.timeout ?? params.timeout,
770
- (partial) => threadStore.updateThread(parallelThreads[index].id, { result: partial }),
780
+ (partial) => threadStore.updateThread(parallelThreads[index].id, { result: partial }),
781
+ () => makeDetails("parallel")([...allResults]),
771
782
  );
772
783
  allResults[index] = result;
773
784
  threadStore.updateThread(parallelThreads[index].id, {
@@ -823,6 +834,7 @@ export default function (pi: ExtensionAPI) {
823
834
  params.agent, params.task, params.cwd,
824
835
  signal, params.timeout,
825
836
  (partial) => threadStore.updateThread(thread.id, { result: partial }),
837
+ () => makeDetails("single")([]),
826
838
  );
827
839
  threadStore.updateThread(thread.id, {
828
840
  status: isFailedResult(result) ? (result.stopReason === "aborted" ? "aborted" : "failed") : "completed",
@@ -63,6 +63,12 @@ export interface SubAgentResult {
63
63
  // Public API
64
64
  // ---------------------------------------------------------------------------
65
65
 
66
+ export function startHeartbeat(onHeartbeat: () => void, intervalMs = 30_000): () => void {
67
+ const timer = setInterval(onHeartbeat, intervalMs);
68
+ timer.unref?.();
69
+ return () => clearInterval(timer);
70
+ }
71
+
66
72
  export async function runSubAgent(options: {
67
73
  cwd: string;
68
74
  systemPrompt: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-subagent",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "In-process subagents for Pi with isolated SDK sessions, parallel and chained delegation, and inspectable threads.",
5
5
  "type": "module",
6
6
  "license": "MIT",