@d3ara1n/pi-subagent 1.6.0 → 1.7.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d3ara1n/pi-subagent",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
5
  "description": "Role-based subagent orchestration for pi — delegates tasks to specialized pi child processes with configurable model roles",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -162,6 +162,10 @@ export default function subagentExtension(pi: ExtensionAPI) {
162
162
  "- Parallel: multiple subagent_delegate calls at the same time run concurrently — foreground and background alike, no special flag.",
163
163
  "- Background (background: true): non-blocking — returns an id immediately so you can do your own work while the run executes.",
164
164
  "",
165
+ "RUN HISTORY:",
166
+ "",
167
+ "- Every spawned run is audited to ~/.pi/subagent/history/{sessionId}/{toolCallId}.json (toolCallId = the delegate call's id). The file holds the FULL raw output even when you received a compressed/truncated version, plus the task, activity log, and usage.",
168
+ "",
165
169
  "BACKGROUND DELEGATION:",
166
170
  "",
167
171
  "- Use it only when you have your own work this turn (including an ongoing discussion with the user) while the run executes; otherwise let the call block and return the result directly.",
package/src/utils.test.ts CHANGED
@@ -41,6 +41,7 @@ import {
41
41
  createThrottler,
42
42
  terminalResultLine,
43
43
  buildDisplayItems,
44
+ formatToolCall,
44
45
  } from "./utils.ts";
45
46
  import type { ActivityEntry, SubagentResult, SubagentRole } from "./types.ts";
46
47
 
@@ -427,6 +428,28 @@ describe("terminalResultLine", () => {
427
428
  test("finishedText replaces the success chain (wait's status-only line)", () => {
428
429
  assert.equal(terminalResultLine(baseResult(), id, "finished"), "\u2713 finished");
429
430
  });
431
+ test("error message newlines are flattened to one line", () => {
432
+ assert.equal(
433
+ terminalResultLine(baseResult({ exitCode: 1, errorMessage: "boom\n at frame 2\nat frame 3" }), id),
434
+ "\u2717 boom at frame 2 at frame 3",
435
+ );
436
+ });
437
+ });
438
+
439
+ // ── formatToolCall: single-line guarantee for TUI rows ──
440
+ describe("formatToolCall newline sanitization", () => {
441
+ const id = (_color: string, text: string) => text;
442
+
443
+ test("multi-line bash command renders as one line", () => {
444
+ const out = formatToolCall("bash", { command: "echo a\necho b\n echo c" }, id);
445
+ assert.ok(!out.includes("\n"));
446
+ assert.equal(out, "$ echo a echo b echo c");
447
+ });
448
+ test("default branch preview flattens embedded newlines", () => {
449
+ const out = formatToolCall("fetch", { url: "https://x.test/a\n/b" }, id);
450
+ assert.ok(!out.includes("\n"));
451
+ assert.ok(out.includes("https://x.test/a /b"));
452
+ });
430
453
  });
431
454
 
432
455
  // ── createThrottler: burst coalescing for onUpdate ──
package/src/utils.ts CHANGED
@@ -136,10 +136,30 @@ export function shortenPath(p: string): string {
136
136
  return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
137
137
  }
138
138
 
139
+ /** Flatten embedded newlines (multi-line bash commands, patterns, error
140
+ * messages) into single spaces so a row never spans multiple terminal lines.
141
+ * Every caller renders the result as ONE TUI row — inline rows join it with
142
+ * "\n" separators and the :view overlay wraps each row in a border frame. */
143
+ function oneLine(s: string): string {
144
+ return s.replace(/\s*\r?\n\s*/g, " ");
145
+ }
146
+
147
+ /**
148
+ * One-line tool-call row for TUI display. Newline sanitization happens here
149
+ * at the single choke point so every tool branch is covered.
150
+ */
139
151
  export function formatToolCall(
140
152
  toolName: string,
141
153
  args: Record<string, unknown>,
142
154
  fg: (color: string, text: string) => string,
155
+ ): string {
156
+ return oneLine(renderToolCall(toolName, args, fg));
157
+ }
158
+
159
+ function renderToolCall(
160
+ toolName: string,
161
+ args: Record<string, unknown>,
162
+ fg: (color: string, text: string) => string,
143
163
  ): string {
144
164
  switch (toolName) {
145
165
  case "subagent_delegate": {
@@ -306,7 +326,7 @@ function failureResultText(r: {
306
326
  const isCancelled = r.stopReason === "cancelled";
307
327
  return {
308
328
  content:
309
- r.errorMessage ||
329
+ oneLine(r.errorMessage || "") ||
310
330
  (isTimeout ? "Timed out" : isBudget ? "Budget exceeded" : isCancelled ? "Cancelled" : "failed"),
311
331
  // Timeout/budget/cancel are intentional stops with partial output —
312
332
  // warning, not the error red reserved for real failures.