@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.3.16",
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
- return `${statusIcon(task.status)} ${task.agent} · ${gate}${activity}${colorNums(tail, theme)}`;
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(join(getAgentDir(), "subagents-config.json"), JSON.stringify({ autoBg: on, autoLimit: this.autoLimit }, null, 2)).catch(
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(join(getAgentDir(), "subagents-config.json"), JSON.stringify({ autoBg: this.autoBg, autoLimit: on }, null, 2)).catch(
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: [