@hcflabs/pi-subagent 28.2.3 → 28.3.0

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/README.md CHANGED
@@ -43,7 +43,7 @@ Commands, prompts, and UI are for you. Tools and the packaged skill are for Main
43
43
  | `subagent_abort` | tool | Terminate an isolated request without discarding retained work. Retained candidates and integration work require release or proof of absence. |
44
44
  | `git_read` | tool | Let Role-declared children inspect bounded local Git evidence without Bash, tests, network access, or writes. Not available to Main. |
45
45
  | `pi-subagent` | skill | Guide Main through authorization, delegation, checks, integration, and recovery. |
46
- | Direct status widget (`D`) | ui | Show direct workflows as a tree under `Main` with references, names, Role badges, status, routes, elapsed time, measured task tokens, cost, and tool activity. In fullscreen mode, a click on a working row focuses that worker's verified Herdr pane. |
46
+ | Direct status widget (`D`) | ui | Show direct workflows as a tree under `Main` with references, names, Role badges, status, routes, elapsed time, measured task tokens, cost, and tool activity. In fullscreen mode, a click on a working or finished worker row focuses that worker's verified Herdr pane. |
47
47
  | Isolated status widget (`I`) | ui | Show recorded task state, Role/model-class badges, and retained workspaces in the TUI. Readiness does not mean promotion. |
48
48
 
49
49
  `delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only. Main must make these decisions visibly; `codemode` scripts can call only `subagent_status`.
@@ -200,7 +200,7 @@ Main · 2 working · 1 complete
200
200
  └─ ✓ D8.1 [R] complete · Review patch · …
201
201
  ```
202
202
 
203
- In Pi fullscreen mode, click a working worker row to focus its Herdr pane. Before the extension moves focus, Herdr must confirm that the pane still runs the exact launched agent. If the check fails, Pi shows a warning and focus does not change. The header, group rows, finished rows, the overflow row, and rows with a truncated reference do nothing when you click them. Regular mode leaves the mouse to the terminal. Use the Herdr tab label or `/subagent` to find a worker from the keyboard. A click moves focus only. It gives no ownership or control authority.
203
+ In Pi fullscreen mode, click a worker row to focus its Herdr pane. This applies to working and finished workers, because a finished worker keeps its Herdr tab. A finished row stays in the widget until your next input. Before the extension moves focus, Herdr must confirm that the pane still runs the exact launched agent. If the check fails, for example because the tab was closed, Pi shows a warning and focus does not change. The header, group rows, the overflow row, and rows with a truncated reference do nothing when you click them. Regular mode leaves the mouse to the terminal. Use the Herdr tab label or `/subagent` to find a worker from the keyboard. A click moves focus only. It gives no ownership or control authority.
204
204
 
205
205
  The isolated widget shows the recorded task state and any retained workspace. It does not show reliable live model, thinking, or token readings from Herdr. Attention shows before ordinary work, and overflow uses `+N more · /subagent`. An aborted request with visible tasks has its own `request aborted` row. This row does not change the recorded statuses of the tasks.
206
206
 
@@ -117,14 +117,15 @@ function statusCounts(items: readonly WidgetItem[]): string {
117
117
  return STATUS_COUNT_ORDER.flatMap((status) => counts[status] ? [`${counts[status]} ${statusLabel(status)}`] : []).join(" · ");
118
118
  }
119
119
 
120
- /** One rendered widget line. `focus` names a live worker item that a click may focus. */
120
+ /** One rendered widget line. `focus` names a worker item that a click may focus. */
121
121
  type WidgetRow = { text: string; focus?: string };
122
122
  type TreeLine = { top: number; child?: { last: boolean }; item?: [string, WidgetItem]; group?: WidgetItem[] };
123
123
 
124
124
  /**
125
125
  * Render the direct widget as a tree under a Main header.
126
126
  * A workflow with one launched worker is a leaf row. A workflow with more workers gets a group row.
127
- * `focusable` tells which item IDs have a live handle. Only fully visible references become click targets.
127
+ * `focusable` tells which item IDs have a launched handle. Working and finished worker rows are click targets.
128
+ * Only fully visible references become click targets.
128
129
  */
