@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 +1 -1
- package/docs/orchestration.md +1 -1
- package/extensions/subagent.ts +13 -8
- package/package.json +1 -1
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`.
|
package/docs/orchestration.md
CHANGED
|
@@ -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
|
|
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
|
|
package/extensions/subagent.ts
CHANGED
|
@@ -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
|
|
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
|
|
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, ...(
|
|
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 =
|
|
526
|
+
const handle = focusHandles.get(id);
|
|
524
527
|
const item = widgetItems.get(id);
|
|
525
|
-
if (!handle || item
|
|
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) =>
|
|
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
|