@trim21/personal-pi-extensions 0.1.587 → 0.1.589

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/spawn-agent.ts +41 -75
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.587",
3
+ "version": "0.1.589",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -22,7 +22,10 @@
22
22
  * so one log entry is always exactly one rendered line. The final line is
23
23
  * always the subagent name as a code span (`` `scout` ``), followed by the
24
24
  * live usage stats when there are any; it rides outside the rolling window so
25
- * it is never trimmed.
25
+ * it is never trimmed. While the model is thinking, a transient
26
+ * `thinking ( N chars )` line sits between the log and the footer showing the
27
+ * live character count of the streamed thinking; it disappears when the
28
+ * thinking block ends.
26
29
  *
27
30
  * Security default: without an explicit `tools:` in the frontmatter, the
28
31
  * subagent only gets read-only tools (read/grep/find/ls) — no bash/write/edit.
@@ -52,7 +55,6 @@ import {
52
55
  SessionManager,
53
56
  SettingsManager,
54
57
  truncateTail,
55
- truncateToVisualLines,
56
58
  } from "@earendil-works/pi-coding-agent";
57
59
  import { Type } from "typebox";
58
60
 
@@ -148,7 +150,7 @@ interface UsageStats {
148
150
  turns: number;
149
151
  }
150
152
 