129
130
  function renderWidgetRows(
130
131
  items: ReadonlyArray<[string, WidgetItem]>,
@@ -168,7 +169,7 @@ function renderWidgetRows(
168
169
  const text = truncateToWidth(`${branch}${renderWorkerRow(item, room, now, spinnerIndex, theme)}`, width);
169
170
  // The glyph, a space, and the full reference must be visible before a click can name this worker.
170
171
  const referenceVisible = visibleWidth(connector) + 2 + visibleWidth(item.ref) <= width;
171
- rows.push({ text, ...(item.status === "working" && referenceVisible && focusable(id) ? { focus: id } : {}) });
172
+ rows.push({ text, ...(referenceVisible && focusable(id) ? { focus: id } : {}) });
172
173
  }
173
174
  if (hidden.length) rows.push({ text: truncateToWidth(`${theme.fg("dim", "└─ ")}${theme.fg("muted", `… ${hidden.length} more · ${statusCounts(hidden)}`)}`, width) });
174
175
  return rows;
@@ -242,6 +243,8 @@ export default function subagentExtension(pi: ExtensionAPI): void {
242
243
  });
243
244
  const widgetItems = new Map<string, WidgetItem>();
244
245
  const tokenHandles = new Map<string, DirectHandle>();
246
+ // A finished worker keeps its Herdr tab. Its row keeps a focus handle until the row leaves the widget.
247
+ const focusHandles = new Map<string, Pick<DirectHandle, "focus">>();
245
248
  const samplingTokens = new Set<string>();
246
249
  const configStore = createSubagentConfigStore();
247
250
  const configReporter = registerConfigReporting(pi, [configStore]);
@@ -520,9 +523,9 @@ export default function subagentExtension(pi: ExtensionAPI): void {
520
523
  let focusInFlight = false;
521
524
  // A click is a request only. The handle checks the exact Herdr agent identity before it moves focus.
522
525
  const focusWorker = (id: string) => {
523
- const handle = tokenHandles.get(id);
526
+ const handle = focusHandles.get(id);
524
527
  const item = widgetItems.get(id);
525
- if (!handle || item?.status !== "working" || focusInFlight) return;
528
+ if (!handle || !item || focusInFlight) return;
526
529
  const epoch = sessionEpoch;
527
530
  const ctx = latestCtx;
528
531
  focusInFlight = true;
@@ -543,7 +546,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
543
546
  return {
544
547
  invalidate() {},
545
548
  render(width) {
546
- const rows = renderWidgetRows([...widgetItems], width, Date.now(), spinnerIndex, theme, (id) => tokenHandles.has(id));
549
+ const rows = renderWidgetRows([...widgetItems], width, Date.now(), spinnerIndex, theme, (id) => focusHandles.has(id));
547
550
  renderedWidth = width;
548
551
  hits = rows.flatMap(({ text, focus }, row) => focus ? [{ row, to: visibleWidth(text), id: focus }] : []);
549
552
  return rows.map(({ text }) => text);
@@ -574,6 +577,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
574
577
  for (const [oldestId, item] of widgetItems) {
575
578
  if (item.status === "working" || item.taskId === taskId) continue;
576
579
  widgetItems.delete(oldestId);
580
+ focusHandles.delete(oldestId);
577
581
  if (widgetItems.size < MAX_WIDGET_ITEMS) break;
578
582
  }
579
583
  }
@@ -659,6 +663,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
659
663
  stopWidgetTimer();
660
664
  widgetItems.clear();
661
665
  tokenHandles.clear();
666
+ focusHandles.clear();
662
667
  samplingTokens.clear();
663
668
  activeTui = undefined;
664
669
  widgetInstalled = false;
@@ -679,7 +684,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
679
684
  pi.on("input", (event) => {
680
685
  if (event.source === "extension") return;
681
686
  for (const [id, item] of widgetItems) {
682
- if (item.status !== "working") widgetItems.delete(id);
687
+ if (item.status !== "working") { widgetItems.delete(id); focusHandles.delete(id); }
683
688
  }
684
689
  requestWidgetRender();
685
690
  });
@@ -981,7 +986,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
981
986
  await transient.cleanup();
982
987
  handles.push(handle);
983
988
  handleByEntry.set(entry.id, handle);
984
- if (widgetItems.has(entry.id)) tokenHandles.set(entry.id, handle);
989
+ if (widgetItems.has(entry.id)) { tokenHandles.set(entry.id, handle); focusHandles.set(entry.id, handle); }
985
990
  return handle;
986
991
  };
987
992
  // The first tab is verified before returning a handle; subsequent independent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hcflabs/pi-subagent",
3
- "version": "28.2.3",
3
+ "version": "28.3.0",
4
4
  "description": "Delegate bounded direct work or durable checked isolated task graphs to Pi roles.",
5
5
  "keywords": [
6
6
  "pi-package",