oira666_pi-subagent 0.2.27 → 0.2.29

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: team-lead
3
- description: Focused on tasks managment, delegates work to its subagents. For more compex tasks.
3
+ description: A Team of agents with different specializations, that can take any complex task, split it to parts and implement: arhitecture, generation, review or any other kind of task.
4
4
  ---
5
5
 
6
6
  You are an experienced team lead, focused on tasks management. You don't do any work yourself. You delegate.
package/index.ts CHANGED
@@ -42,13 +42,22 @@ import {
42
42
  type DelegationMode,
43
43
  type SingleResult,
44
44
  type SubagentDetails,
45
+ type SubagentUsageSummary,
45
46
  DEFAULT_DELEGATION_MODE,
47
+ buildLiveSubagentDetails,
46
48
  buildSubagentDetails,
47
49
  getFinalOutput,
48
50
  getNestedSubagentResults,
49
51
  isResultError,
50
52
  isSubagentDetails,
53
+ emptyUsage,
54
+ addUsage,
55
+ emptyUsageSummary,
56
+ addUsageSummary,
57
+ usageSummaryToUsageStats,
58
+ usageSummaryFromUsage,
51
59
  } from "./types.js";
60
+ import { formatCombinedUsageStatusLine } from "./tree.js";
52
61
 
53
62
  // ---------------------------------------------------------------------------
54
63
  // Limits
@@ -294,15 +303,106 @@ function makeDetailsFactory(
294
303
  projectAgentsDir: string | null,
295
304
  delegationMode: DelegationMode,
296
305
  ) {
297
- return (mode: "single" | "parallel") =>
298
- (results: SingleResult[]): SubagentDetails =>
306
+ return (mode: "single" | "parallel") => {
307
+ const makeDetails = (results: SingleResult[]): SubagentDetails =>
299
308
  buildSubagentDetails(mode, delegationMode, projectAgentsDir, results);
309
+ makeDetails.live = (results: SingleResult[]): SubagentDetails =>
310
+ buildLiveSubagentDetails(mode, delegationMode, projectAgentsDir, results);
311
+ return makeDetails;
312
+ };
300
313
  }
301
314
 
302
315
  function formatAgentNames(agents: AgentConfig[]): string {
303
316
  return agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
304
317
  }
305
318
 