151
- /** 子 agent 的一次运行结果:runAgent 的返回值,也是 details.result 的内容。 */
153
+ /** 子 agent 的一次运行结果:runAgent 的返回值。 */
152
154
  interface SubagentResult {
153
155
  agent: string;
154
156
  task: string;
@@ -162,8 +164,6 @@ interface SubagentResult {
162
164
  }
163
165
 
164
166
  interface SubagentDetails {
165
- /** 子 agent 运行结果(除 pendant 外的字段都收在这里)。 */
166
- result: SubagentResult;
167
167
  /** 折叠 markdown 面板:父 agent 的 prompt 与父 agent 看到的子 agent 结果。 */
168
168
  pendant?: ToolPendant;
169
169
  }
@@ -421,6 +421,9 @@ export async function runAgent(
421
421
  // 不同名按调用顺序罗列;任何非工具行都会打断合并。
422
422
  let toolLineSegments: string[] = [];
423
423
  let toolLine: { name: string; count: number } | undefined;
424
+ // 思考中状态:thinking_start 打开、thinking_delta 累计字符数、thinking_end
425
+ // 关闭;非 undefined 时 emitUpdate 在 footer 上方插一行瞬态 thinking 状态。
426
+ let thinkingChars: number | undefined;
424
427
 
425
428
  const pushLogLine = (line: string) => {
426
429
  logLines.push(line);
@@ -463,9 +466,12 @@ export async function runAgent(
463
466
  const usageLine = formatUsageStats(result.usage, result.model);
464
467
  const name = sanitizeProgressLine(result.agent);
465
468
  const footer = usageLine ? `\`${name}\` ${usageLine}` : `\`${name}\``;
469
+ const lines = [...logLines];
470
+ if (thinkingChars !== undefined) lines.push(`thinking ( ${thinkingChars} chars )`);
471
+ lines.push(footer);
466
472
  onUpdate?.({
467
- content: [{ type: "text", text: [...logLines, footer].join("\n") }],
468
- details: { result: { ...result } },
473
+ content: [{ type: "text", text: lines.join("\n") }],
474
+ details: {},
469
475
  });
470
476
  };
471
477
 
@@ -473,11 +479,33 @@ export async function runAgent(
473
479
  switch (event.type) {
474
480
  case "message_update": {
475
481
  // A completed text block (text_end carries the full content) becomes a
476
- // `text:` log line. Deltas/thinking are intentionally not logged.
482
+ // `text:` log line. Deltas are intentionally not logged; thinking
483
+ // deltas feed the transient `thinking ( N chars )` status line instead.
477
484
  const delta = event.assistantMessageEvent;
478
- if (delta.type === "text_end") {
479
- pushLogLine(`text: ${foldProgressLine(sanitizeProgressLine(delta.content))}`);
480
- emitUpdate();
485
+ switch (delta.type) {
486
+ case "text_end": {
487
+ pushLogLine(`text: ${foldProgressLine(sanitizeProgressLine(delta.content))}`);
488
+ emitUpdate();
489
+ break;
490
+ }
491
+ case "thinking_start": {
492
+ // 思考横跨轮次边界:打断工具行合并,下一批工具调用另起一行。
493
+ toolLine = undefined;
494
+ thinkingChars = 0;
495
+ emitUpdate();
496
+ break;
497
+ }
498
+ case "thinking_delta": {
499
+ thinkingChars = (thinkingChars ?? 0) + delta.delta.length;
500
+ emitUpdate();
501
+ break;
502
+ }
503
+ case "thinking_end": {
504
+ thinkingChars = undefined;
505
+ emitUpdate();
506
+ break;
507
+ }
508
+ // No default
481
509
  }
482
510
 
483
511
  break;
@@ -591,20 +619,7 @@ export default function spawnAgent(pi: ExtensionAPI) {
591
619
  text: `Unknown agent "${params.agent}". Available agents: ${formatAgentList(agents)}`,
592
620
  },
593
621
  ],
594
- details: {
595
- result: {
596
- agent: params.agent,
597
- task: params.task,
598
- exitCode: 1,
599
- messages: [],
600
- stderr: "",
601
- usage: {
602
- cost: 0,
603
- contextTokens: 0,
604
- turns: 0,
605
- },
606
- },
607
- },
622
+ details: {},
608
623
  isError: true,
609
624
  };
610
625
  }
@@ -627,7 +642,6 @@ export default function spawnAgent(pi: ExtensionAPI) {
627
642
  { type: "text", text: `Subagent "${result.agent}" failed (${reason}):\n${message}` },
628
643
  ],
629
644
  details: {
630
- result,
631
645
  pendant: {
632
646
  subtitle: result.agent,
633
647
  markdown: formatPendantMarkdown(params.task, message),
@@ -640,12 +654,11 @@ export default function spawnAgent(pi: ExtensionAPI) {
640
654
  const output = getFinalOutput(result.messages) || "(no output)";
641
655
  const truncation = truncateTail(output, { maxBytes: MAX_OUTPUT_BYTES });
642
656
  const text = truncation.truncated
643
- ? `${truncation.content}\n\n[Output truncated to ${formatTokens(truncation.content.length)} bytes. Full result preserved in tool details.]`
657
+ ? `${truncation.content}\n\n[Output truncated to ${formatTokens(truncation.content.length)} bytes.]`
644
658
  : output;
645
659
  return {
646
660
  content: [{ type: "text", text }],
647
661
  details: {
648
- result,
649
662
  pendant: {
650
663
  subtitle: result.agent,
651
664
  markdown: formatPendantMarkdown(params.task, text),
@@ -653,52 +666,5 @@ export default function spawnAgent(pi: ExtensionAPI) {
653
666
  },
654
667
  };
655
668
  },
656
-
657
- renderCall(args, theme) {
658
- const preview = args.task.length > 60 ? `${args.task.slice(0, 60)}...` : args.task;
659
- const line =
660
- theme.fg("toolTitle", theme.bold("spawn_agent ")) + theme.fg("accent", args.agent);
661
- const detail = ` ${theme.fg("dim", preview)}`;
662
- return {
663
- render: (width: number) =>
664
- truncateToVisualLines(`${line}\n${detail}`, 2, width).visualLines,
665
- invalidate: (): void => undefined,
666
- };
667
- },
668
-
669
- renderResult(result, { expanded }, theme) {
670
- const details = result.details.result;
671
- const isError =
672
- details.exitCode !== 0 ||
673
- details.stopReason === "error" ||
674
- details.stopReason === "aborted";
675
- const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
676
- const finalOutput = getFinalOutput(details.messages);
677
- const usageStr = formatUsageStats(details.usage, details.model);
678
-
679
- const lines: string[] = [];
680
- let header = `${icon} ${theme.fg("toolTitle", theme.bold(details.agent))}`;
681
- if (details.stopReason) header += ` ${theme.fg("error", `[${details.stopReason}]`)}`;
682
- lines.push(header);
683
- if (isError && details.errorMessage) {
684
- lines.push(theme.fg("error", `Error: ${details.errorMessage}`));
685
- }
686
- if (expanded) {
687
- lines.push("", theme.fg("muted", "─── Task ───"), theme.fg("dim", details.task));
688
- if (finalOutput) {
689
- lines.push("", theme.fg("muted", "─── Output ───"), finalOutput.trim());
690
- }
691
- } else if (finalOutput) {
692
- lines.push(theme.fg("toolOutput", finalOutput.split("\n").slice(0, 5).join("\n")));
693
- } else {
694
- lines.push(theme.fg("muted", "(no output)"));
695
- }
696
- if (usageStr) lines.push(theme.fg("dim", usageStr));
697
- return {
698
- render: (width: number) =>
699
- truncateToVisualLines(lines.join("\n"), Infinity, width).visualLines,
700
- invalidate: (): void => undefined,
701
- };
702
- },
703
669
  });
704
670
  }