@ferris1225/pi-subagents 4.1.1 → 4.1.2
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 +506 -481
- package/agents/cleaner.md +14 -4
- package/agents/documenter.md +44 -0
- package/agents/reviewer.md +3 -2
- package/agents/worker.md +4 -1
- package/package.json +2 -2
- package/src/agents.ts +12 -0
- package/src/announcements.ts +18 -1
- package/src/config.ts +43 -13
- package/src/dispatch.ts +637 -704
- package/src/fixloop.ts +266 -52
- package/src/index.ts +3 -3
- package/src/monitor.ts +12 -3
- package/src/prompt.ts +47 -12
- package/src/rpc-run.ts +23 -7
- package/src/runtime.ts +8 -7
- package/src/setup.ts +24 -7
- package/src/spawn.ts +45 -11
- package/src/thread-lifecycle.ts +203 -49
- package/src/tools.ts +23 -9
- package/src/widget.ts +4 -4
- package/src/worktree.ts +27 -4
package/src/tools.ts
CHANGED
|
@@ -60,16 +60,17 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
60
60
|
label: "Subagent Control",
|
|
61
61
|
description: [
|
|
62
62
|
"Control an existing sub-agent thread by stable run id.",
|
|
63
|
-
"steer queues an instruction after the current child
|
|
64
|
-
"retarget
|
|
63
|
+
"steer queues an instruction after the current tool batch while the top-level RPC child is active.",
|
|
64
|
+
"retarget replaces the objective in that same active top-level child.",
|
|
65
|
+
"Managed downstream documenter/reviewer/fix stages are controlled by the parent queue rather than its settled RPC control: use park or stop there, then resume with an objective to redirect retained context.",
|
|
65
66
|
"park aborts to a stable checkpoint, terminates the child, preserves context, and releases its concurrency slot.",
|
|
66
67
|
"resume restarts a parked, completed, or failed retained thread with the same run id; objective is optional.",
|
|
67
68
|
"fork copies a parked/completed/failed retained session branch into a new logical thread and run id; an isolated checkpoint must be settled and integrated first; objective is optional.",
|
|
68
69
|
].join(" "),
|
|
69
|
-
promptSnippet: "Control a subagent thread: steer
|
|
70
|
+
promptSnippet: "Control a subagent thread: steer/retarget an active top-level child; park/stop a managed downstream stage; resume or fork retained context.",
|
|
70
71
|
promptGuidelines: [
|
|
71
|
-
"Use subagent_control steer to refine active
|
|
72
|
-
"Use subagent_control retarget
|
|
72
|
+
"Use subagent_control steer to refine an active top-level RPC child without restarting it; the instruction is delivered after its current tool batch.",
|
|
73
|
+
"Use subagent_control retarget only while that top-level child is active. During a managed downstream stage, park it and resume with a replacement objective instead.",
|
|
73
74
|
"Use subagent_control park to checkpoint useful context while releasing the process/concurrency slot, and resume to continue the same run id later.",
|
|
74
75
|
"Use subagent_control fork only on a parked or settled retained thread; isolated work must settle and integrate before it can fork. Fork creates a new run id while leaving the source untouched.",
|
|
75
76
|
"Use subagent_stop only for destructive cancellation; it retires that thread's retained session without retiring independent forks.",
|
|
@@ -409,6 +410,8 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
409
410
|
if (active) {
|
|
410
411
|
const parked = active.status === "parked";
|
|
411
412
|
const activeThread = runtime.threads.get(active.id);
|
|
413
|
+
const managedDownstream =
|
|
414
|
+
activeThread?.state === "running" && activeThread.control.getPhase() === "settled";
|
|
412
415
|
const metadata = [
|
|
413
416
|
activeThread?.isolation === "worktree" ? `worktree ${active.integrationStatus ?? activeThread.worktree?.state ?? "active"}` : undefined,
|
|
414
417
|
activeThread?.forkedFromRunId !== undefined ? `forked from #${activeThread.forkedFromRunId}` : undefined,
|
|
@@ -420,7 +423,9 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
420
423
|
type: "text",
|
|
421
424
|
text: parked
|
|
422
425
|
? `Run #${active.id} ${active.agent} is parked with retained context${metadata ? ` (${metadata})` : ""}. Use subagent_control resume to restart it, or subagent_stop to retire it.`
|
|
423
|
-
:
|
|
426
|
+
: managedDownstream
|
|
427
|
+
? `Run #${active.id} ${active.agent} is in a managed downstream stage (${active.activity ?? statusLabel(active.status)}${metadata ? ` · ${metadata}` : ""}). Use subagent_wait for its result, subagent_control park to checkpoint it, or subagent_stop to cancel it. Steer/retarget are unavailable until you park and resume the retained stage.`
|
|
428
|
+
: `Run #${active.id} ${active.agent} is still active (${active.activity ?? statusLabel(active.status)}${metadata ? ` · ${metadata}` : ""}). Use subagent_wait to block for its result, subagent_control to steer/park it, or subagent_stop to cancel it.`,
|
|
424
429
|
},
|
|
425
430
|
],
|
|
426
431
|
details: {},
|
|
@@ -617,9 +622,19 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
617
622
|
});
|
|
618
623
|
}
|
|
619
624
|
|
|
625
|
+
// Interrupt every claimed generation before awaiting any one of them.
|
|
626
|
+
// An isolated stop may need the repository lane for final integration;
|
|
627
|
+
// cancelling all holders first prevents stop-all from waiting behind a
|
|
628
|
+
// later shared workflow that this same operation has not interrupted yet.
|
|
629
|
+
const interruptionPromises = claimed.map(({ thread, stopMessage, controller }) => {
|
|
630
|
+
const stopping = thread.control.stop(stopMessage).catch(() => undefined);
|
|
631
|
+
runtime.backgroundQueue.cancel(controller);
|
|
632
|
+
return stopping;
|
|
633
|
+
});
|
|
634
|
+
|
|
620
635
|
const stopped: string[] = [];
|
|
621
636
|
const retainedIntegration: string[] = [];
|
|
622
|
-
for (const claim of claimed) {
|
|
637
|
+
for (const [claimIndex, claim] of claimed.entries()) {
|
|
623
638
|
const {
|
|
624
639
|
runId,
|
|
625
640
|
thread,
|
|
@@ -634,8 +649,7 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
634
649
|
stopVersion,
|
|
635
650
|
stopMessage,
|
|
636
651
|
} = claim;
|
|
637
|
-
await
|
|
638
|
-
runtime.backgroundQueue.cancel(controller);
|
|
652
|
+
await interruptionPromises[claimIndex];
|
|
639
653
|
await completion;
|
|
640
654
|
if (runtime.runControllers.get(runId) === controller) runtime.runControllers.delete(runId);
|
|
641
655
|
if (thread.queueController === controller) thread.queueController = undefined;
|
package/src/widget.ts
CHANGED
|
@@ -93,8 +93,8 @@ function runActivityLine(run: RunView, theme: Theme, width: number, indent: stri
|
|
|
93
93
|
return [truncateToWidth(`${indent}${dim(activitySummary)}`, width, "")];
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/** Render active runs as a tree: main-agent dispatches are roots
|
|
97
|
-
*
|
|
96
|
+
/** Render active runs as a tree: main-agent dispatches are roots and managed
|
|
97
|
+
* documenter/reviewer/fix steps nest under the stable parent row. No run ids
|
|
98
98
|
* appear here — the tree and the task label say what each row is, and ids stay
|
|
99
99
|
* available through subagent_status when a thread must be controlled. */
|
|
100
100
|
export function formatActiveRunLines(
|
|
@@ -120,8 +120,8 @@ export function formatActiveRunLines(
|
|
|
120
120
|
for (const root of roots) {
|
|
121
121
|
const children = childrenOf.get(root.id) ?? [];
|
|
122
122
|
lines.push(runPrimaryLine(root, theme, width, now, ""));
|
|
123
|
-
// The parent's
|
|
124
|
-
//
|
|
123
|
+
// The parent's managed-workflow placeholder is redundant while a child
|
|
124
|
+
// row shows live progress; keep it only between stages.
|
|
125
125
|
if (children.length === 0) lines.push(...runActivityLine(root, theme, width, " "));
|
|
126
126
|
children.forEach((child, index) => {
|
|
127
127
|
const connector = index === children.length - 1 ? "└ " : "├ ";
|
package/src/worktree.ts
CHANGED
|
@@ -298,11 +298,18 @@ export function isPathInside(root: string, candidate: string): boolean {
|
|
|
298
298
|
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
interface RepositoryLocation {
|
|
302
|
+
originalCwd: string;
|
|
303
|
+
originalRoot: string;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Resolve the canonical repository root without requiring a committed HEAD.
|
|
307
|
+
* Managed repository lanes use this for empty repositories as well as normal
|
|
308
|
+
* worktrees; worktree creation validates HEAD separately below. */
|
|
309
|
+
async function resolveRepositoryLocation(
|
|
303
310
|
cwd: string,
|
|
304
|
-
runner: CommandRunner
|
|
305
|
-
): Promise<
|
|
311
|
+
runner: CommandRunner,
|
|
312
|
+
): Promise<RepositoryLocation> {
|
|
306
313
|
const requested = resolve(cwd);
|
|
307
314
|
try {
|
|
308
315
|
if (!(await stat(requested)).isDirectory()) throw new Error("not a directory");
|
|
@@ -335,6 +342,22 @@ export async function resolveWorktreeTarget(
|
|
|
335
342
|
`Requested cwd ${originalCwd} is not inside Git worktree root ${originalRoot}.`,
|
|
336
343
|
);
|
|
337
344
|
}
|
|
345
|
+
return { originalCwd, originalRoot };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function resolveRepositoryRoot(
|
|
349
|
+
cwd: string,
|
|
350
|
+
runner: CommandRunner = runCommand,
|
|
351
|
+
): Promise<string> {
|
|
352
|
+
return (await resolveRepositoryLocation(cwd, runner)).originalRoot;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Resolve and validate the Git repository/worktree that contains cwd. */
|
|
356
|
+
export async function resolveWorktreeTarget(
|
|
357
|
+
cwd: string,
|
|
358
|
+
runner: CommandRunner = runCommand,
|
|
359
|
+
): Promise<WorktreeTarget> {
|
|
360
|
+
const { originalCwd, originalRoot } = await resolveRepositoryLocation(cwd, runner);
|
|
338
361
|
let headResult: CommandResult;
|
|
339
362
|
try {
|
|
340
363
|
headResult = await runGit(
|