@ghyper9023/pi-dev-workflow 0.5.0 → 0.5.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.
@@ -34,7 +34,11 @@ const DEFAULT_TIMEOUT_MS = 180_000;
34
34
  const PROGRESS_INTERVAL_MS = 1_500;
35
35
 
36
36
  /** Hard limit on accumulated output per subagent (prevents OOM on runaway). */
37
- const MAX_BUFFER_BYTES = 500_000;
37
+ // Keep up to 10MB of output — large enough to hold complete session event streams
38
+ // including cargo test compilation output (commonly 100-400 KB).
39
+ // We preserve the END of the buffer (last N bytes) so extractFinalOutput can
40
+ // find the final AI response even when the total output is large.
41
+ const MAX_BUFFER_BYTES = 10_000_000;
38
42
 
39
43
  /**
40
44
  * Locations to auto-load APPEND_SYSTEM.md / append.system.md from (checked in order).
@@ -477,13 +481,14 @@ export async function spawnSubagent(
477
481
 
478
482
  // ── Stream stdout data + immediate progress callback ──
479
483
  // Fix #3: Flush progress on every data chunk
484
+ // Fix #4: Preserve the END of the buffer (last N bytes) instead of the beginning,
485
+ // because extractFinalOutput needs the final AI response which is at the end.
480
486
  proc.stdout.on("data", (data: Buffer) => {
481
487
  const chunk = data.toString();
482
- if (stdout.length < MAX_BUFFER_BYTES) {
483
- stdout += chunk;
484
- if (stdout.length > MAX_BUFFER_BYTES) {
485
- stdout = stdout.slice(0, MAX_BUFFER_BYTES);
486
- }
488
+ stdout += chunk;
489
+ if (stdout.length > MAX_BUFFER_BYTES) {
490
+ // Keep only the last MAX_BUFFER_BYTES bytes
491
+ stdout = stdout.slice(stdout.length - MAX_BUFFER_BYTES);
487
492
  }
488
493
  // Immediate progress on new data
489
494
  if (!settled && onProgress) {
@@ -497,11 +502,9 @@ export async function spawnSubagent(
497
502
 
498
503
  proc.stderr.on("data", (data: Buffer) => {
499
504
  const chunk = data.toString();
500
- if (stderr.length < MAX_BUFFER_BYTES) {
501
- stderr += chunk;
502
- if (stderr.length > MAX_BUFFER_BYTES) {
503
- stderr = stderr.slice(0, MAX_BUFFER_BYTES);
504
- }
505
+ stderr += chunk;
506
+ if (stderr.length > MAX_BUFFER_BYTES) {
507
+ stderr = stderr.slice(stderr.length - MAX_BUFFER_BYTES);
505
508
  }
506
509
  });
507
510
 
@@ -573,11 +576,13 @@ export function extractFinalOutput(jsonOutput: string): string {
573
576
  textEndSeen = true;
574
577
  }
575
578
 
576
- // Format 2: Anthropic-style message events
577
- // message_stop / message_end with content array
579
+ // Format 2: pi agent event — message_end / message_stop / message_complete
580
+ // Only process assistant messages; toolResult and user messages contain
581
+ // raw tool output (e.g. ls -la) that would pollute the extracted summary.
578
582
  if ((event.type === "message_stop" || event.type === "message_end" ||
579
583
  event.type === "message_complete") &&
580
- event.message?.content) {
584
+ event.message?.content &&
585
+ event.message?.role === "assistant") {
581
586
  const parts = Array.isArray(event.message.content)
582
587
  ? event.message.content
583
588
  : [event.message.content];
@@ -608,6 +613,46 @@ export function extractFinalOutput(jsonOutput: string): string {
608
613
  if (event.type === "complete" && event.text) {
609
614
  result = event.text;
610
615
  }
616
+
617
+ // Format 5b: turn_end — extract text from the turn's assistant message.
618
+ // This provides an additional extraction point when message_end is lost
619
+ // due to buffer truncation (large tool results like cargo test output
620
+ // can push the final assistant message beyond the buffer limit).
621
+ if (event.type === "turn_end" &&
622
+ event.message?.content &&
623
+ event.message?.role === "assistant") {
624
+ const parts = Array.isArray(event.message.content)
625
+ ? event.message.content
626
+ : [event.message.content];
627
+ for (const part of parts) {
628
+ if (typeof part === "string") result = part;
629
+ else if (part?.type === "text") result = part.text;
630
+ }
631
+ }
632
+
633
+ // Format 6: agent_end — iterate messages backwards to find the last
634
+ // assistant message with text content. This catches edge cases where
635
+ // the final assistant response has no type:text block (e.g. only
636
+ // thinking/toolCall), which left result polluted by a prior tool result.
637
+ if (event.type === "agent_end" && Array.isArray(event.messages)) {
638
+ for (let i = event.messages.length - 1; i >= 0; i--) {
639
+ const msg = event.messages[i];
640
+ if (msg?.role === "assistant" && Array.isArray(msg.content)) {
641
+ for (let j = msg.content.length - 1; j >= 0; j--) {
642
+ const block = msg.content[j];
643
+ if (typeof block === "string") {
644
+ result = block;
645
+ break;
646
+ }
647
+ if (block?.type === "text" && block.text) {
648
+ result = block.text;
649
+ break;
650
+ }
651
+ }
652
+ if (result) break;
653
+ }
654
+ }
655
+ }
611
656
  } catch {
612
657
  // If a line isn't JSON, it might be raw text output - collect it
613
658
  if (!result) {
@@ -1378,6 +1378,14 @@ async function executeSingleStep(
1378
1378
  } else {
1379
1379
  updateChainContext(chainKey, `${agentName} 已完成执行,未检测到文件变更。`);
1380
1380
  }
1381
+
1382
+ // ── Capture AI work summary as supplementary chain context ──
1383
+ const workSummary = extractFinalOutput(result.output);
1384
+ if (workSummary) {
1385
+ updateChainContext(`${agentName} 工作总结`,
1386
+ `${agentName} 已完成工作,以下是其工作总结:\n\n${workSummary}`
1387
+ );
1388
+ }
1381
1389
  }
1382
1390
 
1383
1391
  // ═══════════════════════════════════════════════════════════════
@@ -1471,6 +1479,14 @@ async function executeLoopGroup(
1471
1479
  );
1472
1480
  }
1473
1481
 
1482
+ // ── Capture loop agent work summary as supplementary chain context ──
1483
+ const loopFinalText = extractFinalOutput(agentResult.output);
1484
+ if (loopFinalText) {
1485
+ updateChainContext(`${step.loopAgentName} 工作总结`,
1486
+ `${step.loopAgentName} 已完成工作,以下是其工作总结:\n\n${loopFinalText}`
1487
+ );
1488
+ }
1489
+
1474
1490
  if (isTimeoutResult(agentResult)) {
1475
1491
  if (mode === "full-auto") {
1476
1492
  contextPrompt = `[TIMEOUT_WARNING] 上一个 ${step.loopAgentName} 执行超时。\n\n${buildReviewTask(prompt, planFileRelPath, _workflowCwd, _workflowId, buildChainContext())}`;
@@ -1526,6 +1542,14 @@ async function executeLoopGroup(
1526
1542
  );
1527
1543
  }
1528
1544
 
1545
+ // ── Capture reviewer work summary as supplementary chain context ──
1546
+ // Reuse already-parsed extractedOutput to avoid double parsing
1547
+ if (extractedOutput) {
1548
+ updateChainContext(`审查工作总结`,
1549
+ `审查者已完成审查,以下是其审查总结:\n\n${extractedOutput}`
1550
+ );
1551
+ }
1552
+
1529
1553
  // ── Decide whether to loop ──
1530
1554
  if (reviewSummary?.maxSeverity === "critical" && loopCount < maxLoops) {
1531
1555
  const reviewReportHint = `\n完整审查报告在 .pi-dev-output/pi-review/md/(grep UUID "${_workflowId}" 查找)。`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghyper9023/pi-dev-workflow",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "keywords": [
5
5
  "pi-package"
6
6
  ],