@arhen/pi-core-subagent 1.3.16 → 1.3.18
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/format.ts +12 -1
- package/src/manager.ts +28 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arhen/pi-core-subagent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
|
|
6
6
|
"license": "MIT",
|
package/src/format.ts
CHANGED
|
@@ -90,7 +90,10 @@ export function themedTaskLine(task: TaskSnapshot, theme: Theme, activity = ""):
|
|
|
90
90
|
if (TERMINAL.includes(task.status)) {
|
|
91
91
|
return theme.fg("dim", `${statusIcon(task.status)} ${task.agent} · ${tail}`);
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
// Talking (mailbox/intercom tool in flight): pulse the name accent↔dim; normal otherwise.
|
|
94
|
+
pulsePhase += 1;
|
|
95
|
+
const name = isTalking(task) ? theme.fg(pulsePhase % 2 === 0 ? "accent" : "dim", `${task.agent}⇄`) : task.agent;
|
|
96
|
+
return `${statusIcon(task.status)} ${name} · ${gate}${activity}${colorNums(tail, theme)}`;
|
|
94
97
|
}
|
|
95
98
|
/**
|
|
96
99
|
* Human-readable activity line: "Read src/index.ts", "Grep wrapSingleLine".
|
|
@@ -127,6 +130,14 @@ export function activitySnippet(text: string): string {
|
|
|
127
130
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
128
131
|
return flat.length > 90 ? `${flat.slice(0, 90)}…` : flat;
|
|
129
132
|
}
|
|
133
|
+
|
|
134
|
+
/** Mailbox/intercom tools — while one is the task's last activity, the agent is "talking". */
|
|
135
|
+
export const TALK_TOOLS = ["poll_agent_messages", "send_agent_message", "ask_parent", "notify_parent"];
|
|
136
|
+
export function isTalking(task: TaskSnapshot): boolean {
|
|
137
|
+
const a = task.lastActivity?.toLowerCase() ?? "";
|
|
138
|
+
return TALK_TOOLS.some((t) => a.startsWith(t));
|
|
139
|
+
}
|
|
140
|
+
let pulsePhase = 0; // flips per render; talking agents alternate between the two name styles
|
|
130
141
|
/** Static compact lines (tool-result stream, subagent_status, /subagents). */
|
|
131
142
|
export function compactLines(run: RunSnapshot): string[] {
|
|
132
143
|
const lines: string[] = [];
|
package/src/manager.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
activitySnippet,
|
|
22
22
|
describeCall,
|
|
23
23
|
getFirstText,
|
|
24
|
+
isTalking,
|
|
24
25
|
makeNotice,
|
|
25
26
|
makeTaskNotice,
|
|
26
27
|
SubagentsWidget,
|
|
@@ -224,18 +225,20 @@ export class SubagentManager {
|
|
|
224
225
|
/** Flip the background-by-default flag; persists to the agent dir. Returns the new value. */
|
|
225
226
|
setAutoBg(on: boolean): boolean {
|
|
226
227
|
this.autoBg = on;
|
|
227
|
-
void writeFile(
|
|
228
|
-
()
|
|
229
|
-
|
|
228
|
+
void writeFile(
|
|
229
|
+
join(getAgentDir(), "subagents-config.json"),
|
|
230
|
+
JSON.stringify({ autoBg: on, autoLimit: this.autoLimit }, null, 2),
|
|
231
|
+
).catch(() => {});
|
|
230
232
|
return on;
|
|
231
233
|
}
|
|
232
234
|
|
|
233
235
|
/** Flip the auto-limit flag; persists to the agent dir. Returns the new value. */
|
|
234
236
|
setAutoLimit(on: boolean): boolean {
|
|
235
237
|
this.autoLimit = on;
|
|
236
|
-
void writeFile(
|
|
237
|
-
()
|
|
238
|
-
|
|
238
|
+
void writeFile(
|
|
239
|
+
join(getAgentDir(), "subagents-config.json"),
|
|
240
|
+
JSON.stringify({ autoBg: this.autoBg, autoLimit: on }, null, 2),
|
|
241
|
+
).catch(() => {});
|
|
239
242
|
return on;
|
|
240
243
|
}
|
|
241
244
|
|
|
@@ -286,6 +289,10 @@ export class SubagentManager {
|
|
|
286
289
|
this.runControllers.clear();
|
|
287
290
|
this.mailboxes = createMailbox();
|
|
288
291
|
this.widgetTui = null; // force re-registration on the next session
|
|
292
|
+
if (this.pulseTimer) {
|
|
293
|
+
clearTimeout(this.pulseTimer);
|
|
294
|
+
this.pulseTimer = null;
|
|
295
|
+
}
|
|
289
296
|
for (const t of this.widgetTimers.values()) clearTimeout(t);
|
|
290
297
|
this.widgetTimers.clear();
|
|
291
298
|
this.widgetRuns = [];
|
|
@@ -404,9 +411,23 @@ export class SubagentManager {
|
|
|
404
411
|
this.ensureWidget(ctx);
|
|
405
412
|
this.widgetTui?.requestRender();
|
|
406
413
|
}
|
|
414
|
+
this.maybePulse(ctx);
|
|
407
415
|
}, WIDGET_THROTTLE_MS),
|
|
408
416
|
);
|
|
409
417
|
}
|
|
418
|
+
|
|
419
|
+
/** While any live task's last activity is a talk tool, keep re-rendering so its name pulses. */
|
|
420
|
+
private pulseTimer: ReturnType<typeof setTimeout> | null = null;
|
|
421
|
+
private maybePulse(ctx?: ExtensionContext): void {
|
|
422
|
+
if (this.pulseTimer || !this.widgetTui) return;
|
|
423
|
+
const talking = this.widgetRuns.some((r) => r.tasks.some(isTalking));
|
|
424
|
+
if (!talking) return; // last tick stops the loop: talking→normal resumes instantly
|
|
425
|
+
this.pulseTimer = setTimeout(() => {
|
|
426
|
+
this.pulseTimer = null;
|
|
427
|
+
this.widgetTui?.requestRender();
|
|
428
|
+
this.maybePulse(ctx);
|
|
429
|
+
}, 700);
|
|
430
|
+
}
|
|
410
431
|
private flushWidget(run: RunSnapshot | undefined, ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
|
|
411
432
|
if (run) {
|
|
412
433
|
const t = this.widgetTimers.get(run.id);
|
|
@@ -420,6 +441,7 @@ export class SubagentManager {
|
|
|
420
441
|
this.ensureWidget(ctx);
|
|
421
442
|
this.widgetTui?.requestRender();
|
|
422
443
|
}
|
|
444
|
+
this.maybePulse(ctx);
|
|
423
445
|
// Transcript gets one status line only — the live per-task view is the widget's job.
|
|
424
446
|
onUpdate?.({
|
|
425
447
|
content: [
|