@henryqw/pi-subagent 27.8.0 → 27.9.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 +4 -0
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +4 -0
- package/docs/orchestration.md +1 -1
- package/extensions/isolated.ts +2 -1
- package/extensions/subagent-command.ts +43 -1
- package/extensions/subagent.ts +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,6 +155,10 @@ The extension sends follow-ups when a candidate becomes ready, a worker needs at
|
|
|
155
155
|
|
|
156
156
|
Open `/subagent` to browse direct work on this session branch and isolated requests in the current canonical Git checkout, including other sessions. The menu groups rows under **Direct**, **Isolated**, and **Orphaned** headers. Each isolated row shows the request ID, the status, and a short goal summary. **Refresh** or a header reloads saved status without recovery or mutation. Invalid state IDs stay preserved with a warning. Direct recovery remains available outside Git or without isolated inventory.
|
|
157
157
|
|
|
158
|
+
Before the menu opens, and after each **Refresh**, a **Current subagent work** report shows which subagents are working. The extension calculates it from local and saved evidence without a model call. Without interactive UI, `/subagent` sends this report before the request list.
|
|
159
|
+
|
|
160
|
+
A direct workflow is working only when this Pi process owns it and has a running worker. Direct work with a delivered result shows its outcome under **Completed / history** and is not in the report. Other recorded-only direct work shows as unverified. An isolated request is working only when its status is active and a live run holds the repository lease. Without a live lease, an active request shows as interrupted; run `/subagent recover`.
|
|
161
|
+
|
|
158
162
|
Each row starts with a status symbol: `●` active, `✓` completed, `⚠` needs attention, `✗` failed. In the terminal UI, `/subagent` reports (Roles, details, reviews, and results) show as colored transcript entries. The model does not get these entries. In RPC mode, they show as notifications. Without interactive UI, they show as plain text messages.
|
|
159
163
|
|
|
160
164
|
Requests whose parent checkout was removed show in the **Orphaned · parent checkout removed** group from any checkout of the same repository. Every isolated request, including an orphaned request, has **Close and delete worktrees (discard work)**. Select it to review owned resources and confirm deletion. **This permanently deletes committed, uncommitted, and ignored content in the listed checkouts.** Cancel makes no change.
|
package/dist/runner.d.ts
CHANGED
|
@@ -332,6 +332,8 @@ export declare class IsolatedRunner {
|
|
|
332
332
|
invalidIds: string[];
|
|
333
333
|
states: RunState[];
|
|
334
334
|
}>;
|
|
335
|
+
/** True while a live productive run holds this root's lease; the lease does not name its request. */
|
|
336
|
+
hasProductiveRunLease(root: string): Promise<boolean>;
|
|
335
337
|
/** Classify orphaned work under the same repository lease as productive execution.
|
|
336
338
|
* Status remains read-only; Main alone chooses any subsequent action. */
|
|
337
339
|
recoverRepository(root: string): Promise<{
|
package/dist/runner.js
CHANGED
|
@@ -639,6 +639,10 @@ export class IsolatedRunner {
|
|
|
639
639
|
})),
|
|
640
640
|
})) };
|
|
641
641
|
}
|
|
642
|
+
/** True while a live productive run holds this root's lease; the lease does not name its request. */
|
|
643
|
+
async hasProductiveRunLease(root) {
|
|
644
|
+
return await this.store.hasProductiveRunLease(root);
|
|
645
|
+
}
|
|
642
646
|
/** Classify orphaned work under the same repository lease as productive execution.
|
|
643
647
|
* Status remains read-only; Main alone chooses any subsequent action. */
|
|
644
648
|
async recoverRepository(root) {
|
package/docs/orchestration.md
CHANGED
|
@@ -269,7 +269,7 @@ After readiness, use `subagent_stage revise` with the exact candidate, generatio
|
|
|
269
269
|
|
|
270
270
|
### Inspect and revise active work
|
|
271
271
|
|
|
272
|
-
To select work by label, open `/subagent` without arguments. The menu shows direct records on this session branch and isolated requests in the current canonical Git checkout. This includes requests launched in other sessions. Select an isolated request. Then select **Inspect status and recovery**.
|
|
272
|
+
To select work by label, open `/subagent` without arguments. First, a **Current subagent work** report shows the work that local and saved evidence proves is running: locally owned direct workflows with a running worker, and active isolated requests while a live run holds the repository lease. The report lists active requests without a live lease as interrupted, and recorded-only direct work as unverified. A live lease does not identify its request, so several active requests in one checkout all show as working. The menu shows direct records on this session branch and isolated requests in the current canonical Git checkout. This includes requests launched in other sessions. Select an isolated request. Then select **Inspect status and recovery**.
|
|
273
273
|
|
|
274
274
|
The menu also shows requests whose saved parent checkout no longer exists, in their own **Orphaned · parent checkout removed** group. You can open this view from any checkout of the same repository. The saved worker or integration path identifies the primary repository. Orphaned requests have the same **Close and delete worktrees (discard work)** action, but no inspection or follow-up actions. The extension reads their original state directories; it does not recreate the removed parent checkout. Unreadable files stay preserved and are reported.
|
|
275
275
|
|
package/extensions/isolated.ts
CHANGED
|
@@ -842,7 +842,8 @@ export function registerIsolatedExtension(pi: ExtensionAPI, options: RegisterIso
|
|
|
842
842
|
catch (error) { console.error("Pi Subagent workspace widget update failed.", error); }
|
|
843
843
|
}
|
|
844
844
|
const orphans = await getComponents().runner.listOrphanRequests(root);
|
|
845
|
-
|
|
845
|
+
const leaseLive = await getComponents().runner.hasProductiveRunLease(root);
|
|
846
|
+
return { root, leaseLive, requests: [...requests, ...orphans.requests], invalidIds: [...new Set([...invalidIds, ...orphans.invalidIds])] };
|
|
846
847
|
},
|
|
847
848
|
async recover(cwd) {
|
|
848
849
|
const root = await lookupRoot(cwd);
|
|
@@ -56,6 +56,8 @@ export interface IsolatedRequest {
|
|
|
56
56
|
}
|
|
57
57
|
export interface IsolatedInventory {
|
|
58
58
|
root: string;
|
|
59
|
+
/** A live productive run holds this root's lease; the lease does not name its request. */
|
|
60
|
+
leaseLive: boolean;
|
|
59
61
|
requests: readonly IsolatedRequest[];
|
|
60
62
|
invalidIds: readonly string[];
|
|
61
63
|
}
|
|
@@ -291,6 +293,41 @@ function directReport(task: DirectTask): Report {
|
|
|
291
293
|
] };
|
|
292
294
|
}
|
|
293
295
|
|
|
296
|
+
const WORKING_REPORT_CODE_POINTS = 4_000;
|
|
297
|
+
/** Current work from local and saved evidence only. Recorded-only direct work and isolated work without a live lease never count as working. */
|
|
298
|
+
export function workingReport(direct: readonly DirectTask[], inventory: IsolatedInventory | undefined): Report {
|
|
299
|
+
const working: Row[] = [];
|
|
300
|
+
const attention: Row[] = [];
|
|
301
|
+
const unverified: Row[] = [];
|
|
302
|
+
for (const task of direct) {
|
|
303
|
+
const text = `${directLabel(task)} · ${task.status}`;
|
|
304
|
+
// Read workflows list their running workers; write workflows stay observed locally until their workers are proved stopped.
|
|
305
|
+
if (task.status === "observed locally" && task.stoppable?.length !== 0) working.push({ text: task.stoppable ? `${directLabel(task)} · ${task.stoppable.length} running` : text });
|
|
306
|
+
else if (/retained|attention/.test(task.status)) attention.push({ text });
|
|
307
|
+
else if (completed(task.status)) continue;
|
|
308
|
+
else unverified.push({ text: task.status === "observed locally" ? `${text}; no running worker` : text });
|
|
309
|
+
}
|
|
310
|
+
for (const request of inventory?.requests ?? []) {
|
|
311
|
+
const active = !request.orphaned && ["pending", "running"].includes(request.status);
|
|
312
|
+
if (active && inventory!.leaseLive) working.push({ text: isolatedLabel(request) });
|
|
313
|
+
else if (active) attention.push({ text: `${isolatedLabel(request)} · interrupted; run /subagent recover` });
|
|
314
|
+
else if (!request.orphaned && (request.status === "needs_attention" || /retained/.test(request.status))) attention.push({ text: isolatedLabel(request) });
|
|
315
|
+
}
|
|
316
|
+
const sections: Section[] = [
|
|
317
|
+
working.length ? { heading: `Working (${working.length})`, tone: "info", rows: working } : { rows: [{ text: inventory ? "No subagents are working." : "No direct subagents are working. Isolated status is unavailable." }] },
|
|
318
|
+
{ heading: `Needs attention (${attention.length})`, tone: "warning", rows: attention },
|
|
319
|
+
{ heading: `Unverified (${unverified.length})`, note: "not proof of running or completion", tone: "warning", rows: unverified },
|
|
320
|
+
];
|
|
321
|
+
const head: Report = { tone: attention.length ? "warning" : "info", title: "Current subagent work", sections: [] };
|
|
322
|
+
const items = sections.flatMap((section) => section.rows.map((row, index) => ({ section, index, row })));
|
|
323
|
+
const fitted = fitItems(reportMarkdown(head), items, ({ section, index, row }) => index ? sectionMarkdown({ rows: [row] }) : `\n${sectionMarkdown({ ...section, rows: [row] })}`,
|
|
324
|
+
WORKING_REPORT_CODE_POINTS, "open /subagent with interactive UI to see them.");
|
|
325
|
+
return { ...head, sections: [...sections.flatMap((section) => {
|
|
326
|
+
const rows = fitted.shown.filter((item) => item.section === section).map(({ row }) => row);
|
|
327
|
+
return rows.length ? [{ ...section, rows }] : [];
|
|
328
|
+
}), ...moreSection(fitted.more)] };
|
|
329
|
+
}
|
|
330
|
+
|
|
294
331
|
type Choice<T> = { label: string; value: T };
|
|
295
332
|
async function choose<T>(ctx: ExtensionContext, title: string, items: readonly Choice<T>[]): Promise<T | undefined> {
|
|
296
333
|
// Numeric suffixes only tell duplicate labels apart. Never parse display text into an identity.
|
|
@@ -462,6 +499,7 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
|
|
|
462
499
|
}
|
|
463
500
|
if (!ctx.hasUI) {
|
|
464
501
|
const inventory = await adapter.isolated(ctx.cwd, current);
|
|
502
|
+
deliver(ctx, workingReport(adapter.direct(ctx), inventory));
|
|
465
503
|
const head: Report = { tone: "info", title: "Isolated requests (report only)", sections: [] };
|
|
466
504
|
const row = (request: IsolatedRequest): Row => ({ text: `${glyph(request.status)} ${request.orphaned ? "orphaned · " : ""}${isolatedLabel(request)}` });
|
|
467
505
|
const listed = fitItems(`${reportMarkdown(head)}\n`, inventory.requests, (request) => sectionMarkdown({ rows: [row(request)] }), ROLES_REPORT_CODE_POINTS,
|
|
@@ -473,6 +511,8 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
|
|
|
473
511
|
return;
|
|
474
512
|
}
|
|
475
513
|
let history = false;
|
|
514
|
+
// The working report shows on open and on each Refresh, not after other menu actions.
|
|
515
|
+
let report = true;
|
|
476
516
|
for (;;) {
|
|
477
517
|
if (!valid()) return;
|
|
478
518
|
// Direct recovery is independent of Git and isolated config/inventory failures.
|
|
@@ -482,6 +522,8 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
|
|
|
482
522
|
try { inventory = await adapter.isolated(ctx.cwd, current); }
|
|
483
523
|
catch (error) { unavailable = errorText(error); }
|
|
484
524
|
if (!valid()) return;
|
|
525
|
+
if (report) deliver(ctx, workingReport(direct, inventory));
|
|
526
|
+
report = false;
|
|
485
527
|
if (unavailable) ctx.ui.notify(`Isolated inventory unavailable (${unavailable}). Direct branch recovery remains available; check the canonical Git checkout/configuration.`, "warning");
|
|
486
528
|
if (inventory?.invalidIds.length) for (const id of inventory.invalidIds) ctx.ui.notify(`Unreadable isolated state ID: ${JSON.stringify(id)}. Preserve this file; inspect the state store before making changes.`, "warning");
|
|
487
529
|
type Selection = { kind: "direct"; task: DirectTask } | { kind: "isolated"; request: IsolatedRequest; root: string } | { kind: "refresh" } | { kind: "history" } | { kind: "many" } | { kind: "close" };
|
|
@@ -509,7 +551,7 @@ export function registerSubagentCommand(pi: ExtensionAPI, adapter: SubagentComma
|
|
|
509
551
|
if (!unavailable && !direct.length && !inventory?.requests.length && !inventory?.invalidIds.length) ctx.ui.notify("No direct tasks recorded on this session branch or isolated requests in this checkout. Ask Main to start delegation.", "info");
|
|
510
552
|
const selected = await choose(ctx, history ? "Subagent · completed / history" : "Subagent · current work", selections);
|
|
511
553
|
if (!valid() || !selected || selected.kind === "close") return;
|
|
512
|
-
if (selected.kind === "refresh") continue;
|
|
554
|
+
if (selected.kind === "refresh") { report = true; continue; }
|
|
513
555
|
if (selected.kind === "history") { history = !history; continue; }
|
|
514
556
|
if (selected.kind === "many") {
|
|
515
557
|
const picked = await chooseMany(ctx, "Subagent · select work", rows.map(({ label, value }) => ({ label: label.trim(), value })), [], valid);
|
package/extensions/subagent.ts
CHANGED
|
@@ -300,7 +300,10 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
300
300
|
const local = directTasks.get(record.taskId);
|
|
301
301
|
let task = grouped.get(record.taskId);
|
|
302
302
|
if (!task) grouped.set(record.taskId, task = { id: record.taskId, name: record.taskId,
|
|
303
|
-
status: local?.recovery ? "admission retained" : local ? "observed locally"
|
|
303
|
+
status: local?.recovery ? "admission retained" : local ? "observed locally"
|
|
304
|
+
// A recovery result did not prove worker termination, so it stays visible for recovery.
|
|
305
|
+
: results.get(record.taskId)?.recovery ? "needs attention: worker termination unproved"
|
|
306
|
+
: results.get(record.taskId)?.outcome ?? "recorded only; not proof of running or completion",
|
|
304
307
|
...(results.get(record.taskId)?.cost === undefined ? {} : { cost: results.get(record.taskId)!.cost }),
|
|
305
308
|
...(local?.recovery ? { recovery: local.recovery } : {}), canClose: Boolean(local?.close),
|
|
306
309
|
...(local?.stopWorker ? { stoppable: local.stopWorker.running() } : {}), workers: [], tabs: [] });
|