@fosterg4/pi-subagent 1.0.4 → 1.0.6

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 +14 -6
  2. package/package.json +1 -1
  3. package/ui.ts +51 -53
package/index.ts CHANGED
@@ -647,10 +647,12 @@ export default function (pi: ExtensionAPI) {
647
647
  let widgetHandle: ReturnType<typeof pi.ui.custom> | undefined;
648
648
 
649
649
  const closeWidget = () => {
650
+ if (widgetTimer) { clearInterval(widgetTimer); widgetTimer = undefined; }
650
651
  if (widgetHandle) {
651
652
  widgetHandle.close();
652
653
  widgetHandle = undefined;
653
654
  }
655
+ widgetRef = undefined;
654
656
  };
655
657
 
656
658
  for (let i = 0; i < params.chain.length; i++) {
@@ -673,16 +675,22 @@ export default function (pi: ExtensionAPI) {
673
675
  }
674
676
 
675
677
  // Spawn live widget for this step
678
+ let widgetRef: AgentWidget | undefined;
679
+ let widgetTimer: ReturnType<typeof setInterval> | undefined;
676
680
  if (ctx.hasUI) {
677
- const widget = new AgentWidget();
678
- widget.addAgent(step.agent, step.task.replace(/\{[^}]+\}/g, "").trim());
679
- widgetHandle = ctx.ui.custom(widget, { overlay: true });
681
+ widgetRef = new AgentWidget();
682
+ widgetRef.addAgent(step.agent, step.task.replace(/\{[^}]+\}/g, "").trim());
683
+ widgetHandle = ctx.ui.custom(
684
+ (_tui, _theme, _kb, done) => {
685
+ widgetTimer = setInterval(() => widgetRef?.invalidate(), 200);
686
+ return widgetRef!;
687
+ },
688
+ { overlay: true },
689
+ );
680
690
  }
681
691
 
682
692
  const stepStats = (stats: { turns: number; tokens: number }) => {
683
- if (widgetHandle) {
684
- widgetHandle.requestRender();
685
- }
693
+ widgetRef?.invalidate();
686
694
  };
687
695
 
688
696
  const chainUpdate: OnUpdateCallback | undefined = onUpdate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fosterg4/pi-subagent",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
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/ui.ts CHANGED
@@ -12,7 +12,6 @@
12
12
  * handle.close();
13
13
  */
14
14
 
15
- import { Container, Spacer, Text } from "@earendil-works/pi-tui";
16
15
  import { fmt } from "./utils.ts";
17
16
 
18
17
  // ---------------------------------------------------------------------------
