@fosterg4/pi-subagent 1.0.2 → 1.0.3

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.
Files changed (3) hide show
  1. package/index.ts +36 -10
  2. package/package.json +1 -1
  3. package/utils.ts +37 -0
package/index.ts CHANGED
@@ -35,12 +35,15 @@ import {
35
35
  formatAgentList,
36
36
  } from "./agents.ts";
37
37
  import { type ValidationResult, validateSchema } from "./validate.ts";
38
+ import { fmt, usageLine, sumUsage } from "./utils.ts";
38
39
 
39
40
  const MAX_PARALLEL_TASKS = 8;
40
41
  const MAX_CONCURRENCY = 4;
41
42
  const PER_TASK_OUTPUT_CAP = 50 * 1024;
42
43
 
44
+ /* eslint-disable @typescript-eslint/no-unused-vars */
43
45
 
46
+ interface UsageStats {
44
47
  input: number;
45
48
  output: number;
46
49
  cacheRead: number;
@@ -954,11 +957,13 @@ export default function (pi: ExtensionAPI) {
954
957
  return theme.fg("error", "\u2717");
955
958
  };
956
959
 
957
- // --- Single mode ---
960
+ // --- Single mode ---
958
961
  if (details.mode === "single" && details.results.length === 1) {
959
962
  const r = details.results[0];
960
963
  const status = getStatusText(r);
961
964
  const finalOutput = getFinalOutputText(r.messages);
965
+ const usage = usageLine(r.usage);
966
+ const usg = usage ? theme.fg("dim", usage) : "";
962
967
 
963
968
  if (expanded) {
964
969
  const container = new Container();
@@ -969,6 +974,10 @@ export default function (pi: ExtensionAPI) {
969
974
  container.addChild(new Spacer(1));
970
975
  container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme));
971
976
  }
977
+ if (usg) {
978
+ container.addChild(new Spacer(1));
979
+ container.addChild(new Text(usg, 0, 0));
980
+ }
972
981
  return container;
973
982
  }
974
983
 
@@ -981,6 +990,7 @@ export default function (pi: ExtensionAPI) {
981
990
  } else {
982
991
  text += `\n${theme.fg("muted", "(no output)")}`;
983
992
  }
993
+ if (usg) text += `\n${usg}`;
984
994
  return new Text(text, 0, 0);
985
995
  }
986
996
 
@@ -991,6 +1001,8 @@ export default function (pi: ExtensionAPI) {
991
1001
  const allOk = details.results.every((r) => r.exitCode === 0);
992
1002
  const icon = allOk ? theme.fg("success", "\u2713") : theme.fg("error", "\u2717");
993
1003
  const steps = details.results.map((r) => r.agent).join(" \u2192 ");
1004
+ const total = sumUsage(details.results);
1005
+ const totalUsg = usageLine(total);
994
1006
 
995
1007
  if (expanded) {
996
1008
  const container = new Container();
@@ -999,18 +1011,19 @@ export default function (pi: ExtensionAPI) {
999
1011
  );
1000
1012
  for (const r of details.results) {
1001
1013
  const out = getFinalOutputText(r.messages);
1014
+ const stepUsage = usageLine(r.usage);
1015
+ const stepUsg = stepUsage ? theme.fg("dim", stepUsage) : "";
1016
+ const label = `${getStatusText(r)} ${theme.fg("accent", r.agent)}${r.model ? theme.fg("muted", ` \u00B7 ${r.model}`) : ""}${stepUsg ? " " + stepUsg : ""}`;
1002
1017
  if (out) {
1003
1018
  container.addChild(new Spacer(1));
1004
- container.addChild(
1005
- new Text(
1006
- `${getStatusText(r)} ${theme.fg("accent", r.agent)}${r.model ? theme.fg("muted", ` \u00B7 ${r.model}`) : ""}`,
1007
- 0,
1008
- 0,
1009
- ),
1010
- );
1019
+ container.addChild(new Text(label, 0, 0));
1011
1020
  container.addChild(new Markdown(out.trim(), 0, 0, mdTheme));
1012
1021
  }
1013
1022
  }
1023
+ if (totalUsg && details.results.length > 1) {
1024
+ container.addChild(new Spacer(1));
1025
+ container.addChild(new Text(totalUsg, 0, 0));
1026
+ }
1014
1027
  return container;
1015
1028
  }
1016
1029
 
@@ -1023,6 +1036,7 @@ export default function (pi: ExtensionAPI) {
1023
1036
  } else {
1024
1037
  text += `\n${theme.fg("muted", "(no output)")}`;
1025
1038
  }
1039
+ if (totalUsg) text += `\n${totalUsg}`;
1026
1040
  return new Text(text, 0, 0);
1027
1041
  }
