@arhen/pi-core-subagent 1.1.20 → 1.1.22
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 +1 -1
- package/src/index.ts +3 -2
- package/src/peek.ts +54 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arhen/pi-core-subagent",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "pi extension: fast in-process subagents with single/parallel/chain, background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -232,7 +232,8 @@ function taskLine(task: TaskSnapshot): string {
|
|
|
232
232
|
* inside the ANSI escape codes themselves ("38;2;139;136;122m16 tools").
|
|
233
233
|
*/
|
|
234
234
|
export function colorNums(text: string, theme: Theme): string {
|
|
235
|
-
|
|
235
|
+
// A value keeps its unit: "460.6k" and "2m30s" each color as one token, not digit-by-digit.
|
|
236
|
+
return text.replace(/((?:\d+(?:\.\d+)?[a-zA-Z]*)+)|([^\d]+)/g, (_m, num?: string, rest?: string) => (num ? theme.fg("syntaxNumber", num) : theme.fg("muted", rest ?? "")));
|
|
236
237
|
}
|
|
237
238
|
/**
|
|
238
239
|
* Themed one-liner. Finished tasks dim entirely (stats included); live tasks
|
|
@@ -1238,7 +1239,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1238
1239
|
createPeekPane(getTasks, theme, () => tui.requestRender(), () => done(undefined), (t) => {
|
|
1239
1240
|
if (manager.cancelTask(t.runId, t.taskId, ctx)) ctx.ui.notify(`Aborted subagent ${t.agent}.`, "warning");
|
|
1240
1241
|
}),
|
|
1241
|
-
{ overlay: true, overlayOptions: { anchor: "center", width: "
|
|
1242
|
+
{ overlay: true, overlayOptions: { anchor: "center", width: "70%", minWidth: 60, maxHeight: "70%", margin: 2 } },
|
|
1242
1243
|
);
|
|
1243
1244
|
};
|
|
1244
1245
|
pi.registerCommand("peek", { description: "Peek at running subagents (shift+↑↓ or j/k move, enter tails)", handler: (_args, ctx) => openPeek(ctx) });
|
package/src/peek.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Peek pane — quick, read-only look at what subagents are doing.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* session file, esc goes back / closes. Never touches run state:
|
|
6
|
-
* no
|
|
4
|
+
* shift+↑/↓ (or j/k) move between agents, enter opens a live tail of that
|
|
5
|
+
* child's session file, esc goes back / closes. Never touches run state:
|
|
6
|
+
* no abort except the explicit x + y confirmation.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { closeSync, openSync, readSync, statSync } from "node:fs";
|
|
10
|
-
import { Key, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
10
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
11
11
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
|
|
13
13
|
/** Tail window: last 64KB of the child session file is plenty for a peek. */
|
|
14
14
|
const TAIL_BYTES = 64 * 1024;
|
|
15
15
|
const POLL_MS = 700;
|
|
16
|
+
const TAIL_ROWS = 18;
|
|
16
17
|
|
|
17
18
|
export interface PeekTask {
|
|
18
19
|
runId: string;
|
|
@@ -37,8 +38,13 @@ function readTail(path: string): string {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
function clip(text: string, max: number): string {
|
|
42
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
43
|
+
return flat.length > max ? `${flat.slice(0, max)}…` : flat;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One session-file line → one display line: [gutter, text]. Irrelevant → null. */
|
|
47
|
+
function eventLine(raw: string): [string, string] | null {
|
|
42
48
|
let entry: any;
|
|
43
49
|
try {
|
|
44
50
|
entry = JSON.parse(raw);
|
|
@@ -47,25 +53,26 @@ function eventLine(raw: string): string | null {
|
|
|
47
53
|
}
|
|
48
54
|
const msg = entry?.message;
|
|
49
55
|
if (!msg) return null;
|
|
50
|
-
const
|
|
56
|
+
const out: [string, string][] = [];
|
|
51
57
|
for (const block of msg.content ?? []) {
|
|
52
|
-
if (block.type === "
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
if (block.type === "toolCall") {
|
|
59
|
+
const arg = Object.values(block.arguments ?? {}).find((v) => typeof v === "string" && v.trim() !== "") as string | undefined;
|
|
60
|
+
out.push(["→", `${block.name}${arg ? ` ${clip(arg, 120)}` : ""}`]);
|
|
61
|
+
} else if (block.type === "text" && block.text?.trim()) {
|
|
62
|
+
out.push([msg.role === "toolResult" ? "←" : "·", clip(block.text, 160)]);
|
|
63
|
+
}
|
|
55
64
|
}
|
|
56
|
-
|
|
57
|
-
const who = msg.role === "assistant" ? "" : msg.role === "toolResult" ? " ← " : `${msg.role}: `;
|
|
58
|
-
return `${who}${parts.join(" ").replace(/\s+/g, " ")}`;
|
|
65
|
+
return out[0] ?? null;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
function tailLines(path: string, max: number): string[] {
|
|
68
|
+
function tailLines(path: string, max: number): [string, string][] {
|
|
62
69
|
let text: string;
|
|
63
70
|
try {
|
|
64
71
|
text = readTail(path);
|
|
65
72
|
} catch {
|
|
66
|
-
return ["(session file not readable yet)"];
|
|
73
|
+
return [["·", "(session file not readable yet)"]];
|
|
67
74
|
}
|
|
68
|
-
const lines: string[] = [];
|
|
75
|
+
const lines: [string, string][] = [];
|
|
69
76
|
// First line of a mid-file read is usually a fragment — drop it.
|
|
70
77
|
for (const raw of text.split("\n").slice(1)) {
|
|
71
78
|
const line = eventLine(raw);
|
|
@@ -99,20 +106,44 @@ export function createPeekPane(
|
|
|
99
106
|
|
|
100
107
|
const clamp = (n: number, len: number) => (len === 0 ? 0 : Math.max(0, Math.min(len - 1, n)));
|
|
101
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Every row is padded to the full pane width and background-filled, so the
|
|
111
|
+
* transcript underneath never shows through the overlay.
|
|
112
|
+
*/
|
|
113
|
+
const row = (content: string, width: number): string => {
|
|
114
|
+
const inner = width - 4; // 2 cols padding each side
|
|
115
|
+
const text = truncateToWidth(content, Math.max(0, inner), "…");
|
|
116
|
+
const pad = Math.max(0, inner - visibleWidth(text));
|
|
117
|
+
return theme.bg("selectedBg", ` ${text}${" ".repeat(pad)} `);
|
|
118
|
+
};
|
|
119
|
+
|
|
102
120
|
return {
|
|
103
121
|
render(width: number): string[] {
|
|
104
122
|
const tasks = getTasks();
|
|
105
123
|
selected = clamp(selected, tasks.length);
|
|
106
|
-
if (tasks.length === 0) return [theme.fg("dim", "No subagents in this session.")];
|
|
124
|
+
if (tasks.length === 0) return [row(theme.fg("dim", "No subagents in this session."), width)];
|
|
107
125
|
const task = tasks[selected]!;
|
|
108
|
-
const hint = confirming
|
|
109
|
-
|
|
126
|
+
const hint = confirming
|
|
127
|
+
? theme.fg("error", `abort ${task.agent}? y / n`)
|
|
128
|
+
: theme.fg("dim", tailing ? "esc back · x abort" : "shift+↑↓ / jk move · enter tail · x abort · esc close");
|
|
129
|
+
const title = `${theme.fg("accent", theme.bold(tailing ? task.agent : "Subagents"))} ${theme.fg("muted", `${selected + 1}/${tasks.length}`)}`;
|
|
130
|
+
const lines = [row("", width), row(title, width), row(hint, width), row("", width)];
|
|
131
|
+
|
|
110
132
|
if (!tailing) {
|
|
111
|
-
|
|
133
|
+
for (const [i, t] of tasks.entries()) {
|
|
134
|
+
const marker = i === selected ? theme.fg("accent", "❯ ") : " ";
|
|
135
|
+
lines.push(row(`${marker}${t.line}`, width));
|
|
136
|
+
}
|
|
137
|
+
} else if (!task.sessionFile) {
|
|
138
|
+
lines.push(row(theme.fg("dim", "(no session file — agent has not started yet)"), width));
|
|
139
|
+
} else {
|
|
140
|
+
// ponytail: re-reads the tail each render (700ms poll). A watcher only pays off for files far bigger than a child session.
|
|
141
|
+
for (const [gutter, text] of tailLines(task.sessionFile, TAIL_ROWS)) {
|
|
142
|
+
lines.push(row(`${theme.fg("dim", gutter)} ${gutter === "←" ? theme.fg("dim", text) : text}`, width));
|
|
143
|
+
}
|
|
112
144
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return [head, ...tailLines(task.sessionFile, 18).map((l) => truncateToWidth(` ${l}`, width, "…"))];
|
|
145
|
+
lines.push(row("", width));
|
|
146
|
+
return lines;
|
|
116
147
|
},
|
|
117
148
|
handleInput(data: string): void {
|
|
118
149
|
const tasks = getTasks();
|