319
+ function liveDetailsSignature(details: SubagentDetails): string {
320
+ return JSON.stringify(details.results.map((result) => ({
321
+ agent: result.agent,
322
+ task: result.task ?? "",
323
+ })));
324
+ }
325
+
326
+ function collectLiveUsageSummary(details: SubagentDetails): SubagentUsageSummary {
327
+ const summary = emptyUsageSummary();
328
+ for (const result of details.results) {
329
+ addUsageSummary(summary, usageSummaryFromUsage(result.usage));
330
+
331
+ const completedNested = getNestedSubagentResults(result.messages ?? []);
332
+ const completedNestedIds = new Set<string>();
333
+ const completedNestedSignatureCounts = new Map<string, number>();
334
+ for (const nested of completedNested) {
335
+ if (nested.toolCallId) completedNestedIds.add(nested.toolCallId);
336
+ const signature = liveDetailsSignature(nested.details);
337
+ completedNestedSignatureCounts.set(
338
+ signature,
339
+ (completedNestedSignatureCounts.get(signature) ?? 0) + 1,
340
+ );
341
+ addUsageSummary(
342
+ summary,
343
+ nested.details.usageSummary ?? collectLiveUsageSummary(nested.details),
344
+ );
345
+ }
346
+
347
+ for (const [liveToolCallId, liveNested] of Object.entries(result.liveNestedSubagents ?? {})) {
348
+ if (!isSubagentDetails(liveNested)) continue;
349
+ // Prefer durable completed toolResult messages over matching live progress.
350
+ // Some Pi versions key live progress differently than final tool results,
351
+ // so also de-dupe by requested child agent/task signature as a multiset.
352
+ if (completedNestedIds.has(liveToolCallId)) continue;
353
+ const signature = liveDetailsSignature(liveNested);
354
+ const completedSignatureCount = completedNestedSignatureCounts.get(signature) ?? 0;
355
+ if (completedSignatureCount > 0) {
356
+ completedNestedSignatureCounts.set(signature, completedSignatureCount - 1);
357
+ continue;
358
+ }
359
+ addUsageSummary(summary, collectLiveUsageSummary(liveNested));
360
+ }
361
+ }
362
+ return summary;
363
+ }
364
+
365
+ function collectCombinedUsageStatusLine(
366
+ ctx: any,
367
+ liveSummaries: SubagentUsageSummary[] = [],
368
+ ): string | undefined {
369
+ const entries = typeof ctx?.sessionManager?.getEntries === "function" || typeof ctx?.sessionManager?.getBranch === "function"
370
+ ? branchEntries(ctx)
371
+ : [];
372
+ if (entries.length === 0 && liveSummaries.length === 0) return undefined;
373
+
374
+ const parentUsage = emptyUsage();
375
+ const subagents = emptyUsageSummary();
376
+ for (const entry of entries as any[]) {
377
+ if (entry?.type !== "message") continue;
378
+ const msg = entry.message;
379
+ if (!msg) continue;
380
+ if (msg.role === "assistant" && msg.provider !== RESUME_PROVIDER) {
381
+ const usage = msg.usage;
382
+ if (usage) {
383
+ parentUsage.input += usage.input || 0;
384
+ parentUsage.output += usage.output || 0;
385
+ parentUsage.cacheRead += usage.cacheRead || 0;
386
+ parentUsage.cacheWrite += usage.cacheWrite || 0;
387
+ parentUsage.cost += typeof usage.cost === "number" ? usage.cost : usage.cost?.total || 0;
388
+ parentUsage.turns += 1;
389
+ }
390
+ }
391
+ if (msg.role === "toolResult" && msg.toolName === "subagent" && isSubagentDetails(msg.details)) {
392
+ addUsageSummary(
393
+ subagents,
394
+ msg.details.usageSummary ?? usageSummaryFromUsage(msg.details.aggregatedUsage),
395
+ );
396
+ }
397
+ }
398
+ for (const liveSummary of liveSummaries) {
399
+ addUsageSummary(subagents, liveSummary);
400
+ }
401
+ const subagentUsage = usageSummaryToUsageStats(subagents) ?? emptyUsage();
402
+ addUsage(parentUsage, subagentUsage);
403
+ return formatCombinedUsageStatusLine(parentUsage, subagents.subagentCount);
404
+ }
405
+
306
406
  function getCycleViolations(
307
407
  requestedNames: Set<string>,
308
408
  ancestorAgentStack: string[],
@@ -644,6 +744,7 @@ export default function (pi: ExtensionAPI) {
644
744
  let modelToRestoreAfterResume: any | undefined;
645
745
  const approvedProjectAgentDirsForSession = new Set<string>();
646
746
  const activeSubagents = new Map<number, { agent: string; task: string; handle: RunningSubagentHandle }>();
747
+ const activeSubagentUsageSummaries = new Map<string, SubagentUsageSummary>();
647
748
  const latestBroadcastTargets = {
648
749
  all: [] as BroadcastTarget[],
649
750
  youngest: [] as BroadcastTarget[],
@@ -815,9 +916,10 @@ export default function (pi: ExtensionAPI) {
815
916
  }
816
917
 
817
918
  function extractPendingSubagentTaskCounts(result: SingleResult): number[] {
818
- const completedToolCallIds = new Set(getNestedSubagentResults(result.messages).map((nested) => nested.toolCallId));
919
+ const messages = Array.isArray((result as any).messages) ? (result as any).messages : [];
920
+ const completedToolCallIds = new Set(getNestedSubagentResults(messages).map((nested) => nested.toolCallId));
819
921
  const counts: number[] = [];
820
- for (const message of result.messages as any[]) {
922
+ for (const message of messages) {
821
923
  if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
822
924
  for (const part of message.content) {
823
925
  if (part?.type !== "toolCall" || part?.name !== "subagent") continue;
@@ -1006,9 +1108,36 @@ export default function (pi: ExtensionAPI) {
1006
1108
  }
1007
1109
  }
1008
1110
 
1111
+ // Pi exposes footer/status-line extension text through ctx.ui.setStatus().
1112
+ // Keep a dedicated line updated with parent + active-branch subagent totals.
1113
+ // This is shown by the normal footer underneath Pi's built-in session stats.
1114
+ function formatFooterStatusText(ctx: any, text: string): string {
1115
+ return typeof ctx?.ui?.theme?.fg === "function"
1116
+ ? ctx.ui.theme.fg("dim", text)
1117
+ : text;
1118
+ }
1119
+
1120
+ function updateCombinedUsageStatus(ctx?: any): void {
1121
+ const targetCtx = ctx ?? latestSessionCtx;
1122
+ if (!targetCtx?.ui || typeof targetCtx.ui.setStatus !== "function") return;
1123
+ try {
1124
+ const line = collectCombinedUsageStatusLine(
1125
+ targetCtx,
1126
+ Array.from(activeSubagentUsageSummaries.values()),
1127
+ );
1128
+ targetCtx.ui.setStatus(
1129
+ "subagent-usage",
1130
+ line ? formatFooterStatusText(targetCtx, `total ${line}`) : undefined,
1131
+ );
1132
+ } catch (err) {
1133
+ console.error("[pi-subagent] Failed to update combined subagent status line:", err);
1134
+ }
1135
+ }
1136
+
1009
1137
  // Auto-discover agents on session start
1010
1138
  pi.on("session_start", async (event, ctx) => {
1011
1139
  latestSessionCtx = ctx;
1140
+ updateCombinedUsageStatus(ctx);
1012
1141
  try {
1013
1142
  // Always repair sessions left on the synthetic resume model, even in
1014
1143
  // nested subagents that can no longer delegate. Those leaf processes
@@ -1118,10 +1247,26 @@ export default function (pi: ExtensionAPI) {
1118
1247
  }
1119
1248
  });
1120
1249
 
1121
- pi.on("agent_end", async () => {
1250
+ pi.on("agent_end", async (_event, ctx) => {
1251
+ updateCombinedUsageStatus(ctx);
1122
1252
  await restoreModelAfterResumeFailure();
1123
1253
  });
1124
1254
 
1255
+ pi.on("message_end", (_event, ctx) => {
1256
+ latestSessionCtx = ctx;
1257
+ updateCombinedUsageStatus(ctx);
1258
+ setTimeout(() => updateCombinedUsageStatus(ctx), 0);
1259
+ });
1260
+
1261
+ pi.on("tool_execution_end", (event, ctx) => {
1262
+ latestSessionCtx = ctx;
1263
+ if (event.toolName === "subagent") {
1264
+ activeSubagentUsageSummaries.delete(event.toolCallId);
1265
+ updateCombinedUsageStatus(ctx);
1266
+ setTimeout(() => updateCombinedUsageStatus(ctx), 0);
1267
+ }
1268
+ });
1269
+
1125
1270
  pi.on("resources_discover", (_event, ctx) => {
1126
1271
  const prompt = pendingInteractiveResumePrompt;
1127
1272
  if (!prompt) return;
@@ -1251,7 +1396,9 @@ calls one after another. Do NOT put dependent tasks in the same array.
1251
1396
  nextActiveSubagentId += tasks.length;
1252
1397
  const trackedOnUpdate = (partial: any) => {
1253
1398
  if (isSubagentDetails(partial?.details)) {
1399
+ activeSubagentUsageSummaries.set(toolCallId, collectLiveUsageSummary(partial.details));
1254
1400
  updateLatestBroadcastTargets(partial.details, topLevelBaseId);
1401
+ updateCombinedUsageStatus(ctx);
1255
1402
  emitNestedProgressToParent(toolCallId, partial.details);
1256
1403
  }
1257
1404
  onUpdate?.(partial);
@@ -1466,7 +1613,7 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
1466
1613
  const errorMsg =
1467
1614
  result.errorMessage ||
1468
1615
  result.stderr ||
1469
- getFinalOutput(result.messages) ||
1616
+ getFinalOutput(result.messages, result.finalOutput) ||
1470
1617
  "(no output)";
1471
1618
  return {
1472
1619
  content: [
@@ -1483,7 +1630,7 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
1483
1630
  content: [
1484
1631
  {
1485
1632
  type: "text" as const,
1486
- text: getFinalOutput(result.messages) || "(no output)",
1633
+ text: getFinalOutput(result.messages, result.finalOutput) || "(no output)",
1487
1634
  },
1488
1635
  ],
1489
1636
  details: makeDetails("single")([result]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oira666_pi-subagent",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
4
4
  "description": "Subagent extension for Pi coding agent. Delegate tasks to specialized agents.",
5
5
  "type": "module",
6
6
  "main": "index.ts",