1028
1042
 
@@ -1039,6 +1053,8 @@ export default function (pi: ExtensionAPI) {
1039
1053
 
1040
1054
  if (expanded && running === 0) {
1041
1055
  const container = new Container();
1056
+ const total = sumUsage(details.results);
1057
+ const totalUsg = usageLine(total);
1042
1058
  container.addChild(
1043
1059
  new Text(
1044
1060
  `${icon} ${theme.fg("accent", `${done} tasks`)}`,
@@ -1048,11 +1064,13 @@ export default function (pi: ExtensionAPI) {
1048
1064
  );
1049
1065
  for (const r of details.results) {
1050
1066
  const out = getFinalOutputText(r.messages);
1067
+ const stepUsage = usageLine(r.usage);
1068
+ const stepUsg = stepUsage ? theme.fg("dim", stepUsage) : "";
1051
1069
  if (out) {
1052
1070
  container.addChild(new Spacer(1));
1053
1071
  container.addChild(
1054
1072
  new Text(
1055
- `${getStatusText(r)} ${theme.fg("accent", r.agent)}`,
1073
+ `${getStatusText(r)} ${theme.fg("accent", r.agent)}${stepUsg ? " " + stepUsg : ""}`,
1056
1074
  0,
1057
1075
  0,
1058
1076
  ),
@@ -1060,11 +1078,19 @@ export default function (pi: ExtensionAPI) {
1060
1078
  container.addChild(new Markdown(out.trim(), 0, 0, mdTheme));
1061
1079
  }
1062
1080
  }
1081
+ if (totalUsg && details.results.length > 1) {
1082
+ container.addChild(new Spacer(1));
1083
+ container.addChild(new Text(totalUsg, 0, 0));
1084
+ }
1063
1085
  return container;
1064
1086
  }
1065
1087
 
1088
+ const usg = usageLine(sumUsage(details.results));
1066
1089
  let text = `${icon} ${theme.fg("accent", `${done}/${details.results.length} tasks`)}`;
1067
- if (running === 0 && !expanded) text += theme.fg("muted", " (Ctrl+O to expand)");
1090
+ if (running === 0) {
1091
+ if (usg) text += `\n${usg}`;
1092
+ if (!expanded) text += theme.fg("muted", " (Ctrl+O to expand)");
1093
+ }
1068
1094
  return new Text(text, 0, 0);
1069
1095
  }
1070
1096
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fosterg4/pi-subagent",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Delegate tasks to specialized subagents with isolated context windows, structured JSON handoff, contract schemas, and live TUI streaming",
5
5
  "keywords": [
6
6
  "pi-package",
package/utils.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared formatting helpers — testable without pi runtime deps
3
+ */
4
+
5
+ import type { UsageStats } from "./index.ts";
6
+
7
+ export function fmt(n: number): string {
8
+ if (n < 1000) return n.toString();
9
+ if (n < 10000) return (n / 1000).toFixed(1) + "k";
10
+ if (n < 1000000) return Math.round(n / 1000) + "k";
11
+ return (n / 1000000).toFixed(1) + "M";
12
+ }
13
+
14
+ export function usageLine(u: UsageStats): string {
15
+ const parts: string[] = [];
16
+ if (u.input) parts.push("\u2191" + fmt(u.input));
17
+ if (u.output) parts.push("\u2193" + fmt(u.output));
18
+ if (u.cacheRead) {
19
+ parts.push("R" + fmt(u.cacheRead));
20
+ const total = u.input + u.cacheRead;
21
+ if (total > 0) parts.push("CH" + ((u.cacheRead / total) * 100).toFixed(1) + "%");
22
+ }
23
+ if (u.cost) parts.push("$" + u.cost.toFixed(4));
24
+ return parts.join(" ");
25
+ }
26
+
27
+ export function sumUsage(results: ReadonlyArray<{ usage: UsageStats }>): UsageStats {
28
+ const u: UsageStats = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 };
29
+ for (const r of results) {
30
+ u.input += r.usage.input;
31
+ u.output += r.usage.output;
32
+ u.cacheRead += r.usage.cacheRead;
33
+ u.cacheWrite += r.usage.cacheWrite;
34
+ u.cost += r.usage.cost;
35
+ }
36
+ return u;
37
+ }