@arhen/pi-core-subagent 1.3.53 → 1.3.55

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.53",
3
+ "version": "1.3.55",
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
@@ -60,8 +60,12 @@ function taskStatsWithUsage(task: TaskSnapshot): string {
60
60
  const usage = formatUsage(task.usage);
61
61
  return `${stats}${usage ? ` · ${usage}` : ""}`;
62
62
  }
63
+ export function modelTag(task: TaskSnapshot): string {
64
+ const ref = [task.provider, task.model, task.thinking].filter(Boolean).join("/");
65
+ return ref ? ` [${ref}]` : "";
66
+ }
63
67
  export function taskLine(task: TaskSnapshot): string {
64
- return `${statusIcon(task.status)} ${task.agent} · ${taskStatsWithUsage(task)} · ${taskTimer(task)}`;
68
+ return `${statusIcon(task.status)} ${task.agent}${modelTag(task)} · ${taskStatsWithUsage(task)} · ${taskTimer(task)}`;
65
69
  }
66
70
  export function colorNums(text: string, theme: Theme): string {
67
71
  return text.replace(/((?:\d+(?:\.\d+)?[a-zA-Z]*)+)|([^\d]+)/g, (_m, num?: string, rest?: string) =>
@@ -71,15 +75,16 @@ export function colorNums(text: string, theme: Theme): string {
71
75
  function themedTaskLine(task: TaskSnapshot, theme: Theme, activity = ""): string {
72
76
  const tail = `${taskStatsWithUsage(task)} · ${taskTimer(task)}`;
73
77
 
78
+ const tag = theme.fg("dim", modelTag(task));
74
79
  const gate =
75
80
  task.status === "queued" && task.needs?.length ? `${theme.fg("muted", `↳ waits ${task.needs.join(", ")}`)} · ` : "";
76
81
  if (TERMINAL.includes(task.status)) {
77
- return theme.fg("dim", `${statusIcon(task.status)} ${task.agent} · ${tail}`);
82
+ return theme.fg("dim", `${statusIcon(task.status)} ${task.agent}${tag} · ${tail}`);
78
83
  }
79
84
 
80
85
  pulsePhase += 1;
81
86
  const name = isTalking(task) ? theme.fg(pulsePhase % 2 === 0 ? "accent" : "dim", `${task.agent} ⇄`) : task.agent;
82
- return `${statusIcon(task.status)} ${name} · ${gate}${activity}${colorNums(tail, theme)}`;
87
+ return `${statusIcon(task.status)} ${name}${tag} · ${gate}${activity}${colorNums(tail, theme)}`;
83
88
  }
84
89
  const ARG_KEYS = ["pattern", "query", "command", "path", "file_path", "filePath", "url", "name", "subject", "task"];
85
90
  export function describeCall(toolName: string, args: unknown, cwd?: string): string {
@@ -114,7 +119,8 @@ export function compactLines(run: RunSnapshot): string[] {
114
119
  if (run.tasks.length > MAX_TASKS) lines.push(`… +${run.tasks.length - MAX_TASKS} more`);
115
120
  return lines;
116
121
  }
117
- const WIDGET_MAX_LINES = 10;
122
+ // header + 4 task rows; live tasks are ordered first, so the cap only ever hides finished work
123
+ const WIDGET_MAX_LINES = 5;
118
124
 
119
125
  export class SubagentsWidget implements Component {
120
126
  constructor(
@@ -139,17 +145,21 @@ export class SubagentsWidget implements Component {
139
145
  ),
140
146
  ];
141
147
  const budget = WIDGET_MAX_LINES - 1;
148
+ // live tasks first so the budget never hides work in progress
149
+ const all = runs.flatMap((run) => run.tasks);
150
+ const ordered = [
151
+ ...all.filter((t) => !TERMINAL.includes(t.status)),
152
+ ...all.filter((t) => TERMINAL.includes(t.status)),
153
+ ];
142
154
  let shown = 0;
143
- outer: for (const run of runs) {
144
- for (const task of run.tasks) {
145
- if (shown >= budget) break outer;
146
- shown += 1;
147
- const activity = task.lastActivity ? `${this.theme.fg("dim", `→ ${task.lastActivity}`)} · ` : "";
155
+ for (const task of ordered) {
156
+ if (shown >= budget) break;
157
+ shown += 1;
158
+ const activity = task.lastActivity ? `${this.theme.fg("dim", `→ ${task.lastActivity}`)} · ` : "";
148
159
 
149
- lines.push(
150
- truncateToWidth(`${this.theme.fg("dim", "├─")} ${themedTaskLine(task, this.theme, activity)}`, width, "…"),
151
- );
152
- }
160
+ lines.push(
161
+ truncateToWidth(`${this.theme.fg("dim", "├─")} ${themedTaskLine(task, this.theme, activity)}`, width, "…"),
162
+ );
153
163
  }
154
164
  const hidden = total - shown;
155
165
  if (hidden > 0) {
package/src/index.ts CHANGED
@@ -140,6 +140,7 @@ export default function (pi: ExtensionAPI) {
140
140
  "Define each agent inline: invented name, focused system prompt, read-only by default (write:true to edit). A matched agent file takes over (see description); matching is by description, not name — name the agent whatever fits the goal.",
141
141
  "Right after spawning, call subagent_status(runId) ONCE before any other work — a child that died on spawn (or never started) is invisible until far later otherwise. If it shows a task failed/never started, fix or respawn immediately.",
142
142
  "Never block with nothing to do: if you have no work left after spawning, end your turn — completion notifies you and wakes a fresh turn with the results. await_subagent/autoAwait while idle only burns time and tokens.",
143
+ "A failed task interrupts you immediately as a steering message — handle it in the same turn (resume, swap model, re-dispatch) instead of finishing the plan on a broken intermediate result. Completes and aborts queue as follow-ups.",
143
144
  "autoAwait:true only when this SAME turn must consume the result immediately. await_subagent is for syncing with your own parallel work — not the default follow-up to a spawn.",
144
145
  "A task that failed mid-work (provider error, rate limit, timeout) keeps its session file and branch: resume_subagent(runId, taskId, model?) revives it with full context — prefer that over respawning. Respawn only when it never started (no session file).",
145
146
  ],
package/src/manager.ts CHANGED
@@ -437,7 +437,9 @@ export class SubagentManager {
437
437
  }
438
438
 
439
439
  private deliverMode(kind: string, task: TaskSnapshot): "steer" | "followUp" {
440
- return isStartupFailure(task, kind) ? "steer" : "followUp";
440
+ // failures interrupt the leader's turn: it must decide immediately (resume, swap model, respawn)
441
+ // instead of finding out after its own turn ended. Aborts are user-initiated, so they queue.
442
+ return kind === "failed" || isStartupFailure(task, kind) ? "steer" : "followUp";
441
443
  }
442
444
 
443
445
  private notifyTask(run: RunSnapshot, task: TaskSnapshot, kind: "completed" | "failed" | "aborted"): void {
@@ -470,7 +472,7 @@ export class SubagentManager {
470
472
  ? `A subagent is asking you a question (task ${extra?.taskId}): ${extra?.question ?? ""}\nReply with reply_subagent(runId: "${run.id}", taskId: "${extra?.taskId}", message: ...).`
471
473
  : makeNotice(run, kind);
472
474
  try {
473
- this.pi.sendUserMessage(body, { deliverAs: "followUp" });
475
+ this.pi.sendUserMessage(body, { deliverAs: kind === "failed" ? "steer" : "followUp" });
474
476
  } catch {}
475
477
  this.emit("subagent:notification", { runId: run.id, kind, body });
476
478
  }
@@ -825,6 +827,7 @@ export class SubagentManager {
825
827
  task: input.task,
826
828
 
827
829
  model: model?.id ?? input.model,
830
+ provider: model?.provider,
828
831
  thinking,
829
832
  tools,
830
833
  },
@@ -878,7 +881,13 @@ export class SubagentManager {
878
881
  this.updateTask(
879
882
  run,
880
883
  task,
881
- { status: "running", sessionId: child.sessionId, sessionFile: child.sessionFile },
884
+ {
885
+ status: "running",
886
+ sessionId: child.sessionId,
887
+ sessionFile: child.sessionFile,
888
+ provider: child.model?.provider ?? task.provider,
889
+ thinking: child.thinkingLevel,
890
+ },
882
891
  ctx,
883
892
  onUpdate,
884
893
  );
package/src/types.ts CHANGED
@@ -33,6 +33,7 @@ export interface TaskSnapshot {
33
33
  notifiedParent?: boolean;
34
34
  error?: string;
35
35
  model?: string;
36
+ provider?: string;
36
37
  modelNote?: string;
37
38
  toolsNote?: string;
38
39
  thinking?: string;