@arhen/pi-core-subagent 1.3.17 → 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 +20 -0
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,
|
|
@@ -288,6 +289,10 @@ export class SubagentManager {
|
|
|
288
289
|
this.runControllers.clear();
|
|
289
290
|
this.mailboxes = createMailbox();
|
|
290
291
|
this.widgetTui = null; // force re-registration on the next session
|
|
292
|
+
if (this.pulseTimer) {
|
|
293
|
+
clearTimeout(this.pulseTimer);
|
|
294
|
+
this.pulseTimer = null;
|
|
295
|
+
}
|
|
291
296
|
for (const t of this.widgetTimers.values()) clearTimeout(t);
|
|
292
297
|
this.widgetTimers.clear();
|
|
293
298
|
this.widgetRuns = [];
|
|
@@ -406,9 +411,23 @@ export class SubagentManager {
|
|
|
406
411
|
this.ensureWidget(ctx);
|
|
407
412
|
this.widgetTui?.requestRender();
|
|
408
413
|
}
|
|
414
|
+
this.maybePulse(ctx);
|
|
409
415
|
}, WIDGET_THROTTLE_MS),
|
|
410
416
|
);
|
|
411
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
|
+
}
|
|
412
431
|
private flushWidget(run: RunSnapshot | undefined, ctx?: ExtensionContext, onUpdate?: (partial: any) => void): void {
|
|
413
432
|
if (run) {
|
|
414
433
|
const t = this.widgetTimers.get(run.id);
|
|
@@ -422,6 +441,7 @@ export class SubagentManager {
|
|
|
422
441
|
this.ensureWidget(ctx);
|
|
423
442
|
this.widgetTui?.requestRender();
|
|
424
443
|
}
|
|
444
|
+
this.maybePulse(ctx);
|
|
425
445
|
// Transcript gets one status line only — the live per-task view is the widget's job.
|
|
426
446
|
onUpdate?.({
|
|
427
447
|
content: [
|