@@ -71,8 +70,6 @@ export class AgentWidget {
71
70
  private entries: WidgetEntry[] = [];
72
71
  private frame = 0;
73
72
  private startTimes: Map<string, number> = new Map();
74
- private cachedWidth?: number;
75
- private cachedLines?: string[];
76
73
 
77
74
  addAgent(name: string, task: string): void {
78
75
  const exists = this.entries.find((e) => e.name === name);
@@ -93,7 +90,6 @@ export class AgentWidget {
93
90
  const entry = this.entries.find((e) => e.name === name);
94
91
  if (!entry) return;
95
92
  Object.assign(entry, update);
96
- entry.elapsedMs = Date.now() - (this.startTimes.get(name) ?? Date.now());
97
93
  this.invalidate();
98
94
  }
99
95
 
@@ -107,70 +103,72 @@ export class AgentWidget {
107
103
  return this.entries.find((e) => e.name === name);
108
104
  }
109
105
 
110
- invalidate(): void {
111
- this.cachedWidth = undefined;
112
- this.cachedLines = undefined;
106
+ markDone(name: string): void {
107
+ const entry = this.entries.find((e) => e.name === name);
108
+ if (entry) { entry.status = "done"; }
109
+ this.invalidate();
113
110
  }
114
111
 
115
- render(width: number): string[] {
116
- if (this.cachedLines && this.cachedWidth === width) {
117
- return this.cachedLines;
118
- }
112
+ invalidate(): void {
113
+ // No cache to invalidate — render() is always fresh
114
+ }
119
115
 
116
+ private entryLine(entry: WidgetEntry, width: number): string[] {
120
117
  const lines: string[] = [];
118
+ const now = Date.now();
119
+ const elapsedMs = entry.elapsedMs || (now - (this.startTimes.get(entry.name) ?? now));
121
120
 
122
- if (this.entries.length === 0) {
123
- this.cachedWidth = width;
124
- this.cachedLines = [];
125
- return [];
126
- }
121
+ const icon = statusIcon(entry.status, this.frame);
122
+ const parts: string[] = [];
127
123
 
128
- // Header
129
- lines.push("● Agents");
124
+ // Always show icon + name
125
+ let line = `\u00A0${icon} ${entry.name}`;
130
126
 
131
- for (const entry of this.entries) {
132
- const icon = statusIcon(entry.status, this.frame);
133
- const parts: string[] = [];
134
-
135
- // Turns
136
- if (entry.turns > 0) parts.push(`↻${entry.turns}`);
137
-
138
- // Tokens
139
- if (entry.tokens > 0) {
140
- const tokStr = fmt(entry.tokens);
141
- parts.push(`${tokStr} token`);
142
- if (entry.contextUsagePct !== undefined && entry.contextUsagePct > 0) {
143
- parts.push(`(${entry.contextUsagePct}%)`);
144
- }
127
+ // Turns
128
+ parts.push(entry.turns > 0 ? `↻${entry.turns}` : "↻0");
129
+
130
+ // Tokens
131
+ if (entry.tokens > 0) {
132
+ const tokStr = fmt(entry.tokens);
133
+ parts.push(`${tokStr} token`);
134
+ if (entry.contextUsagePct !== undefined && entry.contextUsagePct > 0) {
135
+ parts.push(`(${entry.contextUsagePct}%)`);
145
136
  }
137
+ }
146
138
 
147
- // Time
148
- if (entry.elapsedMs > 0) parts.push(formatTime(entry.elapsedMs));
139
+ // Time
140
+ if (elapsedMs > 99) parts.push(formatTime(elapsedMs));
141
+ else parts.push("0.0s");
149
142
 
150
- // Model
151
- if (entry.model) parts.push(entry.model);
143
+ // Model
144
+ if (entry.model) parts.push(entry.model);
152
145
 
153
- const stats = parts.length > 0 ? ` · ${parts.join(" · ")}` : "";
146
+ line += ` · ${parts.join(" · ")}`;
147
+ if (line.length > width) line = line.slice(0, width - 3) + "…";
148
+ lines.push(line);
154
149
 
155
- let line = ` ${icon} ${entry.name}${stats}`;
156
- if (line.length > width) {
157
- line = line.slice(0, width - 1) + "…";
158
- }
159
- lines.push(line);
160
-
161
- // Activity sub-line
162
- if (entry.task && entry.status === "running") {
163
- const preview =
164
- entry.task.length > 60
165
- ? `${entry.task.slice(0, 57)}…`
166
- : entry.task;
167
- lines.push(` ⎿ ${preview}`);
168
- }
150
+ // Activity sub-line (running agents only)
151
+ if (entry.task && entry.status === "running") {
152
+ const preview = entry.task.length > 60 ? `${entry.task.slice(0, 57)}…` : entry.task;
153
+ lines.push(` ⎿ ${preview}`);
154
+ }
155
+
156
+ return lines;
157
+ }
158
+
159
+ render(width: number): string[] {
160
+ const lines: string[] = [];
161
+
162
+ if (this.entries.length === 0) return [];
163
+
164
+ // Header
165
+ lines.push("\u25CF Agents");
166
+
167
+ for (const entry of this.entries) {
168
+ lines.push(...this.entryLine(entry, width));
169
169
  }
170
170
 
171
171
  this.frame++;
172
- this.cachedWidth = width;
173
- this.cachedLines = lines;
174
172
  return lines;
175
173
  }
176
174
  }