@arhen/pi-core-subagent 1.1.14 → 1.1.15
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 +29 -2
- package/src/peek.ts +27 -4
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.15",
|
|
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
|
@@ -1027,6 +1027,22 @@ class SubagentManager {
|
|
|
1027
1027
|
return { run: cloneRun(run), background: true };
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
|
+
/** Abort ONE task; siblings keep running. Returns false when unknown or already finished. */
|
|
1031
|
+
cancelTask(runId: string, taskId: string, ctx?: ExtensionContext): boolean {
|
|
1032
|
+
const run = this.runs.get(runId);
|
|
1033
|
+
const task = run?.tasks.find((t) => t.id === taskId);
|
|
1034
|
+
if (!run || !task || TERMINAL.includes(task.status)) return false;
|
|
1035
|
+
// Mark first: runChild's catch reads task.status to classify the outcome as aborted.
|
|
1036
|
+
task.status = "aborted";
|
|
1037
|
+
task.error = task.error || "Canceled from peek";
|
|
1038
|
+
task.endedAt = Date.now();
|
|
1039
|
+
this.liveChildren.get(`${runId}:${taskId}`)?.abort();
|
|
1040
|
+
this.mailboxes.close(`${runId}:${taskId}`);
|
|
1041
|
+
if (ctx) this.flushWidget(run, ctx);
|
|
1042
|
+
this.emit("subagent:task-aborted", { runId, taskId });
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1030
1046
|
cancelRun(runId: string): { aborted: number } {
|
|
1031
1047
|
const run = this.runs.get(runId);
|
|
1032
1048
|
if (!run) return { aborted: 0 };
|
|
@@ -1178,13 +1194,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
1178
1194
|
manager
|
|
1179
1195
|
.listRuns()
|
|
1180
1196
|
.flatMap((run) => run.tasks)
|
|
1181
|
-
.map((task) => ({
|
|
1197
|
+
.map((task) => ({
|
|
1198
|
+
runId: task.runId,
|
|
1199
|
+
taskId: task.id,
|
|
1200
|
+
agent: task.agent,
|
|
1201
|
+
status: task.status,
|
|
1202
|
+
running: !TERMINAL.includes(task.status),
|
|
1203
|
+
sessionFile: task.sessionFile,
|
|
1204
|
+
line: taskLine(task),
|
|
1205
|
+
}));
|
|
1182
1206
|
if (getTasks().length === 0) {
|
|
1183
1207
|
ctx.ui.notify("No subagents in this session.", "info");
|
|
1184
1208
|
return;
|
|
1185
1209
|
}
|
|
1186
1210
|
await ctx.ui.custom<void>(
|
|
1187
|
-
(tui, theme, _keybindings, done) =>
|
|
1211
|
+
(tui, theme, _keybindings, done) =>
|
|
1212
|
+
createPeekPane(getTasks, theme, () => tui.requestRender(), () => done(undefined), (t) => {
|
|
1213
|
+
if (manager.cancelTask(t.runId, t.taskId, ctx)) ctx.ui.notify(`Aborted subagent ${t.agent}.`, "warning");
|
|
1214
|
+
}),
|
|
1188
1215
|
{ overlay: true, overlayOptions: { anchor: "center", width: "80%", maxHeight: "70%" } },
|
|
1189
1216
|
);
|
|
1190
1217
|
};
|
package/src/peek.ts
CHANGED
|
@@ -15,8 +15,11 @@ const TAIL_BYTES = 64 * 1024;
|
|
|
15
15
|
const POLL_MS = 700;
|
|
16
16
|
|
|
17
17
|
export interface PeekTask {
|
|
18
|
+
runId: string;
|
|
19
|
+
taskId: string;
|
|
18
20
|
agent: string;
|
|
19
21
|
status: string;
|
|
22
|
+
running: boolean;
|
|
20
23
|
sessionFile?: string;
|
|
21
24
|
line: string; // pre-rendered stats line from the caller
|
|
22
25
|
}
|
|
@@ -82,9 +85,16 @@ export interface PeekPane {
|
|
|
82
85
|
* Build the peek component. `getTasks` is polled live, so the pane keeps
|
|
83
86
|
* updating while agents run.
|
|
84
87
|
*/
|
|
85
|
-
export function createPeekPane(
|
|
88
|
+
export function createPeekPane(
|
|
89
|
+
getTasks: () => PeekTask[],
|
|
90
|
+
theme: Theme,
|
|
91
|
+
requestRender: () => void,
|
|
92
|
+
close: () => void,
|
|
93
|
+
abort: (task: PeekTask) => void,
|
|
94
|
+
): PeekPane {
|
|
86
95
|
let selected = 0;
|
|
87
96
|
let tailing = false;
|
|
97
|
+
let confirming = false;
|
|
88
98
|
const timer = setInterval(requestRender, POLL_MS);
|
|
89
99
|
|
|
90
100
|
const clamp = (n: number, len: number) => (len === 0 ? 0 : Math.max(0, Math.min(len - 1, n)));
|
|
@@ -95,7 +105,7 @@ export function createPeekPane(getTasks: () => PeekTask[], theme: Theme, request
|
|
|
95
105
|
selected = clamp(selected, tasks.length);
|
|
96
106
|
if (tasks.length === 0) return [theme.fg("dim", "No subagents in this session.")];
|
|
97
107
|
const task = tasks[selected]!;
|
|
98
|
-
const hint = tailing ? "esc back" : "↑↓ move · enter tail · esc close";
|
|
108
|
+
const hint = confirming ? theme.fg("error", `abort ${task.agent}? y / n`) : tailing ? "esc back · x abort" : "↑↓ move · enter tail · x abort · esc close";
|
|
99
109
|
const head = `${theme.fg("accent", theme.bold(tailing ? task.agent : "Subagents"))} ${theme.fg("dim", `(${selected + 1}/${tasks.length}) · ${hint}`)}`;
|
|
100
110
|
if (!tailing) {
|
|
101
111
|
return [head, ...tasks.map((t, i) => truncateToWidth(`${i === selected ? theme.fg("accent", "❯ ") : " "}${t.line}`, width, "…"))];
|
|
@@ -105,8 +115,21 @@ export function createPeekPane(getTasks: () => PeekTask[], theme: Theme, request
|
|
|
105
115
|
return [head, ...tailLines(task.sessionFile, 18).map((l) => truncateToWidth(` ${l}`, width, "…"))];
|
|
106
116
|
},
|
|
107
117
|
handleInput(data: string): void {
|
|
108
|
-
const
|
|
109
|
-
|
|
118
|
+
const tasks = getTasks();
|
|
119
|
+
const len = tasks.length;
|
|
120
|
+
if (confirming) {
|
|
121
|
+
// Abort is irreversible, so it always costs a second keystroke.
|
|
122
|
+
confirming = false;
|
|
123
|
+
if (data === "y" || data === "Y") {
|
|
124
|
+
const task = tasks[selected];
|
|
125
|
+
if (task) abort(task);
|
|
126
|
+
}
|
|
127
|
+
requestRender();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (data === "x" || data === "X") {
|
|
131
|
+
if (tasks[selected]?.running) confirming = true;
|
|
132
|
+
} else if (matchesKey(data, Key.escape)) {
|
|
110
133
|
if (tailing) tailing = false;
|
|
111
134
|
else close();
|
|
112
135
|
} else if (matchesKey(data, Key.enter) || matchesKey(data, Key.right)) {
|